/* * * 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 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.flex.compiler.clients.ASC; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.filespecs.FileSpecification; import org.apache.flex.compiler.filespecs.IFileSpecification; import org.apache.flex.compiler.internal.as.codegen.CodeGeneratorManager; import org.apache.flex.compiler.internal.parsing.as.ASParser; import org.apache.flex.compiler.internal.parsing.as.DeferFunctionBody; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.projects.DefinitionPriority; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.semantics.PostProcessStep; import org.apache.flex.compiler.internal.tree.as.ClassNode; import org.apache.flex.compiler.internal.tree.as.FileNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.XMLLiteralNode; import org.apache.flex.compiler.internal.units.requests.ASFileScopeRequestResult; import org.apache.flex.compiler.internal.units.requests.SWFTagsRequestResult; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.projects.IASProject; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IFileNodeAccumulator; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.units.ICompilationUnit; 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.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() }); ast.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; } }