/* * * 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.apache.flex.compiler.internal.definitions; import static org.apache.flex.compiler.common.ISourceLocation.UNKNOWN; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.compiler.asdoc.IASDocComment; import org.apache.flex.compiler.common.ASModifier; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.ModifiersSet; import org.apache.flex.compiler.common.NodeReference; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.constants.IMetaAttributeConstants; import org.apache.flex.compiler.constants.INamespaceConstants; import org.apache.flex.compiler.definitions.IAccessorDefinition; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IDocumentableDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.IPackageDefinition; import org.apache.flex.compiler.definitions.IScopedDefinition; import org.apache.flex.compiler.definitions.metadata.IDeprecationInfo; import org.apache.flex.compiler.definitions.metadata.IMetaTag; import org.apache.flex.compiler.definitions.metadata.IMetaTagAttribute; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.common.Counter; import org.apache.flex.compiler.internal.definitions.metadata.DeprecationInfo; import org.apache.flex.compiler.internal.definitions.metadata.MetaTag; import org.apache.flex.compiler.internal.parsing.as.OffsetLookup; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.scopes.ASProjectScope; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.ASScopeBase; import org.apache.flex.compiler.internal.scopes.SWCFileScopeProvider.SWCFileScope; import org.apache.flex.compiler.mxml.IMXMLTagData; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.scopes.IDefinitionSet; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IDefinitionNode; import org.apache.flex.compiler.tree.as.IDocumentableDefinitionNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.units.ICompilationUnit; /** * This class is the abstract base class for all ActionScript definitions in the * symbol table. * <p> * After definitions are added to the symbol table, they should always be * accessed through the read-only definition interfaces (which are * <code>IDefinition</code> and its subinterfaces) rather than through these * implementation classes. * <p> * Note that this class also implements {@link IDefinitionSet} so that a * scope's map can point to a single definition acting as it own * definition-set-of-size-1. */ public abstract class DefinitionBase implements IDocumentableDefinition, IDefinitionSet { // Mask constants for bitflags in the 'flags' field, // a short value that we use in place of multiple boolean fields. protected static final short FLAG_CAST_FUNCTION = 1 << 0; protected static final short FLAG_CONSTRUCTOR = 1 << 1; protected static final short FLAG_DYNAMIC = 1 << 2; protected static final short FLAG_FINAL = 1 << 3; protected static final short FLAG_IMPLICIT = 1 << 4; protected static final short FLAG_NATIVE = 1 << 5; protected static final short FLAG_OVERRIDE = 1 << 6; protected static final short FLAG_REST = 1 << 7; protected static final short FLAG_STATIC = 1 << 8; protected static final short FLAG_DEFAULT = 1 << 9; protected static final short FLAG_CONTINGENT = 1 << 10; protected static final short FLAG_GENERATED_EMBED_CLASS = 1 << 11; protected static final short FLAG_HAS_INIT = 1 << 12; protected static final short FLAG_DEPRECATED = 1 << 13; protected static final short FLAG_DECLARED_IN_CONTROL_FLOW = 1 << 14; /** * Constructor. * * @param name The "constructor name" of the definition. * This is currently in an inconsistent form; for example, * for the <code>flash.display.Sprite</code> class definition * constructed from source code it is <code>"Sprite"</code> * but for the same definition constructed from ABC it is * <code>"flash.display.Sprite"</code>. * The "constructor name" will be converted to a "storage name" * and stored in the <code>storageName</code> field. */ // TODO Fix construction of definitions so that // the storage name is always passed in. public DefinitionBase(String name) { assert name != null; this.storageName = toStorageName(name); if (Counter.COUNT_DEFINITIONS) countDefinitions(); } // The scope that contains this definition. // This field is set when the addDefinition() method of ASScope // calls the package-private setContainingScope() method of this class. private IASScope containingScope; // Bit flags for this definition, such as whether it is implicit. protected short flags; // The namespace reference for this definition. // For most definitions this will be non-null, even if no namespace was specified // in the source code. For example, if the namespace is omitted on a member of a class, // "internal" is implied; therefore this field will be a reference to the internal // namespace for the package containing the class. However, some types of definitions, // such as a for a function parameter, do have a null namespace reference. private INamespaceReference namespaceReference; // The name stored for this definition. See getStorageName() for details. private final String storageName; // The type reference for this definition. See getTypeReference() for details. // Note that this is an IReference, which is basically a reference-by-name // to a definition, which can resolve to different definitions in different projects. private IReference typeReference; // metaTags is never null. As an optimization, it re-uses a singleton empty array when // it has nothing in it private IMetaTag[] metaTags = singletonEmptyMetaTags; protected static final IMetaTag[] singletonEmptyMetaTags = new IMetaTag[0]; // Hold a reference to the node this definition came from // (NodeReference only holds onto the node weakly, so we don't have to worry about leaks). protected NodeReference nodeRef = NodeReference.noReference; private int absoluteNameStart = 0; private int absoluteNameEnd = 0; /** * Called by {@code MXMLScopeBuilder} when building definitions from * {@code MXMLData}. Creates a nodeless {@code NodeReference} for this * definition that keeps track of the file and offset for the MXML tag that * produced this definition. Later, after the MXML tree is produced, this * {@code NodeReference} can be used to locate the appropriate tree node. * * @param tag The {@code MXMLTagData} that is producing this definition. */ public void setLocation(IMXMLTagData tag) { nodeRef = new NodeReference(tag.getSource(), tag.getAbsoluteStart()); } /** * Attaches a weak reference to the source node that produced this * definition, and copies over enough source-location information to * re-associate the node if it is garbage-collected. * * @param node The {@link IDefinitionNode} that is producing * this definition. */ public void setNode(IDefinitionNode node) { if (node == null) { nodeRef = NodeReference.noReference; absoluteNameStart = UNKNOWN; absoluteNameEnd = UNKNOWN; } else { nodeRef = new NodeReference(node); IExpressionNode nameNode = node.getNameExpressionNode(); if (nameNode != null) { absoluteNameStart = nameNode.getAbsoluteStart(); absoluteNameEnd = nameNode.getAbsoluteEnd(); } else { absoluteNameStart = UNKNOWN; absoluteNameEnd = UNKNOWN; } } } /** * This method is used to set the name location when there is no name node * from which it can be determined. */ public void setNameLocation(int absoluteNameStart, int absoluteNameEnd) { this.absoluteNameStart = absoluteNameStart; this.absoluteNameEnd = absoluteNameEnd; } @Override public IASScope getContainingScope() { return containingScope; } public void setContainingScope(IASScope scope) { containingScope = scope; } @Override public IDefinition getParent() { IASScope scope = getContainingScope(); // Walk up the scope chain until we find a scope that has // a containing definition. The following types of scope do // not have an immediate containing definition: // CatchScope's // WithScope's // FunctionScope's for MXML event handler's. while ((scope != null) && (scope.getDefinition() == null)) scope = scope.getContainingScope(); return scope != null ? scope.getDefinition() : null; } @Override public IDefinition getAncestorOfType(Class<? extends IDefinition> ancestorType) { IDefinition definition = getParent(); while (definition != null && !(ancestorType.isInstance(definition))) definition = definition.getParent(); return definition; } // TODO Eliminate this method and its overrides // by constructing the definition with its storage name. protected String toStorageName(String name) { // Most constructors pass in either a base name or a qname, // which we convert to a base name. // However, this method is overridin in DefinitionPromise, // PackageDefinition, and AppliedVectorDefinition to NOT // reduce the constructor name to a base name. int i = name.lastIndexOf('.'); return i == -1 ? name : name.substring(i + 1); } /** * Gets the name that is actually stored for this definition. * <p> * This is not a public API, and is not in the {@link IDefinition} * interface, because different types of definitions store different * types of names. However, the storage name of a definition does * not depend on whether it came from source code or from ABC. * <p> * For class, interface, function, variable, constant, and namespace * definitions -- whose base name and the qualified name can differ * when the definition is at package scope -- the storage name is the * base name (e.g., <code>"Sprite"</code>, * not <code>"flash.display.Sprite"</code>). * <p> * In the case of vector types, the storage name is a base name * like <code>"Vector.<Sprite>"</code> * (not <code>"Vector.<flash.display.Sprite>"</code>), * which is considered a base name despite technically having a dot. * <p> * For parameter definitions -- which cannot be at package scope - * and for event, style, and effect definitions -- which as metadata * don't live in any scope -- there is no distinction between the base * name and the qualified name; the storage name is the same as both * of these. E.g., <code>i</code> for a parameter definition or * <code>"click"</code> for an event definition. * <p> * For package definitions, the storage name is the package name * (e.g., <code>"flash.display"</code>), which is considered * both the base name and the qualified name of a package definition. * <p> * Finally, for definition promises, the storage name is a dotted, * fully-qualified name (e.g., <code>"flash.display.Sprite"</code>, * not <code>"Sprite"</code>). */ protected final String getStorageName() { return storageName; } /** */ public IFileSpecification getFileSpecification() { return nodeRef.getFileSpecification(); } @Override public String getSourcePath() { IFileSpecification fileSpec = getFileSpecification(); return fileSpec != null ? fileSpec.getPath() : null; } private OffsetLookup getOffsetLookup() { final ASFileScope fileScope = getFileScope(); if (fileScope == null) return null; return fileScope.getOffsetLookup(); } @Override public int getStart() { // Because the starting offset is what is used // connect a node to a definition, // we can't just call getStart() on getNode(); // because this leads to deadlock. // Instead, we have to use the starting offset // stored in the NodeReference. if (nodeRef == null) return UNKNOWN; final int absoluteStart = nodeRef.getAbsoluteStart(); if (absoluteStart == UNKNOWN) return UNKNOWN; final OffsetLookup offsetLookup = getOffsetLookup(); if (offsetLookup == null) return absoluteStart; return offsetLookup.getLocalOffset(absoluteStart); } @Override public int getEnd() { final IDefinitionNode node = getNode(); if (node == null) return UNKNOWN; return node.getEnd(); } @Override public int getLine() { final IDefinitionNode node = getNode(); if (node == null) return UNKNOWN; return node.getLine(); } @Override public int getColumn() { final IDefinitionNode node = getNode(); if (node == null) return UNKNOWN; return node.getColumn(); } @Override public int getAbsoluteStart() { // Because the starting offset is what is used // connect a node to a definition, // we can't just call getAbsoluteStart() on getNode(); // because this leads to deadlock. // Instead, we have to use the starting offset // stored in the NodeReference. if (nodeRef == null) return UNKNOWN; return nodeRef.getAbsoluteStart(); } @Override public int getAbsoluteEnd() { final IDefinitionNode node = getNode(); if (node == null) return UNKNOWN; return node.getAbsoluteEnd(); } private IExpressionNode getNameNode() { IDefinitionNode node = getNode(); if (node == null) return null; return node.getNameExpressionNode(); } @Override public int getNameStart() { if (absoluteNameStart == UNKNOWN) return UNKNOWN; OffsetLookup offsetLookup = getOffsetLookup(); if (offsetLookup == null) return absoluteNameStart; return offsetLookup.getLocalOffset(absoluteNameStart); } @Override public int getNameEnd() { if (absoluteNameEnd == UNKNOWN) return UNKNOWN; OffsetLookup offsetLookup = getOffsetLookup(); if (offsetLookup == null) return absoluteNameEnd; return offsetLookup.getLocalOffset(absoluteNameEnd); } /** * Get line number for the name of this definition, if it can be determined. * * @return the line number, or -1 if we couldn't figure it out */ @Override public int getNameLine() { final IExpressionNode nameNode = getNameNode(); if (nameNode == null) return UNKNOWN; return nameNode.getLine(); } /** * Get column number for the name of this definition, if it can be * determined. * * @return the column number, or -1 if we couldn't figure it out */ @Override public int getNameColumn() { final IExpressionNode nameNode = getNameNode(); if (nameNode == null) return UNKNOWN; return nameNode.getColumn(); } @Override public String getContainingFilePath() { ASFileScope fileScope = getFileScope(); if (fileScope == null) return null; return fileScope.getContainingPath(); } @Override public String getContainingSourceFilePath(final ICompilerProject project) { // If node reference is set, find the source file name from // offset lookup if (nodeRef != NodeReference.noReference) { final ASFileScope fileScope = getFileScope(); if (fileScope != null && fileScope.getOffsetLookup() != null) { return fileScope.getOffsetLookup().getFilename(getAbsoluteStart()); } } ASScope containingScope = (ASScope)getContainingScope(); if (containingScope != null) return containingScope.getContainingSourcePath(getQualifiedName(), project); return null; } private static String namespaceReferenceToPackageName(INamespaceReference nsRef) { if (nsRef instanceof NamespaceDefinition.INamespaceWithPackageName) return ((NamespaceDefinition.INamespaceWithPackageName)nsRef).getNamespacePackageName(); return null; } /** * * @param definition is the definition whose containing top level definition we want * @return the top level deinition, or null if none exists. */ private static DefinitionBase getContainingToplevelDefinition(DefinitionBase definition) { ASScope currentContainingScope = definition.getContainingASScope(); DefinitionBase currentDefinition = definition; IScopedDefinition containingDefinition = currentContainingScope.getContainingDefinition(); while (containingDefinition != null) { currentDefinition = (DefinitionBase)containingDefinition; currentContainingScope = currentDefinition.getContainingASScope(); // With some synthetic definitions you can't find a containint top level definition. // This happens with Vector<T>, for example. In this case, return null if (currentContainingScope == null) return null; containingDefinition = currentContainingScope.getContainingDefinition(); } assert currentDefinition != null; return currentDefinition; } @Override public String getPackageName() { // Ugghhh!! This method is supposed to return the fully qualified name // of the package that contains this definition. The method works even // if this definition is not a top level definition. // Sub-classes that will never have a containing scope ( like DefinitionPromise's ) // should overload this method. assert getContainingScope() != null : "This method should not be called until we have a containing scope.!"; // If we get here, we have a containing scope, so we can just walk the scope // chain until we get to a definition that is a direct child of a package or file // scope. DefinitionBase containingToplevelDefinition = getContainingToplevelDefinition(this); if (containingToplevelDefinition == null) { // If there is no containing top level definition they return the top level package. // In the case of member functions of the Vector<T> class, this will be correct return ""; } assert (!(containingToplevelDefinition instanceof FunctionDefinition)) || (!((FunctionDefinition)containingToplevelDefinition).isConstructor()) : "Constructors should always be contained by class definitions!"; INamespaceReference toplevelDefinitionNSRef = containingToplevelDefinition.getNamespaceReference(); assert toplevelDefinitionNSRef != null; String packageNameFromNSRef = namespaceReferenceToPackageName(toplevelDefinitionNSRef); if (packageNameFromNSRef != null) return packageNameFromNSRef; assert getBaseName().indexOf('.') == -1; return ""; } @Override public String getBaseName() { // Most definitions store their base name, so this base method // simply returns it. However, a few subclasses override this. return storageName; } @Override public String getQualifiedName() { // For top-level definitions, report the package name concatenated // with the base name. If it's not in a package (e.g. a file private // definition), then the package name will be "". if (isTopLevelDefinition()) { String packageName = getPackageName(); if (packageName != null && !packageName.isEmpty()) return packageName + "." + storageName; } return storageName; } /** * Is this definition a toplevel definition * * @return true if this definition is declared at file scope, or package * scope. */ public boolean isTopLevelDefinition() { return getParent() instanceof IPackageDefinition || getParent() == null; } @Override public boolean isDynamic() { return (flags & FLAG_DYNAMIC) != 0; } public void setDynamic() { flags |= FLAG_DYNAMIC; } @Override public boolean isFinal() { return (flags & FLAG_FINAL) != 0; } public void setFinal() { flags |= FLAG_FINAL; } @Override public boolean isNative() { return (flags & FLAG_NATIVE) != 0; } public void setNative() { flags |= FLAG_NATIVE; } @Override public boolean isOverride() { return (flags & FLAG_OVERRIDE) != 0; } public void setOverride() { flags |= FLAG_OVERRIDE; } public void unsetOverride() { if (isOverride()) flags -= FLAG_OVERRIDE; } @Override public boolean isStatic() { return (flags & FLAG_STATIC) != 0; } public void setStatic() { flags |= FLAG_STATIC; } @Override public boolean hasModifier(ASModifier modifier) { // Note: These get checked in decreasing order of frequency of use. if (modifier == ASModifier.OVERRIDE) { return (flags & FLAG_OVERRIDE) != 0; } else if (modifier == ASModifier.STATIC) { return (flags & FLAG_STATIC) != 0; } else if (modifier == ASModifier.FINAL) { return (flags & FLAG_FINAL) != 0; } else if (modifier == ASModifier.DYNAMIC) { return (flags & FLAG_DYNAMIC) != 0; } else if (modifier == ASModifier.NATIVE) { return (flags & FLAG_NATIVE) != 0; } else if (modifier == ASModifier.VIRTUAL) { // Ignore "virtual" modifier. return false; } else { assert false : "Unknown modifier: " + modifier; return false; } } @Override public ModifiersSet getModifiers() { ModifiersSet result = new ModifiersSet(); if ((flags & FLAG_OVERRIDE) != 0) result.addModifier(ASModifier.OVERRIDE); if ((flags & FLAG_STATIC) != 0) result.addModifier(ASModifier.STATIC); if ((flags & FLAG_FINAL) != 0) result.addModifier(ASModifier.FINAL); if ((flags & FLAG_DYNAMIC) != 0) result.addModifier(ASModifier.DYNAMIC); if ((flags & FLAG_NATIVE) != 0) result.addModifier(ASModifier.NATIVE); return result; } public void setModifier(ASModifier modifier) { // Note: These get checked in decreasing order of frequency of use. if (modifier == ASModifier.OVERRIDE) flags |= FLAG_OVERRIDE; else if (modifier == ASModifier.STATIC) flags |= FLAG_STATIC; else if (modifier == ASModifier.FINAL) flags |= FLAG_FINAL; else if (modifier == ASModifier.DYNAMIC) flags |= FLAG_DYNAMIC; else if (modifier == ASModifier.NATIVE) flags |= FLAG_NATIVE; else assert false; } @Override public boolean isPublic() { return namespaceReference instanceof INamespaceDefinition.IPublicNamespaceDefinition; } @Override public boolean isPrivate() { return namespaceReference instanceof INamespaceDefinition.IPrivateNamespaceDefinition; } @Override public boolean isProtected() { return namespaceReference instanceof INamespaceDefinition.IProtectedNamespaceDefinition; } @Override public boolean isInternal() { return namespaceReference instanceof INamespaceDefinition.IInternalNamespaceDefinition; } @Override public boolean hasNamespace(INamespaceReference namespace, ICompilerProject project) { // If the namespaces being compared are language namespaces // (which have singleton NamespaceReferences) // then we can just compare the NamespaceReferences. if (namespace.isLanguageNamespace() && this.namespaceReference.isLanguageNamespace()) { return namespace == this.namespaceReference; } // Otherwise, the namespaces are equivalent if they resolve to // equivalent namespaceReference definitions (i.e., two with the same URI). INamespaceDefinition ns1 = namespace.resolveNamespaceReference(project); INamespaceDefinition ns2 = this.namespaceReference.resolveNamespaceReference(project); return ((ns1 != null && ns2 != null) ? ns1.equals(ns2) : false); } @Override public INamespaceReference getNamespaceReference() { return namespaceReference; } public void setNamespaceReference(INamespaceReference value) { namespaceReference = value; NamespaceDefinition.setContainingDefinitionOfReference(value, this); } public void setPublic() { setNamespaceReference(NamespaceDefinition.getPublicNamespaceDefinition()); } /** * Utility to mark a definition as bindable. This method should only ever be * called during construction or initialization of a definition. */ public void setBindable() { MetaTag bindableMetaTag = new MetaTag(this, IMetaAttributeConstants.ATTRIBUTE_BINDABLE, new IMetaTagAttribute[0]); addMetaTag(bindableMetaTag); } @Override public INamespaceDefinition resolveNamespace(ICompilerProject project) { return namespaceReference != null ? namespaceReference.resolveNamespaceReference(project) : null; } @Override public String getTypeAsDisplayString() { return typeReference != null ? typeReference.getDisplayString() : ""; } @Override public IReference getTypeReference() { return typeReference; } /** * Sets the type reference for this definition. * * @param typeReference An [@Link IReference} to a class or interface. */ public void setTypeReference(IReference typeReference) { this.typeReference = typeReference; } /** * Gets the {@link DependencyType} that should be used when resolving the * type of this definition. * <p> * This method is intended to be overridden by sub-classes. * * @return The {@link DependencyType} that should be used when resolving the * type of this variable definition */ protected DependencyType getTypeDependencyType() { return DependencyType.SIGNATURE; } @Override public TypeDefinitionBase resolveType(ICompilerProject project) { DependencyType dt = getTypeDependencyType(); return resolveType(typeReference, project, dt); } @Override public boolean isImplicit() { return (flags & FLAG_IMPLICIT) != 0; } public void setImplicit() { flags |= FLAG_IMPLICIT; } @Override public IMetaTag[] getAllMetaTags() { return metaTags; } protected void addMetaTag(IMetaTag metaTag) { IMetaTag[] newMetaTags = new IMetaTag[metaTags.length + 1]; System.arraycopy(metaTags, 0, newMetaTags, 0, metaTags.length); newMetaTags[metaTags.length] = metaTag; setMetaTags(newMetaTags); } public void setMetaTags(IMetaTag[] newMetaTags) { if (newMetaTags == null) { metaTags = singletonEmptyMetaTags; } else { this.metaTags = newMetaTags; // Use a flag bit to keep track of whether there is a [Deprecated] tag. for (IMetaTag metaTag : metaTags) { String tagName = metaTag.getTagName(); if (tagName.equals(IMetaAttributeConstants.ATTRIBUTE_DEPRECATED)) setDeprecated(); } } } @Override public IMetaTag[] getMetaTagsByName(String name) { // We should never return null from this function - callers do not expect it. // So allocate an empty array here, even if metaTags is null List<IMetaTag> list = new ArrayList<IMetaTag>(); for (IMetaTag tag : metaTags) { if (tag.getTagName().equals(name)) list.add(tag); } return list.toArray(new IMetaTag[0]); } @Override public boolean hasMetaTagByName(String name) { return getMetaTagByName(name) != null; } @Override public IMetaTag getMetaTagByName(String name) { for (IMetaTag tag : metaTags) { if (tag.getTagName().equals(name)) return tag; } return null; } @Override public IASDocComment getExplicitSourceComment() { IDefinitionNode node = getNode(); if (node instanceof IDocumentableDefinitionNode) return ((IDocumentableDefinitionNode)node).getASDocComment(); return null; } @Override public boolean hasExplicitComment() { IDefinitionNode node = getNode(); if (node instanceof IDocumentableDefinitionNode) return ((IDocumentableDefinitionNode)node).hasExplicitComment(); return false; } public ASFileScope getFileScope() { ASScope s = getContainingASScope(); if (s == null) return null; return s.getFileScope(); } @Override public IDefinitionNode getNode() { // If this definition didn't come from source, return null. if (nodeRef == NodeReference.noReference) return null; ASScope containingScope = getContainingASScope(); if (containingScope == null) return null; ASFileScope fileScope = containingScope.getFileScope(); if (fileScope == null) return null; IASNode node = nodeRef.getNode(fileScope.getWorkspace(), getContainingASScope()); if (!(node instanceof IDefinitionNode)) return null; return (IDefinitionNode)node; } /** * Static version of {@code resolveType()} to help sub-classes implement * their overrides of {@link #resolveType(String, ICompilerProject, DependencyType)}. * * @param context {@link DefinitionBase} relative to which the resolve should * be done. */ protected static TypeDefinitionBase resolveType(DefinitionBase context, String typeName, ICompilerProject project, DependencyType dt) { // TODO - should probably return the Definition for "*" once we have an easy way to get that // TODO - definition - this works for now to prevent NPEs if (typeName == null) return null; CompilerProject compilerProject = (CompilerProject)project; ASScope containingScope = (ASScope)(context.containingScope); // TODO at some point this method should take some sort of name object. if (containingScope != null) { int lastIndexOfDot = typeName.lastIndexOf('.'); IDefinition foundDefinition = null; if (lastIndexOfDot != -1) { String unqualifiedName = typeName.substring(lastIndexOfDot + 1); String packageName = typeName.substring(0, lastIndexOfDot); INamespaceDefinition packageNS = ((CompilerProject)project).getWorkspace().getPackageNamespaceDefinitionCache().get(packageName, false); foundDefinition = containingScope.findPropertyQualified(compilerProject, packageNS, unqualifiedName, dt, true); } else { foundDefinition = containingScope.findProperty(compilerProject, typeName, dt, true); } assert (foundDefinition == null) || foundDefinition.isInProject(project); if (foundDefinition instanceof TypeDefinitionBase) return (TypeDefinitionBase)foundDefinition; } return null; } protected static TypeDefinitionBase resolveType(DefinitionBase context, IReference typeRef, ICompilerProject project, DependencyType dt) { // No type means '*' if (typeRef == null) return (TypeDefinitionBase)project.getBuiltinType(IASLanguageConstants.BuiltinType.ANY_TYPE); IDefinition foundDefinition = typeRef.resolve(project, (ASScope)context.containingScope, dt, true); assert (foundDefinition == null) || foundDefinition.isInProject(project); return foundDefinition instanceof TypeDefinitionBase ? (TypeDefinitionBase)foundDefinition : null; } /** * This helper method resolves type references in definitions (i.e., Strings * such as "int", "Sprite", or "flash.events.IEventDispatcher" that are * stored as references to a class or interface) to the definition of the * specified type. * <p> * It is called by * <ul> * <li><code>resolveType()</code> in DefinitionBase to resolve a type * annotation such as the "int" on a declaration like * <code>public var foo:int</code>;</li> * <li><code>resolveReturnType()</code> in FunctionDefinition to resolve the * return type of a function declaration;</li> * <li><code>resolveBaseClass()</code> in ClassDefinition to resolve * <code>extends</code> clause of a class declaration;</li> * <li><code>resolveImplementedInterfaces()</code> in ClassDefinition to * resolve the <code>implements</code> clause of a class declaration;</li> * <li><code>resolveTypeParameters()</code> in ParameterizedClassDefinition * to resolve the type of a Vector;</li> * <li><code>resolveExtendedInterfaces()</code> in InterfaceDefinition to * resolve the <code>extends</code> clause of an interface declaration.</li> * </ul> * * @param typeName The name of the type to resolve. * @param project The compiler project. * @param dt The type of dependency to be created. * @return The definition of the resolved type. */ protected TypeDefinitionBase resolveType(String typeName, ICompilerProject project, DependencyType dt) { return resolveType(this, typeName, project, dt); } public TypeDefinitionBase resolveType(IReference typeRef, ICompilerProject project, DependencyType dt) { return resolveType(this, typeRef, project, dt); } protected String getLocationString() { StringBuilder sb = new StringBuilder(); int start = getStart(); int end = getEnd(); if (start != -1 && end != -1) { sb.append(start); sb.append('-'); sb.append(end); } sb.append(' '); String path = getContainingFilePath(); if (path != null) sb.append(path); return sb.toString(); } /** * Generate an AET Name object for this definition, and log any Problems * encountered while constructing the Name, to the list of problems passed * in. * * @param project the Project to use when resolving the Definitions * Namespace * @return An AET Name object that represents the qualified name of this * definition. Will return null if errors are encountered while constructing * the name (such as, the Namespace of the Definition is unresolved). */ public Name getMName(ICompilerProject project) { Name name = null; Namespace qual = null; if (namespaceReference != null) { qual = namespaceReference.resolveAETNamespace(project); } else { assert false : "All definitions should have a namespaceReference qualifier."; ASScope scope = getContainingASScope(); if (scope != null) qual = ((NamespaceDefinition)NamespaceDefinition.getDefaultNamespaceDefinition(scope)).getAETNamespace(); } // if we can't figure out the namespaceReference, we can't generate a valid name if (qual != null) name = new Name(ABCConstants.CONSTANT_Qname, new Nsset(qual), getBaseName()); return name; } protected final ASScope getContainingASScope() { IASScope s = getContainingScope(); ASScope scope = s instanceof ASScope ? (ASScope)s : null; return scope; } protected String getNamespaceReferenceAsString() { INamespaceReference namespaceReference = getNamespaceReference(); return namespaceReference != null ? namespaceReference.toString() : INamespaceConstants.internal_; } /** * * @return false if and only if we can reject the match due to one or the other or both * being vectors. */ private boolean checkVectorMatch(DefinitionBase node) { IASScope thisScope = getContainingScope(); IASScope nodeScope = node.getContainingScope(); if (thisScope != null && nodeScope != null) { IScopedDefinition thisDef = thisScope.getDefinition(); IScopedDefinition nodeDef = nodeScope.getDefinition(); if (thisDef instanceof AppliedVectorDefinition && nodeDef instanceof AppliedVectorDefinition) { String thisElementQName = ((AppliedVectorDefinition)thisDef).getQualifiedName(); String nodeElementQName = ((AppliedVectorDefinition)nodeDef).getQualifiedName(); if (thisElementQName==null && nodeElementQName==null) return true; // I don't know if this can happen, but it does then they "match" if (thisElementQName == null) return false; return thisElementQName.equals(nodeElementQName); } } return true; // we could not reject this match due to vector specific issues } public boolean matches(DefinitionBase node) { if (node == null) return false; if (node.getClass() != getClass()) return false; if (node == this) return true; INamespaceReference namespace = getNamespaceReference(); if (namespace == null && node.getNamespaceReference() != null) return false; if (namespace != null && namespace.getBaseName().compareTo(node.getNamespaceReference().getBaseName()) != 0) { return false; } if (node.getBaseName().compareTo(getBaseName()) != 0) return false; if (!checkVectorMatch(node)) return false; String packageName = node.getPackageName(); String packageName2 = getPackageName(); if (packageName == null && packageName2 != null) return false; if (packageName != null && packageName2 != null && packageName.compareTo(packageName2) != 0) return false; ASScope leftScope = node.getContainingASScope(); if (leftScope != null) { leftScope = leftScope.getFileScope(); } ASScope rightScope = getContainingASScope(); if (rightScope != null) { rightScope = rightScope.getFileScope(); } if (leftScope instanceof SWCFileScope || rightScope instanceof SWCFileScope) { return true; //we can't verify path because we might be a definition from a library } else if (leftScope instanceof ASFileScope && rightScope instanceof ASFileScope) { if (((ASFileScope)leftScope).getContainingPath().compareTo(((ASFileScope)rightScope).getContainingPath()) != 0) { return false; } } else { //For normal case, just compare the full containing file path //Note that for some synthetic definitions (like Vector), this path will be null String nodePath = node.getContainingFilePath(); String thisPath = this.getContainingFilePath(); if (nodePath == null) { if (thisPath != null) return false; } else if (nodePath.compareTo(thisPath) != 0) return false; } if (node.getTypeAsDisplayString().compareTo(getTypeAsDisplayString()) != 0) return false; return true; } /** * Whether this definition was specified as "Bindable" in its metadata */ private boolean isBindableLocally() { return getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE) != null; } /** * Whether this definition was specified as "Bindable" in its metadata, or * inherits bindability from the containing class * * @return true if this definition is Bindable. */ @Override public boolean isBindable() { // A variable is considered bindable if it is marked bindable, // or if it is a public var in a class marked bindable boolean bindable = isBindableLocally(); if (!bindable) { IDefinition containing = getParent(); if (containing instanceof IClassDefinition && this.getNamespaceReference() == NamespaceDefinition.getPublicNamespaceDefinition()) { DefinitionBase containingBase = (DefinitionBase)containing; bindable = containingBase.isBindableLocally(); } } return bindable; } /** * @return a collection of Bindable event names, if any were specified in * the metadata, as well as any inherited from the class */ @Override public List<String> getBindableEventNames() { // First get any from this definition // Use a Set<> temporarily, so we can do an easy union Set<String> eventNames = new HashSet<String>(getBindableEventNamesLocally()); // Now "or" in any from the containing class IDefinition containing = getParent(); if (containing instanceof IClassDefinition && this.getNamespaceReference() == NamespaceDefinition.getPublicNamespaceDefinition()) { DefinitionBase containingBase = (DefinitionBase)containing; eventNames.addAll(containingBase.getBindableEventNamesLocally()); } // Convert to list, as per the return type in IDefinition return new ArrayList<String>(eventNames); } /** * @return a collection of Bindable event names, if any were specified in * the metadata */ private List<String> getBindableEventNamesLocally() { IMetaTag[] bindableTags = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE); if (bindableTags != null) { List<String> events = new ArrayList<String>(); for (IMetaTag bindableTag : bindableTags) { String eventName = bindableTag.getAttributeValue(IMetaAttributeConstants.NAME_BINDABLE_EVENT); if (eventName == null && bindableTag.getAllAttributes().length == 1) { Boolean isStyle = false; eventName = bindableTag.getAllAttributes()[0].getKey(); if (eventName != null && eventName.equals("style")) isStyle = true; eventName = bindableTag.getAllAttributes()[0].getValue(); if (isStyle && eventName.equals("true")) eventName = "isStyle"; // hack: fake event name "isStyle" } if (eventName != null) events.add(eventName); } return events; } return Collections.emptyList(); } /** * @return a collection of Bindable event names, if any were specified in * the metadata */ @Override public boolean isBindableStyle() { IMetaTag[] bindableTags = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE); if (bindableTags != null) { for (IMetaTag bindableTag : bindableTags) { String style = bindableTag.getAttributeValue(IMetaAttributeConstants.NAME_BINDABLE_STYLE); if (style != null && IASLanguageConstants.TRUE.equals(style)) return true; } } return false; } @Override public boolean isContingent() { return (flags & FLAG_CONTINGENT) != 0; } /** * Marks the definition as contingent. Meaning that it's added to the symbol * table as a placeholder, but codegen will decide whether or not it needs * to actually be created. */ protected void setContingent() { flags |= FLAG_CONTINGENT; } @Override public boolean isGeneratedEmbedClass() { return (flags & FLAG_GENERATED_EMBED_CLASS) != 0; } /** * Marks a class definition as being auto-generated for an embedded asset * (i.e., one created for a <code>var</code> with <code>[Embed(...)]</code> * metadata). * <p> * Adding such a class to the project scope in the middle of a compilation * will not cause the <code>validImports</code> set to be rebuilt. */ public void setGeneratedEmbedClass() { flags |= FLAG_GENERATED_EMBED_CLASS; } @Override public boolean isContingentNeeded(ICompilerProject project) { return ((ASScopeBase)getContainingScope()).isContingentDefinitionNeeded(project, this); } protected IMetaTag getSkinPart() { return getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_SKIN_PART); } protected boolean isRequiredSkinPart(IMetaTag skinPart) { // if the required value is not set, it's not required, as skin parts are // optional by default. String isRequired = skinPart.getAttributeValue(IMetaAttributeConstants.NAME_SKIN_PART_REQUIRED); if (isRequired == null) return false; if (!isRequired.equals(IMetaAttributeConstants.VALUE_SKIN_PART_REQUIRED_TRUE)) return false; return true; } /** * Returns the type specified by <code>[ArrayElementType("...")]</code> * metadata on a variable/getter/setter of type Array. </p> This method is * in DefinitionBase because it is common to VariableDefinition, * GetterDefinition, and SetterDefinition. */ public String getArrayElementType(FlexProject project) { if (getTypeAsDisplayString().equals(IASLanguageConstants.Array)) { return getPropertyMetaTagValue( project, IMetaAttributeConstants.ATTRIBUTE_ARRAYELEMENTTYPE); } return null; } /** * Returns the type specified by <code>[InstanceType("...")]</code> metadata * on a variable/getter/setter of type mx.core.IDeferredInstance. * <p> * This method is in DefinitionBase because it is common to * VariableDefinition, GetterDefinition, and SetterDefinition. */ public String getInstanceType(FlexProject project) { if (getTypeAsDisplayString().equals(project.getDeferredInstanceInterface())) { return getPropertyMetaTagValue( project, IMetaAttributeConstants.ATTRIBUTE_INSTANCETYPE); } return null; } /** * Returns the property name specified by <code>[PercentProxy("...")]</code> * metadata on a variable/getter/setter. </p> This method is in * DefinitionBase because it is common to VariableDefinition, * GetterDefinition, and SetterDefinition. */ public String getPercentProxy(FlexProject project) { IMetaTag metaTag = getPropertyMetaTag( project, IMetaAttributeConstants.ATTRIBUTE_PERCENT_PROXY); return metaTag != null ? metaTag.getValue() : null; } /** * Returns <code>true</code> if there is <code>[RichTextContent]</code> * metadata on a variable/getter/setter. * <p> * This method is in DefinitionBase because it is common to * VariableDefinition, GetterDefinition, and SetterDefinition. */ public boolean hasRichTextContent(FlexProject project) { IMetaTag metaTag = getPropertyMetaTag( project, IMetaAttributeConstants.ATTRIBUTE_RICHTEXTCONTENT); return metaTag != null; } /** * Returns <code>true</code> if there is <code>[CollapseWhiteSpace]</code> * metadata on a variable/getter/setter. * <p> * This method is in DefinitionBase because it is common to * VariableDefinition, GetterDefinition, and SetterDefinition. */ public boolean hasCollapseWhiteSpace(FlexProject project) { IMetaTag metaTag = getPropertyMetaTag( project, IMetaAttributeConstants.ATTRIBUTE_COLLAPSEWHITESPACE); return metaTag != null; } /** * Returns <code>true</code> if there is <code>[Inspectable(...)]</code> * metadata on a vairable/getter/setter that specifies * <code>format="Color"</code>. * <p> * This method is in DefinitionBase because it is common to * VariableDefinition, GetterDefinition, and SetterDefinition. */ public boolean isColor(FlexProject project) { IMetaTag metaTag = getPropertyMetaTag( project, IMetaAttributeConstants.ATTRIBUTE_INSPECTABLE); if (metaTag == null) return false; String format = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_INSPECTABLE_FORMAT); return format.equals(IMetaAttributeConstants.VALUE_INSPECTABLE_FORMAT_COLOR); } private IMetaTag getPropertyMetaTag(FlexProject project, String metadataName) { // Look for metadata with the specified name. IMetaTag metaTag = getMetaTagByName(metadataName); // If we don't find it, and we're a getter or setter, // look on the corresponding setter or getter. if (metaTag == null && this instanceof IAccessorDefinition) { IAccessorDefinition correspondingAccessor = ((IAccessorDefinition)this).resolveCorrespondingAccessor(project); if (correspondingAccessor != null) metaTag = correspondingAccessor.getMetaTagByName(metadataName); } return metaTag; } /** * This helper method is called by {@code getArrayElementType()} and * {@code getInstanceType()}. */ private String getPropertyMetaTagValue(FlexProject project, String metadataName) { // Look for metadata with the specified name. IMetaTag metaTag = getPropertyMetaTag(project, metadataName); // If we found the metadata, return the type that it specifies. // There should be one keyless attribute, and we want its value. return metaTag != null ? metaTag.getValue() : null; } /** * Determines if this definition is in a package namespaceReference. This is here, so * that the various sub-classes can call it from get*Classification() * methods. * * @return true, if the namespaceReference for this definition is some kind of * package namespaceReference. */ protected boolean inPackageNamespace() { INamespaceReference nsRef = getNamespaceReference(); assert nsRef != null : "All definitions should have a namespaceReference reference!"; if (nsRef.isPublicOrInternalNamespace()) return true; return false; } @Override public boolean isDeprecated() { return (flags & FLAG_DEPRECATED) != 0; } /** * Sets a bitflag to indicate that this definition has [Deprecated] metadata. * Checking this flag is faster than walking through the metadata multiple times. */ private void setDeprecated() { flags |= FLAG_DEPRECATED; } @Override public IDeprecationInfo getDeprecationInfo() { for (IMetaTag metaTag : metaTags) { if (metaTag.getTagName().equals(IMetaAttributeConstants.ATTRIBUTE_DEPRECATED)) { String replacement = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_REPLACEMENT); String since = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_SINCE); String message = metaTag.getAttributeValue(IMetaAttributeConstants.NAME_DEPRECATED_MESSAGE); return new DeprecationInfo(replacement, since, message); } } return null; } /** * Used only in asserts. */ public boolean verify() { // Verify the name. assert getBaseName() != null : "Definition has null name"; // TODO: Verify the source location eventually. // assert getSourcePath() != null : "Definition has null source path"; // assert getStart() != UNKNOWN : "Definition has unknown start"; // assert getEnd() != UNKNOWN : "Definition has unknown end"; // assert getLine() != UNKNOWN : "Definition has unknown line"; // assert getColumn() != UNKNOWN : "Definition has unknown column"; return true; } /** * Used only for debugging. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); buildString(sb, false); return sb.toString(); } /** * Used only for debugging, as part of {@link #toString()}. */ public void buildString(StringBuilder sb, boolean withLocation) { buildInnerString(sb); if (withLocation) { sb.append(' '); sb.append(getLocationString()); } } /** * Used only for debugging, as part of {@link #toString()}. */ protected void buildInnerString(StringBuilder sb) { } /** * Counts various types of definitions that are created, * as well as the total number of definitions. */ private void countDefinitions() { Counter counter = Counter.getInstance(); counter.incrementCount(getClass().getSimpleName()); counter.incrementCount("definitions"); } // // IDefinitionSet methods // @Override public boolean isEmpty() { return false; } @Override public int getSize() { return 1; } @Override public int getMaxSize() { return 1; } @Override public IDefinition getDefinition(int i) { assert i == 0; return this; } @Override public boolean isInProject(ICompilerProject project) { final ICompilationUnit compilationUnit = ((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(this); if (compilationUnit != null) { if (compilationUnit.getProject() == project) return true; else return false; } if (AppliedVectorDefinition.isVectorScope(getContainingASScope())) { IDefinition parentDef = getParent(); return parentDef.isInProject(project); } return false; } }