blob: dbeafc55ffe2e8869899f691e944ed06f16f5e75 [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.targets;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.DependencyTypeSet;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.common.VersionInfo;
import org.apache.royale.compiler.common.XMLName;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.exceptions.BuildCanceledException;
import org.apache.royale.compiler.filespecs.IBinaryFileSpecification;
import org.apache.royale.compiler.internal.config.QNameNormalization;
import org.apache.royale.compiler.internal.filespecs.SWCFileSpecification;
import org.apache.royale.compiler.internal.projects.DependencyGraph;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.projects.LibraryPathManager;
import org.apache.royale.compiler.internal.projects.ResourceBundleSourceFileHandler;
import org.apache.royale.compiler.internal.projects.SourcePathManager;
import org.apache.royale.compiler.internal.units.ResourceBundleCompilationUnit;
import org.apache.royale.compiler.problems.DuplicateScriptProblem;
import org.apache.royale.compiler.problems.FileNotFoundProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.NoCompilationUnitForDefinitionProblem;
import org.apache.royale.compiler.problems.NoSourceForClassInNamespaceProblem;
import org.apache.royale.compiler.problems.NoSourceForClassProblem;
import org.apache.royale.compiler.targets.ISWCTarget;
import org.apache.royale.compiler.targets.ISWFTarget;
import org.apache.royale.compiler.targets.ITargetProgressMonitor;
import org.apache.royale.compiler.targets.ITargetReport;
import org.apache.royale.compiler.targets.ITargetSettings;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.ICompilationUnit.UnitType;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.swc.ISWC;
import org.apache.royale.swc.ISWCFileEntry;
import org.apache.royale.swc.ISWCLibrary;
import org.apache.royale.swc.ISWCManager;
import org.apache.royale.swc.ISWCVersion;
import org.apache.royale.swc.SWC;
import org.apache.royale.swc.SWCComponent;
import org.apache.royale.swc.SWCLibrary;
import org.apache.royale.swc.SWCScript;
import org.apache.royale.swf.ISWF;
import org.apache.royale.utils.DAByteArrayOutputStream;
import org.apache.royale.utils.FileID;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
/**
* Compilation target for SWC library.
*/
public class SWCTarget extends Target implements ISWCTarget
{
private static final String LIBRARY_SWF = "library.swf";
public SWCTarget(final RoyaleProject project, ITargetSettings targetSettings, ITargetProgressMonitor progressMonitor)
{
super(project, targetSettings, progressMonitor);
swc = new SWC(targetSettings.getOutput());
royaleProject = project;
}
private final SWC swc;
private final RoyaleProject royaleProject;
private ILibrarySWFTarget librarySWFTarget;
private RootedCompilationUnits rootedCompilationUnits;
@Override
public ISWC build(final Collection<ICompilerProblem> problems)
{
buildStarted();
try
{
Iterable<ICompilerProblem> fatalProblems = getFatalProblems();
if (!Iterables.isEmpty(fatalProblems))
{
Iterables.addAll(problems, fatalProblems);
return null;
}
setVersionInfo();
final HashSet<IDefinition> definitions = new HashSet<IDefinition>();
buildLibrarySWF(definitions, problems);
addComponents(definitions);
addFileEntriesToSWC(problems);
return swc;
}
catch (BuildCanceledException bce)
{
return null;
}
catch (InterruptedException ie)
{
return null;
}
finally
{
buildFinished();
}
}
/**
* Sets version information to be written into the <versions> tag in catalog.xml.
*/
private void setVersionInfo()
{
ISWCVersion swcVersion = swc.getVersion();
swcVersion.setSWCVersion(VersionInfo.getLibVersion());
if (royaleProject.isRoyale())
{
swcVersion.setRoyaleVersion(VersionInfo.getRoyaleVersion());
swcVersion.setRoyaleBuild(VersionInfo.getBuild());
swcVersion.setRoyaleMinSupportedVersion(targetSettings.getRoyaleMinimumSupportedVersion());
}
swcVersion.setCompilerName(VersionInfo.getCompilerName());
swcVersion.setCompilerVersion(VersionInfo.getCompilerVersion());
swcVersion.setCompilerBuild(VersionInfo.getCompilerBuild());
}
@Override
public ISWFTarget getLibrarySWFTarget() throws InterruptedException
{
if (librarySWFTarget == null)
{
Target.RootedCompilationUnits rootedCompilationUnits = getRootedCompilationUnits();
if (royaleProject.isRoyale())
{
librarySWFTarget =
new RoyaleLibrarySWFTarget(royaleProject, targetSettings, rootedCompilationUnits.getUnits());
}
else
{
librarySWFTarget = new LibrarySWFTarget(royaleProject, targetSettings,
rootedCompilationUnits.getUnits());
}
}
assert librarySWFTarget != null;
return librarySWFTarget;
}
@Override
public TargetType getTargetType()
{
return TargetType.SWC;
}
/**
* Creating {@code <component>} tags in catalog.xml.
*
* @param definitions All definitions to be linked into the target.
*/
private void addComponents(final HashSet<IDefinition> definitions)
{
final Set<String> includedNamespaces = ImmutableSet.copyOf(targetSettings.getIncludeNamespaces());
for (String namespace : includedNamespaces)
{
// For each namespace get the set of classes.
// From the classes get the the compilation units.
// Validate the compilation units are resolved to source
// files unless there are lookupOnly entries.
final Collection<String> includeNamespaceQualifiedNames =
royaleProject.getQualifiedClassNamesForManifestNamespaces(
Collections.singleton(namespace));
for (String qName : includeNamespaceQualifiedNames)
{
final Collection<XMLName> tagNames = royaleProject.getTagNamesForClass(qName);
for (XMLName tagName : tagNames)
{
if (includeComponent(tagName, includedNamespaces))
{
final SWCComponent component = new SWCComponent();
component.setName(tagName.getName());
component.setURI(tagName.getXMLNamespace());
component.setQName(qName);
swc.addComponent(component);
}
}
}
}
/*
for (final IDefinition def : definitions)
{
final String qName = def.getQualifiedName();
final Collection<XMLName> tagNames = royaleProject.getTagNamesForClass(qName);
for (XMLName tagName : tagNames)
{
if (includeComponent(tagName, includedNamespaces))
{
final SWCComponent component = new SWCComponent();
component.setName(tagName.getName());
component.setURI(tagName.getXMLNamespace());
component.setQName(qName);
swc.addComponent(component);
}
}
}
*/
}
/**
* Test if a component should be included in a SWC's component list.
* <p>
* The rule for including a component in a SWC's component list is that
* the component must be in one of the SWC's included namespaces
* (meaning those specified by -include-namespace, not -namespace)
* and either the component's lookupOnly flag is false
* or the option -include-lookup-only is true.
*
* @param tagName the tag name of the component to check.
* @param includedNamespaces the namespaces included in this SWC using the
* -include-namespaces option.
* @return true if component should be included, false otherwise.
*/
private boolean includeComponent(XMLName tagName, Set<String> includedNamespaces)
{
if (includedNamespaces.contains(tagName.getXMLNamespace()))
{
return !royaleProject.isManifestComponentLookupOnly(tagName) ||
targetSettings.isIncludeLookupOnlyEnabled();
}
return false;
}
/**
* Add asset files to the SWC model.
*
* @param problems Problem collection.
*/
private void addFileEntriesToSWC(Collection<ICompilerProblem> problems)
{
// -include
Map<String, FileEntryValue> fileEntries = computeIncludedFiles();
for (Entry<String, FileEntryValue> entry : fileEntries.entrySet())
{
processFileEntry(entry, problems);
}
// -include-libraries
Map<String, ISWCFileEntry> includeLibraryFiles = getIncludedLibrariesFiles();
for (ISWCFileEntry fileEntry : includeLibraryFiles.values())
{
swc.addFile(fileEntry);
}
// [IconFile(...)] metadata
Map<String, FileEntryValue> iconFilesMap = computeIconFiles();
for (Entry<String, FileEntryValue> entry : iconFilesMap.entrySet())
{
processFileEntry(entry, problems);
}
}
/**
* Helper method used by addFileEntriesToSWC() to process one file.
*/
private void processFileEntry(Entry<String, FileEntryValue> entry,
Collection<ICompilerProblem> problems)
{
String path = entry.getKey();
IBinaryFileSpecification fileSpec = entry.getValue().getFileSpec();
ISourceLocation sourceLocation = entry.getValue().getSourceLocation();
byte[] contents = getContents(fileSpec);
if (contents != null)
{
long fileDate = 0;
String metadataDate = targetSettings.getSWFMetadataDate();
if (metadataDate != null)
{
String metadataFormat = targetSettings.getSWFMetadataDateFormat();
try {
SimpleDateFormat sdf = new SimpleDateFormat(metadataFormat);
fileDate = sdf.parse(metadataDate).getTime();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
}
fileSpec.setLastModified(fileDate);
}
swc.addFile(path, fileSpec.getLastModified(), contents);
}
else
{
final ICompilerProblem problem =
sourceLocation != null ?
new FileNotFoundProblem(sourceLocation, fileSpec.getPath()) :
new FileNotFoundProblem(fileSpec.getPath());
problems.add(problem);
}
}
/**
* Returns the contents of a binary file as an array of bytes,
* or null if the file cannot be read.
*/
private byte[] getContents(IBinaryFileSpecification fileSpec)
{
byte[] contents = null;
try
{
final DAByteArrayOutputStream buffer = new DAByteArrayOutputStream();
final InputStream fileInputStream = fileSpec.createInputStream();
IOUtils.copy(fileInputStream, buffer);
IOUtils.closeQuietly(buffer);
IOUtils.closeQuietly(fileInputStream);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.DA_BYTEARRAY) == CompilerDiagnosticsConstants.DA_BYTEARRAY)
System.out.println("SWCTarget waiting for lock in getContents");
contents = buffer.getDirectByteArray();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.DA_BYTEARRAY) == CompilerDiagnosticsConstants.DA_BYTEARRAY)
System.out.println("SWCTarget waiting for lock in getContents");
}
catch (IOException e)
{
}
return contents;
}
/**
* Build "library.swf" model and add to the SWC model as a
* {@link ISWCLibrary}.
*
* @param definitionsToBuild After this method returns, all the definitions
* to be build into the target will be stored on this collection. It is
* needed to compute a set of URLs for {@code <component>} tags in
* catalog.xml file.
* @param problems Compiler problems.
* @return the ISWF for the library
* @throws InterruptedException Compilation terminated.
*/
private ISWF buildLibrarySWF(final Collection<IDefinition> definitionsToBuild,
final Collection<ICompilerProblem> problems)
throws InterruptedException
{
getLibrarySWFTarget();
Iterables.addAll(problems, getRootedCompilationUnits().getProblems());
final LinkageChecker externalLinkageChecker = new LinkageChecker(
royaleProject, targetSettings);
((Target)librarySWFTarget).setLinkageChecker(externalLinkageChecker);
setLinkageChecker(externalLinkageChecker);
final ISWF defaultLibrarySWF = librarySWFTarget.build(problems);
// make default library model
final ISWCLibrary defaultLibrary = new SWCLibrary(LIBRARY_SWF, defaultLibrarySWF);
swc.addLibrary(defaultLibrary);
// Deal with all the cu's that don't need to be added via addScript
// save all the add script ones in cuToWrite for the next phase.
Set<ICompilationUnit> cuToWrite = new HashSet<ICompilationUnit>();
for (final ICompilationUnit cu : librarySWFTarget.getCompilationUnits())
{
if (isLinkageExternal(cu, targetSettings))
{
// don't do anything with these
}
//Resource bundles processed uniquely
else if (cu instanceof ResourceBundleCompilationUnit)
{
assert project instanceof RoyaleProject;
processResourceBundle((RoyaleProject)project, (ResourceBundleCompilationUnit)cu,
swc, problems);
}
else
{
// everyone else goes in this list for next step
cuToWrite.add(cu);
}
}
// remove duplicates and log resulting problems, then add to library
filterCompilationUnits(cuToWrite, problems);
for (final ICompilationUnit cu : cuToWrite)
{
defaultLibrary.addScript(createScript(cu, definitionsToBuild));
}
// Add the generated root class and its dependencies to the list of
// scripts.
if (librarySWFTarget.getRootClassName() != null)
{
final SWCScript script = new SWCScript();
script.setName(librarySWFTarget.getRootClassName());
script.addDefinition(librarySWFTarget.getRootClassName());
// This class is synthetic and is supposed to be globally
// unique, so nobody should care what the time stamp is.
// If we set the time stamp to the current time, then
// every SWC we make will be slightly different from all other
// SWCs.
script.setLastModified(1);
// add baseclass as an inheritance dependency
script.addDependency(librarySWFTarget.getBaseClassQName(), DependencyType.INHERITANCE);
defaultLibrary.addScript(script);
}
if (librarySWFTarget.getASMetadataNames() != null)
{
for (String name : librarySWFTarget.getASMetadataNames())
{
defaultLibrary.addNameToKeepAS3MetadataSet(name);
}
}
return defaultLibrarySWF;
}
/**
* removes compilation units from a list if they are shaddowed by other ones
* that try to generate the same script. Logs a problem in this case
*
* @param cuToWrite
* @param problems
* @throws InterruptedException
*/
private void filterCompilationUnits(Set<ICompilationUnit> cuToWrite, Collection<ICompilerProblem> problems) throws InterruptedException
{
// group cu's by script name.
Map<String, Set<ICompilationUnit>> cuMap = new HashMap<String, Set<ICompilationUnit>>();
Comparator<ICompilationUnit> comparator = new Comparator<ICompilationUnit>() {
@Override
public int compare(ICompilationUnit arg0, ICompilationUnit arg1) {
// inverted sort on name will give the behavior of the old compiler:
// last path in alphabetical order wins
return -arg0.getName().compareTo(arg1.getName()); }};
for (ICompilationUnit cu : cuToWrite)
{
String name = cu.getSWFTagsRequest().get().getDoABCTagName();
Set<ICompilationUnit> l = cuMap.get(name);
if (l == null)
{
l = new TreeSet<ICompilationUnit>(comparator);
cuMap.put(name, l);
}
l.add(cu);
}
// now that we have grouped CUs based on script name, we can look for dupes
for (Collection<ICompilationUnit> cus : cuMap.values())
{
assert !cus.isEmpty();
Iterator<ICompilationUnit> it = cus.iterator();
ICompilationUnit cuKept = it.next(); // we are keeping the first one
ICompilationUnit cuDumped = null;
boolean somoneHasVisibleDefinitions = hasExternallyVisibleDefinitions(cuKept);
while (it.hasNext()) // remove all the dupes from the list
{
cuDumped = it.next(); // Remember the last dupe to report it
cuToWrite.remove(cuDumped);
somoneHasVisibleDefinitions |= hasExternallyVisibleDefinitions(cuDumped);
}
// report a problem for dupe, unless none of the shadows has anything in it.
if (cuDumped != null && somoneHasVisibleDefinitions)
{
problems.add(new DuplicateScriptProblem(cuKept.getAbsoluteFilename(), cuDumped.getAbsoluteFilename()));
}
}
}
private boolean hasExternallyVisibleDefinitions(ICompilationUnit cu) throws InterruptedException
{
final IFileScopeRequestResult r = cu.getFileScopeRequest().get();
Collection<IDefinition> vis = r.getExternallyVisibleDefinitions();
return !vis.isEmpty();
}
/**
* Create a new script based on the compilation unit.
*
* @param cu
* @param definitionsToBuild
* @return
* @throws InterruptedException
*/
private SWCScript createScript(ICompilationUnit cu,
Collection<IDefinition> definitionsToBuild) throws InterruptedException
{
// Create a script model per compilation unit.
final SWCScript script = new SWCScript();
script.setName(cu.getSWFTagsRequest().get().getDoABCTagName());
// Add all the externally visible definitions to the script model.
final IFileScopeRequestResult fsResult = cu.getFileScopeRequest().get();
for (final IDefinition def : fsResult.getExternallyVisibleDefinitions())
{
script.addDefinition(def.getQualifiedName());
long mod = cu.getSyntaxTreeRequest().get().getLastModified();
String metadataDate = targetSettings.getSWFMetadataDate();
if (metadataDate != null)
{
String metadataFormat = targetSettings.getSWFMetadataDateFormat();
try {
SimpleDateFormat sdf = new SimpleDateFormat(metadataFormat);
mod = sdf.parse(metadataDate).getTime();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
}
}
script.setLastModified(mod);
if (definitionsToBuild != null)
definitionsToBuild.add(def);
}
final DependencyGraph dependencyGraph =
royaleProject.getDependencyGraph();
Set<ICompilationUnit> directDependencies =
dependencyGraph.getDirectDependencies(cu);
for (ICompilationUnit directDependency : directDependencies)
{
final Map<String, DependencyTypeSet> dependenciesMap =
dependencyGraph.getDependencySet(cu, directDependency);
for (Map.Entry<String, DependencyTypeSet> dependencyEntry : dependenciesMap.entrySet())
{
for (DependencyType type : dependencyEntry.getValue())
script.addDependency(dependencyEntry.getKey(), type);
}
}
return script;
}
/**
* Process a resource bundle represented by the specified compilation unit.
*
* @param project active project
* @param cu resource bundle compilation unit to process
* @param swc target swc
* @param problems problems collection
*/
private void processResourceBundle(RoyaleProject project, ResourceBundleCompilationUnit cu,
SWC swc, Collection<ICompilerProblem> problems)
{
Collection<String> locales = null;
if (cu.getLocale() == null)
{
//Create a file entry for each locale since this compilation unit is not locale specific
locales = project.getLocales();
}
else
{
//This compilation unit is for a locale specific file,
//therefore create a file entry for only the locale comp unit depends on
locales = Collections.singletonList(cu.getLocale());
}
byte[] fileContent = cu.getFileContent(problems); //get file content
if (fileContent == null)
return; //Is this the right thing to do here?
for (String locale : locales)
{
//Build the destination path. Destination path for a .properties file
//is such as locale/{locale}/foo/bar/x.properties
StringBuilder sb = new StringBuilder();
sb.append(ResourceBundleCompilationUnit.LOCALE);
sb.append(File.separator);
sb.append(locale);
sb.append(File.separator);
sb.append(QNameNormalization.normalize(cu.getBundleNameInColonSyntax()).replace('.', File.separatorChar));
sb.append(FilenameUtils.EXTENSION_SEPARATOR_STR);
sb.append(ResourceBundleSourceFileHandler.EXTENSION);
swc.addFile(sb.toString(), cu.getFileLastModified(), fileContent);
}
}
@Override
public boolean addToZipOutputStream(ZipOutputStream output, Collection<ICompilerProblem> problemCollection)
{
throw new UnsupportedOperationException();
}
@Override
protected Target.RootedCompilationUnits computeRootedCompilationUnits() throws InterruptedException
{
final Set<ICompilationUnit> rootCompilationUnits = new HashSet<ICompilationUnit>();
final Collection<File> includedSourceFiles = targetSettings.getIncludeSources();
final Set<String> includeClassNameSet = ImmutableSet.copyOf(targetSettings.getIncludeClasses());
final Set<String> includedNamespaces = ImmutableSet.copyOf(targetSettings.getIncludeNamespaces());
final ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
// Select definitions according to configurations.
// include-namespace
final Collection<ICompilationUnit> includeNamespaceUnits =
getCompilationUnitsForIncludedNamespaces(includedNamespaces, problems);
rootCompilationUnits.addAll(includeNamespaceUnits);
// include-class + include-namespace
rootCompilationUnits.addAll(getCompilationUnitsFromClassNames(null, includeClassNameSet, problems));
// include-source
for (final File includedSourceFileName : includedSourceFiles)
{
// Get all the compilation units in the project that reference the specified file.
Collection<ICompilationUnit> compilationUnitsForFile = project.getWorkspace().getCompilationUnits(includedSourceFileName.getAbsolutePath(), project);
// For compilation units with that differ by qname, choose the compilation that
// appears first on the source-path.
if (compilationUnitsForFile.size() > 1)
{
compilationUnitsForFile = filterUnitsBasedOnSourcePath(compilationUnitsForFile);
}
for (ICompilationUnit cu : compilationUnitsForFile)
{
// IFilter out any compilation unit in the list where the specified file is not the root
// source file compiled by the compilation unit.
if (cu.getAbsoluteFilename().equals(includedSourceFileName.getAbsolutePath()))
rootCompilationUnits.add(cu);
}
}
//Add compilation units for included resource bundles
for (ICompilationUnit rbCompUnit : getIncludedResourceBundlesCompilationUnits(problems))
rootCompilationUnits.add(rbCompUnit);
// -include and -include-libraries
rootCompilationUnits.addAll(getIncludesCompilationUnits());
rootCompilationUnits.addAll(getIncludeLibrariesCompilationUnits());
Target.RootedCompilationUnits units = new Target.RootedCompilationUnits(rootCompilationUnits, problems);
Set<ICompilationUnit> unitSet = units.getUnits();
// for (ICompilationUnit cu : unitSet)
// System.out.println(cu.getName());
return units;
}
@Override
public RootedCompilationUnits getRootedCompilationUnits() throws InterruptedException
{
if (this.rootedCompilationUnits == null)
rootedCompilationUnits = computeRootedCompilationUnits();
assert rootedCompilationUnits != null;
return rootedCompilationUnits;
}
/**
* For compilation units with the same absolute source path, filter based on
* the source path. The compilation unit found on the highest priority
* source path wins. The rest of the compilation units with qnames are
* discared. If a unit is not on the source path or does not have a qname or
* more than one qname, then let it thru the filter.
*
* @param compilationUnitsForFile list of compilation units to filter.
* @return filtered compilation units.
* @throws InterruptedException
*/
private Collection<ICompilationUnit> filterUnitsBasedOnSourcePath(Collection<ICompilationUnit> compilationUnitsForFile) throws InterruptedException
{
List<ICompilationUnit> sourcePathUnits = new ArrayList<ICompilationUnit>(compilationUnitsForFile);
boolean foundHighestPriorityUnit = false;
for (File sourcePath : royaleProject.getSourcePath())
{
for (ICompilationUnit unit : sourcePathUnits)
{
// We only care about filtering units on the source path
// that follow the single definition rule.
UnitType unitType = unit.getCompilationUnitType();
if (unitType == UnitType.AS_UNIT || unitType == UnitType.FXG_UNIT ||
unitType == UnitType.MXML_UNIT || unitType == UnitType.CSS_UNIT)
{
Collection<String> qnames = unit.getQualifiedNames();
if (qnames.size() > 1)
continue;
String unitQname = qnames.isEmpty() ? "" : qnames.iterator().next();
String computedQname = SourcePathManager.computeQName(sourcePath, new File(unit.getAbsoluteFilename()));
if (unitQname.equals(computedQname))
{
// We found a unit on the source path. Only keep the
// first unit found on the source path and remove the
// others.
if (foundHighestPriorityUnit)
compilationUnitsForFile.remove(unit);
foundHighestPriorityUnit = true;
break; // should only be one compilation unit on a source path
}
}
}
}
return compilationUnitsForFile;
}
/**
* Get the compilation units for the given included namespaces. Also perform error
* checking.
*
* @param namespaces the namespaces included in this swc target.
* @param problems A collection where detected problems are added.
* @return A collection of compilation units.
* @throws InterruptedException
*/
private Collection<ICompilationUnit> getCompilationUnitsForIncludedNamespaces(
Collection<String> namespaces,
Collection<ICompilerProblem> problems) throws InterruptedException
{
final Collection<ICompilationUnit> allUnits = new HashSet<ICompilationUnit>();
for (String namespace : namespaces)
{
// For each namespace get the set of classes.
// From the classes get the the compilation units.
// Validate the compilation units are resolved to source
// files unless there are lookupOnly entries.
final Collection<String> includeNamespaceQualifiedNames =
royaleProject.getQualifiedClassNamesForManifestNamespaces(
Collections.singleton(namespace));
final Collection<ICompilationUnit> units =
getCompilationUnitsFromClassNames(namespace, includeNamespaceQualifiedNames, problems);
validateIncludeNamespaceEntries(namespace, units, problems);
allUnits.addAll(units);
}
return allUnits;
}
/**
* Validate that the manifest entries in the included namespaces resolve to
* source files, not classes from other SWCs. The exception is for entries
* that are "lookupOnly".
*
* @param namespace The target namespace.
* @param units The compilation units found in that namespace.
* @param problems detected problems are added to this list.
* @throws InterruptedException
*/
private void validateIncludeNamespaceEntries(String namespace,
Collection<ICompilationUnit> units,
Collection<ICompilerProblem> problems) throws InterruptedException
{
for (ICompilationUnit unit : units)
{
List<String> classNames = unit.getQualifiedNames();
String className = classNames.get(classNames.size() - 1);
Collection<XMLName> xmlNames = royaleProject.getTagNamesForClass(className);
for (XMLName xmlName : xmlNames)
{
if (namespace.equals(xmlName.getXMLNamespace()))
{
if (!royaleProject.isManifestComponentLookupOnly(xmlName) &&
unit.getCompilationUnitType() == UnitType.SWC_UNIT)
{
problems.add(new NoSourceForClassInNamespaceProblem(namespace, className));
}
break;
}
}
}
}
/**
* Return a collection of compilation units for a collection of class names.
*
* @param namespace the namespace of the classes. Null if there is no namespace.
* @param classNames
* @param problems detected problems are added to this list.
* @return a collection of compilation units.
*/
private Collection<ICompilationUnit> getCompilationUnitsFromClassNames(String namespace,
Collection<String> classNames,
final Collection<ICompilerProblem> problems)
{
Collection<String> compilableClassNames = new ArrayList<String>();
for (String className : classNames)
{
Collection<XMLName> tagNames = royaleProject.getTagNamesForClass(className);
boolean okToAdd = true;
for (XMLName tagName : tagNames)
{
if (royaleProject.isManifestComponentLookupOnly(tagName))
okToAdd = false;
}
if (okToAdd)
compilableClassNames.add(className);
}
// Class names are turned into references and then info compilation units.
final Iterable<IResolvedQualifiersReference> references =
Iterables.transform(compilableClassNames, new Function<String, IResolvedQualifiersReference>()
{
@Override
public IResolvedQualifiersReference apply(String qualifiedName)
{
return ReferenceFactory.packageQualifiedReference(project.getWorkspace(), qualifiedName, true);
}
});
Collection<ICompilationUnit> units = new LinkedList<ICompilationUnit>();
for (IResolvedQualifiersReference reference : references)
{
IDefinition def = reference.resolve(royaleProject);
if (def == null)
{
if (namespace == null)
problems.add(new NoSourceForClassProblem(reference.getDisplayString()));
else
problems.add(new NoSourceForClassInNamespaceProblem(namespace, reference.getDisplayString()));
}
else
{
ICompilationUnit defCU = project.getScope().getCompilationUnitForDefinition(def);
if (defCU == null)
problems.add(new NoCompilationUnitForDefinitionProblem(def.getBaseName()));
else
units.add(defCU);
}
}
return units;
}
/**
* @return Expand {@code -include-files} items into normalized file paths.
*/
private Map<String, FileEntryValue> computeIncludedFiles()
{
Map<String, FileEntryValue> includedFiles = new HashMap<String, FileEntryValue>();
for (Entry<String, File> entry : targetSettings.getIncludeFiles().entrySet())
{
String filename = entry.getKey();
if (filename.contains("*"))
{
Collection<File> files = getFiles(entry.getValue());
String basename = entry.getValue().getAbsolutePath();
for (File file : files)
{
String path = file.getAbsolutePath();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget waiting for lock in getLatestBinaryFileSpecification");
IBinaryFileSpecification fileSpec = project.getWorkspace().getLatestBinaryFileSpecification(path);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget done with lock in getLatestBinaryFileSpecification");
if (filename != null && fileSpec != null)
{
String relativePath = path.substring(basename.length() + 1);
relativePath = filename.replace("*", relativePath);
FileEntryValue value = new FileEntryValue(fileSpec, null);
includedFiles.put(relativePath, value);
}
}
}
else
{
String path = entry.getValue().getAbsolutePath();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget waiting for lock in getLatestBinaryFileSpecification");
IBinaryFileSpecification fileSpec = project.getWorkspace().getLatestBinaryFileSpecification(path);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget done with lock in getLatestBinaryFileSpecification");
if (filename != null && fileSpec != null)
{
FileEntryValue value = new FileEntryValue(fileSpec, null);
includedFiles.put(filename, value);
}
}
}
return includedFiles;
}
private Collection<File> getFiles(File file)
{
String filename = file.getAbsolutePath();
if (filename.endsWith(File.separator))
filename = filename.substring(0, filename.length() - 1);
Collection<File> files = FileUtils.listFiles(new File(
filename), null, true);
return files;
}
/**
* The collection of files from all of the libraries found on the
* -include-libraries path.
*
* @return collection of absolute path names of SWC files.
*/
private Map<String, ISWCFileEntry> getIncludedLibrariesFiles()
{
Map<String, ISWCFileEntry> files = new HashMap<String, ISWCFileEntry>();
// Find all of the libraries on the -include-library path
Set<FileID> swcs = LibraryPathManager.discoverSWCFilePaths(
targetSettings.getIncludeLibraries().toArray(new File[0]));
// For each library, get a compilation unit for every class in the
// library.
ISWCManager swcManager = project.getWorkspace().getSWCManager();
for (FileID swcPath : swcs)
{
ISWC swc = swcManager.get(swcPath.getFile());
Map<String, ISWCFileEntry> swcFiles = swc.getFiles();
for (Map.Entry<String, ISWCFileEntry> file : swcFiles.entrySet())
{
ISWCFileEntry currentFileEntry = files.get(file.getKey());
if (currentFileEntry == null ||
(file.getValue().getLastModified() > currentFileEntry.getLastModified()))
{
files.put(file.getKey(), file.getValue());
}
}
}
return files;
}
/**
* Loops over all externally visible definitions in all compilation units
* for this target, looking for class definitions with [IconFile(...)] metadata.
* <p>
* Suppose we find [IconFile("MyComponent.png")] on the class foo.bar.MyComponent.
* Then the returned map will have an entry whose key is "foo/bar/MyComponent"
* and whose value is a file specification that can be used to read the contents
* of this file. A file with the same contents will get created at foo/bar/MyComponent
* inside the SWC.
*/
private Map<String, FileEntryValue> computeIconFiles()
{
Map<String, FileEntryValue> iconFiles = new HashMap<String, FileEntryValue>();
try
{
Collection<ICompilationUnit> compilationUnits = librarySWFTarget.getCompilationUnits();
for (IDefinition definition : getAllExternallyVisibleDefinitions(compilationUnits))
{
if (!(definition instanceof IClassDefinition))
continue;
IClassDefinition classDefinition = (IClassDefinition)definition;
IMetaTag iconFileMetaTag = classDefinition.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_ICON_FILE);
String iconFilePath = classDefinition.getIconFile();
if (iconFilePath == null)
continue;
String packageName = classDefinition.getPackageName();
String key = packageName.replaceAll("\\.", "/") + "/" + iconFilePath;
ICompilationUnit cu = project.getScope().getCompilationUnitForDefinition(classDefinition);
IBinaryFileSpecification fileSpec = null;
if (cu.getCompilationUnitType() == UnitType.SWC_UNIT)
{
ISWC swc = project.getWorkspace().getSWCManager().get(new File(cu.getAbsoluteFilename()));
ISWCFileEntry swcFileEntry = swc.getFile(key);
fileSpec = new SWCFileSpecification(key, swcFileEntry);
}
else
{
String sourcePath = classDefinition.getSourcePath();
iconFilePath = FilenameUtils.getFullPath(sourcePath) + iconFilePath;
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget waiting for lock in getLatestBinaryFileSpecification");
fileSpec = project.getWorkspace().getLatestBinaryFileSpecification(iconFilePath);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("SWCTarget done with lock in getLatestBinaryFileSpecification");
}
FileEntryValue value = new FileEntryValue(fileSpec, iconFileMetaTag);
iconFiles.put(key, value);
}
}
catch (InterruptedException e)
{
}
return iconFiles;
}
@Override
protected ITargetReport computeTargetReport() throws InterruptedException
{
assert librarySWFTarget != null : "Must call build before getting the target report!!";
return librarySWFTarget.getTargetReport();
}
private static class FileEntryValue
{
FileEntryValue(IBinaryFileSpecification fileSpec, ISourceLocation sourceLocation)
{
this.fileSpec = fileSpec;
this.sourceLocation = sourceLocation;
}
private IBinaryFileSpecification fileSpec;
private ISourceLocation sourceLocation;
IBinaryFileSpecification getFileSpec()
{
return fileSpec;
}
ISourceLocation getSourceLocation()
{
return sourceLocation;
}
}
}