/*
*
* 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 flex2.tools.oem;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import flash.util.Trace;
import flex2.compiler.CompilerAPI;
import flex2.compiler.CompilationUnit;
import flex2.compiler.CompilerException;
import flex2.compiler.CompilerSwcContext;
import flex2.compiler.FileSpec;
import flex2.compiler.ResourceBundlePath;
import flex2.compiler.ResourceContainer;
import flex2.compiler.Source;
import flex2.compiler.SourceList;
import flex2.compiler.SourcePath;
import flex2.compiler.SubCompiler;
import flex2.compiler.SymbolTable;
import flex2.compiler.Transcoder;
import flex2.compiler.common.CompilerConfiguration;
import flex2.compiler.common.FontsConfiguration;
import flex2.compiler.config.ConfigurationException;
import flex2.compiler.extensions.ExtensionManager;
import flex2.compiler.extensions.IApplicationExtension;
import flex2.compiler.i18n.I18nUtils;
import flex2.compiler.io.FileUtil;
import flex2.compiler.io.LocalFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.swc.SwcCache;
import flex2.compiler.swc.SwcException;
import flex2.compiler.util.Benchmark;
import flex2.compiler.util.CompilerControl;
import flex2.compiler.util.MimeMappings;
import flex2.compiler.util.NameMappings;
import flex2.compiler.util.PerformanceData;
import flex2.compiler.util.QName;
import flex2.compiler.util.ThreadLocalToolkit;
import flex2.linker.LinkerAPI;
import flex2.linker.ConsoleApplication;
import flex2.linker.FlexMovie;
import flex2.linker.LinkerException;
import flex2.linker.SimpleMovie;
import flex2.tools.Mxmlc;
import flex2.tools.PostLink;
import flex2.tools.PreLink;
import flex2.tools.ToolsConfiguration;
import flex2.tools.WebTierAPI;
import flex2.tools.oem.internal.ApplicationCompilerConfiguration;
import flex2.tools.oem.internal.ApplicationData;
import flex2.tools.oem.internal.OEMConfiguration;
import flex2.tools.oem.internal.OEMReport;
import flex2.tools.oem.internal.OEMUtil;
import macromedia.asc.util.ContextStatics;
/**
* The <code>Application</code> class represents a Flex application. It implements the <code>Builder</code> interface
* which allows for building the application incrementally. There are many ways to define
* a Flex application. The most common way is specify the location of the target source file
* on disk:
*
* <pre>
* Application app = new Application(new File("MyApp.mxml"));
* </pre>
*
* Before the <code>Application</code> object starts the compilation, it must be configured. The most common methods that the client
* calls are <code>setLogger()</code>, <code>setConfiguration()</code>, and <code>setOutput()</code>.
*
* A logger must implement <code>flex2.tools.oem.Logger</code> and use the implementation as the Logger
* for the compilation. The following is an example <code>Logger</code> implementation:
*
* <pre>
* app.setLogger(new flex2.tools.oem.Logger()
* {
* public void log(Message message, int errorCode, String source)
* {
* System.out.println(message);
* }
* });
* </pre>
*
* To specify compiler options for the <code>Application</code> object, the client
* must get a <code>Configuration</code> object populated with default values. Then, the client can set
* compiler options programmatically.
*
* The <code>setOutput()</code> method lets clients specify where the <code>Application</code> object should write
* the output to. If you call the <code>setOutput()</code> method, the <code>build(boolean)</code> method builds and
* writes directly to the location specified by the <code>setOutput()</code> method. For example:
*
* <pre>
* app.setOutput(new File("MyApp.swf"));
* app.build(true);
* </pre>
*
* If you do not call the <code>setOutput()</code> method, the client can use the <code>build(OutputStream, boolean)</code> method
* which requires the client to provide a buffered output stream. For example:
*
* <pre>
* app.build(new BufferedOutputStream(new FileOutputStream("MyApp.swf")), true);
* </pre>
*
* Before the <code>Application</code> object is thrown away, it is possible to save the compilation
* data for reuse by using the <code>save(OutputStream)</code> method. Subsequent compilations can use the
* <code>load(OutputStream)</code> method to get the old data into the <code>Application</code> object.
*
* <pre>
* app.save(new BufferedOutputStream(new FileOutputStream("MyApp.incr")));
* </pre>
*
* When a cache file (such as <code>MyApp.incr</code>) is available from a previous compilation, the client can
* call the <code>load(OutputStream)</code> method before calling the <code>build(boolean)</code> method. For example:
*
* <pre>
* app.load(new BufferedInputStream(FileInputStream("MyApp.incr")));
* app.build();
* </pre>
*
* The <code>build(false)</code> and <code>build(OutputStream, false)</code> methods always rebuild the application. If the <code>Application</code>
* object is new, the first <code>build(true)/build(OutputStream, true)</code> method call performs a full build, which
* is equivalent to <code>build(false)/build(OutputStream, false)</code>, respectively. After a call to the <code>clean()</code> method,
* the <code>Application</code> object always performs a full build.
*
* <p>
* The <code>clean()</code> method not only cleans up compilation data in the <code>Application</code> object, but also the output
* file if the <code>setOutput()</code> method was called.
*
* <p>
* The <code>Application</code> class also supports building applications from a combination of source
* files from the file system and in-memory, dynamically-generated source objects. The client
* must use the <code>Application(String, VirtualLocalFile)</code> or <code>Application(String, VirtualLocalFile[])</code> constructors.
*
* <p>
* The <code>Application</code> class can be part of a <code>Project</code>. For more information, see the <code>Project</code> class's description.
*
* @see flex2.tools.oem.Configuration
* @see flex2.tools.oem.Project
* @version 2.0.1
* @author Clement Wong
*/
public class Application implements Builder
{
static
{
// find all the compiler temp files.
File[] list = null;
try
{
File tempDir = File.createTempFile("Flex2_", "").getParentFile();
list = tempDir.listFiles(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.startsWith("Flex2_");
}
});
}
catch (Throwable e)
{
}
// get rid of compiler temp files.
for (int i = 0, len = list == null ? 0 : list.length; i < len; i++)
{
try { list[i].delete(); } catch (Throwable t) {}
}
// use the protection domain to find the location of flex-compiler-oem.jar.
URL url = Application.class.getProtectionDomain().getCodeSource().getLocation();
try
{
File f = new File(new URI(url.toExternalForm()));
if (f.getAbsolutePath().endsWith("flex-compiler-oem.jar"))
{
// use the location of flex-compiler-oem.jar to set application.home
// assume that the jar file is in <application.home>/lib/flex-compiler-oem.jar
String applicationHome = f.getParentFile().getParent();
System.setProperty("application.home", applicationHome);
}
}
catch (URISyntaxException ex)
{
}
catch (IllegalArgumentException ex)
{
}
}
/**
* Constructor.
*
* @param file The target source file.
* @throws FileNotFoundException Thrown when the specified source file does not exist.
*/
public Application(File file) throws FileNotFoundException
{
this(file, null);
}
/**
* Constructor.
*
* @param file The target source file.
* @param libraryCache A reference to a LibraryCache object. After
* building this Application object the cache may be saved
* and used to compile another Application object that uses
* a similar library path.
* @throws FileNotFoundException Thrown when the specified source file does not exist.
* @since 3.0
*/
public Application(File file, LibraryCache libraryCache) throws FileNotFoundException
{
if (file.exists())
{
init(new VirtualFile[] { new LocalFile(FileUtil.getCanonicalFile(file)) });
}
else
{
throw new FileNotFoundException(FileUtil.getCanonicalPath(file));
}
this.libraryCache = libraryCache;
}
/**
* Constructor.
*
* @param file An in-memory source object.
*/
public Application(VirtualLocalFile file)
{
init(new VirtualFile[] { file });
}
/**
* Constructor.
*
* @param files An array of in-memory source objects. The last element in the array is the target source object.
*/
public Application(VirtualLocalFile[] files)
{
init(files);
}
/**
* Constructor. Use to build resource modules which don't have a target
* source file.
*
*/
public Application()
{
init(new VirtualFile[0]);
}
/**
*
* @param files
*/
private void init(VirtualFile[] files)
{
this.files = new ArrayList(files.length);
for (int i = 0, length = files.length; i < length; i++)
{
this.files.add(files[i]);
}
oemConfiguration = null;
logger = null;
output = null;
mimeMappings = new MimeMappings();
meter = null;
resolver = null;
cc = new CompilerControl();
isGeneratedTargetFile = false;
data = null;
cacheName = null;
configurationReport = null;
messages = new ArrayList<Message>();
}
private List<VirtualFile> files;
private OEMConfiguration oemConfiguration;
private Logger logger;
private File output;
private MimeMappings mimeMappings;
private ProgressMeter meter;
protected PathResolver resolver;
private CompilerControl cc;
private boolean isGeneratedTargetFile;
private ApplicationCache applicationCache;
private LibraryCache libraryCache;
// clean() would null out the following variables.
private ApplicationData data;
private String cacheName, configurationReport;
private List<Message> messages;
private HashMap<String, PerformanceData[]> compilerBenchmarks;
private Benchmark benchmark;
/**
* @inheritDoc
*/
public void setConfiguration(Configuration configuration)
{
oemConfiguration = (OEMConfiguration) configuration;
}
/**
* @inheritDoc
*/
public Configuration getDefaultConfiguration()
{
return getDefaultConfiguration(false);
}
/**
*
* @param processDefaults
* @return
*/
private Configuration getDefaultConfiguration(boolean processDefaults)
{
return OEMUtil.getApplicationConfiguration(constructCommandLine(null), false, false,
OEMUtil.getLogger(logger, messages), resolver,
mimeMappings, processDefaults);
}
/**
* @inheritDoc
*/
public Map<String, PerformanceData[]> getCompilerBenchmarks()
{
return compilerBenchmarks;
}
/**
* @inheritDoc
*/
public Benchmark getBenchmark()
{
return benchmark;
}
/**
* @inheritDoc
*/
public Configuration getConfiguration()
{
return oemConfiguration;
}
/**
* @inheritDoc
*/
public void setLogger(Logger logger)
{
this.logger = logger;
}
/**
* @inheritDoc
*/
public Logger getLogger()
{
return logger;
}
/**
* Sets the location of the compiler's output. This method is necessary if you call the <code>build(boolean)</code> method.
* If you use the <code>build(OutputStream, boolean)</code> method, in which an output stream
* is provided, there is no need to use this method.
*
* @param output An instance of the <code>java.io.File</code> class.
*/
public void setOutput(File output)
{
this.output = output;
}
/**
* Gets the output destination. This method returns <code>null</code> if the <code>setOutput()</code> method was not called.
*
* @return An instance of the <code>java.io.File</code> class, or <code>null</code> if you did not call the <code>setOutput()</code> method.
*/
public File getOutput()
{
return output;
}
/**
* @inheritDoc
*/
public void setSupportedFileExtensions(String mimeType, String[] extensions)
{
mimeMappings.set(mimeType, extensions);
}
/**
* @inheritDoc
*/
public void setProgressMeter(ProgressMeter meter)
{
this.meter = meter;
}
/**
* @inheritDoc
* @since 3.0
*/
public void setPathResolver(PathResolver resolver)
{
this.resolver = resolver;
}
/**
* @inheritDoc
*/
// IMPORTANT: If you make changes here, you probably want to mirror them in Library.build()
public long build(boolean incremental) throws IOException
{
if (output != null)
{
InputStream tempIn = null;
ByteArrayOutputStream tempOut = null;
OutputStream out = null;
long size = 0;
//TODO PERFORMANCE: A lot of unnecessary recopying and buffering here
try
{
int result = compile(incremental);
if (result == SKIP || result == LINK || result == OK)
{
// write to a temp buffer...
tempOut = new ByteArrayOutputStream();
size = (result == OK || result == LINK) ? link(tempOut) : encode(tempOut);
tempOut.flush();
if (size > 0)
{
tempIn = new ByteArrayInputStream(tempOut.toByteArray());
out = new BufferedOutputStream(new FileOutputStream(output));
FileUtil.streamOutput(tempIn, out);
}
}
return size;
}
catch (Throwable t)
{
ThreadLocalToolkit.logError(t.getLocalizedMessage());
return 0;
}
finally
{
if (tempIn != null) { try { tempIn.close(); } catch (Exception ex) {} }
if (tempOut != null) { try { tempOut.close(); } catch (Exception ex) {} }
if (out != null) { try { out.close(); } catch (Exception ex) {} }
if ((benchmark != null) && benchmark.hasStarted(Benchmark.POSTCOMPILE))
{
benchmark.stopTime(Benchmark.POSTCOMPILE, false);
}
runExtensions();
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
true /* cleanThreadLocals */);
}
}
else
{
return 0;
}
}
private void runExtensions()
{
if (oemConfiguration != null)
{
Set<IApplicationExtension> extensions = ExtensionManager.getApplicationExtensions(oemConfiguration.getExtensions());
for ( IApplicationExtension extension : extensions )
{
if (ThreadLocalToolkit.errorCount() == 0)
{
extension.run( (Configuration) oemConfiguration.clone() );
}
}
}
}
/**
* @inheritDoc
*/
public long build(OutputStream out, boolean incremental) throws IOException
{
try
{
int result = compile(incremental);
if (result == OK || result == LINK)
{
runExtensions();
return link(out);
}
else if (result == SKIP)
{
runExtensions();
return encode(out);
}
else
{
return 0;
}
}
finally
{
if ((benchmark != null) && benchmark.hasStarted(Benchmark.POSTCOMPILE))
{
benchmark.stopTime(Benchmark.POSTCOMPILE, false);
}
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
true /* cleanThreadLocals */);
}
}
/**
* @inheritDoc
*/
public void stop()
{
cc.stop();
}
/**
* @inheritDoc
*/
public void clean()
{
clean(true /* cleanData */,
true /* cleanCache */,
true /* cleanOutput */,
true /* cleanConfig */,
true /* cleanMessages */,
true /* cleanThreadLocals */);
}
/**
* @inheritDoc
*/
public void load(InputStream in) throws IOException
{
try
{
cacheName = OEMUtil.load(in, cacheName);
}
finally
{
clean(true /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
true /* cleanThreadLocals */);
}
}
/**
* @inheritDoc
*/
public long save(OutputStream out) throws IOException
{
return OEMUtil.save(out, cacheName, data);
}
/**
* @inheritDoc
*/
public Report getReport()
{
OEMUtil.setupLocalizationManager();
return new OEMReport(data == null ? null : data.sources,
data == null ? null : data.movie,
data == null ? null : data.configuration,
data == null ? null : data.sourceList,
configurationReport,
messages);
}
private void setupFontManager(OEMConfiguration localOEMConfiguration)
{
if (localOEMConfiguration != null && data != null)
{
FontsConfiguration fontsConfiguration = localOEMConfiguration.configuration.getCompilerConfiguration().getFontsConfiguration();
fontsConfiguration.setTopLevelManager(data.fontManager);
}
}
/**
* @param configuration
* @return true, unless a CompilerException occurs.
*/
private boolean setupSourceContainers(OEMConfiguration localOEMConfiguration)
{
ToolsConfiguration configuration = localOEMConfiguration.configuration;
CompilerConfiguration compilerConfig = configuration.getCompilerConfiguration();
VirtualFile[] asClasspath = compilerConfig.getSourcePath();
boolean result = false;
try
{
// If there are no files that means this is a resource module and this
// is the first time compiling the module. When the
// ApplicationCompilerConfiguration was generated the validate() would
// have failed if there were no source files and no included resource
// bundles. See ApplicationCompilerConfiguration.getTargetFile() to
// see how the resource module is initially generated.
if (files.size() == 0)
{
ApplicationCompilerConfiguration acc = (ApplicationCompilerConfiguration)localOEMConfiguration.configuration;
files.add(CompilerAPI.getVirtualFile(acc.getTargetFile(), true));
isGeneratedTargetFile = true;
}
else if (isGeneratedTargetFile)
{
// The resource module file has already been generated but we need to
// regenerate it to do a fresh compile. This file is impacted if
// either the locales or bundleNames have changed.
I18nUtils.regenerateResourceModule((ApplicationCompilerConfiguration)localOEMConfiguration.configuration);
}
VirtualFile targetFile = (VirtualFile) files.get(files.size() - 1);
WebTierAPI.checkSupportedTargetMimeType(targetFile);
// create a FileSpec...
data.fileSpec = new FileSpec(Collections.<VirtualFile>emptyList(), WebTierAPI.getFileSpecMimeTypes());
// create a SourceList
data.sourceList = new SourceList(files, asClasspath, targetFile, WebTierAPI.getSourcePathMimeTypes());
// create a SourcePath...
data.sourcePath = new SourcePath(asClasspath, targetFile, WebTierAPI.getSourcePathMimeTypes(),
compilerConfig.allowSourcePathOverlap());
// create a ResourceContainer...
data.resources = new ResourceContainer();
if (applicationCache != null)
{
if (applicationCache.isConsistent(localOEMConfiguration.configuration))
{
data.resources.setApplicationCache(applicationCache);
data.sourceList.applyApplicationCache(applicationCache);
data.sourcePath.setApplicationCache(applicationCache);
}
else
{
applicationCache.clear();
}
}
// create a ResourceBundlePath...
data.bundlePath = new ResourceBundlePath(compilerConfig, targetFile);
// clear these...
if (data.sources != null) data.sources.clear();
if (data.units != null) data.units.clear();
if (data.swcDefSignatureChecksums != null) data.swcDefSignatureChecksums.clear();
if (data.swcFileChecksums != null) data.swcFileChecksums.clear();
result = true;
}
catch (CompilerException ex)
{
assert ThreadLocalToolkit.errorCount() > 0;
}
catch (ConfigurationException e)
{
ThreadLocalToolkit.logInfo(e.getMessage());
}
return result;
}
/**
* Swap in cached sources from previous compiles, which occurred
* within the same workspace.
*/
private boolean loadCachedSources(Map<String, Source> sources)
{
boolean relink = false;
for (Map.Entry<String, Source> entry : sources.entrySet())
{
String className = entry.getKey();
Source source = entry.getValue();
Source cachedSource = applicationCache.getSource(className);
if ((cachedSource != null) && !cachedSource.isUpdated())
{
CompilationUnit compilationUnit = source.getCompilationUnit();
VirtualFile pathRoot = source.getPathRoot();
CompilationUnit cachedCompilationUnit = cachedSource.getCompilationUnit();
VirtualFile cachedSourcePathRoot = cachedSource.getPathRoot();
if ((((pathRoot == null) && (cachedSourcePathRoot == null)) ||
((pathRoot != null) && pathRoot.equals(cachedSource.getPathRoot()))) &&
(compilationUnit != null) && !compilationUnit.hasTypeInfo &&
(cachedCompilationUnit != null) && cachedCompilationUnit.hasTypeInfo)
{
Source.copyCompilationUnit(cachedCompilationUnit, compilationUnit, true);
source.setFileTime(cachedSource.getFileTime());
source.reused();
relink = true;
}
}
}
return relink;
}
/**
* @param configuration
* @return true, unless an IOException occurs and the source containers can't be setup.
*/
private boolean loadCompilationUnits(OEMConfiguration localOEMConfiguration, CompilerSwcContext swcContext, int[] checksums)
{
ToolsConfiguration configuration = localOEMConfiguration.configuration;
if (data.cacheName == null) // note: NOT (cacheName == null)
{
return true;
}
RandomAccessFile cacheFile = null;
try
{
cacheFile = new RandomAccessFile(data.cacheName, "r");
CompilerAPI.loadCompilationUnits(configuration, data.fileSpec, data.sourceList, data.sourcePath,
data.resources, data.bundlePath, data.sources = new ArrayList<Source>(),
data.units = new ArrayList<CompilationUnit>(), checksums,
data.swcDefSignatureChecksums = new HashMap<QName, Long>(),
data.swcFileChecksums = new HashMap<String, Long>(),
cacheFile, data.cacheName, data.fontManager);
}
catch (FileNotFoundException ex)
{
ThreadLocalToolkit.logInfo(ex.getMessage());
}
catch (IOException ex)
{
ThreadLocalToolkit.logInfo(ex.getMessage());
if (!setupSourceContainers(localOEMConfiguration))
{
return false;
}
}
finally
{
if (cacheFile != null) try { cacheFile.close(); } catch (IOException ex) {}
}
return true;
}
/**
* Compiles the <code>Application</code> object. This method does not link the <code>Application</code>.
*
* @param incremental If <code>true</code>, build incrementally; if <code>false</code>, rebuild.
* @return {@link Builder#OK} if this method call resulted in compilation of some/all parts of the application;
* {@link Builder#LINK} if this method call did not compile anything in the application but advise the caller to link again;
* {@link Builder#SKIP} if this method call did not compile anything in the application;
* {@link Builder#FAIL} if this method call encountered errors during compilation.
*/
protected int compile(boolean incremental)
{
try
{
messages.clear();
// if there is no configuration, use the default... but don't populate this.configuration.
OEMConfiguration tempOEMConfiguration;
if (oemConfiguration == null)
{
tempOEMConfiguration = (OEMConfiguration) getDefaultConfiguration(true);
}
else
{
tempOEMConfiguration = OEMUtil.getApplicationConfiguration(constructCommandLine(oemConfiguration),
oemConfiguration.keepLinkReport(),
oemConfiguration.keepSizeReport(),
OEMUtil.getLogger(logger, messages),
resolver, mimeMappings);
}
// if c is null, which indicates problems, this method will return.
if (tempOEMConfiguration == null)
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
else if (oemConfiguration != null && oemConfiguration.keepConfigurationReport())
{
configurationReport = OEMUtil.formatConfigurationBuffer(tempOEMConfiguration.cfgbuf);
}
setupFontManager(tempOEMConfiguration);
if (oemConfiguration != null)
{
oemConfiguration.cfgbuf = tempOEMConfiguration.cfgbuf;
}
if (tempOEMConfiguration.configuration.benchmark())
{
benchmark = CompilerAPI.runBenchmark();
benchmark.setTimeFilter(tempOEMConfiguration.configuration.getBenchmarkTimeFilter());
benchmark.startTime(Benchmark.PRECOMPILE);
}
else
{
CompilerAPI.disableBenchmark();
}
// initialize some ThreadLocal variables...
cc.run();
OEMUtil.init(OEMUtil.getLogger(logger, messages), mimeMappings, meter, resolver, cc);
Map licenseMap = OEMUtil.getLicenseMap(tempOEMConfiguration.configuration);
if (data == null || !incremental)
{
String compilationType = (cacheName != null) ? "inactive" : "full";
if (benchmark != null)
{
benchmark.benchmark2("Starting " + compilationType + " compile for " + getOutput(), true);
}
int returnValue = recompile(false, licenseMap, tempOEMConfiguration);
if (benchmark != null)
{
benchmark.benchmark2("Ending " + compilationType + " compile for " + getOutput(), true);
}
clean(returnValue == FAIL /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return returnValue;
}
CompilerAPI.setupHeadless(tempOEMConfiguration.configuration);
CompilerConfiguration compilerConfig = tempOEMConfiguration.configuration.getCompilerConfiguration();
NameMappings mappings = CompilerAPI.getNameMappings(tempOEMConfiguration.configuration);
Transcoder[] transcoders = WebTierAPI.getTranscoders(tempOEMConfiguration.configuration);
SubCompiler[] compilers = WebTierAPI.getCompilers(compilerConfig, mappings, transcoders);
CompilerSwcContext swcContext = new CompilerSwcContext(true);
try
{
swcContext.load( compilerConfig.getLibraryPath(),
flex2.compiler.common.Configuration.getAllExcludedLibraries(compilerConfig, tempOEMConfiguration.configuration),
compilerConfig.getThemeFiles(),
compilerConfig.getIncludeLibraries(),
mappings,
I18nUtils.getTranslationFormat(compilerConfig),
data.swcCache );
}
catch (SwcException ex)
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
// save the generated cache if the caller provided a libraryCache.
if (libraryCache != null)
{
libraryCache.setSwcCache(data.swcCache);
}
data.includes = new HashSet<String>(swcContext.getIncludes());
data.excludes = new HashSet<String>(swcContext.getExterns());
tempOEMConfiguration.configuration.addExterns( swcContext.getExterns() );
tempOEMConfiguration.configuration.addIncludes( swcContext.getIncludes() );
tempOEMConfiguration.configuration.getCompilerConfiguration().addThemeCssFiles( swcContext.getThemeStyleSheets() );
// recompile or incrementally compile...
if (OEMUtil.isRecompilationNeeded(data, swcContext, tempOEMConfiguration))
{
data.resources = new ResourceContainer();
if (benchmark != null)
{
benchmark.benchmark2("Starting full compile for " + getOutput(), true);
}
clean(true /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
int returnValue = recompile(true, licenseMap, tempOEMConfiguration);
if (benchmark != null)
{
benchmark.benchmark2("Ending full compile for " + getOutput(), true);
}
clean(returnValue == FAIL /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return returnValue;
}
if (benchmark != null)
{
// We aren't really starting the compile here, but it's
// the earliest that we know that it's going to be an
// active compilation.
benchmark.benchmark2("Starting active compile for " + getOutput(), true);
}
boolean relink = false;
if (applicationCache != null)
{
ContextStatics contextStatics = applicationCache.getContextStatics();
data.perCompileData = contextStatics;
if (applicationCache.isConsistent(tempOEMConfiguration.configuration))
{
relink = (loadCachedSources(data.resources.sources()) ||
loadCachedSources(data.sourceList.sources()) ||
loadCachedSources(data.sourcePath.sources()));
}
else
{
applicationCache.clear();
}
}
// Clear out ASC's userDefined, so definitions from a
// previous compilation don't spill over into this one.
data.perCompileData.userDefined.clear();
data.sourcePath.clearCache();
data.bundlePath.clearCache();
data.resources.refresh();
// validate CompilationUnits
final int count = CompilerAPI.validateCompilationUnits(data.fileSpec, data.sourceList, data.sourcePath, data.bundlePath,
data.resources, swcContext, data.perCompileData, tempOEMConfiguration.configuration);
if ((count > 0) || (data.swcChecksum != swcContext.checksum()))
{
data.configuration = tempOEMConfiguration.configuration;
data.linkChecksum = tempOEMConfiguration.cfgbuf.link_checksum_ts();
data.swcChecksum = swcContext.checksum();
// create a symbol table
SymbolTable symbolTable = new SymbolTable(tempOEMConfiguration.configuration, data.perCompileData);
data.sources = new ArrayList<Source>();
data.units = compile(compilers, swcContext, symbolTable, mappings, licenseMap, data.sources);
boolean forcedToStop = CompilerAPI.forcedToStop();
if (data.units == null || forcedToStop)
{
data.sources = null;
}
if (benchmark != null)
{
benchmark.benchmark2("Ending active compile for " + getOutput(), true);
}
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return (data.units != null && !forcedToStop) ? OK : FAIL;
}
else
{
if (benchmark != null)
{
benchmark.stopTime(Benchmark.PRECOMPILE, false);
benchmark.startTime(Benchmark.POSTCOMPILE);
}
int retVal = SKIP;
if (data != null)
{
CompilerAPI.displayWarnings(data.units);
if ((data.linkChecksum != tempOEMConfiguration.cfgbuf.link_checksum_ts()) || relink)
{
retVal = LINK;
}
}
else
{
retVal = LINK;
}
data.linkChecksum = tempOEMConfiguration.cfgbuf.link_checksum_ts();
data.swcChecksum = swcContext.checksum();
if (benchmark != null)
{
benchmark.benchmark2("Ending active compile for " + getOutput(), true);
}
if (CompilerAPI.forcedToStop()) retVal = FAIL;
if (retVal != LINK)
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
}
return retVal;
}
}
finally
{
// clean thread locals
OEMUtil.clean();
}
}
/**
* @param fullRecompile if true a full recompile is needed, do not attempted to use cache file.
*
* @return {@link Builder#OK} if this method call resulted in compilation of some/all parts of the application;
* {@link Builder#LINK} if this method call did not compile anything in the application but advise the caller to link again;
* {@link Builder#SKIP} if this method call did not compile anything in the application;
* {@link Builder#FAIL} if this method call encountered errors during compilation.
*/
private int recompile(boolean fullRecompile, Map licenseMap, OEMConfiguration localOEMConfiguration)
{
data = new ApplicationData();
data.configuration = localOEMConfiguration.configuration;
data.cacheName = cacheName;
CompilerAPI.setupHeadless(localOEMConfiguration.configuration);
CompilerConfiguration compilerConfig = localOEMConfiguration.configuration.getCompilerConfiguration();
NameMappings mappings = CompilerAPI.getNameMappings(localOEMConfiguration.configuration);
data.fontManager = compilerConfig.getFontsConfiguration().getTopLevelManager();
if (output != null)
{
OEMUtil.setGeneratedDirectory(compilerConfig, output);
}
Transcoder[] transcoders = WebTierAPI.getTranscoders(localOEMConfiguration.configuration);
SubCompiler[] compilers = WebTierAPI.getCompilers(compilerConfig, mappings, transcoders);
// NOT in compile
if (!setupSourceContainers(localOEMConfiguration))
{
clean(true /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
// Setup SWC cache
if (libraryCache != null)
{
ContextStatics contextStatics = libraryCache.getContextStatics();
if (contextStatics != null)
{
// Clear out ASC's userDefined, so definitions from a
// previous compilation don't spill over into this one.
contextStatics.userDefined.clear();
data.swcCache = libraryCache.getSwcCache();
data.perCompileData = contextStatics;
}
}
if (data.swcCache == null)
{
data.swcCache = new SwcCache();
}
// load SWCs
CompilerSwcContext swcContext = new CompilerSwcContext(true);
try
{
swcContext.load( compilerConfig.getLibraryPath(),
flex2.compiler.common.Configuration.getAllExcludedLibraries(compilerConfig, localOEMConfiguration.configuration),
compilerConfig.getThemeFiles(),
compilerConfig.getIncludeLibraries(),
mappings,
I18nUtils.getTranslationFormat(compilerConfig),
data.swcCache );
}
catch (SwcException ex)
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
// save the generated swcCache if the class has a libraryCache.
if (libraryCache != null)
{
libraryCache.setSwcCache(data.swcCache);
}
data.includes = new HashSet<String>(swcContext.getIncludes());
data.excludes = new HashSet<String>(swcContext.getExterns());
localOEMConfiguration.configuration.addExterns( swcContext.getExterns() );
localOEMConfiguration.configuration.addIncludes( swcContext.getIncludes() );
localOEMConfiguration.configuration.getCompilerConfiguration().addThemeCssFiles( swcContext.getThemeStyleSheets() );
data.cmdChecksum = localOEMConfiguration.cfgbuf.checksum_ts(); // OEMUtil.calculateChecksum(data, swcContext, c);
data.linkChecksum = localOEMConfiguration.cfgbuf.link_checksum_ts();
data.swcChecksum = swcContext.checksum();
int[] checksums = new int[] { 0, data.cmdChecksum, data.linkChecksum, data.swcChecksum };
boolean relink = false;
// C: must do loadCompilationUnits() after checksum calculation...
if (!fullRecompile)
{
if (!loadCompilationUnits(localOEMConfiguration, swcContext, checksums))
{
clean(true /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
data.checksum = checksums[0];
if (data.units != null &&
data.units.size() > 0 &&
OEMUtil.isRecompilationNeeded(data, swcContext, localOEMConfiguration))
{
if (!setupSourceContainers(localOEMConfiguration))
{
clean(true /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return FAIL;
}
}
if (applicationCache != null)
{
ContextStatics contextStatics = applicationCache.getContextStatics();
if (contextStatics != null)
{
// Clear out ASC's userDefined, so definitions
// from a previous compilation don't spill over
// into this one.
contextStatics.userDefined.clear();
data.perCompileData = contextStatics;
if (applicationCache.isConsistent(localOEMConfiguration.configuration))
{
relink = (loadCachedSources(data.resources.sources()) ||
loadCachedSources(data.sourceList.sources()) ||
loadCachedSources(data.sourcePath.sources()));
}
else
{
applicationCache.clear();
}
}
}
}
// validate CompilationUnits...
int count = CompilerAPI.validateCompilationUnits(data.fileSpec, data.sourceList,
data.sourcePath, data.bundlePath,
data.resources, swcContext,
data.perCompileData,
localOEMConfiguration.configuration);
SymbolTable symbolTable;
if (data.perCompileData != null)
{
symbolTable = new SymbolTable(localOEMConfiguration.configuration, data.perCompileData);
}
else
{
symbolTable = new SymbolTable(localOEMConfiguration.configuration);
data.perCompileData = symbolTable.perCompileData;
}
if (applicationCache != null)
{
applicationCache.setConfiguration(localOEMConfiguration.configuration);
applicationCache.setContextStatics(data.perCompileData);
}
if (libraryCache != null)
{
libraryCache.setContextStatics(data.perCompileData);
}
data.sources = new ArrayList<Source>();
data.units = compile(compilers, swcContext, symbolTable, mappings, licenseMap, data.sources);
// need to update the checksum here since doing a compile could add some
// some signature checksums and change it.
data.checksum = OEMUtil.calculateChecksum(data, swcContext, localOEMConfiguration);
int result = OK;
if (data.units == null || CompilerAPI.forcedToStop())
{
result = FAIL;
}
else if ((count == 0) && relink)
{
result = LINK;
}
return result;
}
/**
* @param swcContext
* @param symbolTable
* @param licenseMap
* @param sources
* @param OEMConfig
* @param isRecompile - true if called as part of a recompile, false if incremental compile.
*
* @return a list of CompilationUnit
*/
private List<CompilationUnit> compile(SubCompiler[] compilers, CompilerSwcContext swcContext,
SymbolTable symbolTable, NameMappings nameMappings,
Map licenseMap, List<Source> sources)
{
List<CompilationUnit> units = null;
try
{
if (benchmark != null)
{
for (int i = 0; i < compilers.length; i++)
{
compilers[i].initBenchmarks();
}
}
ApplicationCompilerConfiguration config = (ApplicationCompilerConfiguration) data.configuration;
VirtualFile projector = config.getProjector();
if (benchmark != null)
{
benchmark.stopTime(Benchmark.PRECOMPILE, false);
}
if (projector != null && projector.getName().endsWith("avmplus.exe"))
{
units = CompilerAPI.compile(data.fileSpec, data.sourceList, null, data.sourcePath, data.resources, data.bundlePath,
swcContext, symbolTable, nameMappings, data.configuration, compilers,
null, licenseMap, sources);
}
else
{
units = CompilerAPI.compile(data.fileSpec, data.sourceList, null, data.sourcePath, data.resources, data.bundlePath,
swcContext, symbolTable, nameMappings, data.configuration, compilers,
new PreLink(), licenseMap, sources);
}
if (applicationCache != null)
{
data.resources.refresh();
applicationCache.addSources(data.resources.sources());
applicationCache.addSources(data.sourceList.sources());
applicationCache.addSources(data.sourcePath.sources());
}
if (benchmark != null)
{
benchmark.startTime(Benchmark.POSTCOMPILE);
}
if ((benchmark != null) && (ThreadLocalToolkit.getLogger() != null))
{
if (compilerBenchmarks == null)
compilerBenchmarks = new HashMap<String, PerformanceData[]>();
compilerBenchmarks.clear();
flex2.compiler.Logger logger = ThreadLocalToolkit.getLogger();
for (int i = 0; i < compilers.length; i++)
{
SubCompiler compiler = compilers[i];
PerformanceData[] times = compiler.getBenchmarks();
if (times != null)
{
compiler.logBenchmarks(logger);
String compilerName = compiler.getName();
assert(!compilerBenchmarks.containsKey(compilerName));
compilerBenchmarks.put(compilerName, times);
}
// Now check for any embedded compilers and get their phase times.
// "synthesize" compiler name by appending _ebm to main compiler name
times = compiler.getEmbeddedBenchmarks();
if (times != null)
{
compiler.logBenchmarks(logger);
String compilerName = compiler.getName();
compilerName += "_emb";
assert(!compilerBenchmarks.containsKey(compilerName));
compilerBenchmarks.put(compilerName, times);
}
}
}
}
catch (CompilerException ex)
{
assert ThreadLocalToolkit.errorCount() > 0;
}
finally
{
data.sourcePath.clearCache();
data.bundlePath.clearCache();
data.resources.refresh();
OEMUtil.saveSwcFileChecksums(swcContext, data, data.configuration);
OEMUtil.saveSignatureChecksums(units, data, data.configuration);
// Make sure the swcContext is closed so we don't leave any dangling file handles
swcContext.close();
}
return units;
}
/**
* Links the application. This method writes the output to the output stream specified by
* the client. You should use a buffered output stream for best performance.
*
* <p>
* This method is protected. In most circumstances, the client only needs to call the
* <code>build()</code> method. Subclasses can call this method so that it links and outputs
* the application without recompiling.
*
* @param out The <code>OutputStream</code>.
* @return The size of the application, in bytes.
* @throws IOException Thrown when an I/O error occurs during linking.
*/
protected long link(OutputStream out) throws IOException
{
if (data == null || data.units == null)
{
return 0;
}
boolean hasChanged = (oemConfiguration == null) ? false : oemConfiguration.hasChanged();
flex2.compiler.common.Configuration config = null;
if (hasChanged)
{
OEMConfiguration tempOEMConfiguration;
tempOEMConfiguration = OEMUtil.getLinkerConfiguration(oemConfiguration.getLinkerOptions(),
oemConfiguration.keepLinkReport(),
oemConfiguration.keepSizeReport(),
OEMUtil.getLogger(logger, messages),
mimeMappings, resolver,
data.configuration,
oemConfiguration.newLinkerOptionsAfterCompile,
data.includes, data.excludes);
if (tempOEMConfiguration == null)
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
false /* cleanConfig */,
false /* cleanMessages */,
true /* cleanThreadLocals */);
return 0;
}
config = tempOEMConfiguration.configuration;
}
else
{
config = data.configuration;
}
long size = 0;
try
{
OEMUtil.init(OEMUtil.getLogger(logger, messages), mimeMappings, meter, resolver, cc);
ApplicationCompilerConfiguration appConfig = (ApplicationCompilerConfiguration) data.configuration;
VirtualFile projector = appConfig.getProjector();
PostLink postLink = null;
if (config.optimize() && !config.debug())
{
postLink = new PostLink(config);
}
// link
if (projector != null && projector.getName().endsWith("avmplus.exe"))
{
ConsoleApplication temp = data.app;
data.app = LinkerAPI.linkConsole(data.units, postLink, config);
size = encodeConsoleProjector(projector, out);
if (hasChanged && temp != null)
{
data.app = temp;
}
}
else
{
SimpleMovie temp = data.movie;
data.movie = (FlexMovie) LinkerAPI.link(data.units, postLink, config);
size = (projector == null) ? encode(out) : encodeProjector(projector, out);
if (hasChanged && temp != null)
{
data.movie = temp;
}
}
}
catch (LinkerException ex)
{
assert ThreadLocalToolkit.errorCount() > 0;
}
catch (Throwable t)
{
if (Trace.error)
{
t.printStackTrace();
}
ThreadLocalToolkit.logError(t.getLocalizedMessage());
}
finally
{
// clean thread locals
OEMUtil.clean();
}
return size;
}
/**
*
* @param out
* @return Number of bytes written to 'out'
* @throws IOException
*/
private long encode(OutputStream out) throws IOException
{
if (data == null || data.units == null || data.movie == null)
{
return 0;
}
// if (ThreadLocalToolkit.getBenchmark() != null &&
// ThreadLocalToolkit.getLocalizationManager() == null)
// {
// OEMUtil.init(OEMUtil.getLogger(logger, messages), mimeMappings, meter, resolver, cc);
// }
//TODO PERFORMANCE: A lot of unnecessary recopying and buffering here
// output SWF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CompilerAPI.encode(data.configuration, data.movie, baos);
long size = baos.size();
baos.writeTo(out);
out.flush();
return size;
}
/**
*
* @param projector
* @param out
* @return
* @throws IOException
*/
private long encodeProjector(VirtualFile projector, OutputStream out) throws IOException
{
if (data == null || data.units == null || data.movie == null)
{
return 0;
}
// output EXE
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CompilerAPI.encode(data.configuration, data.movie, baos);
return Mxmlc.createProjector(data.configuration, projector, baos, out);
}
/**
*
* @param projector
* @param out
* @return
* @throws IOException
*/
private long encodeConsoleProjector(VirtualFile projector, OutputStream out) throws IOException
{
if (data == null || data.units == null || data.app == null)
{
return 0;
}
// output EXE
ByteArrayOutputStream baos = new ByteArrayOutputStream();
CompilerAPI.encode(data.app, baos);
return Mxmlc.createProjector(data.configuration, projector, baos, out);
}
/**
*
* @param cleanData
* @param cleanCache
* @param cleanOutput
* @param cleanConfig
* @param cleanMessages
* @param cleanThreadLocals
*/
private void clean(boolean cleanData, boolean cleanCache, boolean cleanOutput,
boolean cleanConfig, boolean cleanMessages, boolean cleanThreadLocals)
{
if (cleanThreadLocals)
{
OEMUtil.clean();
}
if (oemConfiguration != null && cleanConfig)
{
oemConfiguration.reset();
}
if (cleanData)
{
data = null;
configurationReport = null;
}
if (cleanCache)
{
if (cacheName != null)
{
File dead = FileUtil.openFile(cacheName);
if (dead != null && dead.exists())
{
dead.delete();
}
cacheName = null;
}
}
if (cleanOutput)
{
if (output != null && output.exists())
{
output.delete();
}
}
if (cleanMessages)
{
messages.clear();
}
}
/**
*
* @param localOEMConfiguration
* @return
*/
private String[] constructCommandLine(OEMConfiguration localOEMConfiguration)
{
String[] options = (localOEMConfiguration != null) ? localOEMConfiguration.getCompilerOptions() : new String[0];
String[] args = new String[options.length + files.size() + 1];
System.arraycopy(options, 0, args, 0, options.length);
args[options.length] = "--" + Mxmlc.FILE_SPECS;
for (int i = 0, size = files.size(); i < size; i++)
{
args[options.length + 1 + i] = files.get(i).getName();
}
return args;
}
/**
* Returns the cache of sources in the source list and source
* path. After building this Application object, the cache may be
* used to compile another Application object with common sources.
*
* @return The active cache. May be null.
*
* @since 4.5
*/
public ApplicationCache getApplicationCache()
{
return applicationCache;
}
/**
* Sets the cache for sources in the source list and source path.
* After compiling an Application object, the cache may be reused
* to build another Application object with common sources.
*
* @param applicationCache A reference to the application cache.
*
* @since 4.5
*/
public void setApplicationCache(ApplicationCache applicationCache)
{
this.applicationCache = applicationCache;
}
// TODO: deprecate getSwcCache() and setSwcCache(), then add
// getLibraryCache() and setLibraryCache().
/**
* Get the cache of swcs in the library path. After building this Application
* object the cache may be saved and used to compile another Application object
* that uses the same library path.
*
* @return The active cache. May be null.
*
* @since 3.0
*/
public LibraryCache getSwcCache()
{
return libraryCache;
}
/**
* Set the cache for swcs in the library path. After compiling an
* Application object the cache may be reused to build another Application
* object that uses the same library path.
*
* @param swcCache A reference to an allocated swc cache.
*
* @since 3.0
*/
public void setSwcCache(LibraryCache libraryCache)
{
this.libraryCache = libraryCache;
}
}