/* * * 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; } }