| /* |
| * 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.Occurrence; |
| 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 { |
| |
| // final protected HashMap<String, JsObject> properties = new HashMap<String, JsObject>(); |
| final protected LinkedHashMap<String, JsObject> properties = new LinkedHashMap<String, JsObject>(); |
| private Identifier declarationName; |
| private JsObject parent; |
| final private List<Occurrence> occurrences = new ArrayList<Occurrence>(); |
| final private NavigableMap<Integer, Collection<TypeUsage>> assignments = new TreeMap<>(); |
| final private Map<String, Integer>assignmentsReverse = new HashMap<>(); |
| private int countOfAssignments = 0; |
| 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<Modifier> 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<Modifier> 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<String, ? extends JsObject> 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; |
| } |
| |
| public void setParent(JsObject newParent) { |
| 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<Occurrence> getOccurrences() { |
| return occurrences; |
| } |
| |
| @Override |
| public void addOccurrence(OffsetRange offsetRange) { |
| // boolean isThere = false; |
| // for (Occurrence occurrence : occurrences) { |
| // if (occurrence.getOffsetRange().equals(offsetRange)) { |
| // isThere = true; |
| // break; |
| // } |
| // } |
| // if (!isThere) { |
| // occurrences.add(new OccurrenceImpl(offsetRange, this)); |
| // } |
| Occurrence occurrence = new Occurrence(offsetRange, this); |
| if (!occurrences.contains(occurrence)) { |
| occurrences.add(occurrence); |
| } |
| } |
| |
| public void addAssignment(Collection<TypeUsage> 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.size() > 0)) { |
| // don't add undefined type, if there are already some types |
| return; |
| } |
| Collection<TypeUsage> types = assignments.get(offset); |
| if (types == null) { |
| // create always empty list, need to be counted for number of assignments. |
| types = new ArrayList<TypeUsage>(); |
| 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<TypeUsage> 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<? extends TypeUsage> getAssignmentForOffset(int offset) { |
| List<? extends TypeUsage> result = new ArrayList<>(); |
| Map.Entry<Integer, Collection<TypeUsage>> found = assignments.floorEntry(offset); |
| int tmpOffset = offset; |
| while (found != null) { |
| result.addAll((Collection)found.getValue()); |
| tmpOffset = found.getKey() - 1; |
| found = assignments.floorEntry(tmpOffset); |
| } |
| // if (result.isEmpty()) { |
| // Collection<TypeUsage> resolved = new HashSet(); |
| // for(TypeUsage item : result) { |
| // TypeUsage type = (TypeUsage)item; |
| // if (type.isResolved()) { |
| // resolved.add(type); |
| // } else { |
| // JsObject jsObject = ModelUtils.findJsObjectByName(ModelUtils.getGlobalObject(this), type.getType()); |
| // if(jsObject != null) { |
| // resolved.addAll(resolveAssignments(jsObject, offset)); |
| // } |
| // } |
| // } |
| // if(resolved.isEmpty()) { |
| // // keep somthink in the assignments. |
| // resolved.add(new TypeUsage("Object", offset, true)); |
| // } |
| // Collection<TypeUsage> resolved = new HashSet(); |
| // //resolved.add(new TypeUsage("Object", offset, true)); |
| // result = resolved; |
| // } |
| |
| return result; |
| } |
| |
| public int getAssignmentCount() { |
| return assignments.size(); |
| } |
| |
| @Override |
| public Collection<? extends TypeUsage> getAssignments() { |
| List<TypeUsage> values; |
| values = new ArrayList<TypeUsage>(); |
| for (Collection<? extends TypeUsage> 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<TypeUsage> resolveAssignments(JsObject jsObject, int offset) { |
| Collection<String> visited = new HashSet<>(); // for preventing infinited loops |
| return resolveAssignments(jsObject, offset, visited); |
| } |
| |
| protected Collection<TypeUsage> resolveAssignments(JsObject jsObject, int offset, Collection<String> visited) { |
| Collection<TypeUsage> result = new HashSet<>(); |
| String fqn = jsObject.getFullyQualifiedName(); |
| if (visited.contains(fqn)) { |
| return result; |
| } |
| visited.add(fqn); |
| Collection<? extends TypeUsage> offsetAssignments = Collections.emptyList(); |
| Map.Entry<Integer, Collection<TypeUsage>> 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<TypeUsage> 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<TypeUsage> resolved = new ArrayList<>(); |
| for (Collection<TypeUsage> unresolved : assignments.values()) { |
| resolved.clear(); |
| JsObject global = ModelUtils.getGlobalObject(parent); |
| for (TypeUsage type : unresolved) { |
| Collection<TypeUsage> resolvedHere = new ArrayList<TypeUsage>(); |
| 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 <JsObject> propertiesCopy = new ArrayList<JsObject>(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<OffsetRange> 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<Occurrence> correctedOccurrences = new ArrayList<Occurrence>(); |
| |
| JsObjectImpl obAssignment = findRightTypeAssignment(getDeclarationName().getOffsetRange().getStart(), global); |
| if (obAssignment != null && !obAssignment.getModifiers().contains(Modifier.PRIVATE)) { |
| obAssignment.addOccurrence(getDeclarationName().getOffsetRange()); |
| } |
| |
| for (Occurrence occurrence : new ArrayList<Occurrence>(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<? extends TypeUsage> protoAssignments = prototype.getAssignments(); |
| if (protoAssignments != null && !protoAssignments.isEmpty()) { |
| protoAssignments = new ArrayList<>(protoAssignments); |
| Collection<? extends JsObject> 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<JsObject> 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<JsObject> findPrototypeChain(JsObject object) { |
| List<JsObject> chain = new ArrayList<JsObject>(); |
| chain.add(object); |
| chain.addAll(findPrototypeChain(object, new HashSet<String>())); |
| return chain; |
| } |
| |
| private static List<JsObject> findPrototypeChain(JsObject object, Set<String> alreadyCheck) { |
| List<JsObject> result = new ArrayList<JsObject>(); |
| 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<TypeUsage> 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<? extends TypeUsage> findedAssignments; |
| JsObject current; |
| JsObject currentParent = this; |
| // save the properties in a list to reuse it later |
| List<String> propertyPath = new ArrayList<String>(); |
| 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 String toString() { |
| // return "JsObjectImpl{" + "declarationName=" + declarationName + ", parent=" + parent + ", kind=" + kind + '}'; |
| // } |
| |
| @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<String>()); |
| } |
| return properties.remove(name) != null; |
| } |
| return false; |
| } |
| |
| private void correctAssignmentsInModel (String fromType, String toType, Set<String> 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 (Integer offset: assignments.keySet()) { |
| Collection<TypeUsage> types = assignments.get(offset); |
| List<TypeUsage> copy = new ArrayList<>(types); |
| String typeR = null; |
| for (TypeUsage type : copy) { |
| 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; |
| } |
| } |