| /* |
| * |
| * 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.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.royale.compiler.common.DependencyType; |
| import org.apache.royale.compiler.common.DependencyTypeSet; |
| import org.apache.royale.compiler.common.Multiname; |
| import org.apache.royale.compiler.constants.IMetaAttributeConstants; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.metadata.IMetaTag; |
| import org.apache.royale.compiler.internal.caches.AssetTagCache; |
| import org.apache.royale.compiler.internal.caches.CacheStoreKeyBase; |
| import org.apache.royale.compiler.internal.caches.FileScopeCache; |
| import org.apache.royale.compiler.internal.caches.SWFCache; |
| import org.apache.royale.compiler.internal.graph.LinkReportWriter.QNameComparator; |
| import org.apache.royale.compiler.internal.projects.CompilerProject; |
| import org.apache.royale.compiler.internal.projects.DefinitionPriority; |
| import org.apache.royale.compiler.internal.projects.RoyaleProject; |
| import org.apache.royale.compiler.internal.resourcebundles.ResourceBundleUtils; |
| import org.apache.royale.compiler.internal.scopes.ASProjectScope; |
| import org.apache.royale.compiler.internal.targets.TagSorter; |
| import org.apache.royale.compiler.internal.units.requests.ABCBytesRequestResult; |
| import org.apache.royale.compiler.internal.units.requests.ABCFileScopeRequestResult; |
| import org.apache.royale.compiler.internal.units.requests.SyntaxTreeRequestResult; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.problems.InternalCompilerProblem; |
| import org.apache.royale.compiler.problems.InvalidABCByteCodeProblem; |
| import org.apache.royale.compiler.problems.NoDefinitionForSWCDependencyProblem; |
| import org.apache.royale.compiler.problems.NoScopesInABCCompilationUnitProblem; |
| import org.apache.royale.compiler.projects.ICompilerProject; |
| import org.apache.royale.compiler.scopes.IASScope; |
| 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.ISWFTagsRequestResult; |
| import org.apache.royale.compiler.units.requests.ISyntaxTreeRequestResult; |
| import org.apache.royale.swc.ISWC; |
| import org.apache.royale.swc.ISWCLibrary; |
| import org.apache.royale.swc.ISWCScript; |
| import org.apache.royale.swf.ITagContainer; |
| import org.apache.royale.swf.SWFFrame; |
| import org.apache.royale.swf.tags.DoABCTag; |
| import org.apache.royale.swf.tags.ICharacterTag; |
| import org.apache.royale.swf.tags.ITag; |
| import org.apache.royale.swf.tags.SymbolClassTag; |
| import com.google.common.collect.SetMultimap; |
| |
| /** |
| * This is a compilation unit for a script in a SWC library. Every SWC library |
| * has one or multiple libraries. (i.e. library.swf) Each library has multiple |
| * script definition. A script has a name which maps to a named DoABC tag in the |
| * library SWF. |
| * <p> |
| * A {@code SWCCompilationUnit} is similar to {@link ABCCompilationUnit} that |
| * they both deal with ABC byte code. The difference is that |
| * {@code ABCCompilationUnit} requires ABC byte code when created, while |
| * {@code SWCCompilationUnit} only need a pointer to the script location. |
| * {@code SWCCompilationUnit} does not request or digest the ABC byte code until |
| * any of the handlers is called. |
| */ |
| public class SWCCompilationUnit extends CompilationUnitBase |
| { |
| /** |
| * Create a compilation unit from ABC byte code in a SWF tag. If a script |
| * has multiple public definitions, they will share one |
| * {@code SWCCompilationUnit}. |
| * |
| * @param project compiler project |
| * @param script script information |
| * @param qnames of qnames within this swc |
| * @param order the order the SWC was added to the project. The lower the |
| * order the higher the priority of the compilation unit. The order is |
| * compared only if the timestamps of two compilation units are equal. |
| */ |
| public SWCCompilationUnit(final CompilerProject project, |
| final ISWC swc, |
| final ISWCLibrary library, |
| final ISWCScript script, |
| final List<String> qnames, |
| final int order) |
| { |
| super(project, swc.getSWCFile().getPath(), DefinitionPriority.BasePriority.LIBRARY_PATH, qnames); |
| this.swc = swc; |
| this.library = library; |
| this.script = script; |
| this.resourceBundles = new HashSet<String>(); |
| |
| DefinitionPriority dp = ((DefinitionPriority)getDefinitionPriority()); |
| dp.setTimestamp(script.getLastModified()); |
| dp.setOrder(order); |
| |
| name = computeName(); // now that definition promises are all set up, we can cache th |
| } |
| |
| private final ISWC swc; |
| private final ISWCLibrary library; |
| private final ISWCScript script; |
| |
| /** |
| * Set of ResourceBundles referenced in the associated script. |
| */ |
| private final Set<String> resourceBundles; |
| |
| @Override |
| public UnitType getCompilationUnitType() |
| { |
| return UnitType.SWC_UNIT; |
| } |
| |
| @Override |
| protected ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException |
| { |
| IFileScopeRequestResult fileResult = getFileScopeRequest().get(); |
| |
| startProfile(Operation.GET_SYNTAX_TREE); |
| try |
| { |
| List<ICompilerProblem> noProblems = Collections.emptyList(); |
| |
| boolean isFlex = false; |
| CompilerProject project = getProject(); |
| |
| if ((project instanceof RoyaleProject) && ((RoyaleProject)project).isRoyale()) |
| { |
| isFlex = true; |
| } |
| |
| // Don't need to collect resource bundles for non-flex projects. |
| if (isFlex) |
| { |
| //Find all the resource bundles required for this script |
| for (IDefinition definition : fileResult.getExternallyVisibleDefinitions()) |
| { |
| for(IMetaTag rbTag : definition.getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_RESOURCEBUNDLE)) |
| { |
| resourceBundles.add(rbTag.getAllAttributes()[0].getValue()); |
| } |
| } |
| } |
| |
| return new SyntaxTreeRequestResult(script.getLastModified(), noProblems) |
| { |
| @Override |
| public Set<String> getRequiredResourceBundles() |
| { |
| return resourceBundles; |
| } |
| }; |
| } |
| finally |
| { |
| stopProfile(Operation.GET_SYNTAX_TREE); |
| } |
| } |
| |
| @Override |
| protected IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException |
| { |
| startProfile(Operation.GET_FILESCOPE); |
| |
| getProject().clearScopeCacheForCompilationUnit(this); |
| |
| final Collection<ICompilerProblem> problems = new HashSet<ICompilerProblem>(); |
| Collection<IASScope> scopeList = null; |
| try |
| { |
| final FileScopeCache fsCache = (FileScopeCache)getProject().getWorkspace().getSWCManager().getFileScopeCache(); |
| final CacheStoreKeyBase key = FileScopeCache.createKey(swc, library.getPath(), script); |
| scopeList = fsCache.get(key); |
| if (scopeList.isEmpty()) |
| { |
| final NoScopesInABCCompilationUnitProblem problem = |
| new NoScopesInABCCompilationUnitProblem(getRootFileSpecification().getPath()); |
| problems.add(problem); |
| } |
| } |
| catch (Exception e) |
| { |
| final InvalidABCByteCodeProblem problem = new InvalidABCByteCodeProblem(getRootFileSpecification().getPath()); |
| problems.add(problem); |
| } |
| |
| ABCFileScopeRequestResult result = new ABCFileScopeRequestResult(problems, scopeList); |
| stopProfile(Operation.GET_FILESCOPE); |
| |
| return result; |
| } |
| |
| /** |
| * Find the {@code DoABC} tag from {@link SWFCache} by script name and |
| * extract the ABC bytes. |
| */ |
| @Override |
| protected IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException |
| { |
| byte[] abcBytes = null; |
| final ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(); |
| |
| final CacheStoreKeyBase key = SWFCache.createKey(swc, library.getPath()); |
| final ITagContainer tags = ((SWFCache)getProject().getWorkspace().getSWCManager().getSWFCache()).get(key); |
| |
| startProfile(Operation.GET_ABC_BYTES); |
| |
| final DoABCTag doABC = SWFCache.findDoABCTagByName(tags, script.getName()); |
| if (doABC == null) |
| { |
| problems.add(new InternalCompilerProblem( |
| new RuntimeException("can't find ABC bytes for : " + script.getName()))); |
| } |
| else |
| { |
| abcBytes = doABC.getABCData(); |
| } |
| |
| ABCBytesRequestResult result = new ABCBytesRequestResult(abcBytes); |
| stopProfile(Operation.GET_ABC_BYTES); |
| |
| return result; |
| } |
| |
| /** |
| * Add a DoABC tag for the definition of this compilation unit and its asset |
| * tags to the SWF. The DoABC tag is copied from the library SWF. The asset |
| * tags are from {@link AssetTagCache}. |
| */ |
| @Override |
| protected ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException |
| { |
| final ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(); |
| final ArrayList<ITag> linkingTags = new ArrayList<ITag>(); |
| |
| // link main definition |
| final CacheStoreKeyBase key = SWFCache.createKey(swc, library.getPath()); |
| final ITagContainer tags = ((SWFCache)getProject().getWorkspace().getSWCManager().getSWFCache()).get(key); |
| final DoABCTag doABC = SWFCache.findDoABCTagByName(tags, script.getName()); |
| if (doABC == null) |
| throw new NullPointerException("can not find DoABC tag: " + script.getName()); |
| |
| startProfile(Operation.GET_SWF_TAGS); |
| |
| linkingTags.add(doABC); |
| |
| // link assets for all the definitions in this script |
| final HashMap<String, ICharacterTag> assetTags = new LinkedHashMap<String, ICharacterTag>(); |
| for (final String defQName : script.getDefinitions()) |
| { |
| final CacheStoreKeyBase assetCacheKey = AssetTagCache.createKey(swc, library.getPath(), script, defQName); |
| final AssetTagCache.AssetTagCacheValue assetCacheValue = ((AssetTagCache)getProject().getWorkspace().getSWCManager().getAssetTagCache()).get(assetCacheKey); |
| if (assetCacheValue.assetTag != null) |
| { |
| linkingTags.add(assetCacheValue.assetTag); |
| linkingTags.addAll(assetCacheValue.referredTags); |
| assetTags.put(defQName, assetCacheValue.assetTag); |
| } |
| } |
| |
| final List<ITag> sortedTags = TagSorter.sortFullGraph(linkingTags); |
| ISWFTagsRequestResult result = new ISWFTagsRequestResult() |
| { |
| @Override |
| public boolean addToFrame(SWFFrame frame) |
| { |
| for (final ITag tag : sortedTags) |
| frame.addTag(tag); |
| |
| for (final Map.Entry<String, ICharacterTag> tag : assetTags.entrySet()) |
| frame.defineSymbol(tag.getValue(), tag.getKey()); |
| |
| return true; |
| } |
| |
| @Override |
| public ICompilerProblem[] getProblems() |
| { |
| return problems.toArray(new ICompilerProblem[0]); |
| } |
| |
| @Override |
| public String getDoABCTagName() |
| { |
| return script.getName(); |
| } |
| |
| @Override |
| public DoABCTag getDoABCTag() |
| { |
| return doABC; |
| } |
| }; |
| stopProfile(Operation.GET_SWF_TAGS); |
| |
| return result; |
| } |
| |
| @Override |
| protected IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException |
| { |
| getSyntaxTreeRequest().get(); |
| |
| startProfile(Operation.GET_SEMANTIC_PROBLEMS); |
| |
| SetMultimap<String,DependencyType> dependencies = script.getDependencies(); |
| addAssetTagDependencies(dependencies); |
| |
| ASProjectScope projectScope = getProject().getScope(); |
| final Collection<ICompilerProblem> problems = new LinkedList<ICompilerProblem>(); |
| for (final Map.Entry<String, Collection<DependencyType>> dependencyEntry : dependencies.asMap().entrySet()) |
| { |
| IDefinition[] defs = projectScope.findAllDefinitionsByName(Multiname.crackDottedQName(getProject(), dependencyEntry.getKey(), true)); |
| if (defs == null || defs.length == 0) |
| { |
| ICompilerProblem problem = new NoDefinitionForSWCDependencyProblem( |
| getRootFileSpecification().getPath(), getAbsoluteFilename(), dependencyEntry.getKey(), |
| script.getDefinitions().iterator().next()); |
| problems.add(problem); |
| } |
| else |
| { |
| assert (defs != null && defs.length <= 1) : "Lookups using a fully qualified name should find at most 1 definition"; |
| ICompilationUnit referencedCU = projectScope.getCompilationUnitForScope(defs[0].getContainingScope()); |
| DependencyTypeSet dependencyTypes = DependencyTypeSet.copyOf(dependencyEntry.getValue()); |
| getProject().addDependency(this, referencedCU, dependencyTypes, defs[0].getQualifiedName()); |
| } |
| } |
| |
| // Add dependencies to the resource bundles used by this compilation unit |
| for (String bundleName : resourceBundles) |
| { |
| ResourceBundleUtils.resolveDependencies(bundleName, this, getProject(), null, problems); |
| } |
| |
| IOutgoingDependenciesRequestResult result = new IOutgoingDependenciesRequestResult() |
| { |
| @Override |
| public ICompilerProblem[] getProblems() |
| { |
| return problems.toArray(new ICompilerProblem[problems.size()]); |
| } |
| }; |
| stopProfile(Operation.GET_SEMANTIC_PROBLEMS); |
| |
| return result; |
| } |
| |
| private void addAssetTagDependencies(SetMultimap<String, DependencyType> dependencies) |
| { |
| final CacheStoreKeyBase key = SWFCache.createKey(swc, library.getPath()); |
| final ITagContainer swfTags = ((SWFCache)getProject().getWorkspace().getSWCManager().getSWFCache()).get(key); |
| final Collection<SymbolClassTag> symbolTags = SWFCache.findAllSymbolClassTags(swfTags); |
| |
| for (final String defQName : script.getDefinitions()) |
| { |
| final CacheStoreKeyBase assetCacheKey = AssetTagCache.createKey(swc, library.getPath(), script, defQName); |
| final AssetTagCache.AssetTagCacheValue assetCacheValue = ((AssetTagCache)getProject().getWorkspace().getSWCManager().getAssetTagCache()).get(assetCacheKey); |
| if (assetCacheValue.referredTags != null) |
| { |
| for (ITag referredTag : assetCacheValue.referredTags) |
| { |
| if (referredTag instanceof ICharacterTag) |
| { |
| for (SymbolClassTag symbolTag : symbolTags) |
| { |
| String symbol = symbolTag.getSymbolName((ICharacterTag)referredTag); |
| if (symbol != null) |
| dependencies.put(symbol, DependencyType.EXPRESSION); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Test if this compilation unit comes from an ANE File. |
| * |
| * @return true if the compilation unit comes from an ANE file, false |
| * otherwise. |
| */ |
| public boolean isANE() |
| { |
| return swc.isANE(); |
| } |
| |
| @Override |
| public String getName() |
| { |
| assert name.equals(computeName()); |
| return name; |
| } |
| |
| private String computeName() |
| { |
| // NOTE: keep this in sync with CrossProjectCompilationUnit.getName() |
| return SWCCompilationUnit.getLinkReportName(this); |
| } |
| |
| private final String name; |
| |
| @Override |
| protected void handleClean(boolean cleanFileScope, Map<ICompilerProject, Set<File>> invalidatedSWCFiles) |
| { |
| if (!cleanFileScope) |
| return; |
| File swcFile = swc.getSWCFile(); |
| addInvalidatedSWCtoList(invalidatedSWCFiles, swcFile, getProject()); |
| } |
| |
| private void addInvalidatedSWCtoList(Map<ICompilerProject, Set<File>> invalidatedSWCFiles, |
| File swcFile, CompilerProject project) |
| { |
| if (invalidatedSWCFiles == null) |
| return; |
| |
| Set<File> swcsInProject = invalidatedSWCFiles.get(project); |
| if (swcsInProject == null) |
| { |
| swcsInProject = new HashSet<File>(); |
| invalidatedSWCFiles.put(project, swcsInProject); |
| } |
| |
| swcsInProject.add(swcFile); |
| } |
| |
| /** |
| * @return A string for link reports |
| */ |
| protected static String getLinkReportName(ICompilationUnit cu) |
| { |
| StringBuilder reportBuilder = new StringBuilder(cu.getAbsoluteFilename()); |
| reportBuilder.append('('); |
| |
| ArrayList<String> definitionQnames = new ArrayList<String>(cu.getDefinitionPromises().size()); |
| |
| for (IDefinition definition : cu.getDefinitionPromises()) |
| definitionQnames.add(definition.getQualifiedName()); |
| |
| Collections.sort(definitionQnames, new QNameComparator()); |
| |
| for (String qname : definitionQnames) |
| { |
| int lastOccurence = qname.lastIndexOf('.'); |
| |
| String xmlStyleScriptName = qname; |
| if(lastOccurence > -1) |
| xmlStyleScriptName = qname.substring(0, lastOccurence) + ":" + qname.substring(lastOccurence + 1, qname.length()); |
| |
| reportBuilder.append(xmlStyleScriptName); |
| reportBuilder.append(", "); |
| } |
| reportBuilder.delete(reportBuilder.length()-2, reportBuilder.length()); |
| reportBuilder.append(')'); |
| |
| return reportBuilder.toString(); |
| } |
| |
| /** |
| * For debugging. |
| */ |
| @Override |
| public String toString() |
| { |
| return "SWC: " + getDefinitionPromises().toString(); |
| } |
| } |