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