| /* |
| * 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.List; |
| import java.util.Set; |
| 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.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; |
| import org.openide.filesystems.FileObject; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| public class JsFunctionImpl extends DeclarationScopeImpl implements JsFunction { |
| |
| final private HashMap <String, JsObject> parametersByName; |
| final private List<JsObject> parameters; |
| final private Set<TypeUsage> returnTypes; |
| private boolean isStrict; |
| |
| public JsFunctionImpl(DeclarationScope scope, JsObject parentObject, Identifier name, |
| List<Identifier> parameters, OffsetRange offsetRange, String mimeType, String sourceLabel) { |
| super(scope, parentObject, name, offsetRange, mimeType, sourceLabel); |
| this.parametersByName = new HashMap<String, JsObject>(parameters.size()); |
| this.parameters = new ArrayList<JsObject>(parameters.size()); |
| for (Identifier identifier : parameters) { |
| JsObject parameter = new ParameterObject(this, identifier, mimeType, sourceLabel); |
| addParameter(parameter); |
| } |
| setAnonymous(false); |
| this.returnTypes = new HashSet<TypeUsage>(); |
| setDeclared(true); |
| if (parentObject != null) { |
| // creating arguments variable |
| JsObjectImpl arguments = new JsObjectImpl(this, |
| new Identifier(ModelUtils.ARGUMENTS, new OffsetRange(name.getOffsetRange().getStart(), name.getOffsetRange().getStart())), |
| name.getOffsetRange(), false, EnumSet.of(Modifier.PRIVATE), mimeType, sourceLabel); |
| arguments.addAssignment(new TypeUsage("Arguments", getOffset(), true), getOffset()); // NOI18N |
| this.addProperty(arguments.getName(), arguments); |
| } |
| } |
| |
| protected JsFunctionImpl(FileObject file, JsObject parentObject, Identifier name, |
| List<Identifier> parameters, String mimeType, String sourceLabel) { |
| this(null, parentObject, name, parameters, name.getOffsetRange(), mimeType, sourceLabel); |
| this.setFileObject(file); |
| this.setDeclared(false); |
| } |
| |
| private JsFunctionImpl(FileObject file, Identifier name, String mimeType, String sourceLabel) { |
| this(null, null, name, Collections.emptyList(), name.getOffsetRange(), mimeType, sourceLabel); |
| this.setFileObject(file); |
| } |
| |
| public static JsFunctionImpl createGlobal(FileObject fileObject, int length, String mimeType) { |
| String name = fileObject != null ? fileObject.getName() : "VirtualSource"; //NOI18N |
| Identifier ident = new Identifier(name, new OffsetRange(0, length)); |
| return new JsFunctionImpl(fileObject, ident, mimeType, null); |
| } |
| |
| @Override |
| public final Collection<? extends JsObject> getParameters() { |
| return this.parameters; |
| } |
| |
| public final void addParameter(JsObject object) { |
| assert object.getParent() == this; |
| this.parametersByName.put(object.getName(), object); |
| this.parameters.add(object); |
| } |
| |
| @Override |
| public Kind getJSKind() { |
| if (kind != null) { |
| return kind; |
| } |
| if (getParent() == null) { |
| // global function |
| return JsElement.Kind.FILE; |
| } |
| String name = getName(); |
| if (name != null && name.startsWith("get ")) { //NOI18N |
| return JsElement.Kind.PROPERTY_GETTER; |
| } |
| if (name != null && name.startsWith("set ")) { //NOI18N |
| return JsElement.Kind.PROPERTY_SETTER; |
| } |
| if (getParent() != null /*&& getParent() instanceof JsFunction*/) { |
| JsObject prototype = null; |
| for (JsObject property : getProperties().values()) { |
| if (property.isDeclared() |
| && (property.getModifiers().contains(Modifier.PROTECTED) |
| || (property.getModifiers().contains(Modifier.PUBLIC) && !property.getModifiers().contains(Modifier.STATIC))) |
| && !isAnonymous() && !property.isAnonymous() |
| && (property.getDeclarationName() != null && property.getDeclarationName().getOffsetRange().getStart() < property.getDeclarationName().getOffsetRange().getEnd())) { |
| if(!ModelUtils.PROTOTYPE.equals(getParent().getName())) { |
| return JsElement.Kind.CONSTRUCTOR; |
| } |
| } |
| if (ModelUtils.PROTOTYPE.equals(property.getName())) { |
| prototype = property; |
| } |
| } |
| if (prototype != null /*&& !prototype.getProperties().isEmpty()*/) { |
| return JsElement.Kind.CONSTRUCTOR; |
| } |
| } |
| // if (getParent() != null && !getParent().isDeclared()) { |
| // |
| // } |
| |
| JsElement.Kind result = JsElement.Kind.FUNCTION; |
| |
| if (!(getParent() instanceof JsObjectReference) && getParent().getJSKind() != JsElement.Kind.FILE) { |
| result = JsElement.Kind.METHOD; |
| } |
| return result; |
| } |
| |
| @Override |
| public JsObject getParameter(String name) { |
| JsObject result = parametersByName.get(name); |
| return result; |
| } |
| |
| private boolean areReturnTypesResolved = false; |
| |
| @Override |
| public Collection<? extends TypeUsage> getReturnTypes() { |
| if (areReturnTypesResolved) { |
| return Collections.emptyList(); |
| } |
| Collection<TypeUsage> returns = new HashSet<>(); |
| HashSet<String> nameReturnTypes = new HashSet<>(); |
| areReturnTypesResolved = true; |
| for(TypeUsage type : returnTypes) { |
| if (type.isResolved()) { |
| if (!nameReturnTypes.contains(type.getType())){ |
| returns.add(type); |
| nameReturnTypes.add(type.getType()); |
| } |
| } else { |
| if (type.getType().startsWith("@")) { |
| String typeName = type.getType(); |
| if (!(typeName.endsWith(getName()) && typeName.startsWith("@call"))) { |
| Collection<TypeUsage> resolved = ModelUtils.resolveTypeFromSemiType(this, type); |
| for (TypeUsage typeResolved : resolved) { |
| if (!nameReturnTypes.contains(type.getType())) { |
| returns.add(typeResolved); |
| nameReturnTypes.add(typeResolved.getType()); |
| } |
| } |
| } |
| } else { |
| JsObject jsObject = ModelUtils.getJsObjectByName(this,type.getType()); |
| if (jsObject == null) { |
| // try to find according the fqn |
| JsObject global = ModelUtils.getGlobalObject(this); |
| jsObject = ModelUtils.findJsObjectByName(global, type.getType()); |
| } |
| if(jsObject != null) { |
| Collection<TypeUsage> resolveAssignments = resolveAssignments(jsObject, type.getOffset()); |
| for (TypeUsage typeResolved: resolveAssignments) { |
| if (!nameReturnTypes.contains(typeResolved.getType())){ |
| returns.add(typeResolved); |
| nameReturnTypes.add(typeResolved.getType()); |
| } |
| } |
| } else { |
| returns.add(type); |
| nameReturnTypes.add(type.getType()); |
| } |
| } |
| } |
| } |
| areReturnTypesResolved = false; |
| return returns; |
| } |
| |
| @Override |
| public void addReturnType(TypeUsage type) { |
| boolean isThere = false; |
| for (TypeUsage typeUsage : this.returnTypes) { |
| if (type.getType().equals(typeUsage.getType())) { |
| isThere = true; |
| } |
| } |
| if (!isThere){ |
| this.returnTypes.add(type); |
| } |
| } |
| |
| public void addReturnType(Collection<TypeUsage> types) { |
| for (TypeUsage typeUsage : types) { |
| addReturnType(typeUsage); |
| } |
| } |
| |
| public boolean areReturnTypesEmpty() { |
| return returnTypes.isEmpty(); |
| } |
| |
| @Override |
| public boolean moveProperty(String name, JsObject newParent) { |
| JsObject property = getProperty(name); |
| if (property != null && (newParent instanceof DeclarationScope)) { |
| ModelUtils.changeDeclarationScope(property, (DeclarationScope)newParent); |
| } |
| return super.moveProperty(name, newParent); |
| } |
| |
| @Override |
| public void resolveTypes(JsDocumentationHolder docHolder) { |
| super.resolveTypes(docHolder); |
| if (!(returnTypes.size() == 1 && Type.UNDEFINED.equals(returnTypes.iterator().next().getType()))) { |
| HashSet<String> nameReturnTypes = new HashSet<String>(); |
| Collection<TypeUsage> resolved = new ArrayList<>(); |
| for (TypeUsage type : returnTypes) { |
| if (!(type.getType().equals(Type.UNRESOLVED) && returnTypes.size() > 1)) { |
| if (!type.isResolved()) { |
| for (TypeUsage rType : ModelUtils.resolveTypeFromSemiType(this, type)) { |
| if (!nameReturnTypes.contains(rType.getType())) { |
| if ("@this;".equals(type.getType())) { // NOI18N |
| rType = new TypeUsage(rType.getType(), -1, rType.isResolved()); |
| } |
| resolved.add(rType); |
| nameReturnTypes.add(rType.getType()); |
| } |
| } |
| // resolved.addAll(ModelUtils.resolveTypeFromSemiType(this, type)); |
| } else { |
| if (!nameReturnTypes.contains(type.getType())) { |
| resolved.add(type); |
| nameReturnTypes.add(type.getType()); |
| } |
| } |
| } |
| } |
| |
| for (TypeUsage type : resolved) { |
| if (type.getOffset() > 0) { |
| String typeName = type.getType(); |
| JsObject jsObject = null; |
| // at first check whether is not a parameter |
| if (typeName.indexOf('.') == -1) { |
| JsObject parameter = null; |
| DeclarationScope scope = this; |
| while (scope != null && parameter == null && jsObject == null) { |
| if (scope instanceof JsFunction) { |
| parameter = ((JsFunction) scope).getParameter(typeName); |
| } |
| jsObject = ((JsObject) scope).getProperty(typeName); |
| scope = scope.getParentScope(); |
| } |
| if (jsObject == null && parameter != null) { |
| jsObject = parameter; |
| } |
| if (jsObject != null) { |
| jsObject.addOccurrence(new OffsetRange(type.getOffset(), type.getOffset() + typeName.length())); |
| } |
| } |
| if (jsObject == null) { |
| jsObject = ModelUtils.findJsObjectByName(this, typeName); |
| if (jsObject == null) { |
| JsObject global = ModelUtils.getGlobalObject(this); |
| jsObject = ModelUtils.findJsObjectByName(global, typeName); |
| } |
| if (jsObject != null && containsOffset(type.getOffset()) && !getJSKind().equals(JsElement.Kind.FILE)) { |
| int index = typeName.lastIndexOf('.'); |
| int typeLength = (index > -1) ? typeName.length() - index - 1 : typeName.length(); |
| ((JsObjectImpl)jsObject).addOccurrence(new OffsetRange(type.getOffset(), jsObject.isAnonymous() ? type.getOffset() : type.getOffset() + typeLength)); |
| } |
| } |
| } |
| } |
| returnTypes.clear(); |
| returnTypes.addAll(resolved); |
| } else if (getJSKind() == JsElement.Kind.CONSTRUCTOR) { |
| Collection<TypeUsage> resolved = ModelUtils.resolveTypeFromSemiType(this, returnTypes.iterator().next()); |
| returnTypes.clear(); |
| returnTypes.addAll(resolved); |
| } else if (returnTypes.size() == 1) { |
| TypeUsage type = returnTypes.iterator().next(); |
| if (Type.UNDEFINED.equals(type.getType()) && !type.isResolved()) { |
| returnTypes.clear(); |
| returnTypes.add(new TypeUsage(type.getType(), type.getOffset(), true)); |
| } |
| } |
| |
| // parameters and type type resolving for occurrences |
| JsObject global = ModelUtils.getGlobalObject(this); |
| for(JsObject param : parameters) { |
| Collection<? extends TypeUsage> types = param.getAssignmentForOffset(param.getDeclarationName().getOffsetRange().getStart()); |
| for(TypeUsage type: types) { |
| JsObject jsObject = ModelUtils.findJsObjectByName(global, type.getType());//getJsObjectByName(this, type.getType()); |
| if (jsObject != null) { |
| ModelUtils.addDocTypesOccurence(jsObject, docHolder); |
| moveOccurrenceOfProperties((JsObjectImpl)jsObject, param); |
| if (type.getType().indexOf('.') > -1) { |
| // mark occurrences also for the parent if the type is like Contex.Object |
| String[] typeParts = type.getType().split("\\."); |
| JsObject parent = jsObject.getParent(); |
| for (int i = (typeParts.length - 2); i > -1 && parent != null; i--) { |
| if (parent.getName().equals(typeParts[i])) { |
| ModelUtils.addDocTypesOccurence(parent, docHolder); |
| } |
| parent = parent.getParent(); |
| } |
| } |
| } |
| } |
| List<JsObject> paramProperties = new ArrayList<>(param.getProperties().values()); |
| for(JsObject paramProperty: paramProperties) { |
| ((JsObjectImpl)paramProperty).resolveTypes(docHolder); |
| } |
| } |
| } |
| |
| // @Override |
| // public String toString() { |
| // return "JsFunctionImpl{" + "declarationName=" + getDeclarationName() + ", parent=" + getParent() + ", kind=" + kind + ", parameters=" + parameters + ", returnTypes=" + returnTypes + '}'; |
| // } |
| |
| @Override |
| protected void correctTypes(String fromType, String toType) { |
| super.correctTypes(fromType, toType); |
| String typeR; |
| String typeFQN; |
| Set<TypeUsage> copy = new HashSet<TypeUsage>(returnTypes); |
| for (TypeUsage type : copy) { |
| typeFQN = type.getType(); |
| typeR = replaceTypeInFQN(typeFQN, fromType, toType); |
| if (typeR != null) { |
| returnTypes.remove(type); |
| returnTypes.add(new TypeUsage(typeR, type.getOffset(), type.isResolved())); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(getDeclarationName().getName()).append("()"); |
| return sb.toString(); |
| } |
| |
| public boolean isStrict() { |
| return isStrict; |
| } |
| |
| public void setStrict(boolean isStrict) { |
| this.isStrict = isStrict; |
| } |
| |
| } |