/* * * 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 java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.tree.as.ExpressionNodeBase; import com.google.common.base.Predicate; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import org.apache.commons.io.FilenameUtils; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.constants.INamespaceConstants; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.IPackageDefinition; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.PackageScope; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.FunctionObjectNode; import org.apache.flex.compiler.internal.tree.as.QualifiedNamespaceExpressionNode; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.INamespaceDecorationNode; import org.apache.flex.compiler.tree.as.INamespaceNode; import org.apache.flex.utils.StringEncoder; /** * Instances of this class represent definitions of ActionScript namespaces in * the symbol table. * <p> * The interfaces may have been declared with the <code>namespace</code> keyword * or may be ones that are implicit in the ActionScript language. * <p> * After a namespace definition is in the symbol table, it should always be * accessed through the read-only <code>INamespaceDefinition</code> interface. */ public abstract class NamespaceDefinition extends DefinitionBase implements INamespaceDefinition, INamespaceReference { private static PublicNamespaceDefinition PUBLIC = new PublicNamespaceDefinition(); private static UserDefinedNamespaceDefinition AS3 = new UserDefinedNamespaceDefinition(INamespaceConstants.AS3, INamespaceConstants.AS3URI); private static ICodeModelImplicitDefinitionNamespaceDefinition CM_IMPLICIT_DEF_NS = new CodeModelImplicitDefinitionNamespaceDefinition(); /** * Namespace definition to represent the any namespace - '*' */ private static AnyNamespaceDefinition ANY = new AnyNamespaceDefinition(); /** * Gets the single public namespace definition for the whole process. * * @return The single public namespace definition for the whole process. */ public static IPublicNamespaceDefinition getPublicNamespaceDefinition() { return PUBLIC; } public static INamespaceDefinition getAS3NamespaceDefinition() { return AS3; } public static INamespaceReference getAS3NamespaceReference() { return AS3; } public static INamespaceReference getAnyNamespaceReference() { return ANY; } /** * We have a single global private namespace, in which we can place implicit * code model definitions that only exist for code model compatibility. this * and super definitions are in this namespace. * * @return An {@code INamespaceDefinition.ICodeModelImplicitDefinitionNamespaceDefinition } for implicit code model * definitions. */ public static ICodeModelImplicitDefinitionNamespaceDefinition getCodeModelImplicitDefinitionNamespace() { return CM_IMPLICIT_DEF_NS; } public static NamespaceDefinition createNamespaceDefinition(Namespace ns) { // Pass in null since that can't be a valid user defined namespace. return createNamespaceDefinition(null, ns); } /** * @param name name of the namespace definition if the namespace was * specified in source code or was read from a const slot in an ABC, null * otherwise. * @param ns An ABC namespace. * @return a namespace definition */ public static NamespaceDefinition createNamespaceDefinition(String name, Namespace ns) { int namespaceKind = ns.getKind(); switch (namespaceKind) { case ABCConstants.CONSTANT_PackageNs: if (ns.getName().length() == 0) return PUBLIC; return new PublicNamespaceDefinition(ns); case ABCConstants.CONSTANT_PackageInternalNs: // "namespace foo;" makes a package internal name in an ABC, // but we need to make a UserDefinedNamespace so the // NamespaceDefinition will have the right base name. if (name == null) return new InternalNamespaceDefinition(ns); else return new UserDefinedNamespaceDefinition(name, ns); case ABCConstants.CONSTANT_PrivateNs: return new PrivateNamespaceDefinition(ns); case ABCConstants.CONSTANT_ProtectedNs: return new ProtectedNamespaceDefinition(ns); case ABCConstants.CONSTANT_StaticProtectedNs: return new StaticProtectedNamespaceDefinition(ns); case ABCConstants.CONSTANT_Namespace: // name will be non-null if we got the Namespace from source // code *or* from a const slot in an ABC. However if we // found the Namespace anywhere else in the ABC, name will // be null. if (name == null) name = ""; return new UserDefinedNamespaceDefinition(name, ns); default: assert false : "Unknown namespace kind!"; return null; } } /** * Create a new private namespace definition. * * @return A new private namespace definition. */ public static IPrivateNamespaceDefinition createPrivateNamespaceDefinition(String uri) { return new PrivateNamespaceDefinition(uri); } /** * Create a new file private namespace definition. * <p> * The distinction between file private namespaces and regular private * namespaces is purely for the benefit of code model clients. Once we have * implemented type analysis in Falcon, CM clients may not need this * distinction anymore. * * @return A new file private namespace definition. */ public static IFilePrivateNamespaceDefinition createFilePrivateNamespaceDefinition(String uri) { return new FilePrivateNamespaceDefinition(uri); } /** * Create a new protected namespace definition. * * @return A new protected namespace definition. */ public static IProtectedNamespaceDefinition createProtectedNamespaceDefinition(String uri) { return new ProtectedNamespaceDefinition(uri); } public static IStaticProtectedNamespaceDefinition createStaticProtectedNamespaceDefinition(String uri) { return new StaticProtectedNamespaceDefinition(uri); } /** * Create a new internal namespace definition for a package. * * @param owningPackageName The package definition the namespace definition * is for. * @return A new internal namespace definition. */ public static IInternalNamespaceDefinition createInternalNamespaceDefinition(String owningPackageName) { return new InternalNamespaceDefinition(owningPackageName); } /** * Create a new interface namespace definition for an interface * * @param owningInterface The interface definition the namespace definition * is for * @return A new interface namespace definition */ public static IInterfaceNamespaceDefinition createInterfaceNamespaceDefinition(InterfaceDefinition owningInterface) { return new InterfaceNamespaceDefinition(owningInterface); } /** * @param packageName The name of the package for which a * {@code INamespaceDefinition.IPublicNamespaceDefinition} should be created. * @return A new {@code INamespaceDefinition.IPublicNamespaceDefinition} for the public namespace * in the specified package. */ public static IPublicNamespaceDefinition createPackagePublicNamespaceDefinition(String packageName) { if (packageName.length() == 0) return PUBLIC; return new PublicNamespaceDefinition(packageName); } /** * Create a new user defined namespace definition. * * @return A new user defined namespace definition. */ public static NamespaceDefinition createUserDefinedNamespace(String name, String uri) { return new UserDefinedNamespaceDefinition(name, uri); } /** * Create a new usder defined namespace definition. For example: * <p> * {@code public namespace foo = "http://foo.com";} * * @param qualifierNamespaceRef The {@link INamespaceReference} that * qualifies the namespace declaration. In the example above this would be a * {@link INamespaceReference} that resolves to the public namespace for the * containing scope. * @param scope The scope in which the namespace definition occurs. * @param name The name of the namespace definition. In the example above * this would be "foo". * @param uri The URI initializer of the namespace declaration or null if * there is not uri specified. In the example above, this would be * "http://foo.com". * @param initializer A NamespaceReference to another namespace that this namespace was initialized with * e.g. * namespace ns2 = ns1; * @return The new namespace definition. */ public static NamespaceDefinition createNamespaceDefintionDirective(INamespaceReference qualifierNamespaceRef, IASScope scope, String name, String uri, INamespaceReference initializer) { NamespaceDefinitionDirective directive = new NamespaceDefinitionDirective(qualifierNamespaceRef, scope, name, uri, initializer); ((ASScope)scope).addNamespaceDirective(directive); return directive; } /** * @param scope The IASScope in which the node should be resolved * @param node The INamespaceDecorationNode that represents the namespace * reference - may not be null. */ public static INamespaceReference createNamespaceReference(IASScope scope, INamespaceDecorationNode node) { return createNamespaceReference(scope, node, false); } /** * Set the containing definition of an {@link INamespaceReference}, if the {@link INamespaceReference} implementation * needs that information. Currently, only a user defined namespace reference will care what its containing definition * is. * @param nsRef the {@link INamespaceReference} to set the containing definition on * @param containingDef the {@link IDefinition} that contains the namespace reference */ public static void setContainingDefinitionOfReference (INamespaceReference nsRef, IDefinition containingDef) { // only user defined namespace references need this info. if( nsRef instanceof UserDefinedNamespaceReference ) ((UserDefinedNamespaceReference) nsRef).setContainingDefinition(containingDef); } /** * @param scope The IASScope in which the node should be resolved * @param node The INamespaceDecorationNode that represents the namespace * reference - may not be null. * @param isStatic whether we want to construct a namespace for a static * property - if true, and the baseName is "protected" then it will * construct a static-protected namespace instead of a protected namespace */ public static INamespaceReference createNamespaceReference(IASScope scope, INamespaceDecorationNode node, boolean isStatic) { assert scope != null; String baseName = getBaseName(node); if (baseName.equals(INamespaceConstants.public_)) { // public members of a class get placed in the global public package "". ClassDefinition classDefinition = getContainingClassDefinition(scope); if (classDefinition != null) return PUBLIC; PackageScope packageScope = getContainingPackageScope(scope); if (packageScope != null) return packageScope.getPublicNamespace(); // If we see a referenc to public outside of a package, but it is used as // a qualifier, then return the unnamed public namespace if( node.isExpressionQualifier() ) return PUBLIC; // If we see a reference to public outside of a package on a definition // that decorates a definition, then the parser should produce a problem. // For compatibility with older version of CM we'll also return a reference // to the code model implicit definition namespace which CM clients always // have in their namespace set, but is not in any namesapce set used by the // compiler. else return getCodeModelImplicitDefinitionNamespace(); } if (baseName.equals(INamespaceConstants.internal_)) { // "internal" is just a way to explicitly refer // to the default name space. return getDefaultNamespaceDefinition(scope); } if (baseName.equals(INamespaceConstants.private_)) { ClassDefinition classDefinition = getContainingClassDefinition(scope); if (classDefinition != null) return classDefinition.getPrivateNamespaceReference(); // If we see a reference to private outside of a class on a definition // that decorates a definition, then the parser should produce a problem. // If we see a reference to private outside of a class in an expression, // then we will generate a reference to the file private namespace for // better compatibility with older version of code model. // Grr... CM unit tests put private definitions in the file scope, // so we'll make: // private var foo : *; // outside of a class a codemodel implicit definition. return getCodeModelImplicitDefinitionNamespace(); } if (baseName.equals(INamespaceConstants.protected_)) { ClassDefinition classDefinition = getContainingClassDefinition(scope); if (classDefinition != null) { if (isStatic) return classDefinition.getStaticProtectedNamespaceReference(); else return classDefinition.getProtectedNamespaceReference(); } // If we see a reference to protected outside of a class on a definition // that decorates a definition, then the parser should produce a problem. // For compatibility with older version of CM we'll also return a reference // to the code model implicit definition namespace which CM clients always // have in their namespace set, but is not in any namesapce set used by the // compiler. return getCodeModelImplicitDefinitionNamespace(); } if( baseName.equals(INamespaceConstants.ANY) ) { return ANY; } if( node instanceof ExpressionNodeBase ) { return ((ExpressionNodeBase)node).computeNamespaceReference(); } else { assert false : "creating a namespace reference from an unknown node type"; return new UserDefinedNamespaceReference((ASScope)scope, node); } } /** * Determine if the INamespaceDecorationNode passed in could refer to more than one namespace when * used as a qualifier. * @param scope scope to resolve things in * @param node the namespace node to check * @return true if the node might resolve to multiple namespaces when it's used as a qualifier. */ public static boolean qualifierCouldBeManyNamespaces(ASScope scope, INamespaceDecorationNode node) { String baseName = getBaseName(node); return ( baseName.equals(INamespaceConstants.public_) || baseName.equals(INamespaceConstants.protected_) ); } /** * Generate a collection of INamespaceReferences that are all the namespace references that the given * INamespaceDecorationNode refers to, when that node is used as the qualifier in an expression, such as: * qualifer::expr * * This returns a collection because some qualifiers (public, protected) may actually refer to multiple * namespaces depending on the context where they are used * * @param scope scope where the namespace reference ocurrs * @param node the qualifier node * @return A collection of 1 or more namespace references that the node refers to */ public static Collection<INamespaceReference> createNamespaceReferencesForQualifier(ASScope scope, INamespaceDecorationNode node ) { Collection<INamespaceReference> nsrefs = new ArrayList<INamespaceReference>(); String baseName = getBaseName(node); INamespaceReference first = createNamespaceReference(scope, node); nsrefs.add(first); if( first instanceof IPublicNamespaceDefinition && baseName.equals(INamespaceConstants.public_) ) { // Anything that resolve to the unnanmed namespace won't have multiple namespaces if( first != PUBLIC ) { PackageScope packageScope = getContainingPackageScope(scope); if( packageScope != null && packageScope.getPublicNamespace() == first ) { // "public" inside a package // need to add the unnamed public namespace nsrefs.add(PUBLIC); } } } else if( first instanceof IProtectedNamespaceDefinition && baseName.equals(INamespaceConstants.protected_) ) { ClassDefinition classDef = getContainingClassDefinition(scope); if( classDef != null && classDef.getProtectedNamespaceReference() == first ) { // protected refers to both the instance protected // and static protected nsrefs.add(classDef.getStaticProtectedNamespaceReference()); } } return nsrefs == null ? Collections.<INamespaceReference>emptyList() : nsrefs; } /** * Create a new UserDefinedNamespaceReference with the given scope, baseName, qualifier, and base reference * @param scope The scope to resolve in * @param baseName The base name of the namespace reference * @param qualifier The qualifier of the namespace reference, eg 'foo' in 'a.foo::b'. May be null if there is * no qualifier * @param base The base reference, 'a' in 'a.b' * @return a new {@link INamespaceReference} */ public static INamespaceReference createNamespaceReference(ASScope scope, String baseName, INamespaceReference qualifier, IReference base) { return new MemberNamespaceReference(scope, base, baseName, qualifier); } /** * Create a new UserDefinedNamespaceReference with the given scope, baseName, and qualifier * @param scope The ASScope in which the node should be resolved * @param baseName The string that represents the name of the namespace * @param qualifier An INamespaceReference for the qualifier, may be null if there is no explicit qualifier * @return An INamespaceReference that can be used to resolve the namespace with the given name and qualifier */ public static INamespaceReference createNamespaceReference(ASScope scope, String baseName, INamespaceReference qualifier) { return new UserDefinedNamespaceReference(scope, baseName, qualifier); } /** * @param scope The IASScope in which the node should be resolved * @param node The INamespaceDecorationNode that represents the namespace * reference - may not be null. */ public static void addUseNamespaceDirectiveToScope(IASScope scope, INamespaceDecorationNode node) { assert scope != null; String baseName = getBaseName(node); if (baseName.equals(INamespaceConstants.public_)) { // public will already be in scope. return; } UseNamespaceDirective directive = new UseNamespaceDirective((ASScope)scope, node); ((ASScope)scope).addUseDirective(directive); } private static String getBaseName(INamespaceDecorationNode node) { // if there is no namespace decoration, must be internal if (node == null) return INamespaceConstants.internal_; // if there is no name on the decoration, must be internal String baseName = node.getName(); if (baseName == null) return INamespaceConstants.internal_; int index = baseName.lastIndexOf("."); if (index != -1) { baseName = baseName.substring(index + 1); } return baseName; } public static ILanguageNamespaceDefinition getDefaultNamespaceDefinition(IASScope scope) { InterfaceDefinition interfaceDef = scope.getDefinition() instanceof InterfaceDefinition ? (InterfaceDefinition)scope.getDefinition() : null; // Interface members go in their own special namespace if (interfaceDef != null) return interfaceDef.getInterfaceNamespaceReference(); PackageScope packageScope = getContainingPackageScope(scope); if (packageScope != null) return packageScope.getInternalNamespace(); return getFileScope(scope).getFilePrivateNamespaceReference(); } private static ASFileScope getFileScope(IASScope scope) { IASScope currScope = scope; while ((currScope != null) && (!(currScope instanceof ASFileScope))) currScope = currScope.getContainingScope(); assert currScope != null : "Could not traverse to a file scope!"; return (ASFileScope)currScope; } private static PackageScope getContainingPackageScope(IASScope scope) { while ((!(scope instanceof PackageScope)) && (scope != null)) scope = scope.getContainingScope(); return (PackageScope)scope; } private static ClassDefinition getContainingClassDefinition(IASScope scope) { if (!(scope instanceof ASScope)) return null; ASScope asScope = (ASScope)scope; IDefinition containingDef = asScope.getContainingDefinition(); if (containingDef == null) return null; if (containingDef instanceof ClassDefinition) return (ClassDefinition)containingDef; ClassDefinition classDefinition = (ClassDefinition)containingDef.getAncestorOfType(ClassDefinition.class); return classDefinition; } /** * Private constructor called from private inner classes in this class. * * @param name Name of the namespace definition. * @param kind ABC kind for the underlying ABC namespace. * @param uri URI for the underlying ABC namespace. */ private NamespaceDefinition(String name, int kind, String uri) { this(name, new Namespace(kind, (uri == null) ? "" : uri)); } /** * Private constructor called from private inner classes in this class. * * @param name Name of the namespace definition. * @param ns An AET namespace for this namespace definition. */ private NamespaceDefinition(String name, Namespace ns) { super(name); aetNamespace = ns; } /** * The AET namespace this namespace definition wraps. */ private final Namespace aetNamespace; @Override public String getURI() { return aetNamespace.getName(); } @Override public INamespaceNode getNode() { return (INamespaceNode)super.getNode(); } @Override public boolean isLanguageNamespace() { return false; } @Override public INamespaceDefinition resolveNamespaceReference(ICompilerProject project) { return this; } @Override public boolean matches(DefinitionBase node) { boolean matches = super.matches(node); if (!matches) return matches; NamespaceDefinition nNode = (NamespaceDefinition)node; if (nNode.getURI().compareTo(getURI()) != 0) { return false; } NamespaceClassification classification = nNode.getNamespaceClassification(); if (classification != getNamespaceClassification()) return false; if (classification == NamespaceClassification.LOCAL || classification == NamespaceClassification.FILE_MEMBER) { if (nNode.getNameStart() != getNameStart() || nNode.getNameEnd() != getNameEnd()) return false; } else if (classification == NamespaceClassification.CLASS_MEMBER) { IASScope type = node.getContainingScope(); IASScope type2 = getContainingScope(); if (type != type2) { return false; } return type == type2; } return true; } @Override public String getBaseName() { return getStorageName(); } @Override public boolean isPublicOrInternalNamespace() { return false; } @Override public int getNamespaceCount() { return 1; } @Override public Set<INamespaceDefinition> getNamespaceSet() { return ImmutableSet.of((INamespaceDefinition)this); } @Override public INamespaceDefinition getFirst () { return this; } /** * Gets the {@link Namespace} object for this {@link INamespaceDefinition} * needed by the code generator to generate an ABC namespace using the AET * library. * <p> * This method is used by the code generator to get the AET namespace from a * NamespaceDefinition. * <p> * TODO We should change AET to define an interface for Namespace that this * class can implement. Then this class would not need to contain an AET * namespace, but would *be* an AET namespace. * * @return The {@link Namespace} object needed by the code generator */ public Namespace getAETNamespace() { return aetNamespace; } @Override public int hashCode() { return aetNamespace.hashCode(); } @Override public boolean equals(Object obj) { boolean ret = false; if (obj instanceof INamespaceDefinition) { ret = equals((INamespaceDefinition)obj); } return ret; } @Override public boolean equals(INamespaceDefinition ns) { if (ns == null) return false; return ((NamespaceDefinition)ns).aetNamespace.equals(aetNamespace); } /** * Interface implemented by use namespace directives and by namespace * declarations. This interface can be used to traverse through the use * namespace directives and namespace declrations file order. This is needed * to resolve namespaces properly. */ public interface INamespaceDirective { INamespaceDirective getNext(); void setNext(INamespaceDirective next); void resolveDirective(NamespaceDirectiveResolver resolver); } /** * Implemented by namespace definitions constructed from namespace * definition directives found in source code. * <p> * eg: namespace ns1 = "http://foo.bar.com"; namespace ns2 = ns1; */ public interface INamepaceDeclarationDirective extends INamespaceDirective, INamespaceDefinition { /** * Resolve the initializer if it refers to another namespace, eg: * namespace ns1 = SomeOtherNamespace; This will just return itself if * no additional resolution is necessary. * * @param project the project to resolve things in * @return A Fully resolved namespace that has the right URI, and can be * used in name lookup */ public INamespaceDefinition resolveConcreteDefinition(ICompilerProject project); /** * Resolve the initializer if it refers to another namespace, eg: * namespace ns1 = SomeOtherNamespace; This will just return itself if * no additional resolution is necessary. * * @param project the project to resolve things in * @param pred a {@code NamespaceDirectiveResolver.NamespaceForwardReferencePredicate} * to use to filter out forward references while computing the concrete definition * @return A Fully resolved namespace that has the right URI, and can be * used in name lookup */ public INamespaceDefinition resolveConcreteDefinition(ICompilerProject project, NamespaceDirectiveResolver.NamespaceForwardReferencePredicate pred); } /** * Implemented by all namespace references from constructed use namespace * directives found in source code. * <p> * eg: use namespace ns1; */ public interface IUseNamespaceDirective extends INamespaceDirective, INamespaceReference { } /** * Private abstract base class for all langage namespace definitions. * Convenient place to make isLanguageNamespace return true and call * setImplicit in the constructor. */ private abstract static class LanguageNamespaceDefinition extends NamespaceDefinition implements ILanguageNamespaceDefinition { private LanguageNamespaceDefinition(String name, int kind, String uri) { super(name, kind, uri); setImplicit(); } private LanguageNamespaceDefinition(String name, Namespace ns) { super(name, ns); setImplicit(); } @Override public boolean isLanguageNamespace() { return true; } @Override public Namespace resolveAETNamespace(ICompilerProject project) { return getAETNamespace(); } @Override public NamespaceClassification getNamespaceClassification() { return NamespaceClassification.LANGUAGE; } @Override public IFileSpecification getFileSpecification() { return null; } @Override public int getStart() { return -1; } @Override public int getNameStart() { return -1; } @Override public int getNameEnd() { return -1; } @Override public String getContainingFilePath() { return null; } @Override public String getContainingSourceFilePath(ICompilerProject project) { return null; } @Override public ASFileScope getFileScope() { return null; } /** * Used only for debugging, as part of {@link #toString()}. */ @Override protected void buildInnerString(StringBuilder sb) { sb.append(getBaseName()); sb.append('('); sb.append(getURI()); sb.append(')'); } @Override public final String getPackageName() { return ""; } } private static final class PublicNamespaceDefinition extends LanguageNamespaceDefinition implements IPublicNamespaceDefinition { private PublicNamespaceDefinition() { this(""); } private PublicNamespaceDefinition(String packageName) { super(INamespaceConstants.public_, ABCConstants.CONSTANT_PackageNs, packageName); setPublic(); } private PublicNamespaceDefinition(Namespace ns) { super(INamespaceConstants.public_, ns); setPublic(); assert ns.getKind() == ABCConstants.CONSTANT_PackageNs; } @Override public boolean isPublicOrInternalNamespace() { return true; } @Override public String getGeneratedURIPrefix() { String uri = getURI(); if (uri.length() == 0) return ""; return uri + ":"; } @Override public String getNamespacePackageName() { return getURI(); } } private static class PrivateNamespaceDefinition extends LanguageNamespaceDefinition implements IPrivateNamespaceDefinition { private PrivateNamespaceDefinition(String uri) { super(INamespaceConstants.private_, ABCConstants.CONSTANT_PrivateNs, uri); } private PrivateNamespaceDefinition(Namespace ns) { super(INamespaceConstants.private_, ns); assert ns.getKind() == ABCConstants.CONSTANT_PrivateNs; } } private static final class FilePrivateNamespaceDefinition extends PrivateNamespaceDefinition implements IFilePrivateNamespaceDefinition { private FilePrivateNamespaceDefinition(String uri) { super(uri); } @Override public String getGeneratedURIPrefix() { return getURI() + ":"; } @Override public String getNamespacePackageName() { return ""; } } private static final class ProtectedNamespaceDefinition extends LanguageNamespaceDefinition implements IProtectedNamespaceDefinition { private ProtectedNamespaceDefinition(String uri) { super(INamespaceConstants.protected_, ABCConstants.CONSTANT_ProtectedNs, uri); } private ProtectedNamespaceDefinition(Namespace ns) { super(INamespaceConstants.protected_, ns); assert ns.getKind() == ABCConstants.CONSTANT_ProtectedNs; } } private static final class StaticProtectedNamespaceDefinition extends LanguageNamespaceDefinition implements IStaticProtectedNamespaceDefinition { private StaticProtectedNamespaceDefinition(String uri) { super(INamespaceConstants.protected_, ABCConstants.CONSTANT_StaticProtectedNs, uri); } private StaticProtectedNamespaceDefinition(Namespace ns) { super(INamespaceConstants.protected_, ns); assert ns.getKind() == ABCConstants.CONSTANT_StaticProtectedNs; } } private static final class InternalNamespaceDefinition extends LanguageNamespaceDefinition implements IInternalNamespaceDefinition { private InternalNamespaceDefinition(String owningPackage) { super(INamespaceConstants.internal_, ABCConstants.CONSTANT_PackageInternalNs, owningPackage); } private InternalNamespaceDefinition(Namespace ns) { super(INamespaceConstants.internal_, ns); assert ns.getKind() == ABCConstants.CONSTANT_PackageInternalNs; } @Override public boolean isPublicOrInternalNamespace() { return true; } @Override public String getGeneratedURIPrefix() { return getURI() + ":"; } @Override public String getNamespacePackageName() { return getURI(); } } /** * represents the Any namespace ('*') */ private static class AnyNamespaceDefinition extends LanguageNamespaceDefinition implements IAnyNamespaceDefinition { private AnyNamespaceDefinition() { // Make a private namespace for the AET namespace, so it won't compare as equal to anything // except itself super(INamespaceConstants.ANY, ABCConstants.CONSTANT_PrivateNs, "*"); } public Namespace getAETNamespace() { // There is not an AET Namespace for the any namespace // instead it usually requires special handling. // For example: // // a.*::b // // requires a QName with a null namespace set be generated for the Name // for *::b. // Also there should be no way to have the Any namespace as part of a multiname // as there is no way to open the any namespace. // If you hit this assert you probably need to check for the Any namespace higher up // and do something special with it. assert false : "Can't get the Namespace for the any namespace!"; return null; } } /** * Represents the namespace that all properties of an interface go into. * Properties of an interface need to go in a special interface namespace, * which has a URI of the fully qualified name of the interface. For more * details, see: * http://livedocs.adobe.com/specs/actionscript/3/wwhelp/wwhimpl * /common/html/ * wwhelp.htm?context=LiveDocs_Parts&file=as3_specification98.html#wp127540 */ private static final class InterfaceNamespaceDefinition extends LanguageNamespaceDefinition implements IInterfaceNamespaceDefinition { private InterfaceNamespaceDefinition(InterfaceDefinition interf) { // FB likes to think of interface namespaces as public, which works well enough. The compiler // doesn't really care what the "name" of the interface namespace is, so just make it public to // keep FB working. super(INamespaceConstants.public_, ABCConstants.CONSTANT_Namespace, interf.generateInterfaceURI()); } private InterfaceNamespaceDefinition(Namespace ns) { // FB likes to think of interface namespaces as public, which works well enough. The compiler // doesn't really care what the "name" of the interface namespace is, so just make it public to // keep FB working. super(INamespaceConstants.public_, ns); assert ns.getKind() == ABCConstants.CONSTANT_PrivateNs; } } /** * TODO: rename this something like invalid namespace definition, as it's * now used for flagging invalid namespaces TODO: such as 'private' used * outside of a class */ private static final class CodeModelImplicitDefinitionNamespaceDefinition extends LanguageNamespaceDefinition implements ICodeModelImplicitDefinitionNamespaceDefinition { private CodeModelImplicitDefinitionNamespaceDefinition() { super("", ABCConstants.CONSTANT_PrivateNs, "CodeModelImplicitDefinitionsNS"); } @Override public Namespace getAETNamespace() { // return a new private namespace so that the code generator can still generate code, but the code will not work // It is important that this is a new Private namespace each time, so the various private namespaces will never // compare as equal - otherwise you could write broken code that would appear to work. // the codegenerator will issue semantic problems when a CM Implicit namespace gets to it, so clients will know // not to trust the resulting ABC with one of these namespaces in it. // We used to assert, but that does not work now that semantics run during code gen. return new Namespace(ABCConstants.CONSTANT_PrivateNs, "<invalid-namespace>"); } } /** * Subclass of {@link NamespaceDefinition} for user defined namespace * declarations like this: * <p> * {@code public namespace foo = "http://foo.com";} */ private static class UserDefinedNamespaceDefinition extends NamespaceDefinition { /** * Generates a URI prefix for a specified qualifier in a specified * scope. * * @param containingScope {@link IASScope} in which the specified * {@link INamespaceReference} occurrs. * @param qualifierNamespaceRef The {@link INamespaceReference} for * which a URI prefix should be generated. * @return URI prefix for a specified qualifier in a specified scope * @see #generateURI(INamespaceReference, IASScope, String) */ private static String generateQualifierPrefixString(IASScope containingScope, INamespaceReference qualifierNamespaceRef) { if (!(qualifierNamespaceRef instanceof INamespaceDefinition)) return qualifierNamespaceRef.getBaseName() + ":"; // file private not handled in here, generatURI handles file private. if (qualifierNamespaceRef instanceof IFilePrivateNamespaceDefinition) { ASFileScope containingFileScope = ((ASScope)containingScope).getFileScope(); assert containingFileScope != null; return generateQualifierPrefixStringForFilePrivate(qualifierNamespaceRef.getBaseName(), containingFileScope) + ":"; } if (qualifierNamespaceRef instanceof IPrivateNamespaceDefinition) return "private:"; INamespaceDefinition qualifierNamespace = (INamespaceDefinition)qualifierNamespaceRef; String uri = qualifierNamespace.getURI(); if (uri.isEmpty()) return ""; return uri + ":"; } /** * Generates a URI prefix for a file private namespace in the specified * {@link ASFileScope}. * * @param baseName The basename of the private namespace * @param fileScope The {@link ASFileScope} which contains the file * private namespace for which a URI prefix is to be generated. * @return The URI prefix for the file private namespace of the * specified {@link ASFileScope}. * @see #generateURI(INamespaceReference, IASScope, String) */ private static String generateQualifierPrefixStringForFilePrivate(String baseName, ASFileScope fileScope) { String sourcePath = fileScope.getContainingPath(); String dirName = FilenameUtils.getPathNoEndSeparator(sourcePath); String md5String = StringEncoder.stringToMD5String(dirName); return FilenameUtils.getName(baseName) + "$" + md5String; } /** * Finds the inner most non-package definition that contains the * specified {@link IASScope}. * * @param scope {@link IASScope} whose containing non-package definition * should be returned. * @return The inner most non-package definition that contains the * specified {@link IASScope} or null if there is no such definition. */ private static IDefinition getContaininDefinition(IASScope scope) { while (scope != null) { if (scope instanceof PackageScope) return null; IDefinition result = scope.getDefinition(); if (result != null) return result; scope = scope.getContainingScope(); } return null; } /** * Handles constructor and anonymous function when generating URI's for * namespace declarations without a URI initializer. * * @param definition * @return The URI prefix for the specified definition or null if the * specified definition did not need special handling. * @see #generateURI(INamespaceReference, IASScope, String) */ private static String generateSpecialCaseFunctionURIPrefix(IDefinition definition) { String baseName = null; if (!(definition instanceof FunctionDefinition)) return null; FunctionDefinition functionDefinition = (FunctionDefinition)definition; FunctionNode functionNode = (FunctionNode)functionDefinition.getNode(); if (functionNode == null) return null; if (functionNode.isConstructor()) { baseName = "$construct/"; } else if (functionNode.getParent() instanceof FunctionObjectNode) { String functionName = functionDefinition.getBaseName(); if (functionName.isEmpty()) baseName = "anonymous/"; else baseName = functionName + "/"; } else return null; assert baseName != null; return generateURI(PUBLIC, definition.getContainingScope(), baseName); } /** * Generates a URI for a namespace declaration that does not have a URI * initializer. For example: * <p> * {@code public namespace foo;} * </p> * * @param qualifierNamespaceRef The {@link INamespaceReference} that * qualifies the namespace declaration. In the example above, this would * be a {@link INamespaceReference} that resolves to the public * namespace for the containing scope of the namespace declaration. * @param containingScope The {@link IASScope} that contains the * namespace declaration. * @param baseName The name of the namespace declaration. In the example * above, this would be "foo". * @return The generated URI for the namespace declaration. */ private static String generateURI(INamespaceReference qualifierNamespaceRef, IASScope containingScope, String baseName) { IDefinition containingDef = getContaininDefinition(containingScope); String prefix = ""; if (containingDef != null) { String specialCaseFunctionPrefix = generateSpecialCaseFunctionURIPrefix(containingDef); if (specialCaseFunctionPrefix != null) { prefix = specialCaseFunctionPrefix; } else { prefix = generateURI(containingDef.getNamespaceReference(), containingDef.getContainingScope(), containingDef.getBaseName()) + "/"; } } String uri = generateQualifierPrefixString(containingScope, qualifierNamespaceRef) + baseName; return prefix + uri; } private UserDefinedNamespaceDefinition(INamespaceReference qualifierNamespaceRef, IASScope containingScope, String name, String uri) { super(name, uri != null ? ABCConstants.CONSTANT_Namespace : ABCConstants.CONSTANT_PackageInternalNs, uri != null ? uri : generateURI(qualifierNamespaceRef, containingScope, name)); setNamespaceReference(qualifierNamespaceRef); } /** * Constructs a new namespace definition with a specified name and uri. * The uri parameter must not be null, if you need a constructor that * can deal with a null URI, call * {@link UserDefinedNamespaceDefinition#UserDefinedNamespaceDefinition(IASScope, String, String)} * instead. * * @param name The base name of the new namespace definition. * @param uri The URI initializer of the new namespace definition. This * must not be null. */ private UserDefinedNamespaceDefinition(String name, String uri) { // If the URI of a namespace defintion is null, // the namespace is a package internal namespace // with URI taken from the name of the definition. super(name, ABCConstants.CONSTANT_Namespace, uri); // See javadoc for this constructor. assert uri != null; } private UserDefinedNamespaceDefinition(String name, Namespace ns) { super(name, ns); assert ns.getKind() == ABCConstants.CONSTANT_Namespace // User defined namespaces may alias other user defined namespaces // that will be PackageInternalNs if they were created without an initializer || ns.getKind() == ABCConstants.CONSTANT_PackageInternalNs; } @Override public Namespace resolveAETNamespace(ICompilerProject project) { return getAETNamespace(); } @Override public NamespaceClassification getNamespaceClassification() { IDefinition parent = getParent(); if (parent instanceof IFunctionDefinition) return NamespaceClassification.LOCAL; if (parent instanceof IClassDefinition) return NamespaceClassification.CLASS_MEMBER; if (parent instanceof IPackageDefinition) return NamespaceClassification.PACKAGE_MEMBER; if (parent == null) { // Some namespaces from ABCs will not have a namespace reference, but this is ok if (getNamespaceReference() != null && inPackageNamespace()) return NamespaceClassification.PACKAGE_MEMBER; return NamespaceClassification.FILE_MEMBER; } return null; } /** * Used only for debugging, as part of {@link #toString()}. */ @Override protected void buildInnerString(StringBuilder sb) { sb.append(getURI()); } } private static final class NamespaceDefinitionDirective extends UserDefinedNamespaceDefinition implements INamepaceDeclarationDirective { private NamespaceDefinitionDirective(INamespaceReference qualifierNamespaceRef, IASScope scope, String name, String uri, INamespaceReference initializer) { super(qualifierNamespaceRef, scope, name, uri); if( initializer != null ) { // a namespace initialized with another namespace ref should only ever // be initialized with a UserDefinedNamespaceReference assert initializer instanceof UserDefinedNamespaceReference; this.initializer = (UserDefinedNamespaceReference)initializer; this.initializer.setContainingDefinition(this); } } private INamespaceDirective next; private UserDefinedNamespaceReference initializer; @Override public INamespaceDirective getNext() { return next; } @Override public void setNext(INamespaceDirective next) { this.next = next; } @Override public void resolveDirective(NamespaceDirectiveResolver resolver) { NamespaceDefinition resolvedQualifier = resolver.resolveDirectiveReference(this.getNamespaceReference()); // TODO at some point we'll need to support resolving the rhs of the namepace directive! // eg: namespace ns1 = ns2; // ns2 needs to be resolved too! NamespaceDirectiveResolver.ResolvedNamespaceDefinitionDirective resolvedDirective = new NamespaceDirectiveResolver.ResolvedNamespaceDefinitionDirective(resolvedQualifier, this); resolver.addFullyResolvedNamespaceDefinitionDirective(resolvedDirective); } /** * Get the underlying AET Namespace object for this namespace. This * method may need to resolve the namespace initializer, which is why it * needs a project so it knows how to resolve things. This would only * happen for a case where the namespace was defined as: namespace ns1 = * otherNamespace; */ @Override public Namespace resolveAETNamespace(ICompilerProject project) { NamespaceDirectiveResolver.NamespaceForwardReferencePredicate pred = new NamespaceDirectiveResolver.NamespaceForwardReferencePredicate(); pred.addRef(this); return resolveAETNamespace(project, pred); } @Override protected String getNamespaceReferenceAsString () { return super.getNamespaceReferenceAsString(); //To change body of overridden methods use File | Settings | File Templates. } public Namespace resolveAETNamespace(ICompilerProject project, NamespaceDirectiveResolver.NamespaceForwardReferencePredicate pred) { if (initializer != null ) { INamespaceDefinition ns = NamespaceDirectiveResolver.resolveNamespaceReferenceInDirective(project, initializer, this, pred); if( ns instanceof NamespaceDefinition ) return ((NamespaceDefinition)ns).resolveAETNamespace(project); return null; } else { return super.resolveAETNamespace(project); } } /** * Resolve the initializer if it refers to another namespace, eg: * namespace ns1 = SomeOtherNamespace; This will just return itself if * no additional resolution is necessary. * * @param project the project to resolve things in * @return A Fully resolved namespace that has the right URI, and can be * used in name lookup */ @Override public INamespaceDefinition resolveConcreteDefinition(ICompilerProject project) { return resolveConcreteDefinition(project, new NamespaceDirectiveResolver.NamespaceForwardReferencePredicate()); } public INamespaceDefinition resolveConcreteDefinition(ICompilerProject project, NamespaceDirectiveResolver.NamespaceForwardReferencePredicate pred) { if (initializer != null) { Namespace aetNamespace = resolveAETNamespace(project, pred); if (aetNamespace != null) // return a NamespaceDefinition that has the right URI. This is ok, because a user defined namespace // can only refer to other user defined namespaces, and the NamespaceDefinitions will compare as equal // as long as the URIs match. return new UserDefinedNamespaceDefinition("", aetNamespace); return null; } else { return this; } } } private static class UserDefinedNamespaceReference implements INamespaceReference { private static INamespaceReference getQualifierNamespaceIfExists(ASScope scope, INamespaceDecorationNode node) { if (!(node instanceof QualifiedNamespaceExpressionNode)) return null; QualifiedNamespaceExpressionNode qNode = (QualifiedNamespaceExpressionNode)node; // TODO: this cast is kinda bad. QualifiedNamespaceExpressionNode should have a getPackage() method IIdentifierNode prefix = (IIdentifierNode)qNode.getLeftOperandNode(); Workspace w = (Workspace)scope.getWorkspace(); INamespaceReference qualifedNamespace = w.getPackageNamespaceDefinitionCache().get(prefix.getName(), false); return qualifedNamespace; } private UserDefinedNamespaceReference(ASScope scope, INamespaceDecorationNode node) { this(scope, NamespaceDefinition.getBaseName(node), getQualifierNamespaceIfExists(scope, node)); } private UserDefinedNamespaceReference(ASScope scope, String baseName, INamespaceReference qualifier) { assert scope != null; this.scope = scope; this.baseName = baseName; this.qualifierNamespace = qualifier; } /** * For a reference to a built-in namespace, this scope is null. For a * reference to a custom namespace like ns1, ns1::ns2, or * (ns1::ns2)::ns3, this scope is the scope in which "ns1", "ns2", and * "ns3" will be resolved. */ private ASScope scope; /** * For a reference to a built-in namespace, this String is "public", * "private", "protected", or "internal". For a reference to a custom * namespace like ns1, this String is "ns1". For a reference to a custom * namespace like (ns1::ns2)::ns3, this String is "ns3". */ private String baseName; /** * Reference to a qualifier namespace. May be null */ private INamespaceReference qualifierNamespace; /** * The containing definition of this namespace reference */ protected IDefinition def; @Override public final boolean isPublicOrInternalNamespace() { return false; } @Override public final String getBaseName() { return baseName; } /** */ public final INamespaceReference getQualifierNamespace() { return qualifierNamespace; } @Override public final boolean isLanguageNamespace() { return false; } @Override public INamespaceDefinition resolveNamespaceReference(ICompilerProject project) { assert scope != null; IDefinition definition = null; if (qualifierNamespace != null) { INamespaceDefinition qualifier = qualifierNamespace.resolveNamespaceReference(project); if (qualifier != null) { if( needsForwardRefPredicate()) definition = scope.findPropertyQualified(project, getForwardReferencePredicate(), qualifier, baseName, DependencyType.NAMESPACE); else definition = scope.findPropertyQualified(project, qualifier, baseName, DependencyType.NAMESPACE); } } else { if( needsForwardRefPredicate() ) { definition = scope.findProperty(project, baseName, getForwardReferencePredicate(), DependencyType.NAMESPACE, true); } else { definition = scope.findProperty(project, baseName, DependencyType.NAMESPACE); } } INamespaceDefinition ns = definition instanceof INamespaceDefinition ? (INamespaceDefinition)definition : null; if (ns instanceof INamepaceDeclarationDirective) { ns = ((INamepaceDeclarationDirective)ns).resolveConcreteDefinition(project); } return ns; } /** * Does this reference need to use a forward reference predicate to resolve itself * @return true, if the reference needs to pass down a forward ref predicate to resolve itself */ protected boolean needsForwardRefPredicate() { if( this.def != null ) { if( scope.getFirstNamespaceDirective() == null && scope.getLocalDefinitionSetByName(this.baseName) == null) { // If the containing scope does not have any namespace directives, // or local properties that match our base name // then we don't have to worry about forward refs // since this is a lexical ref, anything we find will be in a containing // scope return false; } return true; } return false; } /** * Get an appropriate forward reference predicate to use to resolve this reference * @return A predicate that will prevent the resolution methods from resolving to definitions * that would be forward references from this reference */ protected NamespaceDirectiveResolver.NamespaceForwardReferencePredicate getForwardReferencePredicate () { NamespaceDirectiveResolver.NamespaceForwardReferencePredicate pred = new NamespaceDirectiveResolver.NamespaceForwardReferencePredicate(); pred.addRef(this.def); return pred; } /** * Set the containing definition of this reference * @param d the containing definition */ public void setContainingDefinition(IDefinition d) { this.def = d; } @Override public final boolean equals(Object o) { assert false; // If someone is comparing a reference, they are probably making a mistake // They probably meant to resolve to a definition first. if (o == this) return true; if (o instanceof UserDefinedNamespaceReference) { UserDefinedNamespaceReference other = (UserDefinedNamespaceReference)o; if (other.baseName.equals(this.baseName)) return true; } return false; } /** * For debugging only. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (qualifierNamespace != null) { sb.append(qualifierNamespace.toString()); sb.append(':'); sb.append(':'); } sb.append(baseName); return sb.toString(); } @Override public final Namespace resolveAETNamespace(ICompilerProject project) { INamespaceDefinition def = resolveNamespaceReference(project); assert (def == null) || (def instanceof NamespaceDefinition); if (def instanceof NamespaceDefinition) { NamespaceDefinition concreteDef = (NamespaceDefinition)def; return concreteDef.getAETNamespace(); } return null; } protected final ASScope getScope() { return scope; } } /** * A namespace reference of the form 'a.b' where a is a member access, instead of a package * reference. */ private static class MemberNamespaceReference extends UserDefinedNamespaceReference { IReference baseRef = null; private MemberNamespaceReference (ASScope scope, IReference base, String baseName, INamespaceReference qualifier) { super(scope, baseName, qualifier); this.baseRef = base; } @Override protected boolean needsForwardRefPredicate() { return this.def != null; } @Override public INamespaceDefinition resolveNamespaceReference(ICompilerProject project) { ASScope scope = getScope(); assert getScope() != null; String baseName = getBaseName(); IDefinition definition = null; IDefinition base = baseRef.resolve(project, scope, DependencyType.EXPRESSION, true); if( base != null ) { IDefinition baseType = base.resolveType(project); if( baseType != null ) { if( needsForwardRefPredicate() ) { definition = scope.getPropertyFromDef(project, baseType, baseName, getForwardReferencePredicate(), false); } else { definition = scope.getPropertyFromDef(project, baseType, baseName, false); } } } INamespaceDefinition ns = definition instanceof INamespaceDefinition ? (INamespaceDefinition)definition : null; if (ns instanceof INamepaceDeclarationDirective) { ns = ((INamepaceDeclarationDirective)ns).resolveConcreteDefinition(project); } return ns; } } private static final class UseNamespaceDirective extends UserDefinedNamespaceReference implements IUseNamespaceDirective { private UseNamespaceDirective(ASScope scope, INamespaceDecorationNode node) { super(scope, node); } private INamespaceDirective next; @Override public INamespaceDirective getNext() { return next; } @Override public void setNext(INamespaceDirective next) { this.next = next; } @Override public void resolveDirective(NamespaceDirectiveResolver resolver) { NamespaceDefinition resolvedUsedNamespace = resolver.resolveDirectiveReference(this); resolver.addResolvedUsedNamespace(resolvedUsedNamespace); } @Override public final INamespaceDefinition resolveNamespaceReference(ICompilerProject project) { assert getScope() != null; return NamespaceDirectiveResolver.resolveNamespaceReferenceInDirective(project, this, this); } } /** * This class implements all the magic of resolving namespace references * within a local scope. * <p> * Instances of this class, are only created in * {@link NamespaceDirectiveResolver#resolveNamespaceReferenceInDirective(ICompilerProject, UserDefinedNamespaceReference, INamespaceDirective)}. * <p> * Instances of this class maintain state as we walk through the namespace * definition directives in a lexical scope. * <p> * To resolve namespace references in a lexical scope we walk through all * the namespace declarations and use directives in source file order. Each * declaration or use directive has its state applied to an instance of this * class. When we encounter the directive that contains the reference we * want to resolve we then use the state accumulated in this class to * resolve the reference. */ private static final class NamespaceDirectiveResolver { /** * Resolves a namespace reference found in a particular namespace * directive. * * @param project {@link ICompilerProject} whose symbol table we should * use to resolve reference outside of the file containing the * {@link INamespaceDirective}. * @param namespaceReference Namespace reference to resolve. * @param containingDirective {@link INamespaceDirective} containing the * specified reference. */ public static INamespaceDefinition resolveNamespaceReferenceInDirective(ICompilerProject project, UserDefinedNamespaceReference namespaceReference, INamespaceDirective containingDirective) { assert containingDirective != null; NamespaceForwardReferencePredicate pred = null; pred = new NamespaceForwardReferencePredicate(); return resolveNamespaceReferenceInDirective(project, namespaceReference, containingDirective, pred); } /** * Resolves a namespace reference found in a particular namespace * directive, Using the given {@link NamespaceForwardReferencePredicate} * * @param project {@link ICompilerProject} whose symbol table we should * use to resolve reference outside of the file containing the * {@link INamespaceDirective}. * @param namespaceReference Namespace reference to resolve. * @param containingDirective {@link INamespaceDirective} containing the * specified reference. * @param pred The forward reference predicate to use for the resolution * @return A namespace definition. */ public static INamespaceDefinition resolveNamespaceReferenceInDirective(ICompilerProject project, UserDefinedNamespaceReference namespaceReference, INamespaceDirective containingDirective, NamespaceForwardReferencePredicate pred) { ASScope scope = namespaceReference.getScope(); assert scope != null : "All UserDefinedNamespaceReferences should have a scope!"; pred = pred.copy(); if( containingDirective instanceof IDefinition ) pred.addRef((IDefinition)containingDirective); NamespaceDirectiveResolver resolver = new NamespaceDirectiveResolver(project, scope, pred); INamespaceDirective currentDirective = scope.getFirstNamespaceDirective(); // make a resolver to hold the resolution state as we walk through the directives. // loop over the directives till we find the directive containing the // reference are trying to resolve. while (containingDirective != currentDirective) { // accumulate namespace definition or use namespace information // into the resolver. currentDirective.resolveDirective(resolver); currentDirective = currentDirective.getNext(); assert currentDirective != null; } return resolver.resolveDirectiveReference(namespaceReference); } /** * A predicate that will filter out definitions that are a forward reference, which should * make them invisible during namespace resolution. */ private static class NamespaceForwardReferencePredicate implements Predicate<IDefinition> { /** * Keep track of all definitions in all the source files we are resolving * This is necessary since the chain of namespace references could be arbitrarily deep * and go back and forth between multiple files */ private SetMultimap<String, IDefinition> refLocations; public NamespaceForwardReferencePredicate() { refLocations = HashMultimap.create(); } /** * @return a new NAmespaceForwardReferencePredicate with the same data, which can be modified * without affecting the original. */ NamespaceForwardReferencePredicate copy() { NamespaceForwardReferencePredicate newPred = new NamespaceForwardReferencePredicate(); newPred.refLocations = HashMultimap.create(); newPred.refLocations.putAll(this.refLocations); return newPred; } /** * Add a reference from a definition */ void addRef(IDefinition source) { String sourcePath = source.getSourcePath(); if( sourcePath != null ) { refLocations.put(sourcePath, source); } } public boolean apply (IDefinition def) { if( def != null ) { String sourceFile = def.getSourcePath(); // get the previous references from the same file Set<IDefinition> previousRefs = refLocations.get(sourceFile); if( previousRefs != null ) { for( IDefinition d : previousRefs ) { // fail if any of the references from the same file // produce a forward reference if( isForwardRef(d, def) ) { return false; } } } } return true; } /** * Determine if a reference from one definition to another should be considered a forward * reference * @param from the definition the reference is from * @param to the definition the reference is to * @return true if the reference is a forward reference. */ private boolean isForwardRef(IDefinition from, IDefinition to) { // occurred earlier in the file so its not a forward ref if( to.getAbsoluteStart() < from.getAbsoluteStart() ) return false; IASScope fromScope = from.getContainingScope(); IASScope toScope = to.getContainingScope(); // forward ref within the same scope // so it is a forward ref if( fromScope == toScope ) return true; IASScope current = fromScope; while( current != null ) { // Forward ref, but the to scope is contained by the from // scope so it's ok if( current == toScope ) return false; current = current.getContainingScope(); } // package scopes should count as global scope if( toScope instanceof PackageScope ) return false; // forward ref, and the from scope wasn't contained by the to // scope, so this is a forward ref return true; } } /** * {@link ICompilerProject} whose symbol table we should use to resolve * references. */ private final ICompilerProject project; /** * {@link ASScope} that contains the namespace reference. */ private final ASScope scope; /** * {@link ASSCope} that contains {@link #scope}. null if there is no * such scope. */ private final ASScope containingScope; /** * Current set of open namespaces. These namespaces are fully resolved. */ private Set<INamespaceDefinition> resolvedOpenNamespaces; /** * Current namespace definition symbol table. The qualifiers of these * namespace defintions are fully resolved. */ private Map<String, List<ResolvedNamespaceDefinitionDirective>> resolvedNamespaceDirectiveSymbolTable; /** * The forward reference predicate in use by this directive resolver */ private NamespaceForwardReferencePredicate forwardRefPred; /** * Constructed called only by * {@link #resolveNamespaceReferenceInDirective(ICompilerProject, UserDefinedNamespaceReference, INamespaceDirective)} * . * * @param project {@link ICompilerProject} whose symbol table we should * use to resolve non-local symbols. * @param scope {@link ASScope} in which we are resolving namespace * references. * @param pred {@link NamespaceForwardReferencePredicate} used to resolve the directive */ private NamespaceDirectiveResolver(ICompilerProject project, ASScope scope, NamespaceForwardReferencePredicate pred) { this.project = project; this.scope = scope; containingScope = scope.getContainingScope(); resolvedOpenNamespaces = null; resolvedNamespaceDirectiveSymbolTable = null; forwardRefPred = pred; } /** * Gets the current set of resolved open namespaces. * * @return The current set of resolved open namespaces. */ private Set<INamespaceDefinition> getResolvedOpenNamespaces(String name) { if (resolvedOpenNamespaces == null) { resolvedOpenNamespaces = new HashSet<INamespaceDefinition>(); scope.addLocalImportsToNamespaceSet(project.getWorkspace(), resolvedOpenNamespaces); scope.addImplicitOpenNamespaces((CompilerProject)project, resolvedOpenNamespaces); if (containingScope != null) { Set<INamespaceDefinition> containingScopeNamespaceSet = containingScope.getNamespaceSet(project); resolvedOpenNamespaces.addAll(containingScopeNamespaceSet); } else { ((CompilerProject)project).addGlobalUsedNamespacesToNamespaceSet(resolvedOpenNamespaces); } } if (scope != null) { // If we are looking up a namespace that has been explicitly imported, then add the namespaces // from the imports. Set<INamespaceDefinition> additionalNamespaces = scope.getExplicitImportQualifiers((CompilerProject)this.project, name); if (additionalNamespaces != null) { Set<INamespaceDefinition> set = new HashSet<INamespaceDefinition>(); set.addAll(resolvedOpenNamespaces); set.addAll(additionalNamespaces); return set; } } return resolvedOpenNamespaces; } /** * Called by * {@link NamespaceDefinitionDirective#resolveDirective(NamespaceDirectiveResolver)} * to add a fully resolved namespace defintion to the symbol table * accumulated by this object. * * @param resolvedDirective A resolved namespace definition directive. */ public void addFullyResolvedNamespaceDefinitionDirective(ResolvedNamespaceDefinitionDirective resolvedDirective) { final String baseName = resolvedDirective.getDirective().getBaseName(); if (resolvedNamespaceDirectiveSymbolTable == null) { resolvedNamespaceDirectiveSymbolTable = new HashMap<String, List<ResolvedNamespaceDefinitionDirective>>(); List<ResolvedNamespaceDefinitionDirective> directiveList = new ArrayList<ResolvedNamespaceDefinitionDirective>(1); directiveList.add(resolvedDirective); resolvedNamespaceDirectiveSymbolTable.put(baseName, directiveList); } else { List<ResolvedNamespaceDefinitionDirective> directiveList = resolvedNamespaceDirectiveSymbolTable.get(baseName); if (directiveList == null) { directiveList = new ArrayList<ResolvedNamespaceDefinitionDirective>(1); resolvedNamespaceDirectiveSymbolTable.put(baseName, directiveList); } directiveList.add(resolvedDirective); } } /** * Resolves an {@link INamespaceReference} in the current scope using * the current state accumulated in this object to a * {@link NamespaceDefinition}. * * @param reference {@link INamespaceReference} to resolve using the * current resolver state. * @return An {@link NamespaceDefinition} to which the specified * reference resolves to. */ public NamespaceDefinition resolveDirectiveReference(INamespaceReference reference) { if (reference instanceof NamespaceDefinition) return ((NamespaceDefinition)reference); assert reference instanceof UserDefinedNamespaceReference : "Unexpected implementation of INamespaceReference"; UserDefinedNamespaceReference userDefinedNamespaceReference = ((UserDefinedNamespaceReference)reference); final String baseName = userDefinedNamespaceReference.getBaseName(); final INamespaceReference qualifier = userDefinedNamespaceReference.getQualifierNamespace(); if (qualifier != null) { INamespaceDefinition resolvedQualifier = resolveDirectiveReference(qualifier); if (resolvedQualifier == null) return null; return resolveQualifiedDirectiveReference(resolvedQualifier, baseName); } else { return resolveDirectiveReference(baseName); } } /** * Grr... Java needs dynamic_cast from C++. * * @param definition {@link IDefinition} to cast to a * {@link NamespaceDefinition}. * @return {@link IDefinition} casted to a {@link NamespaceDefinition} * or null if the specified {@link IDefinition} was not a * {@link NamespaceDefinition}. */ private final NamespaceDefinition getResolvedNamespace(ICompilerProject project, IDefinition definition) { if (definition instanceof NamespaceDefinition.INamepaceDeclarationDirective) { definition = ((NamespaceDefinition.INamepaceDeclarationDirective)definition).resolveConcreteDefinition(project, forwardRefPred); } if (definition instanceof NamespaceDefinition) return ((NamespaceDefinition)definition); return null; } /** * Resolves a reference to a {@link NamespaceDefinition} using the * current resolver state. * * @param baseName name to resolve * @return {@link NamespaceDefinition} the specified name resolves to in * the current resolver state or null. */ private NamespaceDefinition resolveDirectiveReference(String baseName) { List<IDefinition> definitions = scope.findProperty((CompilerProject)project, baseName, forwardRefPred, getResolvedOpenNamespaces(baseName), DependencyType.NAMESPACE); return definitions.size() == 1 ? getResolvedNamespace(project, definitions.get(0)) : null; } /** * Resolves a qualified reference using the current resolver state. * * @param resolvedQualifier The reference qualifier * @param baseName name to resolve * @return The {@link NamespaceDefinition} the reference resolves to or * null. */ private NamespaceDefinition resolveQualifiedDirectiveReference(INamespaceDefinition resolvedQualifier, String baseName) { IDefinition definition = scope.findPropertyQualified(project, forwardRefPred, resolvedQualifier, baseName, DependencyType.NAMESPACE); return getResolvedNamespace(project, definition); } /** * Method called by implementation of {@link INamespaceDirective} to add * a resolved used namespace to the resolver state. * * @param ns Resolved used namespace to add to the resolver state. */ public void addResolvedUsedNamespace(NamespaceDefinition ns) { resolvedOpenNamespaces.add(ns); } /** * A pair class that stores a namespace definition and its resolved * qualifier. */ private static final class ResolvedNamespaceDefinitionDirective { ResolvedNamespaceDefinitionDirective(NamespaceDefinition resolvedQualifier, NamespaceDefinitionDirective directive) { this.resolvedQualifier = resolvedQualifier; this.directive = directive; } private final NamespaceDefinition resolvedQualifier; private final NamespaceDefinitionDirective directive; @SuppressWarnings("unused") public NamespaceDefinition getResolvedQualifier() { return resolvedQualifier; } public NamespaceDefinitionDirective getDirective() { return directive; } } } @Override public boolean isStatic() { if (getParent() instanceof ClassDefinition) // Namespaces declared at class level are always static return true; return super.isStatic(); } }