/* * * 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.scopes; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.MapMaker; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.IDefinitionPriority; import org.apache.flex.compiler.common.Multiname; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference; import org.apache.flex.compiler.definitions.references.ReferenceFactory; import org.apache.flex.compiler.internal.definitions.AppliedVectorDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.definitions.PackageDefinition; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.scopes.SWCFileScopeProvider.SWCFileScope; import org.apache.flex.compiler.internal.tree.as.ImportNode; 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.scopes.IFileScope; import org.apache.flex.compiler.units.ICompilationUnit; import org.apache.flex.compiler.units.requests.IFileScopeRequestResult; import org.apache.flex.compiler.units.requests.IRequest; import org.apache.flex.compiler.workspaces.IWorkspace; /** * An ASProjectScope is the topmost scope for a particular project. * <p> * A project scope keeps track of, but does not own, all the externally-visible * definitions for all the compilation units of a single project, so that * multiple compilation units can resolve an identifier to the same definition. * <p> * For example, the ClassDefinition for the Sprite class in playerglobal.swc is * owned by a single ASFileScope produced from that SWC. But this definition is * then shared into the ASProjectScope for each project that uses * playerglobal.swc. * <p> * Note that a workspace might have some projects whose project scope maps * "flash.display.Sprite" to the Sprite class in playerglobal.swc and other * projects whose project scope maps this same qualified name to the Sprite * class in airglobal.swc. * <p> * Unlike other ASScopes, an ASProjectScope does not store information about * <code>import</code> or <code>use namespace</code> directives. * <p> * Since multiple compilation units need to concurrently access a project scope, * it uses a ReadWriteLock to allow either multiple readers with no writer or a * single writer with no readers. * <p> * A project scope can store a special kind of definition called a <i>definition * promise</i>, represented by <code>ASProjectScope.DefinitionPromise</code>. * A definition promise is a small object that serves as a placeholder for an * actual definition, which it can produce on demand but at significant expense. * A promise knows only its qualified name and the compilation unit that produced it; * it has no idea whether the actual definition it can produce will turn out * to be a class definition, an interface definition, a function/getter/setter * definition, a variable/constant definition, or a namespace definition. * Promises are created by compilation units corresponding to files on the * source path or library path, but not for files on the source list. * The files on the source path and library path are recursively enumerated, * compilation units are created for each source file and each SWC script, * and each such compilation unit produces a promise to initially populate * the project scope. For example, the file <code>com/whatever/Foo.as</code> * will produce a promise named <code>com.whatever.Foo</code>. * If code refers to <code>Foo</code>, the promise will be converted * to an actual definition by parsing the file <code>com/whatever/Foo.as</code>, * building a syntax tree, building a file scope, etc. * <p> * Project scopes support <i>definition shadowing</i> since multiple * definitions with the same qualified name can exist in them * without this being an error. * (For example, monkey-patching UIComponent would cause it to be * on the source path and also in framework.swc.) * Definition priorities, represented by <code>IDefinitionPriority</code>, * determine which one of the definitions is made visible to the name * resolution algorithm by being stored in the scope's <i>definition store</i>. * The others are stored, invisible to name resolution, in <i>shadow sets</i> * of definitions, in a map that maps a qualified name to a shadow set. * As definitions with a given qualified name are added to and removed from * the scope, which definition is visible, and which are shadowed, can change. * If a compilation unit initially produces definition promises rather than * actual definitions, then it puts promises into the shadow sets rather than * actual definitions; this allows the <code>removeDefinition</code> method * to be able to remove a definition promise from the project scope * without it ever being converted to an actual definition. */ public class ASProjectScope extends ASScopeBase { /** * Helper method used by getLocalProperty(). Currently our scope APIs use * collections of definitions that are not necessarily sets and therefore * allow duplicates. However, we don't want duplicates occurring when we * search the project scope and find a definition that we already found in a * package scope or a file scope. So until it becomes a set, we simply walk * the collection (which should be small) and avoid adding a duplicate. */ private static void accumulateDefinitions(ICompilationUnit referencingCU, Collection<IDefinition> defs, IDefinition def) { boolean invisible = referencingCU != null ? referencingCU.isInvisible() : false; for (IDefinition d : defs) { if (d == def) return; else if (invisible && d.getQualifiedName().equals(def.getQualifiedName())) return; } defs.add(def); } /** * Adds public and internal definitions of the specified scope to this * ASProjectScope scope. * * @param cu {@link ICompilationUnit} that contains the specified scope. * @param scopes ASScopes from which to collect externally-visible * definitions. */ private void addExternallyVisibleDefinitionsToProjectScope(ICompilationUnit cu, IASScope[] scopes) { if (scopes != null) { for (IASScope iasScope : scopes) { IFileScope scope = (IFileScope)iasScope; if (scope != null) { final Collection<IASScope> compilationUnitScopeList = compilationUnitToScopeList.getUnchecked(cu); assert compilationUnitScopeList != null; compilationUnitScopeList.add(scope); ArrayList<IDefinition> externallVisibleDefs = new ArrayList<IDefinition>(); scope.collectExternallyVisibleDefinitions(externallVisibleDefs, false); for (IDefinition d : externallVisibleDefs) addDefinition(d); } } } } /** * Adds public and internal definitions of the specified scope requests to * this ASProjectScope scope. * * @param scopeRequests a list of scope requests that will have their * externally visible definitions added to this project scope. */ public void addAllExternallyVisibleDefinitions(ArrayList<IRequest<IFileScopeRequestResult, ICompilationUnit>> scopeRequests) throws InterruptedException { int size = scopeRequests.size(); ICompilationUnit[] comps = new ICompilationUnit[size]; IASScope[][] scopes = new IASScope[size][]; for (int i = 0; i < size; ++i) { // Have to run this outside of the lock to prevent deadlocks IRequest<IFileScopeRequestResult, ICompilationUnit> scopeRequest = scopeRequests.get(i); scopes[i] = scopeRequest.get().getScopes(); comps[i] = scopeRequest.getRequestee(); } // Hold the lock until we're done adding all the definitions // so that no look ups occur when we're in the middle of adding definitions to the project. writeLock.lock(); try { for (int i = 0; i < size; ++i) addExternallyVisibleDefinitionsToProjectScope(comps[i], scopes[i]); } finally { writeLock.unlock(); } } private final CompilerProject project; // Locks for controlling concurrent access to a project scope. // When accessing this project scope's fSymbolsToDefinitionSets map // or its scopeToCompilationUnitMap map, we lock readLock to allow // multiple-readers-and-no-writers or lock writeLock to allow // no-readers-and-one-writer. private final ReadWriteLock readWriteLock; private final Lock readLock; private final Lock writeLock; private final Lock newVectorClassLock; /** * The value is a WeakReference to a ICompilationUnit, as the * DependencyGraph should have the only long held hard reference to a * ICompilationUnit to ease memory profiling. * Only SWCFileScopes are stored in a map, as SWC scopes are shared across projects * but other ASFileScopes aren't */ private Map<SWCFileScope, ICompilationUnit> swcFileScopeToCompilationUnitMap = new MapMaker() .weakKeys() .weakValues() .makeMap(); private final Map<ITypeDefinition, AppliedVectorDefinition> vectorElementTypeToVectorClassMap = new MapMaker() .weakKeys() .weakValues() .makeMap(); /** * This map implements definition-shadowing. * It maps a qualified name like "mx.core.UIComponent" * to a "shadow set": a set of definitions that are * stored here rather than in the definition store, * and therefore are invisible to the name resolution * algorithm. */ private HashMap<String, Set<IDefinition>> qnameToShadowedDefinitions; /** * This set of strings (e.g., "Object", "flash.display.Sprite", * "flash.display.*") is created on demand by isValidImport() from the names * of all the definitions in this scope. It is thrown away every time a new * definition is added or removed. Note that the set contains a "wildcard" * version of every dotted name. */ private Set<String> validImports; private final LoadingCache<ICompilationUnit, Collection<IASScope>> compilationUnitToScopeList = CacheBuilder.newBuilder() .weakKeys() .build( new CacheLoader<ICompilationUnit, Collection<IASScope>>() { @Override public Collection<IASScope> load(ICompilationUnit unit) { return new ConcurrentLinkedQueue<IASScope>(); } }); /** * Constructor. * * @param project The project that owns this project scope. */ public ASProjectScope(CompilerProject project) { this.project = project; readWriteLock = new ReentrantReadWriteLock(); readLock = readWriteLock.readLock(); writeLock = readWriteLock.writeLock(); newVectorClassLock = new ReentrantReadWriteLock().writeLock(); qnameToShadowedDefinitions = null; super.addDefinitionToStore(ClassDefinition.getAnyTypeClassDefinition()); super.addDefinitionToStore(ClassDefinition.getVoidClassDefinition()); } /** * Gets the {@link CompilerProject} that owns this scope. * * @return The {@link CompilerProject} that owns this scope. */ public CompilerProject getProject() { return project; } public static DefinitionPromise createDefinitionPromise(String qname, ICompilationUnit compilationUnit) { DefinitionPromise definitionPromise = new DefinitionPromise(qname, compilationUnit); return definitionPromise; } private IDefinitionSet replacePromisesWithDefinitions(IDefinitionSet definitionSet) { // If the existing definition set is a SmallDefinitionSet or a LargeDefinitionSet, // we can return the same set with the promises replaced by actual definitions. // If the existing definition set is a promise acting as its own set-of-size-1, // then we have to return a different set (the actual definition acting as its // own set-of-size-1. IDefinitionSet returnedDefinitionSet = definitionSet; int n = definitionSet.getSize(); for (int i = 0; i < n; i++) { IDefinition definition = definitionSet.getDefinition(i); if (definition instanceof DefinitionPromise) { DefinitionPromise promise = (DefinitionPromise)definition; // Release the writeLock before calling getActualDefinition(), // otherwise we can deadlock on the getFileScopeRequest(). writeLock.unlock(); try { definition = promise.getActualDefinition(); } finally { writeLock.lock(); } if (definition != null) { // Before replacing the promise, we need to check that the // promise hasn't already been replaced by another thread // between giving up the write lock, parsing, and getting // the lock again. if (definitionSet.getDefinition(i) == promise) { if (definitionSet.getMaxSize() == 1) returnedDefinitionSet = (DefinitionBase)definition; else ((IMutableDefinitionSet)definitionSet).replaceDefinition(i, definition); if (shouldBeCached(definition)) setBuiltinDefinition(definition); } } } } return returnedDefinitionSet; } @Override public IDefinitionSet getLocalDefinitionSetByName(String name) { IDefinitionSet definitionSet = null; boolean containsPromise = false; readLock.lock(); try { // Get the definition set from the store. definitionSet = super.getLocalDefinitionSetByName(name); // Does it contain any promises? if (definitionSet != null) { int n = definitionSet.getSize(); for (int i = 0; i < n; i++) { IDefinition definition = definitionSet.getDefinition(i); if (definition instanceof DefinitionPromise) { containsPromise = true; break; } } } } finally { readLock.unlock(); } IDefinitionSet returnedDefinitionSet = definitionSet; if (containsPromise) { // Note that we lock for writing only if there is a promise to replace. writeLock.lock(); try { returnedDefinitionSet = replacePromisesWithDefinitions(definitionSet); if (returnedDefinitionSet != definitionSet) definitionStore.putDefinitionSetByName(name, returnedDefinitionSet); } finally { writeLock.unlock(); } } return returnedDefinitionSet; } private static boolean referenceMatchesQName(IWorkspace workspace, IResolvedQualifiersReference reference, String qualifiedName) { IResolvedQualifiersReference qualifiedNameReference = ReferenceFactory.packageQualifiedReference(workspace, qualifiedName, true); ImmutableSet<INamespaceDefinition> referenceQualifiers = reference.getQualifiers(); for (INamespaceDefinition qNameNS : qualifiedNameReference.getQualifiers()) { if (referenceQualifiers.contains(qNameNS)) return true; } return false; } /** * Find the {@link ICompilationUnit}'s in the project that define or should * define the definitions that the specified list of references refer to. * <p> * This method will not cause any processing for any compilation unit to be * started. * * @param references A list of references. * @return The set of {@link ICompilationUnit}'s in the project that define * or should define the definitions the specified list of references refer * to. */ public Set<ICompilationUnit> getCompilationUnitsForReferences(Iterable<IResolvedQualifiersReference> references) { return getCompilationUnitsForReferences(references, null); } /** * Find the {@link ICompilationUnit}'s in the project that define or should * define the definitions that the specified list of references refer to. * <p> * This method will not cause any processing for any compilation unit to be * started. * * @param references A list of references. * @param unresolvedReferences A collection of references that were not * resolved to compilation units. May be null if the caller is not * interested. * @return The set of {@link ICompilationUnit}'s in the project that define * or should define the definitions the specified list of references refer * to. */ public Set<ICompilationUnit> getCompilationUnitsForReferences(Iterable<IResolvedQualifiersReference> references, Collection<IResolvedQualifiersReference> unresolvedReferences) { Set<ICompilationUnit> compilationUnits = new HashSet<ICompilationUnit>(); IWorkspace workspace = project.getWorkspace(); readLock.lock(); try { for (IResolvedQualifiersReference reference : references) { String baseName = reference.getName(); // use super.getDefinitionSetByName so we don't turn definition promises into // actual definitions. Doing so would require parsing files which would be slow. IDefinitionSet definitionSet = super.getLocalDefinitionSetByName(baseName); ICompilationUnit cu = null; if (definitionSet != null) { int n = definitionSet.getSize(); for (int i = 0; i < n; i++) { IDefinition definition = definitionSet.getDefinition(i); String definitionQualifiedName = definition.getQualifiedName(); if (referenceMatchesQName(workspace, reference, definitionQualifiedName)) { cu = getCompilationUnitForDefinition(definition); assert cu != null : "All symbol table entries should have a corresponding CU!"; compilationUnits.add(cu); } } } if (cu == null && unresolvedReferences != null) unresolvedReferences.add(reference); } } finally { readLock.unlock(); } return compilationUnits; } /** * Finds all the compilation units in the project that define or should * define a symbol with the specified base name. * <p> * This method will not cause any processing for any compilation unit to be * started. * * @param name Base name of a symbol. For example if the definition's * qualified name is: <code>spark.components.Button</code> its base name is * <code>Button</code>. * @return A set of compilation units that define or should define a symbol * with the specified base name. */ public Set<ICompilationUnit> getCompilationUnitsByDefinitionName(String name) { Set<ICompilationUnit> compilationUnits = new HashSet<ICompilationUnit>(); readLock.lock(); try { IDefinitionSet definitionSet = super.getLocalDefinitionSetByName(name); if (definitionSet != null) { int n = definitionSet.getSize(); for (int i = 0; i < n; i++) { IDefinition definition = definitionSet.getDefinition(i); ICompilationUnit compilationUnit; if (definition instanceof DefinitionPromise) { compilationUnit = ((DefinitionPromise)definition).getCompilationUnit(); } else { IASScope containingScope = definition.getContainingScope(); compilationUnit = getCompilationUnitForScope(containingScope); } assert (compilationUnit != null); // should always be able to find our compilation unit compilationUnits.add(compilationUnit); } } } finally { readLock.unlock(); } return compilationUnits; } /** * Find all the {@link ICompilationUnit}s that define definitions with the same qualified name as the definitions defined by the * specified {@link Iterable} of {@link ICompilationUnit}s. * @param workspace {@link IWorkspace} that the {@link ICompilationUnit}s. * @param units {@link Iterable} of {@link ICompilationUnit}s. * @return The {@link Set} of {@link ICompilationUnit}s that define definitions with the same qualified name as the definitions defined by the * specified {@link Iterable} of {@link ICompilationUnit}s. */ public static Set<ICompilationUnit> getCompilationUnitsWithConflictingDefinitions(final IWorkspace workspace, Iterable<ICompilationUnit> units) { // Set of project scopes we have encountered while traversing all the compilation units HashSet<ASProjectScope> projectScopes = new HashSet<ASProjectScope>(); try { HashSet<ICompilationUnit> result = new HashSet<ICompilationUnit>(); for (ICompilationUnit unit : units) { if (unit.isInvisible()) { result.add(unit); continue; } CompilerProject project = (CompilerProject)unit.getProject(); ASProjectScope projectScope = project.getScope(); // If this is the first time we have encountered the project scope // then grab its read lock, we'll release all the read locks below // in the finally block. if (projectScopes.add(projectScope)) projectScope.readLock.lock(); List<String> qNames; try { qNames = unit.getQualifiedNames(); } catch (InterruptedException e) { qNames = Collections.emptyList(); } Set<ICompilationUnit> visibleDefinitionCompilationUnits = projectScope.getCompilationUnitsForReferences(Iterables.transform(qNames, new Function<String, IResolvedQualifiersReference>() { @Override public IResolvedQualifiersReference apply(String qname) { return ReferenceFactory.packageQualifiedReference(workspace, qname); }})); result.addAll(visibleDefinitionCompilationUnits); for (String qName : qNames) { Set<IDefinition> shadowedDefinitions = projectScope.getShadowedDefinitionsByQName(qName); if (shadowedDefinitions != null) { for (IDefinition shadowedDefinition : shadowedDefinitions) { ICompilationUnit shadowedCompilationUnit = projectScope.getCompilationUnitForDefinition(shadowedDefinition); result.add(shadowedCompilationUnit); } } } result.add(unit); } return result; } finally { for (ASProjectScope projectScope : projectScopes) projectScope.readLock.unlock(); } } @Override public Collection<IDefinitionSet> getAllLocalDefinitionSets() { Collection<String> names = getAllLocalNames(); ArrayList<IDefinitionSet> result = new ArrayList<IDefinitionSet>(names.size()); for (String name : names) { // Note: The override of getLocalDefinitionSetByName() in this class // will convert definition promises to actual definitions. IDefinitionSet set = getLocalDefinitionSetByName(name); result.add(set); } return result; } @Override public Collection<IDefinition> getAllLocalDefinitions() { Collection<String> names = getAllLocalNames(); ArrayList<IDefinition> result = new ArrayList<IDefinition>(names.size()); for (String name : names) { // Note: The override of getLocalDefinitionSetByName() in this class // will convert definition promises to actual definitions. IDefinitionSet set = getLocalDefinitionSetByName(name); int n = set.getSize(); for (int i = 0; i < n; i++) { IDefinition definition = set.getDefinition(i); result.add(definition); } } return result; } public IDefinition findDefinitionByName(String definitionName) { Multiname multiname = Multiname.crackDottedQName(project, definitionName); return findDefinitionByName(multiname, false); } public IDefinition[] findAllDefinitionsByName(Multiname multiname) { ArrayList<IDefinition> defs = new ArrayList<IDefinition>(1); getLocalProperty(project, defs, multiname.getBaseName(), multiname.getNamespaceSet()); return defs.toArray(new IDefinition[defs.size()]); } public IDefinition findDefinitionByName(Multiname multiname, boolean ignoreAmbiguous) { ArrayList<IDefinition> defs = new ArrayList<IDefinition>(1); getLocalProperty(project, defs, multiname.getBaseName(), multiname.getNamespaceSet()); if ((defs.size() == 1) || (ignoreAmbiguous && (defs.size() > 0))) return defs.get(0); return null; } /** * Finds a definition currently visible in the symbol table with the same * qualified name as the specified definition. Definitions can be in the * symbol table but be shadowed by other definitions with the same qualified * name. * * @param definition {@link IDefinition} whose qualified name is used to * search the symbol table. * @return An existing definition in the symbol table with the same * qualified name as the specified {@link IDefinition}. Null if no such * {@link IDefinition} exists. */ private DefinitionBase findVisibleDefinition(IDefinition definition) { readLock.lock(); try { String newDefinitionQName = definition.getQualifiedName(); String newDefBaseName = definition.getBaseName(); // It's important that we use super.getLocalDefinitionSetByName() here; // we don't want to cause getActualDefinition() to be called on // any definition promises. IDefinitionSet defSet = super.getLocalDefinitionSetByName(newDefBaseName); if (defSet != null) { int nDefs = defSet.getSize(); for (int i = 0; i < nDefs; ++i) { IDefinition existingDef = defSet.getDefinition(i); String existingDefQName = existingDef.getQualifiedName(); if (existingDefQName.equals(newDefinitionQName)) return (DefinitionBase)existingDef; } } } finally { readLock.unlock(); } return null; } /** * Compares two compilation units by their definition priority. * <p> * This method is used by the definition-shadowing logic. */ private int compareCompilationUnits(ICompilationUnit a, ICompilationUnit b) { IDefinitionPriority priorityA = a.getDefinitionPriority(); IDefinitionPriority priorityB = b.getDefinitionPriority(); return priorityA.compareTo(priorityB); } /** * Compares two definitions by the definition priority of their * compilation units. * <p> * This method is used by the definition-shadowing logic. */ private int compareDefinitions(IDefinition a, IDefinition b) { ICompilationUnit cuA = getCompilationUnitForDefinition(a); ICompilationUnit cuB = getCompilationUnitForDefinition(b); return compareCompilationUnits(cuA, cuB); } /** * Helper method used by <code>addDefinition</code> to handle * definition-shadowing. * <p> * This method adds shadowed definitions to the shadow sets * in the <code>qnameToShadowedDefinitions</code> map. * * @param visibleDefinition The currently visible definition, if any, * with the same qualified name as the definition being added to the scope. * @param newDefinition The new definition being added to the scope. * @return <code>true</code> if the new definition should be added to the store. */ private boolean shadowDefinitionIfNeeded(IDefinition visibleDefinition, IDefinition newDefinition) { // If there is no visible definition that could be shadowed by the new one, // then the new one should simply be added to the store. if (visibleDefinition == null) return true; // Otherwise, one of the two definitions will shadow the other, // so get the set of already-shadowed definitions with the same qname. String qname = visibleDefinition.getQualifiedName(); assert qname.equals(newDefinition.getQualifiedName()); if (qnameToShadowedDefinitions == null) qnameToShadowedDefinitions = new HashMap<String, Set<IDefinition>>(); Set<IDefinition> shadowedDefinitions = qnameToShadowedDefinitions.get(qname); if (shadowedDefinitions == null) { shadowedDefinitions = new HashSet<IDefinition>(1); qnameToShadowedDefinitions.put(qname, shadowedDefinitions); } // Compare the definition priorities of the two definitions' // compilation units to determine which goes into the shadowed set // and which goes into the visible store. ICompilationUnit visibleDefinitionCU = getCompilationUnitForDefinition(visibleDefinition); ICompilationUnit newDefinitionCU = getCompilationUnitForDefinition(newDefinition); if (compareCompilationUnits(visibleDefinitionCU, newDefinitionCU) >= 0) { // Old shadows new. // Put the new definition into the shadow set, // and return a flag saying to leave the old definition visible. shadowedDefinitions.add(newDefinition); return false; } else { // New shadows old. // Put the old visible definition into the shadow set, // and return a flag saying the make the new definition visible. // If the visible compilation unit is using promises, // we need to put a promise into the shadow set rather // than an actual definition. List<IDefinition> promises = visibleDefinitionCU.getDefinitionPromises(); if ((!promises.isEmpty()) && (!(visibleDefinition instanceof DefinitionPromise))) { IDefinition promiseForVisibleDefinition = null; for (IDefinition promise : promises) { if (promise.getQualifiedName().equals(qname)) { promiseForVisibleDefinition = promise; break; } } assert promiseForVisibleDefinition != null; assert ((DefinitionPromise)promiseForVisibleDefinition).getCompilationUnit() == visibleDefinitionCU; assert ((DefinitionPromise)promiseForVisibleDefinition).getActualDefinition() == visibleDefinition; shadowedDefinitions.add(promiseForVisibleDefinition); } else { // The compilation unit does not use definition promises, // so we just shadow the visible definition directly. shadowedDefinitions.add(visibleDefinition); } return true; } } private Set<IDefinition> getShadowedDefinitionsByQName(String qName) { if (qnameToShadowedDefinitions == null) return null; final Set<IDefinition> shadowedDefs = qnameToShadowedDefinitions.get(qName); if (shadowedDefs != null) return shadowedDefs; return null; } /** * Gets the set of definitions that have the same qname as specified definition * but are lower priority and thus not visible. * * @param def The definition to get shadowed definitions for. May not be * null. * @return A set of shadowed definitions. Returns null if there are no * shadowed definitions. * @throws NullPointerException if def is null. */ public Set<IDefinition> getShadowedDefinitions(IDefinition def) { Set<IDefinition> shadowedDefs = null; if (def == null) throw new NullPointerException("def may not be null"); readLock.lock(); try { assert getCompilationUnitForDefinition(def) != null : "def must either be a definition promise or addScopeForCompilationUnit must be called before addDefinition"; if (qnameToShadowedDefinitions != null) { String qname = def.getQualifiedName(); shadowedDefs = qnameToShadowedDefinitions.get(qname); } } finally { readLock.unlock(); } return shadowedDefs; } @Override public void addDefinition(IDefinition def) { writeLock.lock(); try { assert getCompilationUnitForDefinition(def) != null : "def must either be a definition promise or addScopeForCompilationUnit must be called before addDefinition"; // Find the visible definition, if any, with the same qname // as the definition being added. IDefinition existingDef = findVisibleDefinition(def); // Handle the fact that this visible definition might shadow, // or might be shadowed by, the definition being added. // This method will update the shadow sets if necessary, // and return a flag telling us whether to put def into // definition store. boolean shouldAddDef = shadowDefinitionIfNeeded(existingDef, def); if (shouldAddDef) { if (existingDef != null) super.removeDefinition(existingDef); addDefinitionToStore(def); } assert (def instanceof DefinitionPromise) || (getCompilationUnitForScope(def.getContainingScope()) != null); } finally { // Clear the validImports when a definition is added to the project, // so that it will get rebuilt then next time isValidImport() is called. // But don't bother doing this if the definition is for an embed class; // these get added late to the project scope, but they can't be imported // and therefore shouldn't cause pointless rebuilding of validImports. if (!def.isGeneratedEmbedClass()) validImports = null; writeLock.unlock(); } } @Override protected void addDefinitionToStore(IDefinition def) { super.addDefinitionToStore(def); if (!(def instanceof DefinitionPromise) && shouldBeCached(def)) setBuiltinDefinition(def); // Clear the validImports when a definition is added to the project, // so that it will get rebuilt then next time isValidImport() is called. // But don't bother doing this if the definition is for an embed class; // these get added late to the project scope, but they can't be imported // and therefore shouldn't cause pointless rebuilding of validImports. if (!def.isGeneratedEmbedClass()) validImports = null; } /** * Set one of the builtin definitions for fast access when they're needed * * @param def the builtin definition to set */ private void setBuiltinDefinition(IDefinition def) { String defName = def.getBaseName(); if (def.getNamespaceReference() == NamespaceDefinition.getPublicNamespaceDefinition()) { if (defName.equals(IASLanguageConstants.BuiltinType.OBJECT.getName())) objectDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.STRING.getName())) stringDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.ARRAY.getName())) arrayDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.XML.getName())) xmlDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.XMLLIST.getName())) xmllistDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.BOOLEAN.getName())) booleanDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.INT.getName())) intDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.UINT.getName())) uintDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.NUMBER.getName())) numberDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.CLASS.getName())) classDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.FUNCTION.getName())) functionDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.BuiltinType.NAMESPACE.getName())) namespaceDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.UNDEFINED)) undefinedValueDefinition = def; else assert false; // precondition is that this function only called with cachable defs. // If we see one we don't recognize, probably just need to add a handler for it } else if (def.getPackageName().equals(IASLanguageConstants.Vector_impl_package)) { if (defName.equals(IASLanguageConstants.BuiltinType.VECTOR.getName())) vectorDefinition = (ITypeDefinition)def; else if (defName.equals(IASLanguageConstants.Vector_object)) updateAppliedVectorDefinitionBaseClasses(IASLanguageConstants.Vector_object); else if (defName.equals(IASLanguageConstants.Vector_double)) updateAppliedVectorDefinitionBaseClasses(IASLanguageConstants.Vector_double); else if (defName.equals(IASLanguageConstants.Vector_int)) updateAppliedVectorDefinitionBaseClasses(IASLanguageConstants.Vector_int); else if (defName.equals(IASLanguageConstants.Vector_uint)) updateAppliedVectorDefinitionBaseClasses(IASLanguageConstants.Vector_uint); } } private void updateAppliedVectorDefinitionBaseClasses(String changedVectorImplClass) { for (AppliedVectorDefinition appliedVectorDefinition : vectorElementTypeToVectorClassMap.values()) appliedVectorDefinition.updateBaseClass(changedVectorImplClass); } /** * remove one of the builtin definitions * * @param visibleDefinition the definition to remove */ private void removeBuiltinDefinition(IDefinition visibleDefinition) { if (visibleDefinition == objectDefinition) objectDefinition = null; else if (visibleDefinition == stringDefinition) stringDefinition = null; else if (visibleDefinition == arrayDefinition) arrayDefinition = null; else if (visibleDefinition == xmlDefinition) xmlDefinition = null; else if (visibleDefinition == xmllistDefinition) xmllistDefinition = null; else if (visibleDefinition == booleanDefinition) booleanDefinition = null; else if (visibleDefinition == intDefinition) intDefinition = null; else if (visibleDefinition == uintDefinition) uintDefinition = null; else if (visibleDefinition == numberDefinition) numberDefinition = null; else if (visibleDefinition == classDefinition) classDefinition = null; else if (visibleDefinition == functionDefinition) functionDefinition = null; else if (visibleDefinition == namespaceDefinition) namespaceDefinition = null; else if (visibleDefinition == vectorDefinition) vectorDefinition = null; else if (visibleDefinition == undefinedValueDefinition) undefinedValueDefinition = null; else assert false; // precondition is that this function only called with cachable defs. // If we see one we don't recognize, probably just need to add a handler for it } @Override public void removeDefinition(IDefinition definition) { writeLock.lock(); try { // Find the visible definition with the same qname // as the definition being added. IDefinition visibleDefinition = findVisibleDefinition(definition); assert visibleDefinition != null : "Can't remove a definition that is not in the symbol table."; if (visibleDefinition == definition) { // The definition we're removing is visible, not shadowed. if (!(visibleDefinition instanceof DefinitionPromise) && shouldBeCached(visibleDefinition)) removeBuiltinDefinition(visibleDefinition); // The definition we are removing is not shadowed by another // definition, so remove the definition from the definition store. super.removeDefinition(definition); // Next we need to see if the definition we are removing // shadows some other definition. Set<IDefinition> shadowedDefs = getShadowedDefinitions(definition); if (shadowedDefs != null) { // The definition we are removing shadows at least one other // definition, so we need find the definition that was shadowed // with the highest priority, remove that definition from the // shadow set, and add that definition to the definition store. IDefinition nextDef; if (shadowedDefs.size() == 1) { nextDef = shadowedDefs.iterator().next(); // There are no longer any shadowed definitions for this qname. qnameToShadowedDefinitions.remove(definition.getQualifiedName()); } else { // Sort all the shadowed definitions for this qname // by the definition priority of the compilation units // that contain those definitions. IDefinition[] shadowedDefsArr = shadowedDefs.toArray(new IDefinition[0]); Arrays.sort(shadowedDefsArr, new Comparator<IDefinition>() { @Override public int compare(IDefinition d1, IDefinition d2) { assert d1 != null; assert d2 != null; return 0 - compareDefinitions(d1, d2); } }); nextDef = shadowedDefsArr[0]; shadowedDefs.remove(nextDef); } addDefinitionToStore(nextDef); } return; } // The definition we're removing is shadowed. // It might be a promise or an actual definition. if ((!(visibleDefinition instanceof DefinitionPromise)) && (definition instanceof DefinitionPromise)) { // visibleDefinition might be the actual definition of the // promise we are trying to remove. ICompilationUnit visibleDefinitionCU = getCompilationUnitForDefinition(visibleDefinition); if (visibleDefinitionCU == ((DefinitionPromise)definition).getCompilationUnit()) { // visibleDefinition is the actual definition for the definition we are trying to remove. // We'll just remove the actual definition for our promise, by calling this method recursively. assert visibleDefinition == findVisibleDefinition(visibleDefinition) : "assert if we are about to start infinite recursion"; removeDefinition(visibleDefinition); // recursion! return; } } // Remove the definition from the shadow set. String qName = definition.getQualifiedName(); Set<IDefinition> shadowedDefinitions = qnameToShadowedDefinitions.get(qName); assert shadowedDefinitions != null : "If the def to remove is shadowed, we should have a set of shadowed defs."; assert shadowedDefinitions.contains(definition) : "If the def to remove is shadowed it should be in this set."; if (shadowedDefinitions.size() == 1) qnameToShadowedDefinitions.remove(qName); else shadowedDefinitions.remove(definition); } finally { validImports = null; writeLock.unlock(); } } @Override public void compact() { writeLock.lock(); try { super.compact(); } finally { writeLock.unlock(); } } @Override public Collection<String> getAllLocalNames() { readLock.lock(); try { return super.getAllLocalNames(); } finally { readLock.unlock(); } } /** * Gets a collection of the qualified names for all the definitions in this * project scope. Each qualified name is a dotted qualified name of the * form: flash.display.DisplayObject * * @return A collection of dotted qualified names */ public Collection<String> getAllQualifiedNames() { readLock.lock(); try { Collection<String> result = new ArrayList<String>(); // Note: The inherited version of getAllLocalDefinitionSets() will not // convert definition promises to actual definitions. for (IDefinitionSet definitionSet : super.getAllLocalDefinitionSets()) { int nDefinitionsInSet = definitionSet.getSize(); for (int i = 0; i < nDefinitionsInSet; ++i) result.add(definitionSet.getDefinition(i).getQualifiedName()); } return result; } finally { readLock.unlock(); } } /** * Associates an {@link IASScope} for a file or package scope with a * {@link ICompilationUnit}. This association is used to establish * dependencies between {@link ICompilationUnit}'s, when resolving * definition across {@link ICompilationUnit} boundaries. * * @param cu A compilation unit. * @param scope An {@link ASFileScope} for a file */ public void addScopeForCompilationUnit(ICompilationUnit cu, ASFileScope scope) { if (scope.setCompilationUnit(cu)) return; writeLock.lock(); try { assert scope instanceof SWCFileScope : "only SWCFileScope should be added to swcFileScopeToCompilationUnitMap"; swcFileScopeToCompilationUnitMap.put((SWCFileScope)scope, cu); } finally { writeLock.unlock(); } } private void removeScopeForCompilationUnit(ASFileScope scope) { if (scope.setCompilationUnit(null)) { assert (!swcFileScopeToCompilationUnitMap.containsKey(scope)) : "scope should not be in the scopeToCompilationUnitMap when setCompilationUnit returns true"; return; } // there is no need to grab the lock here, as removeScopeForCompilationUnit() // should only be called from removeCompilationUnits() which already // has the lock assert scope instanceof SWCFileScope : "only SWCFileScope should be in swcFileScopeToCompilationUnitMap"; swcFileScopeToCompilationUnitMap.remove(scope); } /** * Get the ICompilationUnit from which the scope is declared * * @param scope The scope in question * @return The ICompilationUnit in which the scope is declared */ public ICompilationUnit getCompilationUnitForScope(IASScope scope) { while ((scope != null) && (!(scope instanceof ASFileScope))) { scope = scope.getContainingScope(); } if (scope == null) return null; ASFileScope fileScope = (ASFileScope)scope; ICompilationUnit compilationUnit = fileScope.getCompilationUnit(); if (compilationUnit != null) { assert compilationUnit.getProject() == getProject(); return compilationUnit; } readLock.lock(); try { assert fileScope instanceof SWCFileScope : "only SWCFileScope should be in swcFileScopeToCompilationUnitMap"; ICompilationUnit swcCompilationUnit = swcFileScopeToCompilationUnitMap.get(fileScope); assert (swcCompilationUnit == null) || (swcCompilationUnit.getProject() == getProject()); return swcCompilationUnit; } finally { readLock.unlock(); } } /** * Adds the specified {@link ASScope} to the list of {@link IASScope}s * associated with the {@link ICompilationUnit} that contains the specified * {@link ASScope}. This method is called for when a {@link ASScopeCache} is * created for an {@link ASScope} or when {@link IDefinition}s from a {@link ICompilationUnit} does * not have {@link DefinitionPromise}s are added to the {@link ASProjectScope}. * @param scope The scope to be added. */ public void addScopeToCompilationUnitScopeList(ASScope scope) { // Scopes inside of Vector types ( like Vector.<String> ), // do not have an associated compilation unit. if (AppliedVectorDefinition.isVectorScope(scope)) return; // find the compilation unit which corresponds to the scope, and maintain a mapping // of compilation unit to scopes, so we can easily invalidate the scope caches ICompilationUnit compilationUnit = getCompilationUnitForScope(scope); assert compilationUnit != null; Collection<IASScope> relatedScopes = compilationUnitToScopeList.getUnchecked(compilationUnit); relatedScopes.add(scope); } /** * Clears the list of {@link IASScope}s associated with the specified * {@link ICompilationUnit}. * * @param compilationUnit The {@link ICompilationUnit} whose associated list * of {@link IASScope}s should be cleared. * @return The {@link List} of {@link IASScope}s associated with the * specified {@link ICompilationUnit} before this method was called. */ public Collection<IASScope> clearCompilationUnitScopeList(ICompilationUnit compilationUnit) { Collection<IASScope> scopeList = compilationUnitToScopeList.getUnchecked(compilationUnit); compilationUnitToScopeList.invalidate(compilationUnit); return scopeList; } /** * Gets the {@link List} of {@link IASScope}s that have an associated {@link ASScopeCache} or define * {@link IDefinition}s currently in the {@link ASProjectScope} for the specified {@link ICompilationUnit}. * * @param compilationUnit {@link ICompilationUnit} of the scopes to query * @return List of scopes for the compilation unit. Never null. */ public Collection<IASScope> getCompilationUnitScopeList(ICompilationUnit compilationUnit) { if (compilationUnitToScopeList.getIfPresent(compilationUnit) == null) return Collections.emptyList(); Collection<IASScope> result = compilationUnitToScopeList.getUnchecked(compilationUnit); assert result != null; assert !result.isEmpty(); return result; } /** * Gets the {@link ICompilationUnit} that contains the specified * {@link IDefinition}. * * @param def {@link IDefinition} whose containing {@link ICompilationUnit} * should be returned. * @return {@link ICompilationUnit} that contains the specified * {@link IDefinition} or null. */ public ICompilationUnit getCompilationUnitForDefinition(IDefinition def) { // This check is really important because this method is called by other methods // in this class that do not want to cause compilation units to do work. if (def instanceof DefinitionPromise) return ((DefinitionPromise)def).getCompilationUnit(); return getCompilationUnitForScope(def.getContainingScope()); } /** * Removes a set of {@link ICompilationUnit}'s from the project scope. * * @param compilationUnitsToRemove The collection of compilation units to remove. */ public void removeCompilationUnits(Collection<ICompilationUnit> compilationUnitsToRemove) { if ((compilationUnitsToRemove == null) || (compilationUnitsToRemove.size() == 0)) return; writeLock.lock(); try { // build up collections of items to remove so we are not // changing data structures as we iterate them. Collection<IDefinition> defsToRemove = new HashSet<IDefinition>(compilationUnitsToRemove.size()); Collection<IASScope> scopesToRemove = new ArrayList<IASScope>(compilationUnitsToRemove.size()); for (ICompilationUnit compilationUnit : compilationUnitsToRemove) { Collection<IASScope> relatedScopes = getCompilationUnitScopeList(compilationUnit); List<IDefinition> definitionPromises = compilationUnit.getDefinitionPromises(); if (definitionPromises.isEmpty()) { Collection<IDefinition> relatedDefs = new ArrayList<IDefinition>(); for (IASScope scope : relatedScopes) { if (scope instanceof ASFileScope) ((ASFileScope)scope).collectExternallyVisibleDefinitions(relatedDefs, false); } defsToRemove.addAll(relatedDefs); } else { defsToRemove.addAll(definitionPromises); } scopesToRemove.addAll(relatedScopes); project.clearScopeCacheForCompilationUnit(compilationUnit); } for (IDefinition defToRemove : defsToRemove) removeDefinition(defToRemove); for (IASScope scope : scopesToRemove) { if (scope instanceof ASFileScope) removeScopeForCompilationUnit((ASFileScope)scope); } } finally { writeLock.unlock(); } } @Override public IASScope getContainingScope() { return null; } private AppliedVectorDefinition getExistingVectorClass(ITypeDefinition elementType) { readLock.lock(); try { return vectorElementTypeToVectorClassMap.get(elementType); } finally { readLock.unlock(); } } public AppliedVectorDefinition newVectorClass(ITypeDefinition elementType) { // First try to get an existing vector class // just using the read lock. AppliedVectorDefinition existingVectorClass = getExistingVectorClass(elementType); if (existingVectorClass != null) return existingVectorClass; // No dice, looks like we might have to create the // vector class. newVectorClassLock.lock(); try { // Now that we have the write lock, make sure nobody created // the vector class we need while we were waiting for the write lock. existingVectorClass = getExistingVectorClass(elementType); if (existingVectorClass != null) return existingVectorClass; // ok. Now we know for sure we'll have to create the new vector class. //Default to creating a subclass of Vector$object. String vectorTypeName = null; // We need to see if the elementType is int, Number, or uint. // If the elementType is int, Number, or uint, then the vector class is a special // class that we just need to look up in the project symbol table, otherwise we // need to sub-class Vector$object. // Some short cuts here to avoid extra lookups. INamespaceReference elementTypeNSRef = elementType.getNamespaceReference(); if ((elementTypeNSRef.equals(NamespaceDefinition.getPublicNamespaceDefinition())) && // int, Number, and uint are all public classes. (elementType.getPackageName().length() == 0) && // int, Number, and uint are all in the unnamed package. (elementType instanceof IClassDefinition) && // int, Number, and uint are all classes. (!(elementType instanceof AppliedVectorDefinition))) // int, Number and uint are not Vector classes. { // Now just compare the elementType's string base name to // int, uint, and Number. If one of those matches, then // we'll lookup the corresponding type and see if it matches // the specified element type. String elementTypeName = elementType.getBaseName(); if (elementTypeName.equals(IASLanguageConstants._int)) vectorTypeName = IASLanguageConstants.Vector_int; else if (elementTypeName.equals(IASLanguageConstants.uint)) vectorTypeName = IASLanguageConstants.Vector_uint; else if (elementTypeName.equals(IASLanguageConstants.Number)) vectorTypeName = IASLanguageConstants.Vector_double; if (vectorTypeName != null) { IDefinition numberTypeClassDef = findDefinitionByName(elementTypeName); assert numberTypeClassDef != null : "Unable to lookup: " + elementTypeName; if (!numberTypeClassDef.equals(elementType)) { // The element type was not actually a number class // back to square 1. vectorTypeName = null; } } } if (vectorTypeName == null) vectorTypeName = IASLanguageConstants.Vector_object; IClassDefinition vectorImplClass = AppliedVectorDefinition.lookupVectorImplClass(project, vectorTypeName); AppliedVectorDefinition result = new AppliedVectorDefinition(project, vectorImplClass, elementType); vectorElementTypeToVectorClassMap.put(elementType, result); if (vectorTypeName == IASLanguageConstants.Vector_object) result.adjustVectorMethods(this.project); return result; } finally { newVectorClassLock.unlock(); } } /** * When doing name resolution in an {@link IInvisibleCompilationUnit} the * file and package scopes need to be consulted to determine if they contain * the definition we are looking for. This extra code is needed because the * package level definitions in an {@link IInvisibleCompilationUnit} are not * registered with the {@link ASProjectScope}. * * @param referencingCU * @param defs * @param baseName * @param namespaceSet */ private void getPropertyForScopeChainInvisibleCompilationUnit(ICompilationUnit referencingCU, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet) { assert referencingCU.isInvisible(); try { final Collection<IDefinition> externallyVisibleDefs = referencingCU.getFileScopeRequest().get().getExternallyVisibleDefinitions(); final ICompilerProject project = referencingCU.getProject(); for (IDefinition externallyVisibleDefinition : externallyVisibleDefs) { if (baseName.equals(externallyVisibleDefinition.getBaseName())) accumulateMatchingDefinitions(referencingCU, project, defs, namespaceSet, true, null, externallyVisibleDefinition); } } catch (InterruptedException e) { // If we get interrupted we'll just pretend there were no definitions // in the file of interest. } } public void getPropertyForScopeChain(ASScope referencingScope, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet, DependencyType dt) { final ICompilationUnit referencingCU = getCompilationUnitForScope(referencingScope); if ((referencingCU != null) && (referencingCU.isInvisible())) { getPropertyForScopeChainInvisibleCompilationUnit(referencingCU, defs, baseName, namespaceSet); } if ((dt != null) && !AppliedVectorDefinition.isVectorScope(referencingScope)) { assert referencingCU != null : "this can only be null if the ref scope is a vector scope, which we guard against."; findDefinitionByName(referencingCU, defs, baseName, namespaceSet, dt); } else { getLocalProperty(referencingCU, project, defs, baseName, namespaceSet, true, null); } } public void findDefinitionByName(ICompilationUnit referencingCU, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet, DependencyType dt) { getLocalProperty(referencingCU, project, defs, baseName, namespaceSet, true, null); if (dt != null) { for (IDefinition def : defs) { assert def != null; if (!def.isImplicit()) { assert referencingCU != null; assert def.getContainingScope() != null : "null containing scope: " + def.getBaseName(); assert (def.getContainingScope() instanceof ASFileScope) || (def.getContainingScope().getDefinition() instanceof PackageDefinition); ICompilationUnit referencedCU = getCompilationUnitForScope(def.getContainingScope()); assert referencedCU != null; project.addDependency(referencingCU, referencedCU, dt, def.getQualifiedName()); } } } // if a definition could not be found, keep track of base definition name // and the CU which referenced the definition, so if at a later stage the // definition is added to the project, we can updated the referencing CU. if (defs.isEmpty()) { project.addUnfoundDefinitionDependency(baseName, referencingCU); } } /** * Determines if the specified definition is visible in the project scope. * <p> * A definition is a project can be hidden by other definitions with same * qualified name that have a higher definition priority. A definition's * priority is influenced by the following: Whether the definition is from * source or library The time stamp associated with a definition */ public boolean isActiveDefinition(IDefinition definition) { assert definition != null; //Only definitions that are in a scope are allowed assert definition.getContainingScope() instanceof ASFileScope || definition.getContainingScope() instanceof PackageScope; IDefinition visibleDefinition = findVisibleDefinition(definition); if (definition == visibleDefinition) return true; if (visibleDefinition instanceof DefinitionPromise) { DefinitionPromise defPromise = (DefinitionPromise)visibleDefinition; if (definition == defPromise.getActualDefinition()) return true; } return false; } public void findDefinitionByName(Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet) { getLocalProperty(project, defs, baseName, namespaceSet); } /** * Determines whether a specified import name such as <code>"MyClass"</code> * , <code>"flash.display.Sprite"</code> or <code>"flash.display.*"</code> * is valid, by determining whether it matches the name of any definition in * this project scope. */ public boolean isValidImport(String importName) { // Determine whether we need to rebuild the validImports Set. // To get validImports, we need the read lock. boolean needToBuildValidImports = false; readLock.lock(); try { if (validImports == null) needToBuildValidImports = true; } finally { readLock.unlock(); } if (needToBuildValidImports) { // We need to rebuild the validImports Set. // To set validImports, we need the write lock. writeLock.lock(); try { // Make sure we still need to rebuild it. // Another thread might have done it // between the read lock and the write kicj, if (validImports == null) { validImports = new HashSet<String>(); for (String name : getAllQualifiedNames()) { validImports.add(name); validImports.add(ImportNode.makeWildcardName(name)); } } } finally { writeLock.unlock(); } } // Query the validImports Set. // This requires the read lock. boolean isValid = false; readLock.lock(); try { isValid = validImports.contains(importName); } finally { readLock.unlock(); } return isValid; } /** * Adds all definitions ( including definitions from base types ) in the * current 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. */ public void getAllProperties(CompilerProject project, Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet) { getAllLocalProperties(project, defs, namespaceSet, null); } /* * Definitions with these qnames are cached in the fields objectDefinition, * stringDefinition, etc. of an ASProjectScope for quick retrieval. */ private static final HashSet<String> CACHED_DEFINITION_QNAMES = new HashSet<String>(); static { CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Object); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.String); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Array); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.XML); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.XMLList); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Boolean); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants._int); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.uint); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Number); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Class); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Function); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Namespace); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.Vector_qname); CACHED_DEFINITION_QNAMES.add(IASLanguageConstants.UNDEFINED); // Note: this is the VALUE "undefined" } /* * Returns true if the specified definition gets cached in a project scope. */ private static boolean shouldBeCached(IDefinition definition) { String qname = definition.getQualifiedName(); return CACHED_DEFINITION_QNAMES.contains(qname); } private ITypeDefinition objectDefinition; private ITypeDefinition stringDefinition; private ITypeDefinition arrayDefinition; private ITypeDefinition xmlDefinition; private ITypeDefinition xmllistDefinition; private ITypeDefinition booleanDefinition; private ITypeDefinition intDefinition; private ITypeDefinition uintDefinition; private ITypeDefinition numberDefinition; private ITypeDefinition classDefinition; private ITypeDefinition functionDefinition; private ITypeDefinition namespaceDefinition; private ITypeDefinition vectorDefinition; private IDefinition undefinedValueDefinition; // This is not class def, it's a variable def. ex: if (foo == undefined) {} public final ITypeDefinition getObjectDefinition() { if (objectDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the objectDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.OBJECT.getName()); } return objectDefinition; } public final ITypeDefinition getStringDefinition() { if (stringDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the stringDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.STRING.getName()); } return stringDefinition; } public final ITypeDefinition getArrayDefinition() { if (arrayDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the arrayDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.ARRAY.getName()); } return arrayDefinition; } public final ITypeDefinition getXMLDefinition() { if (xmlDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the arrayDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.XML.getName()); } return xmlDefinition; } public final ITypeDefinition getXMLListDefinition() { if (xmllistDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the arrayDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.XMLLIST.getName()); } return xmllistDefinition; } public final ITypeDefinition getBooleanDefinition() { if (booleanDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the booleanDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.BOOLEAN.getName()); } return booleanDefinition; } public final ITypeDefinition getIntDefinition() { if (intDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the intDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.INT.getName()); } return intDefinition; } public final ITypeDefinition getUIntDefinition() { if (uintDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the uintDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.UINT.getName()); } return uintDefinition; } public final ITypeDefinition getNumberDefinition() { if (numberDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the numberDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.NUMBER.getName()); } return numberDefinition; } public final ITypeDefinition getClassDefinition() { if (classDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the classDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.CLASS.getName()); } return classDefinition; } public final ITypeDefinition getFunctionDefinition() { if (functionDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the functionDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.FUNCTION.getName()); } return functionDefinition; } public final ITypeDefinition getNamespaceDefinition() { if (namespaceDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the namespaceDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.NAMESPACE.getName()); } return namespaceDefinition; } public final ITypeDefinition getVectorDefinition() { if (vectorDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the classDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.VECTOR.getName()); } return vectorDefinition; } public final IDefinition getUndefinedValueDefinition() { if (undefinedValueDefinition == null) { // If we don't have an Definition for Object yet, call getDefinitionSetByName, // which will transform any DefinitionPromises -> Definitions, and set the classDefinition member. getLocalDefinitionSetByName(IASLanguageConstants.BuiltinType.Undefined.getName()); } return undefinedValueDefinition; } // TODO This method is a copy-and-paste of its supermethod in ASScopeBase, // with defs.add(definition) replaced by accumulateDefinitions(defs, definition) // in order to enforce the Set-ness of the collection to which the project scope // is contributing. See comment on accumulateDefinitions() for more info. // Remove this override when we start using Set<IDefinition>. // We could have made accumulateDefinitions() virtual instead of getLocalProperty(), // but that would have had worse performance. private void getLocalProperty(ICompilationUnit referencingCU, ICompilerProject project, Collection<IDefinition> defs, String baseName, Set<INamespaceDefinition> namespaceSet, boolean getContingents, INamespaceDefinition extraNamespace) { IDefinitionSet defSet = getLocalDefinitionSetByName(baseName); if (defSet != null) { int nDefs = defSet.getSize(); for (int i = 0; i < nDefs; ++i) { IDefinition definition = defSet.getDefinition(i); accumulateMatchingDefinitions(referencingCU, project, defs, namespaceSet, getContingents, extraNamespace, definition); } } } private final void accumulateMatchingDefinitions(ICompilationUnit referencingCU, ICompilerProject project, Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet, boolean getContingents, INamespaceDefinition extraNamespace, IDefinition definition) { if ((!definition.isContingent() || (getContingents && isContingentDefinitionNeeded(project, definition)))) { if ((extraNamespace != null) && (extraNamespace == definition.getNamespaceReference())) { accumulateDefinitions(referencingCU, defs, definition); } else if (namespaceSet == null) { accumulateDefinitions(referencingCU, defs, definition); } else { INamespaceDefinition ns = definition.resolveNamespace(project); if ((extraNamespace != null) && ((ns == extraNamespace) || (extraNamespace.equals(ns)))) accumulateDefinitions(referencingCU, defs, definition); else if (namespaceSet.contains(ns)) accumulateDefinitions(referencingCU, defs, definition); } } } /** * Calling this method from an assert will cause an assertion failure if * there is any {@link IDefinition} in this {@link ASProjectScope} for which an * {@link ICompilationUnit} can <b><em>not</em></b> be found. * @return false if there is any {@link IDefinition} in this {@link ASProjectScope} for which an * {@link ICompilationUnit} can <b><em>not</em></b> be found, true otherwise. */ @Override public boolean verify() { readLock.lock(); try { for (IDefinitionSet defSet : this.definitionStore.getAllDefinitionSets()) { int n = defSet.getSize(); for (int i = 0; i < n; ++i) { IDefinition def = defSet.getDefinition(i); if (def.isImplicit()) continue; if (getCompilationUnitForDefinition(def) == null) return false; } } } finally { readLock.unlock(); } return true; } /** * Represents a promise to provide an {@link IDefinition} in the future. */ public static final class DefinitionPromise extends DefinitionBase { /** * Constructor. * * @param qname The fully-qualified name of the promise, * such as <code>"flash.display.Sprite"</code>. * @param compilationUnit The {@link ICompilationUnit} contributing this promise. */ private DefinitionPromise(String qname, ICompilationUnit compilationUnit) { // Unlike most definitions, which store an undotted name // in the 'storageName' field, a DefinitionPromise stores // its dotted qualified name (e.g. "flash.display.Sprite"). super(qname); assert qname.charAt(qname.length() - 1) != '.' : "Qualified names must not end in '.'"; compilationUnitWeakRef = new WeakReference<ICompilationUnit>(compilationUnit); actualDefinition = null; } /** * This is a WeakReference to the ICompilationUnit, as the * DependencyGraph should have the only long held hard reference to a * ICompilationUnit to ease memory profiling. */ private WeakReference<ICompilationUnit> compilationUnitWeakRef; // TODO Consider eliminating this field. private IDefinition actualDefinition; public IDefinition getActualDefinition() { if (actualDefinition != null) return actualDefinition; final String qname = getQualifiedName(); try { ICompilationUnit compilationUnit = compilationUnitWeakRef.get(); assert (compilationUnit != null); final IFileScopeRequestResult fileScopeRequestResult = compilationUnit.getFileScopeRequest().get(); actualDefinition = fileScopeRequestResult.getMainDefinition(qname); } catch (InterruptedException e) { actualDefinition = null; } return actualDefinition; } /** * Gets the {@link ICompilationUnit} that can satisfy this promise. * * @return {@link ICompilationUnit} that can satisfy this promise. */ public ICompilationUnit getCompilationUnit() { return compilationUnitWeakRef.get(); } /** * Resets the DefinitionPromise to it's original state */ public void clean() { actualDefinition = null; } @Override public String toString() { return "DefinitionPromise \"" + getQualifiedName() + "\""; } @Override protected String toStorageName(String name) { return name; } @Override public final String getPackageName() { // Unlike most definitions, which store an undotted base name in the // 'storageName' field, a DefinitionPromise stores its dotted qualified name. String storageName = getStorageName(); int lastDotIndex = storageName.lastIndexOf('.'); return lastDotIndex != -1 ? storageName.substring(0, lastDotIndex) : ""; } @Override public final String getQualifiedName() { // Unlike most definitions, which store an undotted base name in the // 'storageName' field, a DefinitionPromise stores its dotted qualified name. return getStorageName(); } @Override public final String getBaseName() { // Unlike most definitions, which store an undotted base name in the // 'storageName' field, a DefinitionPromise stores its dotted qualified name. String storageName = getStorageName(); int lastDotIndex = storageName.lastIndexOf('.'); return lastDotIndex != -1 ? storageName.substring(lastDotIndex + 1) : storageName; } @Override public boolean isInProject(ICompilerProject project) { // Always return false, because instances of this // class should never leak out of the name resolution APIs. return false; } } }