| /* |
| * |
| * 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.royale.compiler.internal.scopes; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.royale.compiler.config.CompilerDiagnosticsConstants; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.INamespaceDefinition; |
| import org.apache.royale.compiler.definitions.IScopedDefinition; |
| import org.apache.royale.compiler.definitions.ITypeDefinition; |
| import org.apache.royale.compiler.internal.common.Counter; |
| import org.apache.royale.compiler.internal.definitions.ClassDefinition; |
| import org.apache.royale.compiler.internal.definitions.DefinitionBase; |
| import org.apache.royale.compiler.internal.projects.CompilerProject; |
| import org.apache.royale.compiler.projects.ICompilerProject; |
| import org.apache.royale.compiler.scopes.IASScope; |
| import org.apache.royale.compiler.scopes.IDefinitionSet; |
| import org.apache.royale.compiler.tree.as.IScopedNode; |
| import org.apache.royale.compiler.units.ICompilationUnit; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.ForwardingCollection; |
| |
| /** |
| * ASScopeBase is the abstract base class for all lexical scopes. It has three |
| * concrete subclasses: <code>ASProjectScope</code> for project scopes, |
| * <code>ASFileScope</code> for file scopes, and <code>ASScope</code> for class, |
| * interface, function, and <code>with</code> scopes. |
| * <p> |
| * The primary purpose of a lexical scope is to store a set of definitions that |
| * are potentially visible if the scope is in the chain of scopes used to |
| * resolve an identifier. |
| */ |
| public abstract class ASScopeBase implements IASScope |
| { |
| public static final Set<INamespaceDefinition> allNamespacesSet = null; |
| |
| /** |
| * Used only for debugging, as part of {@link this.toString()}. |
| */ |
| private static void indent(StringBuilder sb, int level) |
| { |
| // Indent six spaces for each scope-nesting level. |
| for (int i = 0; i < level; i++) |
| { |
| sb.append(" "); |
| } |
| } |
| |
| /** |
| * Constructor |
| */ |
| public ASScopeBase() |
| { |
| // Start out with an empty definiton store until a definition is added. |
| definitionStore = EmptyDefinitionStore.SINGLETON; |
| |
| if (Counter.COUNT_SCOPES) |
| countScopes(); |
| } |
| |
| /** |
| * Storage for definitions in this scope, |
| * organized into sets of definitions with the same base name. |
| */ |
| protected IDefinitionStore definitionStore; |
| |
| /** |
| * Minimizes the memory used by this scope. |
| * <p> |
| * The definition store does not get compacted, |
| * but subclasses override this to compact other data structures. |
| */ |
| public void compact() |
| { |
| } |
| |
| /** |
| * Adds the specified definition to this scope. |
| * |
| * @param definition The {@link IDefinition} to be added. |
| */ |
| public void addDefinition(IDefinition definition) |
| { |
| if (definition == null) |
| return; |
| |
| addDefinitionToStore(definition); |
| |
| if (definition instanceof DefinitionBase) |
| ((DefinitionBase)definition).setContainingScope(this); |
| } |
| |
| /** |
| * Helper method called by {@link #addDefinition}(). |
| * <p> |
| * It handles actually adding the definition to the store. |
| * It first tries to add it to the current store. |
| * If it won't fit, it creates a bigger store and adds it to that. |
| * |
| * @param definition The {@link IDefinition} to be added. |
| */ |
| protected void addDefinitionToStore(IDefinition definition) |
| { |
| if (!definitionStore.add(definition)) |
| { |
| definitionStore = definitionStore.createLargerStore(); |
| definitionStore.add(definition); |
| } |
| } |
| |
| /** |
| * Removes the specified definition from this scope. |
| * |
| * @param definition The {@link IDefinition} to be removed. |
| */ |
| public void removeDefinition(IDefinition definition) |
| { |
| removeDefinitionFromStore(definition); |
| |
| // TODO It seems like a good idea to null out the containing |
| // scope of a definition after we remove it from that scope. |
| // But this makes various tests fail. |
| // if (definition instanceof DefinitionBase) |
| // ((DefinitionBase)definition).setContainingScope(null); |
| } |
| |
| /** |
| * Helper method called by {@link #removeDefinition}(). |
| * <p> |
| * It handles actually removing the definition from the store. |
| * It does not bother to check whether the store could be |
| * downgraded with one that has a smaller capacity. |
| * |
| * @param definition The {@link IDefinition} to be added. |
| */ |
| protected void removeDefinitionFromStore(IDefinition definition) |
| { |
| definitionStore.remove(definition); |
| } |
| |
| @Override |
| public IScopedNode getScopeNode() |
| { |
| return null; |
| } |
| |
| @Override |
| public IScopedDefinition getDefinition() |
| { |
| return null; |
| } |
| |
| @Override |
| public IDefinitionSet getLocalDefinitionSetByName(String baseName) |
| { |
| return definitionStore.getDefinitionSetByName(baseName); |
| } |
| |
| @Override |
| public Collection<String> getAllLocalNames() |
| { |
| return definitionStore.getAllNames(); |
| } |
| |
| @Override |
| public Collection<IDefinitionSet> getAllLocalDefinitionSets() |
| { |
| return definitionStore.getAllDefinitionSets(); |
| } |
| |
| @Override |
| public Collection<IDefinition> getAllLocalDefinitions() |
| { |
| return definitionStore.getAllDefinitions(); |
| } |
| |
| /** |
| * Adds definitions with the specified base name whose namespaces match the |
| * specified namespace set to the specified collection of definitions. |
| * <p> |
| * If more that one definition is added to the collection by this method, |
| * then the reference is ambiguous |
| * |
| * @param project {@link ICompilerProject} used to resolve reference to |
| * definitions outside of the {@link ICompilationUnit} that contains this |
| * scope. |
| * @param defs Collection that found {@link IDefinition}'s are added to. |
| * @param baseName Name of property to find. |
| * @param namespaceSet Namespace set in which the qualifier of any matching |
| * definition must exist to be considered a match. |
| */ |
| public final void getLocalProperty(ICompilerProject project, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet) |
| { |
| getLocalProperty(project, defs, baseName, namespaceSet, true); |
| } |
| |
| /** |
| * Adds definitions with the specified base name whose namespaces match the |
| * specified namespace set to the specified collection of definitions. |
| * <p> |
| * If more that one definition is added to the collection by this method, |
| * then the reference is ambiguous |
| * |
| * @param project {@link ICompilerProject} used to resolve reference to |
| * definitions outside of the {@link ICompilationUnit} that contains this |
| * scope. |
| * @param defs Collection that found {@link IDefinition}'s are added to. |
| * @param baseName Name of property to find. |
| * @param namespaceSet Namespace set in which the qualifier of any matching |
| * definition must exist to be considered a match. |
| * @param getContingents If true, non-contingent definitions are ignored. If |
| * false, contingent definitions are ignored. |
| */ |
| // TODO Remove the override in ASProjectScope |
| // and make this final again when we start using Set<IDefinition>. |
| protected final void getLocalProperty(ICompilerProject project, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet, boolean getContingents) |
| { |
| defs = new FilteredCollection<IDefinition>(new NamespaceSetPredicate(project, namespaceSet), defs); |
| getLocalProperty(project, defs, baseName, getContingents); |
| } |
| |
| /** |
| * Adds definitions with the specified base name to the specified collection of definitions. |
| * <p> |
| * If additional constraints on the definitions are required, then the Collection passed in should |
| * implement those. Most commonly, this will be a {@link FilteredCollection} with a {@link NamespaceSetPredicate} |
| * as the predicate. |
| * <p> |
| * If more that one definition is added to the collection by this method, |
| * then the reference is ambiguous |
| * |
| * @param project {@link ICompilerProject} used to resolve reference to |
| * definitions outside of the {@link ICompilationUnit} that contains this |
| * scope. |
| * @param defs Collection that found {@link IDefinition}'s are added to. |
| * @param baseName Name of property to find. |
| * @param getContingents If true, non-contingent definitions are ignored. If |
| * false, contingent definitions are ignored. |
| */ |
| protected final void getLocalProperty(ICompilerProject project, Collection<IDefinition> defs, String baseName, boolean getContingents) |
| { |
| IDefinitionSet defSet = getLocalDefinitionSetByName(baseName); |
| if (defSet != null) |
| { |
| int nDefs = defSet.getSize(); |
| for (int i = 0; i < nDefs; ++i) |
| { |
| IDefinition definition = defSet.getDefinition(i); |
| if ((!definition.isContingent() || (getContingents && isContingentDefinitionNeeded(project, definition)))) |
| { |
| defs.add(definition); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Check whether there is a matching definition in the class hierarchy |
| * already defined in which case the contingent definition is not needed. |
| * |
| * @param project The compiler project. |
| * @param definition A definition. |
| */ |
| public boolean isContingentDefinitionNeeded(ICompilerProject project, IDefinition definition) |
| { |
| assert (definition.isContingent()) : "contingentNeeded() called on non-contingent definition"; |
| |
| IASScope containingScope = definition.getContainingScope(); |
| |
| // for now contingent definitions are only ever class members, so the containing scope def must |
| // be a ClassDefinition. In future this rule can be changed, but code to search the class |
| // hierarchy will become more complex, as the whole definition resolution code needs to be updated |
| assert (containingScope.getDefinition() instanceof ClassDefinition) : "contingent definitions containing scope must be a Class"; |
| ClassDefinition containingType = (ClassDefinition)containingScope.getDefinition(); |
| |
| String contingentName = definition.getBaseName(); |
| for (ITypeDefinition type : definition.isStatic() ? containingType.staticTypeIterable(project, false) : containingType.typeIteratable(project, false)) |
| { |
| ASScope typeScope = (ASScope)type.getContainedScope(); |
| List<IDefinition> defs = new LinkedList<IDefinition>(); |
| typeScope.getLocalProperty(project, defs, contingentName, null, false); |
| // found a non contingent definition, so this contingent def is |
| // not needed. |
| if (!defs.isEmpty()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Adds all local definitions from this scope to the specified collections |
| * of definitions that have a namespace qualifier in the specified |
| * definition set. |
| * |
| * @param project {@link CompilerProject} used to resolve reference to |
| * definitions outside of the {@link ICompilationUnit} that contains this |
| * scope. |
| * @param defs Collection that found {@link IDefinition}'s are added to. |
| * @param namespaceSet Namespace set in which the qualifier of any matching |
| * definition must exist to be considered a match. |
| * @param extraNamespace A single extra {@link INamespaceDefinition} that is |
| * considered part of the namespace set by this method. This is used when |
| * resolving protected definitions in a class scope. |
| */ |
| public void getAllLocalProperties(CompilerProject project, Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet, INamespaceDefinition extraNamespace) |
| { |
| for (IDefinitionSet definitionSet : getAllLocalDefinitionSets()) |
| { |
| for (int i = 0; i < definitionSet.getSize(); ++i) |
| { |
| // we don't want do add definitions that are promises. |
| // when this function is called, any definitions that are still promises |
| // are never going to be resolved - they represent bad code |
| IDefinition definition = definitionSet.getDefinition(i); |
| if (!(definition instanceof ASProjectScope.DefinitionPromise) && |
| (!definition.isContingent() || isContingentDefinitionNeeded(project, definition))) |
| { |
| if ((extraNamespace != null) && (extraNamespace == definition.getNamespaceReference())) |
| { |
| defs.add(definition); |
| } |
| else if (namespaceSet == null) |
| { |
| defs.add(definition); |
| } |
| else |
| { |
| INamespaceDefinition ns = definition.resolveNamespace(project); |
| if (ns != null && (extraNamespace != null) && ((ns == extraNamespace) || (ns.equals(extraNamespace)))) |
| { |
| defs.add(definition); |
| } |
| else if (defs != null && namespaceSet.contains(ns)) |
| { |
| defs.add(definition); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Collection that ignores added items that for which a predicate returns |
| * false. |
| * |
| * @param <T> Type of items in the collection |
| */ |
| public static final class FilteredCollection<T> extends ForwardingCollection<T> |
| { |
| /** |
| * Constructor |
| * |
| * @param predicate Predicate used to filter items as they are added |
| * @param storage Collection to which items are added if the predicate |
| * returns true. |
| */ |
| public FilteredCollection(Predicate<T> predicate, Collection<T> storage) |
| { |
| this.predicate = predicate; |
| this.storage = storage; |
| } |
| |
| private final Predicate<T> predicate; |
| private final Collection<T> storage; |
| |
| @Override |
| protected Collection<T> delegate() |
| { |
| return storage; |
| } |
| |
| @Override |
| public boolean add(T element) |
| { |
| if (predicate.apply(element)) |
| return super.add(element); |
| return false; |
| } |
| |
| @Override |
| public boolean addAll(Collection<? extends T> collection) |
| { |
| return super.addAll(Collections2.filter(collection, predicate)); |
| } |
| |
| } |
| |
| /** |
| * Used only in asserts. |
| */ |
| public boolean verify() |
| { |
| // Verify each definition in this scope. |
| Collection<String> names = getAllLocalNames(); |
| for (String name : names) |
| { |
| // Don't call getDefinitionSetByName() on the scope, because |
| // for a project scope this would actualize every DefinitionPromise. |
| IDefinitionSet definitionSet = definitionStore.getDefinitionSetByName(name); |
| int n = definitionSet.getSize(); |
| for (int i = 0; i < n; i++) |
| { |
| IDefinition definition = definitionSet.getDefinition(i); |
| ((DefinitionBase)definition).verify(); |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * For debugging only. This method displays the definitions contained in |
| * this scope, alphabetically by name. If the definitions have scopes, those |
| * scopes are recursively displayed in an indented fashion. |
| */ |
| @Override |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| buildStringRecursive(sb, 0); |
| return sb.toString(); |
| } |
| |
| /** |
| * Used only for debugging, as part of {@link this.toString()}. |
| */ |
| private void buildStringRecursive(StringBuilder sb, int level) |
| { |
| // Get the names of all the definitions in this scope |
| // and alphabetize them. |
| String[] names = definitionStore.getAllNames().toArray(new String[0]); |
| Arrays.sort(names); |
| |
| // Display a header identifying the scope. |
| indent(sb, level); |
| sb.append(toStringHeader()); |
| sb.append('\n'); |
| |
| for (String name : names) |
| { |
| indent(sb, level); |
| sb.append(' '); |
| sb.append(' '); |
| sb.append(name.length() > 0 ? name : "\"\""); |
| sb.append('\n'); |
| |
| // Get the set of definitions with this name. |
| // Don't call getDefinitionSetByName() on the scope, because |
| // for a project scope this would actualize every DefinitionPromise. |
| IDefinitionSet set = definitionStore.getDefinitionSetByName(name); |
| |
| int n = set.getSize(); |
| for (int i = 0; i < n; i++) |
| { |
| IDefinition d = set.getDefinition(i); |
| indent(sb, level); |
| sb.append(' '); |
| sb.append(' '); |
| sb.append(' '); |
| sb.append(' '); |
| ((DefinitionBase)d).buildString(sb, false); |
| sb.append('\n'); |
| |
| // If the definition has a scope, display that scope recursively. |
| if (d instanceof IScopedDefinition) |
| { |
| ASScopeBase containedScope = (ASScopeBase)((IScopedDefinition)d).getContainedScope(); |
| if (containedScope != null) |
| containedScope.buildStringRecursive(sb, level + 1); |
| } |
| } |
| } |
| } |
| |
| /** |
| * For debugging only. Called by toString() to return the header that is |
| * displayed at the beginning. |
| */ |
| protected String toStringHeader() |
| { |
| return getClass().getSimpleName(); |
| } |
| |
| /** |
| * Counts various types of scopes that are created, |
| * as well as the total number of scopes. |
| */ |
| private void countScopes() |
| { |
| if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER) |
| System.out.println("ASScopeBase incrementing counter for " + getClass().getSimpleName()); |
| Counter counter = Counter.getInstance(); |
| counter.incrementCount(getClass().getSimpleName()); |
| counter.incrementCount("scopes"); |
| if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER) |
| System.out.println("ASScopeBase done incrementing counter for " + getClass().getSimpleName()); |
| } |
| } |