blob: b8266a162a4e4c94c842436319e96541509ed936 [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.units;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.apache.royale.compiler.clients.ASC;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.filespecs.FileSpecification;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.as.codegen.CodeGeneratorManager;
import org.apache.royale.compiler.internal.parsing.as.ASParser;
import org.apache.royale.compiler.internal.parsing.as.DeferFunctionBody;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.DefinitionPriority;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.tree.as.ClassNode;
import org.apache.royale.compiler.internal.tree.as.FileNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.internal.tree.as.XMLLiteralNode;
import org.apache.royale.compiler.internal.units.requests.ASFileScopeRequestResult;
import org.apache.royale.compiler.internal.units.requests.SWFTagsRequestResult;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.IASProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IFileNodeAccumulator;
import org.apache.royale.compiler.tree.as.IFunctionNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.requests.IABCBytesRequestResult;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.compiler.units.requests.IOutgoingDependenciesRequestResult;
import org.apache.royale.compiler.units.requests.IRequest;
import org.apache.royale.compiler.units.requests.ISWFTagsRequestResult;
import org.apache.royale.compiler.units.requests.ISyntaxTreeRequestResult;
import org.apache.royale.utils.JSXUtil;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
public class ASCompilationUnit extends CompilationUnitBase
{
/**
* Implementation of {@link ISyntaxTreeRequestResult} that has the added feature
* of being able to transform the reference to the syntax tree to a weak reference.
*/
private static class ASSyntaxTreeRequestResult implements ISyntaxTreeRequestResult
{
private static class HardToWeakRef<T> extends WeakReference<T>
{
/**
* Constructs a reference to the specified object.
* @param referent Object to refer to.
*/
public HardToWeakRef(T referent)
{
super(referent);
this.referent = referent;
}
private T referent;
public void makeWeak()
{
// No lock here because we are writing a single
// reference variable that is not a long or double.
referent = null;
}
@Override
public T get()
{
// Overriding this method just to make
// sure the java compiler or vm does not
// optimize away the member variable that holds
// strong reference.
// No lock here because we are reading a single reference
// variable that is not a long or double.
//
// We read the member variable into a local so we can return it
// without worrying about the another thread bashing
// the member variable.
T referent = this.referent;
if (referent != null)
return referent;
return super.get();
}
}
ASSyntaxTreeRequestResult(ASCompilationUnit owner, IRequest<ISyntaxTreeRequestResult, ICompilationUnit> syntaxTreeRequest, IASNode ast, ImmutableSet<String> includedFiles, long lastModified, Collection<ICompilerProblem> problems)
{
ownerRef = new WeakReference<ASCompilationUnit>(owner);
this.syntaxTreeRequest = syntaxTreeRequest;
astRef = new HardToWeakRef<IASNode>(ast);
this.includedFiles = includedFiles;
this.problems = problems.toArray(new ICompilerProblem[problems.size()]);
this.lastModified = lastModified;
}
private final WeakReference<ASCompilationUnit> ownerRef;
private final IRequest<ISyntaxTreeRequestResult, ICompilationUnit> syntaxTreeRequest;
private final HardToWeakRef<IASNode> astRef;
private final ImmutableSet<String> includedFiles;
private final long lastModified;
private final ICompilerProblem[] problems;
@Override
public ICompilerProblem[] getProblems()
{
return problems;
}
@Override
public IASNode getAST() throws InterruptedException
{
// This method needs to return the syntax tree
// for this compilation unit.
//
// Since we might have allowed the syntax tree
// to be GC'd we may have to repase the file.
// First see if we still have the AST..
IASNode result = astRef.get();
if (result != null)
return result;
// We allowed the syntax tree to be gc'd.
// Now we have to get hold of our owning
// compilation unit to tell it to reparse the file.
ASCompilationUnit owner = ownerRef.get();
// If our owning compilation unit has been gc'd
// then we are just a stale result object. Just bail.
if (owner == null)
return null;
// The reference to our owner is still good.
// Use compare and set to atomically update our
// owner's reference to us. We don't care if it
// ends up being null or a pointing to someone else.
owner.syntaxTreeRequest.compareAndSet(syntaxTreeRequest, null);
// Now ask our owner for the syntax tree.
return owner.getSyntaxTreeRequest().get().getAST();
}
/**
* Called by the {@link ASCompilationUnit} to make the reference to the syntax
* tree held by this class a weak reference.
*/
public void dropASTRef()
{
astRef.makeWeak();
}
@Override
public Set<String> getRequiredResourceBundles() throws InterruptedException
{
IASNode tree = getAST();
if(tree instanceof IFileNodeAccumulator)
{
return ((IFileNodeAccumulator)tree).getRequiredResourceBundles();
}
return Collections.emptySet();
}
@Override
public ImmutableSet<String> getIncludedFiles()
{
return includedFiles;
}
@Override
public long getLastModified()
{
return lastModified;
}
}
/**
* Create a main compilation unit for ASC client. This factory method will
* setup the included files specified by {@code -in} option onto the
* tokenizer.
* <p>
* Using this factory method so that we don't have to expose
* {@code ASCompilationUnit#setIncludedFiles(List)}, because it is specific
* to {@link ASC} only.
*
* @param project Compiler project.
* @param mainFile Main source file.
* @param asc ASC client instance.
* @return Main ActionScript compilation unit.
*/
public static ASCompilationUnit createMainCompilationUnitForASC(
final CompilerProject project,
final IFileSpecification mainFile,
final ASC asc)
{
assert project != null : "Expecting project.";
assert mainFile != null : "Expecting main file.";
assert asc != null : "Expecting ASC client.";
final ASCompilationUnit mainCompilationUnit = new ASCompilationUnit(
project,
mainFile.getPath(),
DefinitionPriority.BasePriority.SOURCE_LIST);
mainCompilationUnit.includedFiles.addAll(asc.getIncludeFilenames());
return mainCompilationUnit;
}
public ASCompilationUnit(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority)
{
this(project, path, basePriority, 0);
}
public ASCompilationUnit(CompilerProject project, String path, DefinitionPriority.BasePriority basePriority, int order)
{
this(project, path, basePriority, order, null);
}
public ASCompilationUnit(CompilerProject project, String path,
DefinitionPriority.BasePriority basePriority,
int order,
String qname)
{
super(project, path, basePriority, qname);
this.qname = qname;
((DefinitionPriority) getDefinitionPriority()).setOrder(order);
}
// The fully-qualified name of the one externally-visible definition
// expected to be found in this compilation unit, or null if none is expected.
// This qname is determined from the name of the file
// and the file's location relative to the source path.
private final String qname;
/**
* This field is specific to {@link ASC} client. It's a list of files
* included by {@code -in} option.
*/
private final List<String> includedFiles = new ArrayList<String>();
@Override
public UnitType getCompilationUnitType()
{
return UnitType.AS_UNIT;
}
/**
* Creates the FileNode to be returned by the syntax tree request
*
* @param specification the {@link IFileSpecification} for the given file
* @return a {@link FileNode}
*/
protected FileNode createFileNode(IFileSpecification specification)
{
// Only defer function body if the compilation unit is from an actual AS
// file, and the compilation unit is not "invisible" (currently not
// open in IDE). "isInvisible" means the compilation unit is invisible
// to semantic analyzer. It, however, is "visible" to the user in the
// IDE.
final DeferFunctionBody deferFunctionBody;
if(!isInvisible() && specification instanceof FileSpecification)
deferFunctionBody = DeferFunctionBody.ENABLED;
else
deferFunctionBody = DeferFunctionBody.DISABLED;
final IASProject flashProject;
if(getProject() instanceof IASProject)
flashProject = (IASProject)getProject();
else
flashProject = null;
// Parse the AS file into an AST and build a symbol table for it.
return ASParser.parseFile(
specification,
getFileSpecificationGetter(),
EnumSet.of(PostProcessStep.CALCULATE_OFFSETS),
this.getProject().getProjectConfigVariables(),
true,
this.getProject().isAssetEmbeddingSupported(),
includedFiles,
deferFunctionBody,
flashProject,
this);
}
@Override
protected ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException
{
startProfile(Operation.GET_SYNTAX_TREE);
try
{
IRequest<ISyntaxTreeRequestResult, ICompilationUnit> syntaxTreeRequest = this.syntaxTreeRequest.get();
final FileNode ast = createFileNode(getRootFileSpecification());
IRequest<IFileScopeRequestResult, ICompilationUnit> fileScopeRequest = this.fileScopeRequest.get();
if ((fileScopeRequest != null) && (fileScopeRequest.isDone()))
{
ast.reconnectDefinitions((ASFileScope)fileScopeRequest.get().getScopes()[0]);
}
else
{
getProject().clearScopeCacheForCompilationUnit(this);
ast.runPostProcess(EnumSet.of(PostProcessStep.POPULATE_SCOPE));
}
final ImmutableSet<String> includedFiles = ast.getIncludeHandler().getIncludedFiles();
addScopeToProjectScope(new ASFileScope[] { ast.getFileScope() });
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.FILE_NODE) == CompilerDiagnosticsConstants.FILE_NODE)
System.out.println("ASCompilationUnit waiting for lock in parseRequiredFunctionBodies");
ast.parseRequiredFunctionBodies();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.FILE_NODE) == CompilerDiagnosticsConstants.FILE_NODE)
System.out.println("ASCompilationUnit done with lock in parseRequiredFunctionBodies");
final Collection<ICompilerProblem> problemCollection = ast.getProblems();
ASSyntaxTreeRequestResult result = new ASSyntaxTreeRequestResult(this, syntaxTreeRequest, ast, includedFiles, ast.getIncludeTreeLastModified(), problemCollection);
getProject().getWorkspace().addIncludedFilesToCompilationUnit(this, result.getIncludedFiles());
getProject().addToASTCache(ast);
return result;
}
finally
{
stopProfile(Operation.GET_SYNTAX_TREE);
}
}
@Override
protected IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException
{
startProfile(Operation.GET_FILESCOPE);
// Get the AST dig out the symbol table.
final FileNode ast = (FileNode)getSyntaxTreeRequest().get().getAST();
final IASScope scope = ast.getScope();
assert scope instanceof ASFileScope : "Expect ASFileScope as the top-level scope, but found " + scope.getClass();
IFileSpecification rootSource = getRootFileSpecification();
final ASFileScopeRequestResult result =
new ASFileScopeRequestResult(getDefinitionPromises(), getDefinitionPriority(),
Collections.<ICompilerProblem>emptyList(), (ASFileScope)scope, rootSource);
stopProfile(Operation.GET_FILESCOPE);
addProblemsToProject(result);
return result;
}
protected void addProblemsToProject(ASFileScopeRequestResult result)
{
Collection<ICompilationUnit> units = getProject().getIncludingCompilationUnits(getAbsoluteFilename());
// an included file often reports an error that it has no definition
if (units != null && units.size() > 0)
return;
Collections.addAll(getProject().getProblems(), result.getProblems());
}
@Override
protected IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException
{
final ISyntaxTreeRequestResult fsr = getSyntaxTreeRequest().get();
final IASNode rootNode = fsr.getAST();
final CompilerProject project = getProject();
startProfile(Operation.GET_ABC_BYTES);
IABCBytesRequestResult result = CodeGeneratorManager.getCodeGenerator().generate(project.getWorkspace().getExecutorService(),
project.getUseParallelCodeGeneration(),
this.getFilenameNoPath(),
rootNode,
this.getProject(),
this.isInvisible(),
this.getEncodedDebugFiles());
stopProfile(Operation.GET_ABC_BYTES);
return result;
}
private static Comparator<IDefinition> SCRIPT_NAME_DEFINITION_COMPARATOR =
new Comparator<IDefinition>()
{
@Override
public int compare(IDefinition arg0, IDefinition arg1)
{
int result = arg0.getAbsoluteStart() - arg1.getAbsoluteEnd();
if (result != 0)
return result;
result = arg0.getQualifiedName().compareTo(arg1.getQualifiedName());
return result;
}
};
@Override
protected ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException
{
final IABCBytesRequestResult abc = getABCBytesRequest().get();
startProfile(Operation.GET_SWF_TAGS);
try
{
final String tagName;
if (Strings.isNullOrEmpty(qname))
{
final IFileScopeRequestResult fileScopeRR = getFileScopeRequest().get();
final Collection<IDefinition> externallyVisibleDefinitions =
fileScopeRR.getExternallyVisibleDefinitions();
if (!externallyVisibleDefinitions.isEmpty())
{
ArrayList<IDefinition> sortedDefinitions =
new ArrayList<IDefinition>(externallyVisibleDefinitions.size());
Iterables.addAll(sortedDefinitions, externallyVisibleDefinitions);
Collections.sort(sortedDefinitions, SCRIPT_NAME_DEFINITION_COMPARATOR);
tagName = sortedDefinitions.get(0).getQualifiedName().replace('.', '/');
}
else
{
tagName = getName();
}
}
else
{
tagName = qname.replace('.', '/');
}
return new SWFTagsRequestResult(abc.getABCBytes(), tagName, abc.getEmbeds());
}
finally
{
stopProfile(Operation.GET_SWF_TAGS);
}
}
@Override
protected IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException
{
final ISyntaxTreeRequestResult fsr = getSyntaxTreeRequest().get();
final FileNode fn = (FileNode)fsr.getAST();
startParsingImports(fn);
startProfile(Operation.GET_SEMANTIC_PROBLEMS);
Collection<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
getABCBytesRequest().get();
updateEmbedCompilationUnitDependencies(fn.getEmbedNodes(), problems);
updateJSXDependencies(fn);
IOutgoingDependenciesRequestResult result = new IOutgoingDependenciesRequestResult()
{
@Override
public ICompilerProblem[] getProblems()
{
return IOutgoingDependenciesRequestResult.NO_PROBLEMS;
}
};
stopProfile(Operation.GET_SEMANTIC_PROBLEMS);
return result;
}
/**
* Iterate through all methods with [JSX] metadata, adding the
* ICompilationUnit dependencies
*
* @throws InterruptedException
*/
private void updateJSXDependencies(IASNode node) throws InterruptedException
{
if (node instanceof FunctionNode)
{
FunctionNode functionNode = (FunctionNode) node;
if (JSXUtil.hasJSXMetadata(functionNode))
{
//we need to parse XML in this function's body
functionNode.parseFunctionBody(new ArrayList<ICompilerProblem>());
}
}
if (node instanceof XMLLiteralNode)
{
IFunctionNode functionNode = (IFunctionNode) node.getAncestorOfType(IFunctionNode.class);
if (functionNode != null && JSXUtil.hasJSXMetadata(functionNode))
{
XMLLiteralNode xmlNode = (XMLLiteralNode) node;
CompilerProject project = getProject();
ArrayList<String> qualifiedNames = new ArrayList<String>();
JSXUtil.findQualifiedNamesInXMLLiteral(xmlNode, project, qualifiedNames);
for (String qualifiedName : qualifiedNames)
{
ICompilationUnit cu = project.resolveQNameToCompilationUnit(qualifiedName);
if (cu != null)
{
project.addDependency(this, cu, DependencyType.EXPRESSION, cu.getQualifiedNames().get(0));
}
}
}
}
else
{
for (int i = 0, count = node.getChildCount(); i < count; i++)
{
updateJSXDependencies(node.getChild(i));
}
}
}
@Override
protected void removeAST()
{
// This is purely an optimization. If we don't remove the tree we could have that is not
// the end of the world, we'll just pin the tree for longer than we'd like.
// We are attempting to remove all hard references to the AST after codege and
// semantic analysis are complete.
IRequest<ISyntaxTreeRequestResult, ICompilationUnit> syntaxTreeRequest = this.syntaxTreeRequest.get();
boolean canRemoveAST = operationsCompleted(EnumSet.of(ICompilationUnit.Operation.GET_SEMANTIC_PROBLEMS, ICompilationUnit.Operation.GET_ABC_BYTES));
if (canRemoveAST)
{
try
{
assert syntaxTreeRequest != null;
((ASSyntaxTreeRequestResult)syntaxTreeRequest.get()).dropASTRef();
}
catch (InterruptedException e)
{
assert false : "Syntax should have already been built";
}
}
}
/**
* TODO: Replace this with proper API call on CompilationUnit to get the
* root class name.
*
* @param node node to search for class node
* @return root class name
*/
private ClassNode findFirstClassNode(IASNode node)
{
ClassNode classNode = null;
if (node instanceof ClassNode)
{
classNode = (ClassNode)node;
}
else
{
for (int i = 0; i < node.getChildCount(); i++)
{
classNode = findFirstClassNode(node.getChild(i));
if (classNode != null)
{
break;
}
}
}
return classNode;
}
/**
* Get the root class name if defined.
*
* @return root class name
*/
public String getRootClassName() throws InterruptedException
{
final IASNode fileNode = getSyntaxTreeRequest().get().getAST();
final ClassNode classNode = findFirstClassNode(fileNode);
final String rootClassName = classNode.getName();
return rootClassName;
}
}