All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.javascript2.model.JsObjectImpl Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.javascript2.model;

import org.netbeans.modules.javascript2.model.api.ModelUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import org.netbeans.modules.csl.api.Documentation;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.javascript2.doc.spi.JsDocumentationHolder;
import org.netbeans.modules.javascript2.model.api.JsElement;
import org.netbeans.modules.javascript2.model.api.JsFunction;
import org.netbeans.modules.javascript2.model.api.JsObject;
import org.netbeans.modules.javascript2.model.api.Occurrence;
import org.netbeans.modules.javascript2.types.api.DeclarationScope;
import org.netbeans.modules.javascript2.types.api.Identifier;
import org.netbeans.modules.javascript2.types.api.Type;
import org.netbeans.modules.javascript2.types.api.TypeUsage;

/**
 *
 * @author Petr Pisl
 */
public class JsObjectImpl extends JsElementImpl implements JsObject {

    protected final LinkedHashMap properties = new LinkedHashMap<>();
    private Identifier declarationName;
    private JsObject parent;
    private final List occurrences = new ArrayList<>();
    private final NavigableMap> assignments = new TreeMap<>();
    private final MapassignmentsReverse = new HashMap<>();
    private boolean hasName;
    private Documentation documentation;
    protected JsElement.Kind kind;
    private boolean isVirtual;
    private boolean isAnonymous;

    public JsObjectImpl(JsObject parent, Identifier name, OffsetRange offsetRange,
            String mimeType, String sourceLabel) {
        super((parent != null ? parent.getFileObject() : null), name.getName(),
                ModelUtils.PROTOTYPE.equals(name.getName()), offsetRange, EnumSet.of(Modifier.PUBLIC), mimeType, sourceLabel);
        this.declarationName = name;
        this.parent = parent;
        this.hasName = name.getOffsetRange().getStart() != name.getOffsetRange().getEnd();
        this.kind = null;
        this.isVirtual = false;
        this.isAnonymous = false;
    }

    public JsObjectImpl(JsObject parent, Identifier name, OffsetRange offsetRange,
            boolean isDeclared, Set modifiers, String mimeType, String sourceLabel) {
        super((parent != null ? parent.getFileObject() : null), name.getName(),
                isDeclared, offsetRange, modifiers, mimeType, sourceLabel);
        this.declarationName = name;
        this.parent = parent;
        this.hasName = !OffsetRange.NONE.equals(name.getOffsetRange()) && (name.getOffsetRange().getStart() != name.getOffsetRange().getEnd());
        this.kind = null;
        this.isVirtual = false;
    }

    public JsObjectImpl(JsObject parent, Identifier name, OffsetRange offsetRange,
            boolean isDeclared, String mimeType, String sourceLabel) {
        this(parent, name, offsetRange, isDeclared, EnumSet.of(Modifier.PUBLIC), mimeType, sourceLabel);
    }

    protected JsObjectImpl(JsObject parent, String name, boolean isDeclared,
            OffsetRange offsetRange, Set modifiers, String mimeType, String sourceLabel) {
        super((parent != null ? parent.getFileObject() : null), name, isDeclared,
                offsetRange, modifiers, mimeType, sourceLabel);
        this.declarationName = null;
        this.parent = parent;
        this.hasName = false;
    }

    @Override
    public Identifier getDeclarationName() {
        return declarationName;
    }

    @Override
    public String getName() {
        return declarationName != null ? declarationName.getName() : super.getName();
    }

    public void setDeclarationName(Identifier declaration) {
        declarationName = declaration;
        hasName = declaration.getOffsetRange().getLength() > 0;
    }

    @Override
    public Kind getJSKind() {
        if (kind != null) {
            return kind;
        }
        if (parent == null) {
            // global object
            return Kind.FILE;
        }
        if (ModelUtils.PROTOTYPE.equals(getName())) {
            return Kind.OBJECT;
        }
        if (isDeclared()) {
            if (ModelUtils.ARGUMENTS.equals(getName())) {
                // special variable object of every function
                return Kind.VARIABLE;
            }
            if (!getAssignmentForOffset(getDeclarationName().getOffsetRange().getEnd()).isEmpty()
                    && hasOnlyVirtualProperties()) {
                if (getParent().getParent() == null || getModifiers().contains(Modifier.PRIVATE)) {
                    return Kind.VARIABLE;
                } else {
                    return Kind.PROPERTY;
                }
            }
        } else {
            if (!getProperties().isEmpty()) {
                return Kind.OBJECT;
            }
        }
        if (getProperties().isEmpty()) {
            if (getParent().isAnonymous() && (getParent() instanceof AnonymousObject)) {
                return Kind.PROPERTY;
            }
            if (getParent().getParent() == null || getModifiers().contains(Modifier.PRIVATE)) {
                // variable or the global object
                return Kind.VARIABLE;
            }
            if (getParent() instanceof JsFunction) {
                if (isDeclared()) {
                    return getModifiers().contains(Modifier.PRIVATE) ? Kind.VARIABLE : Kind.PROPERTY;
                }
            }
            return Kind.PROPERTY;
        }
        return Kind.OBJECT;
    }

    private boolean hasOnlyVirtualProperties() {
        for (JsObject property : getProperties().values()) {
            if (property.isDeclared() || ModelUtils.PROTOTYPE.equals(property.getName())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public Map getProperties() {
        return properties;
    }

    @Override
    public void addProperty(String name, JsObject property) {
        properties.put(name, property);
    }

    @Override
    public JsObject getProperty(String name) {
        return properties.get(name);
    }

    @Override
    public JsObject getParent() {
        return parent;
    }

    @SuppressWarnings({"NestedAssignment", "AssertWithSideEffects"})
    public void setParent(JsObject newParent) {
        boolean assertionsEnabled = false;
        assert assertionsEnabled = true;
        if (assertionsEnabled) {
            for (JsObject checkParent = newParent; checkParent != null; checkParent = checkParent.getParent()) {
                if (checkParent == this) {
                    throw new AssertionError("Cycle detected in " + getFileObject().getPath());
                }
            }
        }
        this.parent = newParent;
    }

    @Override
    public boolean isVirtual() {
        return isVirtual;
    }

    public void setVirtual(boolean isVirtual) {
        this.isVirtual = isVirtual;
    }

    @Override
    public int getOffset() {
        return declarationName == null ? -1 : declarationName.getOffsetRange().getStart();
    }

    @Override
    public List getOccurrences() {
        return occurrences;
    }

    @Override
    public void addOccurrence(OffsetRange offsetRange) {
        Occurrence occurrence = new Occurrence(offsetRange, this);
        if (!occurrences.contains(occurrence)) {
            occurrences.add(occurrence);
        }
    }

    public void addAssignment(Collection typeNames, int offset) {
        for(TypeUsage type: typeNames) {
            addAssignment(type, offset);
        }
    }

    @Override
    public void clearAssignments() {
        assignments.clear();
    }

    @Override
    public void addAssignment(TypeUsage typeName, int offset) {
        if (typeName == null || (Type.UNDEFINED.equals(typeName.getType()) && !assignments.isEmpty())) {
            // don't add undefined type, if there are already some types
            return;
        }
        Collection types = assignments.get(offset);
        if (types == null) {
            // create always empty list, need to be counted for number of assignments.
            types = new ArrayList<>();
            assignments.put(offset, types);
        }

        Integer alreadyDefinedOffset = assignmentsReverse.get(typeName.getType());
        if (alreadyDefinedOffset != null) {
            // there is already assignment of this type. It's enough to store the
            // assignment with the min offset
            if(alreadyDefinedOffset <= offset) {
                // do nothing, just remember the previous one
                return;
            } else {
                // we need to replace the assignment with bigger offset
                Collection typesToRemove = assignments.get(alreadyDefinedOffset);
                for (TypeUsage type : typesToRemove) {
                    if (type.getType().equals(typeName.getType())) {
                        typesToRemove.remove(type);
                        break;
                    }
                }
            }
        }
        assignmentsReverse.put(typeName.getType(), offset);
        types.add(typeName);
    }

    @Override
    public Collection getAssignmentForOffset(int offset) {
        List result = new ArrayList<>();
        Map.Entry> found = assignments.floorEntry(offset);
        while (found != null) {
            result.addAll((Collection)found.getValue());
            int tmpOffset = found.getKey() - 1;
            found = assignments.floorEntry(tmpOffset);
        }

        return result;
    }

    @Override
    public int getAssignmentCount() {
        return assignments.size();
    }

    @Override
    public Collection getAssignments() {
        List values;
        values = new ArrayList<>();
        for (Collection types : assignments.values()) {
            values.addAll(types);
        }
        return Collections.unmodifiableCollection(values);
    }

    @Override
    public String getFullyQualifiedName() {
        if (getParent() == null) {
            return getName();
        }
        StringBuilder result = new StringBuilder();
        JsObject pObject = this;
        result.append(getName());

        while ((pObject = pObject.getParent()).getParent() != null) {
            result.insert(0, ".");
            result.insert(0, pObject.getName());
        }
        return result.toString();
    }

    @Override
    public boolean isAnonymous() {
        return isAnonymous;
    }

    @Override
    public void setAnonymous(boolean value) {
        this.isAnonymous = value;
    }

    @Override
    public boolean containsOffset(int offset) {
        if (getOffsetRange().containsInclusive(offset)) {
            return true;
        }
        // some methods can be declared outside the main object
        for (JsObject property : getProperties().values()) {
            if (property.getOffsetRange().containsInclusive(offset)) {
                return true;
            }
            if (ModelUtils.PROTOTYPE.equals(property.getName())) {
                if (property.containsOffset(offset)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean hasExactName() {
        return hasName;
    }

    public final void setJsKind(JsElement.Kind kind) {
        this.kind = kind;
    }

    protected Collection resolveAssignments(JsObject jsObject, int offset) {
        Collection visited = new HashSet<>();  // for preventing infinited loops
        return resolveAssignments(jsObject, offset, visited);
    }

    protected Collection resolveAssignments(JsObject jsObject, int offset, Collection visited) {
        Collection result = new HashSet<>();
        String fqn = jsObject.getFullyQualifiedName();
        if (visited.contains(fqn)) {
            return result;
        }
        visited.add(fqn);
        Collection offsetAssignments = Collections.emptyList();
        Map.Entry> found = ((JsObjectImpl) jsObject).assignments.floorEntry(offset);
        if (found != null) {
            offsetAssignments = found.getValue();
        }
        if (offsetAssignments.isEmpty() && !jsObject.getProperties().isEmpty()) {
            result.add(new TypeUsage(jsObject.getFullyQualifiedName(), jsObject.getOffset(), true));
        } else {
            for (TypeUsage assignment : offsetAssignments) {
                if (!visited.contains(assignment.getType())) {
                    if (assignment.isResolved()) {
                        result.add(assignment);
                    } else {
                        if (assignment.getType().startsWith("@")) {
                            result.addAll(ModelUtils.resolveTypeFromSemiType(jsObject, assignment));
                        } else {
                            DeclarationScope scope = ModelUtils.getDeclarationScope(jsObject);
                            JsObject object = ModelUtils.getJsObjectByName(scope, assignment.getType());
                            if (object == null) {
                                JsObject gloal = ModelUtils.getGlobalObject(jsObject);
                                object = ModelUtils.findJsObjectByName(gloal, assignment.getType());
                            }
                            if (object != null) {
                                Collection resolvedFromObject = resolveAssignments(object, found != null ? found.getKey() : -1, visited);
                                if (resolvedFromObject.isEmpty()) {
                                    result.add(new TypeUsage(object.getFullyQualifiedName(), assignment.getOffset(), true));
                                } else {
                                    result.addAll(resolvedFromObject);
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    public void resolveTypes(JsDocumentationHolder jsDocHolder) {
        if (parent == null
                || (parent != null && parent.getOffset() == getOffset() && ModelUtils.ARGUMENTS.equals(getName())) ) {
            return;
        }
        Collection resolved = new ArrayList<>();
        for (Collection unresolved : assignments.values()) {
            resolved.clear();
            JsObject global = ModelUtils.getGlobalObject(parent);
            for (TypeUsage type : unresolved) {
                Collection resolvedHere = new ArrayList<>();
                if (!type.isResolved()) {
                    resolvedHere.addAll(ModelUtils.resolveTypeFromSemiType(this, type));
                } else {
                    resolvedHere.add(type);
                }
                if (!type.getType().contains(ModelUtils.THIS)) {
                    for (TypeUsage typeHere : resolvedHere) {
                        if (typeHere.getOffset() > 0) {
                            TypeUsage newType = typeHere;
                            if (!typeHere.isResolved() && (typeHere.getType().startsWith(SemiTypeResolverVisitor.ST_PRO))) {
                                newType = ModelUtils.createResolvedType(global, typeHere);
                            }
                            String rType = ModelUtils.getFQNFromType(newType);
                            JsObject jsObject = ModelUtils.findJsObjectByName(global, rType);

                            if (jsObject == null && rType.indexOf('.') == -1 && global instanceof DeclarationScope) {
                                DeclarationScope declarationScope = ModelUtils.getDeclarationScope((DeclarationScope) global, typeHere.getOffset());
                                jsObject = ModelUtils.getJsObjectByName(declarationScope, rType);
                                if (jsObject == null) {
                                    JsObject decParent = (this.parent.getJSKind() != JsElement.Kind.ANONYMOUS_OBJECT
                                            && this.parent.getJSKind() != JsElement.Kind.OBJECT_LITERAL)
                                            ? this.parent : this.parent.getParent();
                                    while (jsObject == null && decParent != null) {
                                        jsObject = decParent.getProperty(rType);
                                        decParent = decParent.getParent();
                                    }
                                }
                            }
                            if (jsObject != null) {
//                                if (typeHere.isResolved() && !jsObject.isAnonymous()) {
                                if (typeHere.isResolved()) {
                                    int index = rType.lastIndexOf('.');
                                    int typeLength = (index > -1) ? rType.length() - index - 1 : rType.length();
                                    int offset = typeHere.getOffset();
                                    ((JsObjectImpl) jsObject).addOccurrence(new OffsetRange(offset, jsObject.isAnonymous() ? offset : offset + typeLength));
                                }
                                moveOccurrenceOfProperties((JsObjectImpl) jsObject, this);
                                JsObject parent = jsObject.getParent();
                                if (parent != null && "window".equals(parent.getName())) {
                                    for (JsObject property : getProperties().values()) {
                                        if (property.isDeclared()) {
                                            JsObject gwProp = jsObject.getProperty(property.getName());
                                            if (gwProp == null) {
                                                jsObject.addProperty(property.getName(), property);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                } else if (type.getType().equals("@this;") && resolvedHere.size() == 1) {
                    // we expect something like self = this, so all properties of the object should be assigned to the this.
                    TypeUsage originalType = resolvedHere.iterator().next();
                    JsObject originalObject = ModelUtils.findJsObjectByName(global, originalType.getType());
                    if (originalObject != null) {
                        // move all properties to the original type.
                        // create copy of the new object, but without the properties
                        // the new object is needed for setting new assignment.
                        JsObject newObject = new JsObjectImpl(this.parent, this.declarationName,
                                this.getOffsetRange(), this.isDeclared(), this.getModifiers(), this.getMimeType(), this.getSourceLabel());
                        // replace the object with object without the properties
                        parent.addProperty(this.getName(), newObject);
                        // copy all the properties to the original object that represents this
                        List  propertiesCopy = new ArrayList<>(this.properties.values());
                        for (JsObject property : propertiesCopy) {
                            ModelUtils.moveProperty(originalObject, property);
                        }
                        for (Occurrence occurrence : this.occurrences) {
                            newObject.addOccurrence(occurrence.getOffsetRange());
                        }
                        newObject.addAssignment(new TypeUsage(originalObject.getFullyQualifiedName(), originalObject.getOffset(), true), assignments.keySet().iterator().next().intValue());
                    }
                }
                resolved.addAll(resolvedHere);
            }

            unresolved.clear();
            unresolved.addAll(resolved);
        }

        if (!isAnonymous()) {
            List docOccurrences = jsDocHolder.getOccurencesMap().get(getFullyQualifiedName());
            if (docOccurrences != null) {
                for (OffsetRange offsetRange : docOccurrences) {
                    addOccurrence(offsetRange);
                }
            }
        }

        if (!isAnonymous() && assignments.isEmpty()) {
            // try to recount occurrences
            JsObject global = ModelUtils.getGlobalObject(parent);
            List correctedOccurrences = new ArrayList<>();

            JsObjectImpl obAssignment = findRightTypeAssignment(getDeclarationName().getOffsetRange().getStart(), global);
            if (obAssignment != null && !obAssignment.getModifiers().contains(Modifier.PRIVATE)) {
                obAssignment.addOccurrence(getDeclarationName().getOffsetRange());
            }

            for (Occurrence occurrence : new ArrayList<>(occurrences)) {
                obAssignment = findRightTypeAssignment(occurrence.getOffsetRange().getStart(), global);
                if (obAssignment != null && !obAssignment.getModifiers().contains(Modifier.PRIVATE)) {
                    obAssignment.addOccurrence(occurrence.getOffsetRange());
                } else {
                    correctedOccurrences.add(occurrence);
                }
            }

            for(Occurrence occurrence : correctedOccurrences){
                addOccurrence(occurrence.getOffsetRange());
            }
        }

        // resolving prototype types
        JsObject prototype = getProperty(ModelUtils.PROTOTYPE);
        if (prototype != null) {
            Collection protoAssignments = prototype.getAssignments();
            if (protoAssignments != null && !protoAssignments.isEmpty()) {
                protoAssignments = new ArrayList<>(protoAssignments);
                Collection variables = ModelUtils.getVariables(ModelUtils.getDeclarationScope(this));
                for (TypeUsage typeUsage : protoAssignments) {
                    for (JsObject variable : variables) {
                        if (typeUsage.getType().equals(variable.getName())) {
                            if (!typeUsage.getType().equals(variable.getFullyQualifiedName())) {
                                prototype.addAssignment(new TypeUsage(variable.getFullyQualifiedName(), typeUsage.getOffset(), true), typeUsage.getOffset());
                            }
                        }
                    }
                }
            }
        }

        // Try to find, whether this object is not also property of parent prototype.
        if (!isDeclared() && getParent() != null) {
            prototype = getParent().getProperty(ModelUtils.PROTOTYPE);
            if (prototype != null) {
                JsObject prototypeProperty = prototype.getProperty(getName());
                if (prototypeProperty != null && prototypeProperty.isDeclared()) {
                    // if there is also a property of parent prototype with the same name
                    // and is declared, move all the occurrences to the declared property
                    // and this property remove from parent.
                    for (Occurrence occurrence : getOccurrences()) {
                        prototypeProperty.addOccurrence(occurrence.getOffsetRange());
                    }
                    getParent().getProperties().remove(getName());
                }
            }
        }
    }

    protected void clearOccurrences() {
        occurrences.clear();
    }

    public static void moveOccurrenceOfProperties(JsObjectImpl original, JsObject created) {
        if (original.equals(created)) {
            return;
        }
        Collection prototypeChains = findPrototypeChain(original);
        for (JsObject jsObject : prototypeChains) {
            for (JsObject origProperty : jsObject.getProperties().values()) {
                if (origProperty.getModifiers().contains(Modifier.PUBLIC)
                        || origProperty.getModifiers().contains(Modifier.PROTECTED)) {
                    JsObjectImpl usedProperty = (JsObjectImpl) created.getProperty(origProperty.getName());
                    if (usedProperty != null) {
                        moveOccurrence((JsObjectImpl) origProperty, usedProperty);
                        moveOccurrenceOfProperties((JsObjectImpl) origProperty, usedProperty);
                    }
                }
            }
            JsObject prototype = jsObject.getProperty(ModelUtils.PROTOTYPE);
            if (prototype != null) {
                moveOccurrenceOfProperties((JsObjectImpl) prototype, created);
            }
        }

        if (!original.getAssignments().isEmpty() && created.getAssignments().isEmpty()) {
            // we are add type to help resolve other properties.
            for(TypeUsage type : original.getAssignments()) {
                created.addAssignment(type, -1);
            }
        }

    }

    public static void moveOccurrence(JsObjectImpl original, JsObject created) {
        original.addOccurrence(created.getDeclarationName() != null ? created.getDeclarationName().getOffsetRange(): OffsetRange.NONE);
        for (Occurrence occur : created.getOccurrences()) {
            original.addOccurrence(occur.getOffsetRange());
        }
        ((JsObjectImpl) created).clearOccurrences();
        if (original.isDeclared() && created.isDeclared()) {
            ((JsObjectImpl) created).setDeclared(false); // the property is not declared here
        }
    }

    /**
     * Create prototype chain only from objects in the file
     *
     * @param object
     * @return
     */
    public static Collection findPrototypeChain(JsObject object) {
        List chain = new ArrayList<>();
        chain.add(object);
        chain.addAll(findPrototypeChain(object, new HashSet<>()));
        return chain;
    }

    private static List findPrototypeChain(JsObject object, Set alreadyCheck) {
        List result = new ArrayList<>();
        String fqn = object.getFullyQualifiedName();
        if (!alreadyCheck.contains(fqn)) {
            alreadyCheck.add(fqn);
            JsObject prototype = object.getProperty(ModelUtils.PROTOTYPE);
            if (prototype != null && !prototype.getAssignments().isEmpty()) {
                JsObject global = ModelUtils.getGlobalObject(object);
                for (TypeUsage type : prototype.getAssignments()) {
                    if (!type.isResolved()) {
                        Collection resolved = ModelUtils.resolveTypeFromSemiType(object, type);
                        for (TypeUsage rType : resolved) {
                            if (rType.isResolved()) {
                                JsObject fObject = ModelUtils.findJsObjectByName(global, rType.getType());
                                if (fObject != null) {
                                    result.add(fObject);
                                    result.addAll(findPrototypeChain(fObject, alreadyCheck));
                                }
                            }
                        }
                    } else {
                        JsObject fObject = ModelUtils.findJsObjectByName(global, type.getType());
                        if (fObject != null) {
                            result.add(fObject);
                            result.addAll(findPrototypeChain(fObject, alreadyCheck));
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     * This methods returns JsObject that represents a type for an assignment.
     *
     * @param offset
     * @return return the object
     */
    private JsObjectImpl findRightTypeAssignment(int offset, JsObject global) {
        Collection findedAssignments;
        JsObject current;
        JsObject currentParent = this;
        // save the properties in a list to reuse it later
        List propertyPath = new ArrayList<>();
        do {
            current = currentParent;
            findedAssignments = current.getAssignmentForOffset(offset);
            propertyPath.add(current.getName());
            currentParent = current.getParent();
        } while (findedAssignments.isEmpty() && currentParent != null);

        for (TypeUsage type : findedAssignments) {
            // find the appropriate object for the type in the model
            current = ModelUtils.findJsObjectByName(global, type.getType());

            // map back the properties from the propertyPath to get the right object
            for (int i = propertyPath.size() - 2; i > -1 && current != null; i--) {
                current = current.getProperty(propertyPath.get(i));
            }
            if (current != null) {
                return (JsObjectImpl) current;
            }
        }

        return null;
    }

    @Override
    public Documentation getDocumentation() {
        return documentation;
    }

    @Override
    public void setDocumentation(Documentation doc) {
        this.documentation = doc;
    }

    @Override
    public boolean isDeprecated() {
        return getModifiers().contains(Modifier.DEPRECATED);
    }

    public void setDeprecated(boolean depreceted) {
        if (depreceted) {
            getModifiers().add(Modifier.DEPRECATED);
        } else {
            getModifiers().remove(Modifier.DEPRECATED);
        }
    }

    @Override
    public boolean moveProperty(String name, JsObject newParent) {
        JsObject property = getProperty(name);
        if (property == null) {
            return false;
        }
        if (property instanceof JsObjectImpl) {
            String oldFQN = property.getFullyQualifiedName();
            ((JsObjectImpl)property).setParent(newParent);
            newParent.addProperty(name, property);
            String newFQN = property.getFullyQualifiedName();
            JsObject global = ModelUtils.getGlobalObject(this);
            if (global instanceof JsObjectImpl) {
                ((JsObjectImpl)global).correctAssignmentsInModel(oldFQN, newFQN, new HashSet<>());
            }
            return properties.remove(name) != null;
        }
        return false;
    }

    private void correctAssignmentsInModel (String fromType, String toType, Set done) {
        if (!done.contains(getFullyQualifiedName())) {
            done.add(getFullyQualifiedName());
            correctTypes(fromType, toType);
            for (JsObject property: getProperties().values()) {
                if (property instanceof JsObjectImpl) {
                    ((JsObjectImpl)property).correctAssignmentsInModel(fromType, toType, done);
                }
            }
        }
    }

    protected void correctTypes(String fromType, String toType) {
        for (Collection types: assignments.values()) {
            List copy = new ArrayList<>(types);
            for (TypeUsage type : copy) {
                String typeR = replaceTypeInFQN(type.getType(), fromType, toType);
                if (typeR != null) {
                    types.remove(type);
                    types.add(new TypeUsage(typeR, type.getOffset(), type.isResolved()));
                }
            }
        }
    }

    /**
     *
     * @param typeFQN type that where should be changed
     * @param fromType  the old type or part of a type
     * @param toType    the new type or part of a type
     * @return null, if it's not possible to replace or the result FQN
     */
    protected String replaceTypeInFQN(String typeFQN, String fromType, String toType) {
        String typeR = null;
        if (typeFQN.isEmpty()) {
            return null;
        }
        if (typeFQN.equals(fromType)) {
            typeR = toType;
        } else {
            int index = typeFQN.indexOf(fromType);
            if (typeFQN.startsWith(SemiTypeResolverVisitor.ST_START_DELIMITER)) {
                // it's semitype. we need to mask the semitypes
                int delEndIndex = typeFQN.indexOf(';');
                if (delEndIndex > 0 && index < delEndIndex) {
                    index = typeFQN.indexOf(fromType, delEndIndex);
                }

            }
            if (index > -1 && !typeFQN.contains(toType)
                    && (index == 0 || typeFQN.charAt(index - 1) == '.' || typeFQN.charAt(index - 1) == ';')
                    && ((index + fromType.length()) == typeFQN.length() || typeFQN.charAt(index + fromType.length()) == '.')) {
                boolean replace = (index == 0 || typeFQN.startsWith(SemiTypeResolverVisitor.ST_START_DELIMITER));
                if (!replace && index > 0) {
                    String typePrefix = typeFQN.substring(0, index - 1);
                    JsObject global = ModelUtils.getGlobalObject(this);
                    replace = ModelUtils.findJsObjectByName(global, typePrefix) != null;
                }
                if (replace) {
                    typeR = typeFQN.substring(0, index) + toType + typeFQN.substring(index + fromType.length());
                }
            }
        }
        return typeR;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy