blob: b3f4bb0d9115e52130ca85cebd181287a9854bb2 [file] [log] [blame]
/*
*
* 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.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.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.IDefinitionPriority;
import org.apache.royale.compiler.common.Multiname;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.internal.definitions.AppliedVectorDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.definitions.PackageDefinition;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.scopes.SWCFileScopeProvider.SWCFileScope;
import org.apache.royale.compiler.internal.tree.as.ImportNode;
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.scopes.IFileScope;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.compiler.units.requests.IRequest;
import org.apache.royale.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)
{
if (def == null)
return null;
// 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;
}
}
}