blob: 7a3d1ce0db6659182f70bba86de76275e6e5f309 [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.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();
}
}