org.apache.flex.compiler.internal.scopes.ASProjectScope.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flex.compiler.internal.scopes.ASProjectScope.java

Source

/*
 *
 *  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.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.get(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;

    /**
     * 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;

    // concurrent Map with weak keys
    private final Map<ICompilationUnit, Collection<IASScope>> compilationUnitToScopeList = new MapMaker().weakKeys()
            .makeComputingMap(new Function<ICompilationUnit, Collection<IASScope>>() {
                @Override
                public Collection<IASScope> apply(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();

        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.get(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) {
        return compilationUnitToScopeList.remove(compilationUnit);
    }

    /**
     * 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.containsKey(compilationUnit))
            return Collections.emptyList();
        Collection<IASScope> result = compilationUnitToScopeList.get(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.
        writeLock.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 {
            writeLock.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;

        private 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;
        }
    }
}