blob: fa6f4a7efef56a4f7eedd6325e5bdc968e44f5e1 [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.workspaces;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.royale.compiler.asdoc.IASDocDelegate;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.filespecs.FileSpecification;
import org.apache.royale.compiler.filespecs.IBinaryFileSpecification;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.caches.PackageNamespaceDefinitionCache;
import org.apache.royale.compiler.internal.definitions.references.ReferenceCache;
import org.apache.royale.compiler.internal.embedding.EmbedData;
import org.apache.royale.compiler.internal.mxml.MXMLDataManager;
import org.apache.royale.compiler.internal.parsing.as.NilASDocDelegate;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.DependencyGraph;
import org.apache.royale.compiler.internal.projects.ASProject;
import org.apache.royale.compiler.internal.scopes.ASProjectScope;
import org.apache.royale.compiler.internal.units.CompilationUnitBase;
import org.apache.royale.compiler.internal.units.StringToCompilationUnitMap;
import org.apache.royale.compiler.internal.units.requests.RequestMaker;
import org.apache.royale.compiler.mxml.IMXMLDataManager;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.projects.IASProject;
import org.apache.royale.compiler.scopes.IFileScope;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.ICompilationUnit.UnitType;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.compiler.workspaces.IInvalidationListener;
import org.apache.royale.compiler.workspaces.IWorkspace;
import org.apache.royale.compiler.workspaces.IWorkspaceProfilingDelegate;
import org.apache.royale.compiler.workspaces.IInvalidationListener.InvalidatedDefinition;
import org.apache.royale.swc.ISWCManager;
import org.apache.royale.swc.SWCManager;
import org.apache.royale.utils.FilenameNormalization;
import com.google.common.base.FinalizableReferenceQueue;
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 com.google.common.collect.Sets;
/**
* Maintains a collection of ICompilerProject's in the workspace and state that
* is shared across multiple CompilerProject's
*/
public final class Workspace implements IWorkspace
{
private static boolean assertionsEnabled = false;
static
{
assert assertionsEnabled = true; // intentional side effect
}
private ExecutorService executorService;
protected final Map<CompilerProject, Object> projects;
private IWorkspaceProfilingDelegate profilingDelegate;
private final Set<IInvalidationListener> invalidationListeners;
private final SWCManager swcManager;
private final MXMLDataManager mxmlDataManager;
private final PackageNamespaceDefinitionCache packageNamespaceDefinitionCache;
private final Map<String, IFileSpecification> pathToFileSpecMap;
private final StringToCompilationUnitMap pathToCompilationUnitMapping;
// Map of files which are included from another file to the compilation units
// which are including the file. This is separate from pathToCompilationUnitMapping
// as when we lookup the compilation units to remove based on file name, we
// should not remove compilation units which are including a file which is
// being removed.
private final StringToCompilationUnitMap includeFilesToIncludingCompilationUnitMapping;
// The key of this map is also the value. As EmbedData uses the same hashCode()
// we query if there is an equiv. EmbedData, and if there is, we return the
// value - which is also the key!
private final Map<EmbedData, EmbedData> embedDataCache;
// A FinalizableReferenceQueue which is used InvisibleCompilationUnitRef to remove
// any delegates from the project when an invisible CU is gc'd.
private final FinalizableReferenceQueue invisibleCompilationUnitReferenceQueue;
// This lock is needed as we can add EmbedCompilationUnits during a compile so
// need wrap the creation/addition in EmbedCompilationUnitFactory around a lock.
// Note that add addCompilationUnit() itself is not thread-safe at the moment,
// and if we need to add any other compilation units in the future during compile
// we should problem revisit this locking.
public final ReadWriteLock embedLock;
/**
* Object that is used to synchronize build and file change notifications in
* a workspace. A file change notification must not attempt to invalidate
* compilation units or update project symbol tables while compilation doing
* work.
*
* @see IWorkspace#startBuilding()
* @see IWorkspace#doneBuilding()
* @see IWorkspace#startIdleState()
* @see IWorkspace#endIdleState(Map)
* @see #startRequest(boolean)
* @see #endRequest()
*/
private final BuildSynchronizationState buildSync;
/**
* {@link IASDocDelegate} that provides
* the action script parser with call backs to generated objects
* to contain ASDoc information.
*/
private IASDocDelegate asDocDelegate;
/**
* Constructor
*/
public Workspace()
{
// Limit the number of threads to the number of processors
// If you want to run single threaded, update getNumberOfThreadToUse()
this(new ThreadPoolExecutor(0, getNumberOfThreadToUse(),
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadPoolExecutor.CallerRunsPolicy()));
// this(Executors.newCachedThreadPool(), indexingDelegate);
}
/**
* Constructor
*
* @param es {@link ExecutorService} to use to do background work in this
* workspace.
*/
public Workspace(ExecutorService es)
{
executorService = es;
profilingDelegate = null;
invalidationListeners = new LinkedHashSet<IInvalidationListener>();
swcManager = new SWCManager(this);
mxmlDataManager = new MXMLDataManager();
projects = new MapMaker().weakKeys().makeMap();
pathToFileSpecMap = new HashMap<String, IFileSpecification>();
pathToCompilationUnitMapping = new StringToCompilationUnitMap();
includeFilesToIncludingCompilationUnitMapping = new StringToCompilationUnitMap();
packageNamespaceDefinitionCache = new PackageNamespaceDefinitionCache();
embedDataCache = new WeakHashMap<EmbedData, EmbedData>();
embedLock = new ReentrantReadWriteLock();
invisibleCompilationUnitReferenceQueue = new FinalizableReferenceQueue();
asDocDelegate = NilASDocDelegate.get();
buildSync = new BuildSynchronizationState();
}
private static int getNumberOfThreadToUse()
{
/*
* OK, we needs some heuristic for how many threads to put in the work queue.
* Our "version 1" heuristic was min ( num cores, 8).
* That turned out to be sub-optimal. I believe there are two reasons for this:
* 1) Often with apps like this that do a lot of disk I/O you want more threads than cores,
* because some threads will be waiting for disk I/O, so you need extra threads to keep the work
* going.
* 2) Our current work queuing system will eat up threads as we run into compile time
* dependencies that fan out too far. This is perhaps a defect in the queuing system, but
* in any case it can be beneficial to have extra threads.
*
* I tested on 1,2, and 4 core machines, and in most cases num cores was too few threads.
* I also found that 16 was either optimal, or not noticeably worse than a smaller number.
*
* TODO: we should run some more benchmark on a many core system and a many core + hyper-threading.
* I suspect we may find that those systems can use more cores...
*/
return 16;
}
/**
* Gets the {@link ExecutorService} to use in this workspace.
*
* @return The {@link ExecutorService} to use in this workspace.
*/
public ExecutorService getExecutorService()
{
return executorService;
}
private CompilerProject[] getProjects()
{
return projects.keySet().toArray(new CompilerProject[0]);
}
@Override
public void startIdleState()
{
buildSync.startIdleState();
}
@Override
public void endIdleState(Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate)
{
buildSync.startAllowingFileScopeRequests();
try
{
for (Entry<ICompilerProject, Set<ICompilationUnit>> e : cusToUpdate.entrySet())
{
((CompilerProject)e.getKey()).updatePublicAndInternalDefinitions(e.getValue());
}
}
catch (InterruptedException e1)
{
assert false : "unlockAndUpdateCompilationUnits() should not be interrupted";
}
finally
{
buildSync.endAllowingFileScopeRequests();
buildSync.endIdleState();
}
}
@Override
public void startBuilding()
{
buildSync.startRequest(false);
}
@Override
public void doneBuilding()
{
buildSync.endRequest();
}
/**
* Called by code in {@link RequestMaker} when a request is about to be
* submitted for execution. Blocks until the workspace has left the idle
* state.
*
* @param requestNeededForFileScope true if the request that is about to be
* submitted for execution is needed to build an {@link IFileScope}.
*/
public void startRequest(boolean requestNeededForFileScope)
{
buildSync.startRequest(requestNeededForFileScope);
}
/**
* Called by code in {@link RequestMaker} when a request has completed execution.
*/
public void endRequest()
{
buildSync.endRequest();
}
/**
* Determines if there is any currently running build activity in the
* workspace. This method should only be called from assert statements, it
* is meant only for debugging.
*
* @return true if there is any build activity, false otherwise.
*/
public boolean isBuilding()
{
return buildSync.isBuilding();
}
@Override
public void setProfilingDelegate(final IWorkspaceProfilingDelegate profilingDelegate)
{
this.profilingDelegate = profilingDelegate;
}
@Override
public IWorkspaceProfilingDelegate getProfilingDelegate()
{
return profilingDelegate;
}
@Override
public void addInvalidationListener(IInvalidationListener invalidationListner)
{
invalidationListeners.add(invalidationListner);
}
@Override
public void removeInvalidationListener(IInvalidationListener invalidationListner)
{
invalidationListeners.remove(invalidationListner);
}
/**
* @return the swcManager
*/
@Override
public ISWCManager getSWCManager()
{
return swcManager;
}
public PackageNamespaceDefinitionCache getPackageNamespaceDefinitionCache()
{
return packageNamespaceDefinitionCache;
}
/**
* Close the workspace. It is illegal to use the workspace after it has been
* closed.
*/
public void close()
{
executorService.shutdown();
executorService = null;
}
private final Collection<ICompilationUnit> collectAssociatedCompilationUnits(IFileSpecification file)
{
String filename = file.getPath();
Collection<WeakReference<ICompilationUnit>> relatedCompilationUnits = pathToCompilationUnitMapping.getVisibleAndInvisible(filename);
// relatedCompilationUnits should never be null, but it is OK for it to be empty, as
// we can be null, if someone passes us in a arbitrary file which has no compilation
// units associated with it.
assert (relatedCompilationUnits != null) : "relatedCompilationUnits should never be null";
// add any compilation units which include the file, as they need to be recompiled also
Collection<WeakReference<ICompilationUnit>> includingCompilationUnits = includeFilesToIncludingCompilationUnitMapping.get(filename);
Collection<WeakReference<ICompilationUnit>> allRelatedCompilationUnits = new HashSet<WeakReference<ICompilationUnit>>();
allRelatedCompilationUnits.addAll(relatedCompilationUnits);
allRelatedCompilationUnits.addAll(includingCompilationUnits);
HashSet<ICompilationUnit> associatedCompilationUnits = new HashSet<ICompilationUnit>();
for (WeakReference<ICompilationUnit> relatedCURef : allRelatedCompilationUnits)
{
ICompilationUnit relatedCU = relatedCURef.get();
if (relatedCU != null)
{
associatedCompilationUnits.add(relatedCU);
}
}
final Set<ICompilationUnit> associatedCompilationUnitsAccountingForConflictingDefinitions =
ASProjectScope.getCompilationUnitsWithConflictingDefinitions(this, associatedCompilationUnits);
return associatedCompilationUnitsAccountingForConflictingDefinitions;
}
private final void invalidate(IFileSpecification fileSpec, Collection<ICompilationUnit> compilationUnits, Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate)
{
mxmlDataManager.invalidate(fileSpec);
// Tell the SWC manager the SWC file is invalid.
getSWCManager().remove(new File(fileSpec.getPath()));
if (compilationUnits.size() == 0)
return;
Set<ICompilationUnit> unitsToInvalidate = new HashSet<ICompilationUnit>();
unitsToInvalidate.addAll(compilationUnits);
Set<ICompilationUnit> unitsToClean = Sets.<ICompilationUnit>union(DependencyGraph.computeInvalidationSet(unitsToInvalidate), getCompilationUnitsDependingOnMissingDefinitions(unitsToInvalidate));
notifyInvalidationListener(unitsToClean);
// Do the actual invalidation
Map<ICompilerProject, Set<File>> invalidatedSWCFiles = new HashMap<ICompilerProject, Set<File>>();
for (ICompilationUnit compilationUnit : unitsToClean)
{
boolean clearCUFileScope = unitsToInvalidate.contains(compilationUnit);
compilationUnit.clean(invalidatedSWCFiles, cusToUpdate, clearCUFileScope);
}
// invalidate any library files in the project
for (Map.Entry<ICompilerProject, Set<File>> e : invalidatedSWCFiles.entrySet())
{
if (e.getKey() instanceof IASProject)
((IASProject)e.getKey()).invalidateLibraries(e.getValue());
}
}
private void notifyInvalidationListener(Collection<ICompilationUnit> unitsToClean)
{
if (invalidationListeners.isEmpty())
return;
Map<ICompilerProject, Collection<InvalidatedDefinition>> invalidationMap =
new HashMap<ICompilerProject, Collection<InvalidatedDefinition>>();
for (ICompilationUnit compilationUnit : unitsToClean)
{
// Collect all definitions associated with the compilation unit
Collection<IDefinition> definitions = compilationUnit.getDefinitionPromises();
if (definitions.size() == 0)
{
// no definition promises, so getting the file scope should be cheap.
try
{
IFileScopeRequestResult fsr = compilationUnit.getFileScopeRequest().get();
definitions = fsr.getExternallyVisibleDefinitions();
}
catch (InterruptedException e1)
{
assert false : "Since this is a single threaded method, we should never be interrupted";
}
}
// for all the found definition, build up a map of projects to a list of InvalidatedDefinitions
// and pass this map onto the registered invalidation listener to do with what it will
if (definitions.size() > 0)
{
Collection<InvalidatedDefinition> invalidatedDefinitions = invalidationMap.get(compilationUnit.getProject());
if (invalidatedDefinitions == null)
{
invalidatedDefinitions = new LinkedList<InvalidatedDefinition>();
invalidationMap.put(compilationUnit.getProject(), invalidatedDefinitions);
}
String filename = compilationUnit.getAbsoluteFilename();
for (IDefinition definition : definitions)
{
String qName = definition.getQualifiedName();
InvalidatedDefinition invalidatedDefinition = new InvalidatedDefinition(qName, filename);
invalidatedDefinitions.add(invalidatedDefinition);
}
}
}
for (IInvalidationListener listener : invalidationListeners)
listener.definitionsChanged(invalidationMap);
}
/**
* When an ISWC has changed in memory, invalidate any compilation units which depend on the
* units which depend on the SWC
*
* @param unitsRemoved The collection of compilation units to be removed.
* @param unitsAdded The collection compilation units to be added.
*/
public void swcChanged(Collection<ICompilationUnit> unitsRemoved, Collection<ICompilationUnit> unitsAdded, Runnable runWhileIdle)
{
final Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate = new HashMap<ICompilerProject, Set<ICompilationUnit>>();
final Set<ICompilationUnit> unitsRemoveSet = ImmutableSet.copyOf(unitsRemoved);
startIdleState();
try
{
// Find all the compilation units reference a definition with the same base
// name as a definition defined by any of the compilation units in the SWC that is
// changing.
final Collection<ICompilationUnit> unitsDependingOnMissingDefinitions =
getCompilationUnitsDependingOnMissingDefinitions(unitsAdded);
// Compute the set of compilation units to invalidate by starting with the union of
// the unitsDependingOnMissingDefinitions and the list of
// compilation units we are removing.
Set<ICompilationUnit> unitsToInvalidate =
DependencyGraph.computeInvalidationSet(Iterables.concat(unitsRemoved, unitsDependingOnMissingDefinitions));
notifyInvalidationListener(unitsToInvalidate);
// Do the actual invalidation
Map<ICompilerProject, Set<File>> invalidatedSWCFiles = new HashMap<ICompilerProject, Set<File>>();
for (ICompilationUnit compilationUnit : unitsToInvalidate)
{
compilationUnit.clean(invalidatedSWCFiles, cusToUpdate, unitsRemoveSet.contains(compilationUnit));
}
runWhileIdle.run();
}
finally
{
endIdleState(cusToUpdate);
}
}
@Override
public void fileChanged(IFileSpecification changedFile)
{
// paths passed into this function need to have been normalized
assert (changedFile.getPath().equals(FilenameNormalization.normalize(changedFile.getPath()))) : "Path not normalized";
Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate = new HashMap<ICompilerProject, Set<ICompilationUnit>>();
startIdleState();
try
{
Collection<ICompilationUnit> relatedCompilationUnits = collectAssociatedCompilationUnits(changedFile);
HashSet<ICompilationUnit> compilationUnitsToInvalidate = new HashSet<ICompilationUnit>();
compilationUnitsToInvalidate.addAll(relatedCompilationUnits);
invalidate(changedFile, relatedCompilationUnits, cusToUpdate);
pathToFileSpecMap.put(changedFile.getPath(), changedFile);
}
finally
{
endIdleState(cusToUpdate);
}
}
@Override
public void fileRemoved(IFileSpecification removedFile)
{
// paths passed into this function need to have been normalized
assert (removedFile.getPath().equals(FilenameNormalization.normalize(removedFile.getPath()))) : "Path not normalized";
final String path = removedFile.getPath();
final Set<ASProject> affectedProjects = new HashSet<ASProject>();
Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate = new HashMap<ICompilerProject, Set<ICompilationUnit>>();
Collection<ICompilationUnit> relatedCompilationUnits = Collections.emptyList();
startIdleState();
try
{
relatedCompilationUnits = collectAssociatedCompilationUnits(removedFile);
// collect the affected projects before invalidating the relatedCompilationUnits, as removed
// compilation units will have their projects null'd out during invalidate, causing an NPE.
for (ICompilationUnit compilationUnit : relatedCompilationUnits)
{
if (compilationUnit == null)
continue;
ICompilerProject containingProject = compilationUnit.getProject();
assert(containingProject instanceof ASProject);
affectedProjects.add((ASProject)containingProject);
}
invalidate(removedFile, relatedCompilationUnits, cusToUpdate);
}
finally
{
File f = new File(path);
for (ASProject project : affectedProjects)
{
project.removeSourceFile(f);
}
// update the pathToCompilationUnitMapping CU by CU, rather than
// just taking the whole path away, as we don't want to loose mappings
// between SWC compilation units and the SWC path
for (ICompilationUnit cu : relatedCompilationUnits)
{
if (cu.getCompilationUnitType() != UnitType.SWC_UNIT)
{
pathToCompilationUnitMapping.remove(path, cu);
includeFilesToIncludingCompilationUnitMapping.remove(path, cu);
}
}
pathToFileSpecMap.remove(path);
endIdleState(cusToUpdate);
}
}
@Override
public void fileAdded(IFileSpecification addedFile)
{
// paths passed into this function need to have been normalized
assert (addedFile.getPath().equals(FilenameNormalization.normalize(addedFile.getPath()))) : "Path not normalized";
Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate = new HashMap<ICompilerProject, Set<ICompilationUnit>>();
startIdleState();
try
{
// It would be nice to be able to assert that the file being added
// is not a known file, but this is not currently possible. When builder project
// settings are changed in ways such as adding a new source path, there are two notifications,
// 1) The project has changed and Configurator.applyToProject() is called which adds any new files to the workspace.
// 2) eclipse then sends file adds on a file which we know about because of 1).
// Until the notification is straightened out, we can't have this assert. This just means that
// we're potentially doing a slightly more costly invalidation when fileAdded() is called instead
// of fileChanged(), but as this only really happens when project settings are changed, it shouldn't
// be a real performance hit.
// assert (!isKnownFile(addedFile));
String path = addedFile.getPath();
pathToFileSpecMap.put(path, addedFile);
getSWCManager().remove(new File(path));
File f = new File(path);
CompilerProject[] projects = getProjects();
boolean compilationUnitAdded = false;
for (CompilerProject project : projects)
{
compilationUnitAdded = project.handleAddedFile(f) || compilationUnitAdded;
if (project instanceof ASProject)
compilationUnitAdded = ((ASProject)project).invalidateLibraries(Collections.singleton(f)) || compilationUnitAdded;
}
Set<ICompilationUnit> compilationUnitsToInvalidate = new HashSet<ICompilationUnit>();
if (compilationUnitAdded)
{
// we now have compilation units from the newly added file, get it's
// name, and see if there's either:
// - any unresolved dependencies which could be resolved by this new name
// - any compilation units which depend on the new name, and could now have
// an ambiguous reference
Collection<ICompilationUnit> relatedCompilationUnits = collectAssociatedCompilationUnits(addedFile);
compilationUnitsToInvalidate.addAll(relatedCompilationUnits);
compilationUnitsToInvalidate.addAll(getCompilationUnitsDependingOnMissingDefinitions(relatedCompilationUnits));
}
// even if no compilation units were added, the added file may be a missing file which was
// a source for an embed, so need to invalidate any CUs which have a dependency on the missing filename
for (CompilerProject project : projects)
{
compilationUnitsToInvalidate.addAll(project.getDependenciesOnUnfoundReferencedSourceFile(addedFile.getPath()));
}
invalidate(addedFile, compilationUnitsToInvalidate, cusToUpdate);
}
finally
{
endIdleState(cusToUpdate);
}
}
private Set<ICompilationUnit> getCompilationUnitsDependingOnMissingDefinitions(Collection<ICompilationUnit> addedUnits)
{
Set<ICompilationUnit> compilationUnitsToInvalidate = new HashSet<ICompilationUnit>();
for (ICompilationUnit addedCompilationUnit : addedUnits)
{
try
{
CompilerProject project = (CompilerProject)addedCompilationUnit.getProject();
List<String> newIdentifierNames = addedCompilationUnit.getShortNames();
for (String newIdentifierName : newIdentifierNames)
{
compilationUnitsToInvalidate.addAll(project.getDependenciesOnUnfoundDefinition(newIdentifierName));
compilationUnitsToInvalidate.addAll(project.getDependenciesOnDefinition(newIdentifierName));
}
}
catch (InterruptedException e)
{
// should never happen, as all threads should be stopped
e.printStackTrace();
}
}
return compilationUnitsToInvalidate;
}
/**
* Determine if a file is currently referenced by any part of any project
* in the workspace.
*
* @param fileSpecification An {@link IFileSpecification} that can be used
* to get the name of the file to check.
* @return true if the specified file is referenced by any part of any
* project in the workspace, false otherwise.
*/
@SuppressWarnings("unused")
private boolean isKnownFile(IFileSpecification fileSpecification)
{
// make sure that nobody is calling isKnownFile outside of an assert
if (!assertionsEnabled)
throw new RuntimeException("isKnownFile() should only ever be called from an assert");
// paths passed into this function need to have been normalized
assert (fileSpecification.getPath().equals(FilenameNormalization.normalize(fileSpecification.getPath()))) : "Path not normalized";
Collection<WeakReference<ICompilationUnit>> relatedCompilationUnits = pathToCompilationUnitMapping.get(fileSpecification.getPath());
return (relatedCompilationUnits != null) && (relatedCompilationUnits.size() > 0);
}
/**
* Gets the {@link IFileSpecification} for the root source file of the
* specified {@link ICompilationUnit}.
*
* @param compilationUnit A compilation unit.
* @return Tthe {@link IFileSpecification} for the root source file of the
* specified {@link ICompilationUnit}
*/
public IFileSpecification getFileSpecificationForCompilationUnit(ICompilationUnit compilationUnit)
{
String path = compilationUnit.getAbsoluteFilename();
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
// Make sure that we seen this path associated with a compilation unit before.
assert pathToCompilationUnitMapping.get(path) != null;
return getFileSpecification(path);
}
/**
* Maintain a mapping between filenames and compilation units. Needed for
* incremental compilation.
*/
public void addCompilationUnit(ICompilationUnit compilationUnit)
{
String path = compilationUnit.getAbsoluteFilename();
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
pathToCompilationUnitMapping.add(path, compilationUnit);
}
/**
* Specifies that the specified compilation unit includes the specified list
* of files. Called by {@link ICompilationUnit}'s when they discover
* included files.
*
* @param includingCompilationUnit {@link ICompilationUnit} that contains
* include statements that reference the specified list of files.
* @param includedFiles The included files.
*/
public void addIncludedFilesToCompilationUnit(ICompilationUnit includingCompilationUnit, Collection<String> includedFiles)
{
includeFilesToIncludingCompilationUnitMapping.add(includedFiles.toArray(new String[includedFiles.size()]), includingCompilationUnit);
}
/**
* Remove any references to the compilation unit to the collection of include files it includes.
*
* @param includingCompilationUnit {@link ICompilationUnit} that contains
* include statements that reference the specified list of files.
* @param includedFiles The included files.
*/
public void removeIncludedFilesToCompilationUnit(ICompilationUnit includingCompilationUnit, Collection<String> includedFiles)
{
for (String includedFile : includedFiles)
{
includeFilesToIncludingCompilationUnitMapping.remove(includedFile, includingCompilationUnit);
}
}
/**
* Remove a compilation unit from the filename to compilation unit map
*
* @param compilationUnit The compilation unit to be removed.
*/
public void removeCompilationUnit(ICompilationUnit compilationUnit)
{
String path = compilationUnit.getAbsoluteFilename();
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
pathToCompilationUnitMapping.remove(path, compilationUnit);
((CompilationUnitBase)compilationUnit).clearIncludedFilesFromWorkspace();
// only remove the file spec if there are no more remaining CUs tied
// to that path
if (pathToCompilationUnitMapping.get(path).isEmpty() && includeFilesToIncludingCompilationUnitMapping.get(path).isEmpty())
{
pathToFileSpecMap.remove(path);
}
}
/**
* Get all compilation units from the filename across all projects
*
* @param path String to source filename
*/
public Collection<WeakReference<ICompilationUnit>> getCompilationUnits(String path)
{
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
return pathToCompilationUnitMapping.get(path);
}
/**
* Get all invisible compilation units from the filename across all projects
*
* @param path String to source filename
*/
public Collection<WeakReference<ICompilationUnit>> getInvisibleCompilationUnits(String path)
{
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
return pathToCompilationUnitMapping.getInvisible(path);
}
private static Collection<ICompilationUnit> getCompilationUnits(final StringToCompilationUnitMap compilationUnitMap, final String sortKey, ICompilerProject project)
{
Collection<WeakReference<ICompilationUnit>> compilationUnitRefs = compilationUnitMap.get(sortKey, project);
ArrayList<ICompilationUnit> compilationUnits = new ArrayList<ICompilationUnit>(compilationUnitRefs.size());
for (WeakReference<ICompilationUnit> cuRef : compilationUnitRefs)
{
final ICompilationUnit cu = cuRef.get();
// The get method pathToCompilationUnitMapping will filter out compilation units
// that have been removed from the project, so at this point
// we can assert the weak references will always return non-null.
assert cu != null
: "ICompilerProject's dependency graph should be pinning all the compilation units in the collection.";
compilationUnits.add(cu);
}
return compilationUnits;
}
private static Collection<ICompilationUnit> getInvisibleCompilationUnits(final StringToCompilationUnitMap compilationUnitMap, final String sortKey, ICompilerProject project)
{
Collection<WeakReference<ICompilationUnit>> compilationUnitRefs = compilationUnitMap.getInvisible(sortKey, project);
ArrayList<ICompilationUnit> compilationUnits = new ArrayList<ICompilationUnit>(compilationUnitRefs.size());
for (WeakReference<ICompilationUnit> cuRef : compilationUnitRefs)
{
final ICompilationUnit cu = cuRef.get();
// Nothing in the compiler pins the invisible compilation units
// so we have to check that the weak reference is still good.
if (cu != null)
{
assert cu.isInvisible()
: "StringToCompilationUnitMap.getInvisible returned a visible compilation unit.";
compilationUnits.add(cu);
}
}
return compilationUnits;
}
@Override
public Collection<ICompilationUnit> getCompilationUnits(String path, ICompilerProject project)
{
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
return getCompilationUnits(pathToCompilationUnitMapping, path, project);
}
private static Iterable<ICompilationUnit> getInvisibleAndVisibleCompilationUnits(final StringToCompilationUnitMap compilationUnitMap,
final String sortKey, final ICompilerProject project)
{
final Iterable<ICompilerProject> projectIterable = Collections.singleton(project);
final Iterable<Iterable<ICompilationUnit>> invisibleIterableIterable = Iterables.transform(projectIterable, new Function<ICompilerProject, Iterable<ICompilationUnit>>()
{
@Override
public Iterable<ICompilationUnit> apply(final ICompilerProject input)
{
return getInvisibleCompilationUnits(compilationUnitMap, sortKey, input);
}
});
final Iterable<Iterable<ICompilationUnit>> visibleIterableIterable = Iterables.transform(projectIterable, new Function<ICompilerProject, Iterable<ICompilationUnit>>()
{
@Override
public Iterable<ICompilationUnit> apply(final ICompilerProject input)
{
return getCompilationUnits(compilationUnitMap, sortKey, input);
}
});
return Iterables.concat(Iterables.concat(invisibleIterableIterable, visibleIterableIterable));
}
@Override
public Iterable<ICompilationUnit> getInvisibleAndVisibleCompilationUnits(final String path, final ICompilerProject project)
{
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
return getInvisibleAndVisibleCompilationUnits(pathToCompilationUnitMapping, path, project);
}
/**
* Get all compilation units from which the filename is included related to the specified
* project
*
* @param path String to source filename
* @param project containing project
*/
public Collection<ICompilationUnit> getIncludingCompilationUnits(String path, ICompilerProject project)
{
// paths passed into this function need to have been normalized
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
return getCompilationUnits(includeFilesToIncludingCompilationUnitMapping, path, project);
}
/**
* If there is an existing EmbedData which is equivalent to the data
* EmbedData, return the equiv. otherwise return the entered EmbedData
* @param data The embedding data.
* @return the canonical EmbedData
*/
public EmbedData getCanonicalEmbedData(EmbedData data)
{
EmbedData cachedData = embedDataCache.get(data);
if (cachedData != null)
return cachedData;
embedDataCache.put(data, data);
return data;
}
/**
* Returns all EmbedDatas
*
* @return map of EmbedDatas
*/
public Map<EmbedData, EmbedData> getEmbedDatas()
{
return embedDataCache;
}
/**
* Returns the invisibleCompilationUnitReferenceQueue which is used to remove
* the backing Compilation Units when invisible compilation units get gc'd
*
* @return the invisibleCompilationUnitReferenceQueue
*/
public FinalizableReferenceQueue getInvisibleCompilationUnitReferenceQueue()
{
return invisibleCompilationUnitReferenceQueue;
}
@Override
public IMXMLDataManager getMXMLDataManager()
{
return mxmlDataManager;
}
/**
* Returns the most recent {@link IFileSpecification} given to the workspace
* for a specified path. If the workspace has not seen the specified path
* before a new {@link FileSpecification} is returned.
* @param path Path for which a {@link IFileSpecification} should be returned.
* @return The most recent {@link IFileSpecification} given to the workspace
* for a specified path.
*/
@Override
public synchronized IFileSpecification getFileSpecification(String path)
{
assert (path.equals(FilenameNormalization.normalize(path))) : "Path not normalized";
IFileSpecification fileSpec = pathToFileSpecMap.get(path);
if (fileSpec == null)
{
fileSpec = new FileSpecification(path);
pathToFileSpecMap.put(path, fileSpec);
}
return fileSpec;
}
@Override
public IWorkspace getWorkspace()
{
return this;
}
/**
* Returns the most recent {@link IBinaryFileSpecification} given to the workspace
* for a specified path. If the workspace has not seen the specified path
* before a new {@link IBinaryFileSpecification} is returned.
* This method assumes that if there is an existing filespec, it is a binary
* filespec.
* @param path Path for which a {@link IBinaryFileSpecification} should be returned.
* @return The most recent {@link IBinaryFileSpecification} given to the workspace
* for a specified path.
*/
public synchronized IBinaryFileSpecification getLatestBinaryFileSpecification(String path)
{
IFileSpecification fileSpec = getFileSpecification(path);
IBinaryFileSpecification binaryFileSpec = null;
if (fileSpec instanceof IBinaryFileSpecification)
{
binaryFileSpec = (IBinaryFileSpecification)fileSpec;
}
else
{
// TODO: may need to handle this case, and replace with a binary filespec, but for
// now assert, and cross that bridge if we get to it.
assert false : "requesting binary fileSpec but existing fileSpec not of binary type";
}
return binaryFileSpec;
}
private ReferenceCache refCache = new ReferenceCache();
/**
* Get the ReferenceCache used by this workspace. This is to facilitate caching
* of IReference objects across the workspace, as many references will be repeated
* throughout a workspace. Since the IReferences are immutable, they need not be tied
* to a particular Project
* @return the ReferenceCache this workspace is using.
*/
public ReferenceCache getReferenceCache()
{
return refCache;
}
/**
* Delete the reference of compiler project from the workspace
*
* @param compilerProject ICompilerProject
*/
public void deleteProject(ICompilerProject compilerProject)
{
projects.remove(compilerProject);
}
@Override
public IASDocDelegate getASDocDelegate()
{
return asDocDelegate;
}
@Override
public void setASDocDelegate(IASDocDelegate asDocDelegate)
{
assert asDocDelegate != null : "ASDoc delegate can not be null, use default implementation instead!";
this.asDocDelegate = asDocDelegate;
}
/**
* Class that keeps track of:
* <ul>
* <li>how many build operations are currently pending</li>
* <li>which threads have requested the idle state</li>
* <li>which thread currently owns the idle state</li>
* <li>whether or not build operations that build file scopes should be
* allowed to proceed even if a thread owns the idle state.</li>
*/
private static final class BuildSynchronizationState
{
private final ReentrantLock lock;
private final Condition condition;
/**
* Used to allow pairs of startIdleState/endIdleState to nest.
*
*/
private int idleStateCount;
/**
* Count of how many build operations are currently pending.
*/
private int activitiyCount;
/**
* True if build operations needed to build file scopes are allowed to
* proceed.
*/
private boolean allowFileScopeRequests;
/**
* Queue of threads that have requested to own the idle state.
*/
private final LinkedList<Thread> threadsRequestingIdle;
BuildSynchronizationState()
{
lock = new ReentrantLock();
condition = lock.newCondition();
threadsRequestingIdle = new LinkedList<Thread>();
}
void startRequest(boolean isFileScopeRequest)
{
lock.lock();
try
{
while (!canStartRequest(isFileScopeRequest))
condition.awaitUninterruptibly();
++activitiyCount;
}
finally
{
lock.unlock();
}
}
void endRequest()
{
lock.lock();
try
{
assert activitiyCount > 0;
--activitiyCount;
condition.signalAll();
}
finally
{
lock.unlock();
}
}
void startIdleState()
{
lock.lock();
try
{
final Thread currentThread = Thread.currentThread();
final Thread currentIdleThread = threadsRequestingIdle.peekFirst();
if (currentIdleThread == currentThread)
{
assert idleStateCount > 0;
++idleStateCount;
assert idleStateCount > 0;
return;
}
threadsRequestingIdle.add(currentThread);
while ((activitiyCount > 0) || (threadsRequestingIdle.getFirst() != currentThread))
condition.awaitUninterruptibly();
assert idleStateCount == 0;
assert activitiyCount == 0;
assert threadsRequestingIdle.getFirst() == currentThread;
idleStateCount = 1;
}
finally
{
lock.unlock();
}
}
void startAllowingFileScopeRequests()
{
lock.lock();
try
{
final Thread currentThread = Thread.currentThread();
final Thread currentIdleThread = threadsRequestingIdle.peekFirst();
assert currentThread == currentIdleThread;
assert !allowFileScopeRequests;
allowFileScopeRequests = true;
condition.signalAll();
}
finally
{
lock.unlock();
}
}
void endAllowingFileScopeRequests()
{
lock.lock();
try
{
final Thread currentThread = Thread.currentThread();
final Thread currentIdleThread = threadsRequestingIdle.peekFirst();
assert currentThread == currentIdleThread;
assert allowFileScopeRequests;
while (activitiyCount > 0)
condition.awaitUninterruptibly();
allowFileScopeRequests = false;
}
finally
{
lock.unlock();
}
}
void endIdleState()
{
lock.lock();
try
{
final Thread currentThread = Thread.currentThread();
assert currentThread == threadsRequestingIdle.getFirst();
assert !allowFileScopeRequests;
assert idleStateCount > 0;
assert activitiyCount == 0;
--idleStateCount;
assert idleStateCount >= 0;
if (idleStateCount == 0)
{
threadsRequestingIdle.remove();
assert currentThread != threadsRequestingIdle.peekFirst();
condition.signalAll();
}
}
finally
{
lock.unlock();
}
}
boolean isBuilding()
{
lock.lock();
try
{
return (!allowFileScopeRequests) && (activitiyCount > 0);
}
finally
{
lock.unlock();
}
}
private boolean canStartRequest(boolean isFileScopeRequest)
{
if (allowFileScopeRequests)
{
assert idleStateCount > 0 : "workspace must be in an idle state if only file scope requests are allowed to proceed.";
assert !threadsRequestingIdle.isEmpty();
if (isFileScopeRequest)
return true;
else
return false;
}
// A request can start if there is already at least on requesting running
// or if no thread has requested the workspace to enter the idle state.
return (activitiyCount > 0) || (threadsRequestingIdle.isEmpty());
}
}
public void addProject(CompilerProject project)
{
// Need to give a non-null value, the class object for Object
// is a good a non-value as anything.
projects.put(project, Object.class);
}
}