blob: 99d656ecbcefa918880d5d35cf930bca082c86b8 [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.flex.compiler.internal.units;
import static com.google.common.collect.Collections2.transform;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.io.FilenameUtils;
import org.apache.flex.compiler.common.DependencyType;
import org.apache.flex.compiler.common.IDefinitionPriority;
import org.apache.flex.compiler.common.IEmbedResolver;
import org.apache.flex.compiler.common.IFileSpecificationGetter;
import org.apache.flex.compiler.css.ICSSDocument;
import org.apache.flex.compiler.css.ICSSRule;
import org.apache.flex.compiler.css.ICSSSelector;
import org.apache.flex.compiler.definitions.IClassDefinition;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.filespecs.IFileSpecification;
import org.apache.flex.compiler.internal.css.CSSFunctionCallPropertyValue;
import org.apache.flex.compiler.internal.css.codegen.CSSCompilationSession;
import org.apache.flex.compiler.internal.css.semantics.CSSSemanticAnalyzer;
import org.apache.flex.compiler.internal.embedding.EmbedAttribute;
import org.apache.flex.compiler.internal.embedding.EmbedData;
import org.apache.flex.compiler.internal.graph.LinkReportWriter;
import org.apache.flex.compiler.internal.projects.ASProject;
import org.apache.flex.compiler.internal.projects.CompilerProject;
import org.apache.flex.compiler.internal.projects.DefinitionPriority;
import org.apache.flex.compiler.internal.projects.DependencyGraph;
import org.apache.flex.compiler.internal.scopes.ASFileScope;
import org.apache.flex.compiler.internal.scopes.ASProjectScope;
import org.apache.flex.compiler.internal.scopes.ASProjectScope.DefinitionPromise;
import org.apache.flex.compiler.internal.tree.as.NodeBase;
import org.apache.flex.compiler.internal.units.requests.ABCBytesRequestResult;
import org.apache.flex.compiler.internal.units.requests.FileScopeRequestResultBase;
import org.apache.flex.compiler.internal.units.requests.RequestMaker;
import org.apache.flex.compiler.internal.units.requests.SyntaxTreeRequestResult;
import org.apache.flex.compiler.mxml.IXMLNameResolver;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.problems.InternalCompilerProblem2;
import org.apache.flex.compiler.projects.ICompilerProject;
import org.apache.flex.compiler.projects.IFlexProject;
import org.apache.flex.compiler.targets.ITarget.TargetType;
import org.apache.flex.compiler.tree.as.IASNode;
import org.apache.flex.compiler.tree.as.IFileNodeAccumulator;
import org.apache.flex.compiler.tree.as.IImportNode;
import org.apache.flex.compiler.units.ICompilationUnit;
import org.apache.flex.compiler.units.IInvisibleCompilationUnit;
import org.apache.flex.compiler.units.requests.IABCBytesRequestResult;
import org.apache.flex.compiler.units.requests.IFileScopeRequestResult;
import org.apache.flex.compiler.units.requests.IOutgoingDependenciesRequestResult;
import org.apache.flex.compiler.units.requests.IRequest;
import org.apache.flex.compiler.units.requests.ISWFTagsRequestResult;
import org.apache.flex.compiler.units.requests.ISyntaxTreeRequestResult;
import org.apache.flex.compiler.workspaces.IWorkspaceProfilingDelegate;
import org.apache.flex.swf.SWFFrame;
import org.apache.flex.swf.tags.DoABCTag;
import org.apache.flex.utils.FilenameNormalization;
import org.apache.flex.utils.StringEncoder;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.base.FinalizableWeakReference;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
/**
* Abstract class used to share implementation of some ICompilationUnit methods
* across multiple concrete implementation classes.
* <p>
* This class converts calls to the getXXXRequest methods to calls into
* handleXXXRequest methods. Subclasses can just implement the handleXXXRequest
* methods without having to worry about creating an IRequest implementation.
*/
public abstract class CompilationUnitBase implements ICompilationUnit
{
protected static final String DEFAULT_DO_ABC_TAG_NAME = "script";
private static class InvisibleCompilationUnitRef extends FinalizableWeakReference<InvisibleCompilationUnit>
{
private final CompilationUnitBase delegate;
/**
* @param referent
* @param queue
*/
protected InvisibleCompilationUnitRef(CompilationUnitBase delegate, InvisibleCompilationUnit referent, FinalizableReferenceQueue queue)
{
super(referent, queue);
this.delegate = delegate;
}
@Override
public void finalizeReferent()
{
CompilerProject project = delegate.getProject();
// if the project is null, the delegate CU has
// already been manually removed by a client, so
// nothing more to do.
if (project == null)
return;
// when the InvisibleCompilationUnit get's gc'd, make sure
// we also remove the CU which isn't pointing too.
project.removeCompilationUnit(delegate);
}
}
/**
* Semantic analyze a collection of CSS model objects, and return a
* {@link CSSCompilationSession} that contains the resolved symbols.
* <ol>
* <li>Resolve type selectors to {@link IClassDefinition} definitions.</li>
* <li>Find all the dependencies introduced by {@code ClassReference()} and
* {@code Embed()} property values.</li>
* </ol>
*
* @param cssCompilationSession A {@code CSSCompilationSession} object that
* contains resolved symbols from all the CSS models in {@code cssDocuments}
* collection.
* @param xmlNameResolver Resolve type selectors to definitions.
* @param cssDocuments A list of CSS model objects.
* @param problems Compiler problem collection.
*/
protected void updateStyleCompilationUnitDependencies(
final CSSCompilationSession cssCompilationSession,
final IXMLNameResolver xmlNameResolver,
final Iterable<ICSSDocument> cssDocuments,
final Collection<ICompilerProblem> problems)
{
final Set<IClassDefinition> classReferences = new LinkedHashSet<IClassDefinition>();
final Set<EmbedCompilationUnit> dependentEmbedCompilationUnits = new LinkedHashSet<EmbedCompilationUnit>();
for (final ICSSDocument cssDocument : cssDocuments)
{
final boolean isFlex3CSS = ((IFlexProject)project).getCSSManager().isFlex3CSS();
final ImmutableMap<ICSSSelector, String> resolvedSelectors =
CSSSemanticAnalyzer.resolveSelectors(xmlNameResolver, cssDocument, problems, isFlex3CSS);
// Store resolved type selectors required by CSS code generation.
cssCompilationSession.resolvedSelectors.putAll(resolvedSelectors);
// Store resolved embed compilation units required by CSS code generation.
for (final ICSSRule cssRule : cssDocument.getRules())
{
final Map<CSSFunctionCallPropertyValue, EmbedCompilationUnit> resolvedEmbedProperties =
new HashMap<CSSFunctionCallPropertyValue, EmbedCompilationUnit>();
CSSSemanticAnalyzer.resolveDependencies(
resolvedEmbedProperties,
cssRule,
project,
classReferences,
dependentEmbedCompilationUnits,
problems);
cssCompilationSession.resolvedEmbedProperties.putAll(resolvedEmbedProperties);
}
}
// Convert from "IClassDefinition" to "ICompilationUnit".
final Function<IClassDefinition, ICompilationUnit> findCompilationUnitForClass = new Function<IClassDefinition, ICompilationUnit>()
{
@Override
public ICompilationUnit apply(final IClassDefinition classDefinition)
{
return project.getScope().getCompilationUnitForDefinition(classDefinition);
}
};
final Collection<ICompilationUnit> classReferenceCompilationUnits =
transform(classReferences, findCompilationUnitForClass);
// "The 'IClassDefinition' was resolved from the project, so there should be a compilation
// unit for each IClassDefinition definition. If there's an exception for a null compilation
// unit being added to the ImmutableSet, there's probably a bug in
// CSSSemanticAnalyzer.resolveDependencies().
// Merge dependencies from ClassReference() and Embed().
final ImmutableSet<ICompilationUnit> dependencies = new ImmutableSet.Builder<ICompilationUnit>()
.addAll(classReferenceCompilationUnits)
.addAll(dependentEmbedCompilationUnits)
.build();
for (final ICompilationUnit dependee : dependencies)
{
project.getDependencyGraph().addDependency(this, dependee, DependencyType.EXPRESSION);
}
}
private CompilerProject project;
private final String absoluteFilename;
private List<IDefinition> definitionPromises;
private final DefinitionPriority definitionPriority;
protected final AtomicReference<IRequest<ISyntaxTreeRequestResult, ICompilationUnit>> syntaxTreeRequest;
protected final AtomicReference<IRequest<IFileScopeRequestResult, ICompilationUnit>> fileScopeRequest;
protected final AtomicReference<IRequest<IABCBytesRequestResult, ICompilationUnit>> abc;
private final AtomicReference<IRequest<ISWFTagsRequestResult, ICompilationUnit>> tags;
protected final AtomicReference<IRequest<IOutgoingDependenciesRequestResult, ICompilationUnit>> problems;
private final Set<ICompilationUnit> embedCompilationUnits;
private IFileSpecificationGetter fileSpecificationGetter;
private InvisibleCompilationUnitRef invisibleCURef;
private final AtomicInteger state;
/**
* Cached name of this compilation unit. Must be set in constructor
*
* It would be nice if this could be final, but it can't because of the multiplicity of constructors in this class
*/
private String name;
/**
* Tracks whether or not the syntax tree must be built before the file scope
* for this {@link ICompilationUnit} can be built. This flag is needed to
* make incremental building of projects containing files on the source list
* work.
*/
private final boolean scopeBuiltFromSyntaxTree;
private static final RequestMaker<ISyntaxTreeRequestResult, ICompilationUnit, CompilationUnitBase> syntaxTreeRequestMaker =
new RequestMaker<ISyntaxTreeRequestResult, ICompilationUnit, CompilationUnitBase>()
{
@Override
protected Callable<ISyntaxTreeRequestResult> getCallable(final CompilationUnitBase u)
{
return new Callable<ISyntaxTreeRequestResult>()
{
@Override
public ISyntaxTreeRequestResult call() throws InterruptedException
{
return u.processSyntaxTreeRequest();
}
};
}
@Override
protected ISyntaxTreeRequestResult getResultForThrowable(CompilationUnitBase u, Throwable throwable)
{
final ICompilerProblem prob = new InternalCompilerProblem2(
u.getRootFileSpecification().getPath(), throwable, "syntax tree request");
return new SyntaxTreeRequestResult(u.getRootFileSpecification().getLastModified(), Collections.singleton(prob));
}
};
private static final RequestMaker<IFileScopeRequestResult, ICompilationUnit, CompilationUnitBase> fileScopeRequestMaker =
new RequestMaker<IFileScopeRequestResult, ICompilationUnit, CompilationUnitBase>()
{
@Override
protected Callable<IFileScopeRequestResult> getCallable(final CompilationUnitBase u)
{
return new Callable<IFileScopeRequestResult>()
{
@Override
public IFileScopeRequestResult call() throws InterruptedException
{
return u.processFileScopeRequest();
}
};
}
@Override
protected IFileScopeRequestResult getResultForThrowable(CompilationUnitBase u, Throwable throwable)
{
final ICompilerProblem prob = new InternalCompilerProblem2(
u.getRootFileSpecification().getPath(), throwable, "file scope builder");
return new FileScopeRequestResultBase(Collections.singleton(prob), null);
}
};
private static final RequestMaker<IABCBytesRequestResult, ICompilationUnit, CompilationUnitBase> abcBytesRequestMaker =
new RequestMaker<IABCBytesRequestResult, ICompilationUnit, CompilationUnitBase>()
{
@Override
protected Callable<IABCBytesRequestResult> getCallable(final CompilationUnitBase u)
{
return new Callable<IABCBytesRequestResult>()
{
@Override
public IABCBytesRequestResult call() throws Exception
{
return u.processABCBytesRequest();
}
};
}
@Override
protected IABCBytesRequestResult getResultForThrowable(CompilationUnitBase u, Throwable throwable)
{
return new ABCBytesRequestResult(new ICompilerProblem[] {
new InternalCompilerProblem2(u.getAbsoluteFilename(), throwable, "ABC generator")});
}
};
private static final RequestMaker<ISWFTagsRequestResult, ICompilationUnit, CompilationUnitBase> swfTagsRequestMaker =
new RequestMaker<ISWFTagsRequestResult, ICompilationUnit, CompilationUnitBase>()
{
@Override
protected Callable<ISWFTagsRequestResult> getCallable(final CompilationUnitBase u)
{
return new Callable<ISWFTagsRequestResult>()
{
@Override
public ISWFTagsRequestResult call() throws Exception
{
return u.processSWFTTagsRequest();
}
};
}
@Override
protected ISWFTagsRequestResult getResultForThrowable(CompilationUnitBase u, Throwable throwable)
{
final ICompilerProblem prob = new InternalCompilerProblem2(
u.getRootFileSpecification().getPath(), throwable, "SWFTags");
return new ISWFTagsRequestResult()
{
@Override
public ICompilerProblem[] getProblems()
{
return new ICompilerProblem[] {prob};
}
@Override
public boolean addToFrame(SWFFrame f)
{
return false;
}
@Override
public String getDoABCTagName()
{
return "";
}
@Override
public DoABCTag getDoABCTag()
{
return null;
}
};
}
};
private static final RequestMaker<IOutgoingDependenciesRequestResult, ICompilationUnit, CompilationUnitBase> outgoingDependenciesRequestMaker =
new RequestMaker<IOutgoingDependenciesRequestResult, ICompilationUnit, CompilationUnitBase>()
{
@Override
protected Callable<IOutgoingDependenciesRequestResult> getCallable(final CompilationUnitBase u)
{
return new Callable<IOutgoingDependenciesRequestResult>()
{
@Override
public IOutgoingDependenciesRequestResult call() throws Exception
{
return u.processOutgoingDependenciesRequest();
}
};
}
@Override
protected IOutgoingDependenciesRequestResult getResultForThrowable(CompilationUnitBase u, Throwable throwable)
{
final ICompilerProblem prob = new InternalCompilerProblem2(
u.getRootFileSpecification().getPath(), throwable, "outgoing dependency");
return new IOutgoingDependenciesRequestResult()
{
@Override
public ICompilerProblem[] getProblems()
{
return new ICompilerProblem[] {prob};
}
};
}
};
/**
* Master constructor. Other accesible constructors end up funneling down to
* this one.
*
* @param project
* @param path
* @param basePriority
* @param doInitDefinitionPromises - if true will "complete" the
* construction by allocating an empty list of definitions, and also
* initialize the name field. If false, caller must initialize
* this.definitionPromises and this.name
* @param scopeBuiltFromSyntaxTree If true, the request that builds the file
* scope requires that the request that builds the syntax tree be completed
* first.
*/
private CompilationUnitBase(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority, boolean doInitDefinitionPromises, boolean scopeBuiltFromSyntaxTree)
{
this.project = project;
this.absoluteFilename = FilenameNormalization.normalize(path);
definitionPriority = new DefinitionPriority(basePriority, 0);
syntaxTreeRequest = new AtomicReference<IRequest<ISyntaxTreeRequestResult, ICompilationUnit>>();
fileScopeRequest = new AtomicReference<IRequest<IFileScopeRequestResult, ICompilationUnit>>();
abc = new AtomicReference<IRequest<IABCBytesRequestResult, ICompilationUnit>>();
tags = new AtomicReference<IRequest<ISWFTagsRequestResult, ICompilationUnit>>();
problems = new AtomicReference<IRequest<IOutgoingDependenciesRequestResult, ICompilationUnit>>();
embedCompilationUnits = new HashSet<ICompilationUnit>();
fileSpecificationGetter = project.getWorkspace();
state = new AtomicInteger();
if (doInitDefinitionPromises)
{
definitionPromises = Collections.emptyList();
name = computeName();
}
this.scopeBuiltFromSyntaxTree = scopeBuiltFromSyntaxTree;
}
/**
* This is the most generic constructor
*
* @param project
* @param path
* @param basePriority
* @param qnamesOfDefinitions is a collection with all the definition promises for the compilation unit
*/
protected CompilationUnitBase(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority, Collection<String> qnamesOfDefinitions)
{
this(project, path, basePriority, false, qnamesOfDefinitions.isEmpty());
definitionPromises = createDefinitionPromisesFromQnames(qnamesOfDefinitions, this);
name = computeName(); // now that the definitions are set, we can cache the name
}
/**
* Use this constructor if derived class has no definition promises
*/
protected CompilationUnitBase(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority, boolean scopeBuiltFromSytaxTree)
{
this(project, path, basePriority, true, scopeBuiltFromSytaxTree);
}
/**
* Use this constructor if the derived class has at most one definition promise
*
* @param project
* @param path
* @param basePriority
* @param qnameOfDefinition is the definition promise, or null
*/
protected CompilationUnitBase(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority, String qnameOfDefinition)
{
this(project, path, basePriority, qnameOfDefinition == null ? Collections.<String>emptyList() : Collections.<String>singletonList(qnameOfDefinition));
}
private static List<IDefinition> createDefinitionPromisesFromQnames(Collection<String> qnames, ICompilationUnit cu)
{
List<IDefinition> ret = new ArrayList<IDefinition>();
for (String qname : qnames)
{
if (qname != null) // some ctors pass in null qname string - it makes things easier. Just ignore them
ret.add(ASProjectScope.createDefinitionPromise(qname, cu));
}
return ret;
}
/**
* Helper method that atomically updates the state of this compilation unit
* to reflect that the specified operation is now complete.
* @param operation The operation that is now complete.
*/
private final void operationComplete(ICompilationUnit.Operation operation)
{
boolean done = false;
do
{
int currentState = state.get();
int newState = currentState | operation.mask;
done = state.compareAndSet(currentState, newState);
} while (!done);
}
/**
* Atomically checks the state of this compilation unit to determine
* if all the specified operations are complete.
* @param operations The set of operation to check.
* @return true if all the specified operations are complete, false otherwise.
*/
protected final boolean operationsCompleted(EnumSet<ICompilationUnit.Operation> operations)
{
int currentState = state.get();
for (ICompilationUnit.Operation operation : operations)
{
if ((currentState & operation.mask) == 0)
return false;
}
return true;
}
/**
* @return List of definition promises
*/
@Override
public List<IDefinition> getDefinitionPromises()
{
return definitionPromises;
}
@Override
public CompilerProject getProject()
{
return project;
}
/**
* Builds a {@link ISyntaxTreeRequestResult}. In many cases the {@link ISyntaxTreeRequestResult}
* is built by parsing a source file into a syntax tree.
* <p>
* Called by this class from potentially any thread. This class guarantees
* that this method will only be entered from one thread at a time for a
* given instance of this class, so implementations do not need to make this
* a synchronized method.
*
* @return The {@link ISyntaxTreeRequestResult} for this compilation unit.
* @throws InterruptedException
*/
protected abstract ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException;
/**
* Builds a {@link IFileScopeRequestResult}. In many cases the
* {@link IFileScopeRequestResult} is built by walking an AST produced by a parser.
* <p>
* Called by this class from potentially any thread. This class guarantees
* that this method will only be entered from one thread at a time for a
* given instance of this class, so implementations do not need to make this
* a synchronized method.
*
* @return The {@link IFileScopeRequestResult} for this compilation unit.
*/
protected abstract IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException;
/**
* Builds a IABCBytesRequestResult. In many cases the IABCBytesRequestResult
* is built running a code generator over an AST produced by a parser.
* <p>
* Called by this class from potentially any thread. This class guarantees
* that this method will only be entered from one thread at a time for a
* given instance of this class, so implementations do not need to make this
* a synchronized method.
*
* @return The IABCBytesRequestResult for this compilation unit.
*/
protected abstract IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException;
/**
* Builds a ISWFTagsRequestResult. In many cases the ISWFTagsRequestResult is built
* getting the IABCBytesRequestResult and wrapping its abc bytes in a
* DoABCTag.
* <p>
* Called by this class from potentially any thread. This class guarantees
* that this method will only be entered from one thread at a time for a
* given instance of this class, so implementations do not need to make this
* a synchronized method.
*
* @return The ISWFTagsRequestResult for this compilation unit.
*/
protected abstract ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException;
/**
* Builds a IOutgoingDependenciesRequestResult. In many cases the
* IOutgoingDependenciesRequestResult is built getting the walking an AST.
* <p>
* Called by this class from potentially any thread. This class guarantees
* that this method will only be entered from one thread at a time for a
* given instance of this class, so implementations do not need to make this
* a synchronized method.
*
* @return The IOutgoingDependenciesRequestResult for this compilation unit.
*/
protected abstract IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException;
@Override
public IRequest<ISyntaxTreeRequestResult, ICompilationUnit> getSyntaxTreeRequest()
{
return syntaxTreeRequestMaker.getRequest(this, syntaxTreeRequest, project.getWorkspace(), scopeBuiltFromSyntaxTree);
}
@Override
public IRequest<IFileScopeRequestResult, ICompilationUnit> getFileScopeRequest()
{
return fileScopeRequestMaker.getRequest(this, fileScopeRequest, project.getWorkspace(), true);
}
@Override
public IRequest<IABCBytesRequestResult, ICompilationUnit> getABCBytesRequest()
{
return abcBytesRequestMaker.getRequest(this, abc, project.getWorkspace(), false);
}
@Override
public IRequest<ISWFTagsRequestResult, ICompilationUnit> getSWFTagsRequest()
{
return swfTagsRequestMaker.getRequest(this, tags, project.getWorkspace(), false);
}
@Override
public IRequest<IOutgoingDependenciesRequestResult, ICompilationUnit> getOutgoingDependenciesRequest()
{
return outgoingDependenciesRequestMaker.getRequest(this, problems, project.getWorkspace(), false);
}
private Collection<IDefinition> getAllDefinitions() throws InterruptedException
{
Collection<IDefinition> definitions;
if (definitionPromises.isEmpty())
{
IFileScopeRequestResult fileScopeRequestResult = getFileScopeRequest().get();
definitions = fileScopeRequestResult.getExternallyVisibleDefinitions();
}
else
{
definitions = definitionPromises;
}
return definitions;
}
@Override
public List<String> getShortNames() throws InterruptedException
{
Collection<IDefinition> definitions = getAllDefinitions();
List<String> shortNames = new ArrayList<String>(definitions.size());
for (IDefinition definition : definitions)
{
shortNames.add(definition.getBaseName());
}
return shortNames;
}
@Override
public List<String> getQualifiedNames() throws InterruptedException
{
Collection<IDefinition> definitions = getAllDefinitions();
List<String> qualifiedNames = new ArrayList<String>(definitions.size());
for (IDefinition definition : definitions)
{
qualifiedNames.add(definition.getQualifiedName());
}
return qualifiedNames;
}
@Override
public final String getAbsoluteFilename()
{
return absoluteFilename;
}
protected final String getFilenameNoPath()
{
return FilenameUtils.getName(absoluteFilename);
}
protected IFileSpecification getRootFileSpecification()
{
final String fileName = getAbsoluteFilename();
return fileSpecificationGetter.getFileSpecification(fileName);
}
protected IFileSpecificationGetter getFileSpecificationGetter()
{
return fileSpecificationGetter;
}
@Override
public boolean clean(Map<ICompilerProject, Set<File>> invalidatedSWCFiles, Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate, final boolean clearFileScope)
{
IWorkspaceProfilingDelegate profilingDelegate = project.getWorkspace().getProfilingDelegate();
if (profilingDelegate != null)
profilingDelegate.operationStarted(this, Operation.INVALIDATE_CU);
project.removeDependencies(Collections.<ICompilationUnit>singletonList(this));
if (clearFileScope)
{
clearIncludedFilesFromWorkspace();
// If this compilation unit is invisible don't try to
// remove definitions from the project scope.
if (!isInvisible())
project.getScope().removeCompilationUnits(Collections.<ICompilationUnit>singletonList(this));
fileScopeRequest.set(null);
// If we are are invalidating the file scope,
// we should also invalidate the ast.
syntaxTreeRequest.set(null);
project.removeAnyUnfoundDependencies(this);
}
abc.set(null);
tags.set(null);
problems.set(null);
embedCompilationUnits.clear();
project.resetScopeCacheForCompilationUnit(this);
if (clearFileScope)
updateDefinitions(cusToUpdate);
// delegate to a virtual method that sub-classes can override to
// do additional cleaning.
handleClean(clearFileScope, invalidatedSWCFiles);
if (profilingDelegate != null)
profilingDelegate.operationCompleted(this, Operation.INVALIDATE_CU);
return true;
}
/**
* This method is used to update the Workspace
* includeFilesToIncludingCompilationUnitMapping map when a file get's cleaned. It
* goes to great pains to not reparse the file, as if the file hasn't been parsed,
* there shouldn't be any files related to this compilation unit in the map.
*/
public void clearIncludedFilesFromWorkspace()
{
try
{
IRequest<ISyntaxTreeRequestResult, ICompilationUnit> req = syntaxTreeRequest.get();
if (req != null && req.isDone())
project.getWorkspace().removeIncludedFilesToCompilationUnit(this, req.get().getIncludedFiles());
}
catch (InterruptedException e)
{
// this should never happen, as req is only ever called when it's already done.
}
}
/**
* This method is overriden by base classes to
* clean additional processing results not cleaned by
* the clean method above.
* @param invalidatedSWCFiles
*/
protected void handleClean(boolean clearFileScope, Map<ICompilerProject, Set<File>> invalidatedSWCFiles)
{
}
private void updateDefinitions(Map<ICompilerProject, Set<ICompilationUnit>> cusToUpdate)
{
// If this compilation unit is the delegate
// of an invisible compilation unit, then do
// *not* add any definition to the project
// symbol table.
if (isInvisible())
return;
// Add back in any definition promises or FileScopes depending how the
// input file was added to the project
if (definitionPromises.isEmpty()) // no definitionPromises, so parse file
{
Set<ICompilationUnit> cus = cusToUpdate.get(getProject());
if (cus == null)
{
cus = new HashSet<ICompilationUnit>();
cusToUpdate.put(getProject(), cus);
}
cus.add(this);
}
else // add definition promises
{
for (IDefinition definitionPromise : definitionPromises)
{
if (definitionPromise instanceof DefinitionPromise)
((DefinitionPromise)definitionPromise).clean();
project.getScope().addDefinition(definitionPromise);
}
}
}
protected final void startProfile(Operation operation)
{
IWorkspaceProfilingDelegate profilingDelegate = project.getWorkspace().getProfilingDelegate();
if (profilingDelegate == null)
return;
profilingDelegate.operationStarted(this, operation);
}
protected final void stopProfile(Operation operation)
{
IWorkspaceProfilingDelegate profilingDelegate = project.getWorkspace().getProfilingDelegate();
if (profilingDelegate == null)
return;
profilingDelegate.operationCompleted(this, operation);
}
private ISyntaxTreeRequestResult processSyntaxTreeRequest() throws InterruptedException
{
ISyntaxTreeRequestResult result = handleSyntaxTreeRequest();
IASNode ast = result.getAST();
verifyAST(ast);
operationComplete(ICompilationUnit.Operation.GET_SYNTAX_TREE);
return result;
}
protected void verifyAST(IASNode ast)
{
if (ast != null)
assert ((NodeBase)ast).verify() : "AST failed verification";
}
protected final void addScopeToProjectScope(ASFileScope[] scopes)
{
ASProjectScope projectScope = project.getScope();
for (ASFileScope scope : scopes)
{
assert scope.verify() : "Scope failed verification";
projectScope.addScopeForCompilationUnit(this, scope);
}
}
private IFileScopeRequestResult processFileScopeRequest() throws InterruptedException
{
FileScopeRequestResultBase result = (FileScopeRequestResultBase)handleFileScopeRequest();
// add the scopes for this compilation unit
ASFileScope[] scopes = result.getFileScopes();
addScopeToProjectScope(scopes);
operationComplete(ICompilationUnit.Operation.GET_FILESCOPE);
return result;
}
private IABCBytesRequestResult processABCBytesRequest() throws InterruptedException
{
IABCBytesRequestResult result = handleABCBytesRequest();
operationComplete(ICompilationUnit.Operation.GET_ABC_BYTES);
removeAST();
return result;
}
private ISWFTagsRequestResult processSWFTTagsRequest() throws InterruptedException
{
ISWFTagsRequestResult result = handleSWFTagsRequest();
operationComplete(ICompilationUnit.Operation.GET_SWF_TAGS);
return result;
}
private IOutgoingDependenciesRequestResult processOutgoingDependenciesRequest () throws InterruptedException
{
IOutgoingDependenciesRequestResult result = handleOutgoingDependenciesRequest();
operationComplete(ICompilationUnit.Operation.GET_SEMANTIC_PROBLEMS);
removeAST();
return result;
}
/**
* Iterate through all imports within a IFileNodeAccumulator and start parsing any
* non-wildcard imports. This will help speed discovery of dependencies to
* allow better thread utilization.
*
* @param fna IFileNodeAccumulator
*/
protected void startParsingImports(IFileNodeAccumulator fna)
{
ASProjectScope projectScope = getProject().getScope();
List<IImportNode> importNodes = fna.getImportNodes();
Set<ICompilationUnit> compilationUnits = new HashSet<ICompilationUnit>();
for (IImportNode importNode : importNodes)
{
if (!importNode.isWildcardImport())
{
String importName = importNode.getImportName();
int index = importName.lastIndexOf('.');
if (index != -1)
{
String className = importName.substring(index + 1);
compilationUnits.addAll(projectScope.getCompilationUnitsByDefinitionName(className));
}
}
}
// Now that we have the compilation units from the imports,
// start a parallel build on them.
for (ICompilationUnit compilationUnit : compilationUnits)
{
compilationUnit.startBuildAsync(TargetType.SWF);
}
}
/**
* Iterate through all specified embeds, adding the
* EmbedCompilationUnit dependencies, and removing any EmbedCompilationUnits
* which no longer exist
* <p>
* This function adds an empty definition dependency to the {@link DependencyGraph},
* so it will not be printed to the link-report
* @throws InterruptedException
*/
protected void updateEmbedCompilationUnitDependencies(List<IEmbedResolver> embedNodes, Collection<ICompilerProblem> problems) throws InterruptedException
{
Set<ICompilationUnit> previousEmbedCompilationUnits = new HashSet<ICompilationUnit>(embedCompilationUnits);
embedCompilationUnits.clear();
CompilerProject project = getProject();
for (IEmbedResolver embedNode : embedNodes)
{
ICompilationUnit cu = embedNode.resolveCompilationUnit(project);
if (cu != null)
{
// if the cu has already been part of this CU, nothing to do
if (!previousEmbedCompilationUnits.remove(cu))
{
// this dependency targets the embedNode's (import etc) data qname
project.addDependency(this, cu, DependencyType.EXPRESSION, cu.getQualifiedNames().get(0));
}
embedCompilationUnits.add(cu);
}
}
// remove any CUs which are no longer referenced in this file
project.removeCompilationUnits(previousEmbedCompilationUnits);
}
@Override
public Collection<String> getEmbeddedFilenames()
{
if (embedCompilationUnits.isEmpty())
return Collections.emptySet();
Set<String> filenames = new HashSet<String>();
for (ICompilationUnit cu : embedCompilationUnits)
{
EmbedData data = ((EmbedCompilationUnit)cu).getEmbedData();
String sourcePath = (String)data.getAttribute(EmbedAttribute.SOURCE);
// the source attribute can be null or empty if the file doesn't exist
// or wasn't specified.
if (sourcePath != null && !sourcePath.isEmpty())
filenames.add(sourcePath);
}
return filenames;
}
protected void removeAST()
{
// don't do anything by default
}
@Override
public String getName()
{
assert name.equals(computeName()); // verify that our cache is valid, but only if asserts are enabled
return name;
}
/**
* Puts together the compilation unit's name.
* Normally would be cached, as it can be slow.
*/
private String computeName()
{
final String filename = FilenameUtils.getName(getAbsoluteFilename()).replace('.', '_');
final String encodedAbsolutePath = StringEncoder.stringToHashCodeString(getAbsoluteFilename());
String encodedName = encodedAbsolutePath + ":" + filename;
if (definitionPromises.isEmpty())
{
return encodedName;
}
try
{
List<String> qualifiedNames = getQualifiedNames();
Collections.sort(qualifiedNames, new LinkReportWriter.QNameComparator());
return encodedName + ":" + Joiner.on(' ').join(qualifiedNames);
}
catch (InterruptedException e)
{
}
assert false : "Should not get here, because we should be able to compute qnames for def promises without getting interrupted!";
return null;
}
/**
* @return a string, which is helpful when inspecting in a debugger.
*/
@Override
public String toString()
{
return getAbsoluteFilename();
}
@Override
public IDefinitionPriority getDefinitionPriority()
{
return definitionPriority;
}
@Override
public void clearProject()
{
project = null;
}
@Override
public void waitForBuildFinish(final Collection<ICompilerProblem> problems, TargetType targetType) throws InterruptedException
{
assert problems != null : "Expected 'problems'. Do not ignore problems.";
Collections.addAll(problems, getSyntaxTreeRequest().get().getProblems());
Collections.addAll(problems, getFileScopeRequest().get().getProblems());
Collections.addAll(problems, getOutgoingDependenciesRequest().get().getProblems());
Collections.addAll(problems, getABCBytesRequest().get().getProblems());
Collections.addAll(problems, getSWFTagsRequest().get().getProblems());
}
@Override
public void startBuildAsync(TargetType targetType)
{
boolean onlyDoOutgoing = false;
if (!onlyDoOutgoing)
{
getSyntaxTreeRequest();
getFileScopeRequest();
}
getOutgoingDependenciesRequest();
if (!onlyDoOutgoing)
{
getABCBytesRequest();
getSWFTagsRequest();
}
}
/*
* Sets the {@link IFileSpecificationGetter} used by this
* {@link ICompilationUnit} to open files.
* <p>
* This method should be called very shortly after the constructor and
* before any other methods on this class or its sub-classes are called
* (except {@link #makeInvisible(InvisibleCompilationUnit)}).
* The {@link IFileSpecificationGetter}
* should really be a constructor argument, but that would require updating
* all the sub-classes of this class and the factory class that constructs
* {@link ICompilationUnit}'s.
*
* @param getter The {@link IFileSpecificationGetter} used by this
* {@link ICompilationUnit} to open files.
*/
public void setFileSpecificationGetter(IFileSpecificationGetter getter)
{
fileSpecificationGetter = getter;
}
/**
* Marks this {@link ICompilationUnit} as the delegate of an
* {@link IInvisibleCompilationUnit}, which will prevent this
* {@link ICompilationUnit} from contributing any symbols to the containing
* {@link ICompilerProject}'s {@link ASProjectScope}.
* <p>
* This method should be called very shortly after the constructor and
* before any other methods on this class or its sub-classes are called (
* except {@link #setFileSpecificationGetter(IFileSpecificationGetter)} ).
* <p>
* This method must only be called zero or one time on each
* {@link ICompilationUnit}.
*/
public void makeInvisible(InvisibleCompilationUnit invisibleCU)
{
assert invisibleCURef == null : "makeInvisible should called exactly once or not at all";
invisibleCURef = new InvisibleCompilationUnitRef(this, invisibleCU, getProject().getWorkspace().getInvisibleCompilationUnitReferenceQueue());
}
@Override
public final boolean isInvisible()
{
return invisibleCURef != null;
}
protected Map<String, String> getEncodedDebugFiles() throws InterruptedException
{
assert (this instanceof ASCompilationUnit || this instanceof MXMLCompilationUnit) : "getEncodedDebugFiles should only be called by AS or MXML compilation units";
// only file on the source path are encoded, and only ASProject's have
// source path, so bail if it's not an ASProject
if (!(getProject() instanceof ASProject))
return Collections.<String, String>emptyMap();
ASProject asProject = (ASProject)getProject();
// bail if the file isn't on the source path
File sourceFile = new File(getAbsoluteFilename());
if (!asProject.isFileOnSourcePath(sourceFile))
return Collections.<String, String>emptyMap();
// as we're only dealing with AS or MXML compilation units on the source path,
// there should only ever be one definition, so just grab the first def.
IDefinition def = Iterables.getOnlyElement(getAllDefinitions());
String packagePath = def.getPackageName().replace('.', File.separatorChar);
String filenameNoPath = getFilenameNoPath();
String rootPath;
if (packagePath.isEmpty())
rootPath = getAbsoluteFilename().replace(File.separatorChar + filenameNoPath, "");
else
rootPath = getAbsoluteFilename().replace(File.separatorChar + packagePath + File.separatorChar + filenameNoPath, "");
String encodedPath = rootPath + ';' + packagePath + ';' + getFilenameNoPath();
Map<String, String> encodedDebugFiles = new HashMap<String, String>(1);
encodedDebugFiles.put(getAbsoluteFilename(), encodedPath);
return encodedDebugFiles;
}
}