blob: 5c5cfe541fbeca156b2ffd7b1980abecd8a63083 [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 flex2.tools.oem;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.flex.compiler.clients.MXMLC;
import org.apache.flex.compiler.clients.problems.ProblemFormatter;
import org.apache.flex.compiler.clients.problems.ProblemQuery;
import org.apache.flex.compiler.problems.CompilerProblemSeverity;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.problems.annotations.DefaultSeverity;
import org.apache.flex.swf.ISWF;
import org.apache.flex.swf.types.RGB;
import flash.swf.tags.SetBackgroundColor;
import flex2.compiler.Source;
import flex2.compiler.SourceList;
import flex2.compiler.io.FileUtil;
import flex2.compiler.io.LocalFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.util.Benchmark;
import flex2.compiler.util.CompilerControl;
import flex2.compiler.util.CompilerMessage;
import flex2.compiler.util.MimeMappings;
import flex2.compiler.util.PerformanceData;
import flex2.compiler.util.ThreadLocalToolkit;
import flex2.linker.SimpleMovie;
import flex2.tools.ToolsConfiguration;
import flex2.tools.oem.internal.OEMConfiguration;
import flex2.tools.oem.internal.OEMReport;
import flex2.tools.oem.internal.OEMUtil;
/**
* 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 (Exception 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 (Exception 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
*/
@SuppressWarnings("unchecked")
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 String cacheName, configurationReport;
private List<Message> messages;
/**
* @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 null;
}
/**
* @inheritDoc
*/
public Benchmark getBenchmark()
{
return null;
}
/**
* @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
{
@SuppressWarnings("unused")
int result = compile(incremental);
return size;
}
catch (Exception e)
{
ThreadLocalToolkit.logError(e.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) {} }
//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
{
@SuppressWarnings("unused")
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
{
clean(false /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
true /* cleanThreadLocals */);
}
}
/**
* @inheritDoc
*/
public void stop()
{
cc.stop();
}
/**
* @inheritDoc
*/
public void clean()
{
}
/**
* @inheritDoc
*/
public void load(InputStream in) throws IOException
{
}
/**
* @inheritDoc
*/
public long save(OutputStream out) throws IOException
{
return 1;
}
/**
* @inheritDoc
*/
public Report getReport()
{
//OEMUtil.setupLocalizationManager();
return new OEMReport( sources,
movie,
null,
sourceList,
configurationReport,
messages);
}
/**
* 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);
}
if (oemConfiguration != null)
{
oemConfiguration.cfgbuf = tempOEMConfiguration.cfgbuf;
}
// initialize some ThreadLocal variables...
cc.run();
OEMUtil.init(OEMUtil.getLogger(logger, messages), mimeMappings, meter, resolver, cc);
//Map licenseMap = OEMUtil.getLicenseMap(tempOEMConfiguration.configuration);
mxmlc = new MXMLC();
//int returnValue = mxmlc.mainCompileOnly(constructCommandLine2(tempOEMConfiguration.configuration), null);
int returnValue = mxmlc.mainCompileOnly(constructCommandLine(oemConfiguration), null);
if (returnValue == 0)
returnValue = OK;
else
returnValue = FAIL;
processMXMLCReport(mxmlc, tempOEMConfiguration);
clean(returnValue == FAIL /* cleanData */,
false /* cleanCache */,
false /* cleanOutput */,
true /* cleanConfig */,
false /* cleanMessages */,
false /* cleanThreadLocals */);
return returnValue;
}
finally
{
// clean thread locals
OEMUtil.clean();
}
}
public long link(OutputStream output)
{
return mxmlc.writeSWF(output);
}
private MXMLC mxmlc = new MXMLC();
private List<Source> sources;
private SimpleMovie movie;
private SourceList sourceList;
void processMXMLCReport(MXMLC mxmlc, OEMConfiguration config)
{
/* not sure we need this
ApplicationCompilerConfiguration acc = ((ApplicationCompilerConfiguration)config.configuration);
sources = new ArrayList<Source>();
VirtualFile[] sourcePaths = acc.getCompilerConfiguration().getSourcePath();
List<String> sourceFiles = mxmlc.getSourceList();
String mainFile = mxmlc.getMainSource();
for (String sourceFile : sourceFiles)
{
for (VirtualFile sourcePath : sourcePaths)
{
String pathName = sourcePath.getName();
if (sourceFile.indexOf(pathName) == 0)
{
String relPath = sourceFile.substring(pathName.length());
int lastSep = relPath.lastIndexOf(File.separator);
String shortName = relPath.substring(lastSep + 1);
relPath = relPath.substring(0, lastSep);
boolean isRoot = sourceFile.equals(mainFile);
Source source = new Source(sourcePath, relPath, shortName, null, false, isRoot);
sources.add(source);
break;
}
}
}
*/
ProblemQuery pq = mxmlc.getProblems();
List<ICompilerProblem> probs = pq.getProblems();
for (ICompilerProblem prob : probs)
{
Class aClass = prob.getClass();
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof DefaultSeverity){
DefaultSeverity myAnnotation = (DefaultSeverity) annotation;
CompilerProblemSeverity cps = myAnnotation.value();
String level;
if (cps.equals(CompilerProblemSeverity.ERROR))
level = Message.ERROR;
else if (cps.equals(CompilerProblemSeverity.WARNING))
level = Message.WARNING;
else
break; // skip if IGNORE?
CompilerMessage msg = new CompilerMessage(level,
prob.getSourcePath(),
prob.getLine() + 1,
prob.getColumn());
try
{
String errText = ProblemFormatter.DEFAULT_FORMATTER.format(prob);
msg.setMessage(errText);
}
catch (IllegalArgumentException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (SecurityException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
messages.add(msg);
try
{
logger.log(msg, aClass.getField("errorCode").getInt(null), prob.getSourcePath());
}
catch (IllegalArgumentException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (SecurityException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchFieldException e)
{
try
{
logger.log(msg, aClass.getField("warningCode").getInt(null), prob.getSourcePath());
}
catch (IllegalArgumentException e1)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (SecurityException e1)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IllegalAccessException e1)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (NoSuchFieldException e1)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
ISWF swf = mxmlc.getSWFTarget();
movie = new SimpleMovie(null);
org.apache.flex.swf.types.Rect r = swf.getFrameSize();
flash.swf.types.Rect fr = new flash.swf.types.Rect();
fr.xMin = r.xMin();
fr.yMin = r.yMin();
fr.xMax = r.xMax();
fr.yMax = r.yMax();
movie.size = fr;
RGB bg = swf.getBackgroundColor();
int red = bg.getRed();
red = red << 16;
int green = bg.getGreen();
green = green << 8;
int blue = bg.getBlue();
movie.bgcolor = new SetBackgroundColor(red + green + blue);
movie.topLevelClass = swf.getTopLevelClass();
}
/**
*
* @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)
{
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] = "--file-specs";// + Mxmlc.FILE_SPECS;
for (int i = 0, size = files.size(); i < size; i++)
{
args[options.length + 1 + i] = files.get(i).getName();
}
return args;
}
/**
*
* @param localOEMConfiguration
* @return
*/
private String[] constructCommandLine2(ToolsConfiguration localToolsConfiguration)
{
String[] options = (localToolsConfiguration != null) ? processToolsConfig(localToolsConfiguration): new String[0];
String[] args = new String[options.length + files.size() + 1];
System.arraycopy(options, 0, args, 0, options.length);
args[options.length] = "--file-specs";// + Mxmlc.FILE_SPECS;
for (int i = 0, size = files.size(); i < size; i++)
{
args[options.length + 1 + i] = files.get(i).getName();
}
return args;
}
private String[] processToolsConfig(ToolsConfiguration tc)
{
String[] results = new String[1];
results[0] = "-debug=" + (tc.debug() ? "true" : "false");
return results;
}
/**
* 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;
}
}