/*
*
* 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.internal;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import flash.localization.LocalizationManager;
import flash.localization.ResourceBundleLocalizer;
import flash.util.FileUtils;
import flash.util.Trace;
import flex2.compiler.CompilerAPI;
import flex2.compiler.CompilationUnit;
import flex2.compiler.CompilerSwcContext;
import flex2.compiler.Source;
import flex2.compiler.common.CompilerConfiguration;
import flex2.compiler.common.DefaultsConfigurator;
import flex2.compiler.config.CommandLineConfigurator;
import flex2.compiler.config.ConfigurationBuffer;
import flex2.compiler.config.ConfigurationException;
import flex2.compiler.config.FileConfigurator;
import flex2.compiler.config.SystemPropertyConfigurator;
import flex2.compiler.io.FileUtil;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.swc.Swc;
import flex2.compiler.swc.SwcScript;
import flex2.compiler.util.CompilerControl;
import flex2.compiler.util.MimeMappings;
import flex2.compiler.util.QName;
import flex2.compiler.util.ThreadLocalToolkit;
import flex2.tools.CommandLineConfiguration;
import flex2.tools.CompcConfiguration;
import flex2.tools.Mxmlc;
import flex2.tools.ToolsConfiguration;
import flex2.tools.oem.*;
/**
* A collection of utility methods used by classes in flex2.tools.oem.
*
* @version 2.0.1
* @author Clement Wong
*/
public class OEMUtil
{
/**
*
*/
public static final LocalizationManager setupLocalizationManager()
{
LocalizationManager l10n = ThreadLocalToolkit.getLocalizationManager();
if (l10n == null)
{
// set up for localizing messages
l10n = new LocalizationManager();
l10n.addLocalizer(new ResourceBundleLocalizer());
ThreadLocalToolkit.setLocalizationManager(l10n);
}
return l10n;
}
/**
*
* @param logger
* @param mimeMappings
*/
public static final void init(Logger logger,
MimeMappings mimeMappings,
ProgressMeter meter,
PathResolver resolver,
CompilerControl cc)
{
CompilerAPI.useAS3();
CompilerAPI.usePathResolver(resolver != null ? new OEMPathResolver(resolver) : null);
setupLocalizationManager();
ThreadLocalToolkit.setLogger(new OEMLogAdapter(logger));
ThreadLocalToolkit.setMimeMappings(mimeMappings);
ThreadLocalToolkit.setProgressMeter(meter);
ThreadLocalToolkit.setCompilerControl(cc);
}
/**
*
*
*/
public static final void clean()
{
CompilerAPI.removePathResolver();
ThreadLocalToolkit.setLogger(null);
ThreadLocalToolkit.setLocalizationManager(null);
ThreadLocalToolkit.setMimeMappings(null);
ThreadLocalToolkit.setProgressMeter(null);
ThreadLocalToolkit.setCompilerControl(null);
ThreadLocalToolkit.setCompatibilityVersion(null);
}
/**
*
* @param in
* @return
* @throws IOException
*/
public static final String load(InputStream in, String cacheName) throws IOException
{
// grab the data and put it in a temp file...
File temp = null;
if (cacheName == null)
{
temp = FileUtil.createTempFile(in);
temp.deleteOnExit();
}
else
{
temp = FileUtil.openFile(cacheName);
FileUtil.writeBinaryFile(temp, in);
}
return FileUtil.getCanonicalPath(temp);
}
/**
*
* @param out
* @param cacheName
* @param data
* @return
* @throws IOException
*/
public static final long save(OutputStream out, String cacheName, ApplicationData data) throws IOException
{
if (cacheName == null)
{
// this will create a temp file and populate cacheName.
cacheName = load(new ByteArrayInputStream(new byte[0]), null);
}
// only save when there is something for us to save (i.e. data != null)
if (cacheName != null && data != null)
{
// delete the existing cache file
File dead = FileUtil.openFile(cacheName);
if (dead != null && dead.exists())
{
dead.delete();
}
RandomAccessFile cacheFile = null;
try
{
cacheFile = new RandomAccessFile(cacheName, "rw");
CompilerAPI.persistCompilationUnits(data.configuration, data.fileSpec, data.sourceList,
data.sourcePath, data.resources, data.bundlePath,
data.sources, data.units, data.checksum,
data.cmdChecksum, data.linkChecksum, data.swcChecksum,
data.swcDefSignatureChecksums, data.swcFileChecksums,
"", cacheFile);
}
finally
{
if (cacheFile != null) { try { cacheFile.close(); } catch (IOException ex) {} }
}
// write the data to the specified output stream
InputStream in = null;
try
{
in = new BufferedInputStream(new FileInputStream(cacheName));
FileUtil.streamOutput(in, out);
}
finally
{
if (in != null) { try { in.close(); } catch (IOException ex) {} }
}
return new File(cacheName).length();
}
else
{
return 0;
}
}
/**
*
* @param args
* @param logger
* @param mimeMappings
* @return
*/
public static final OEMConfiguration getApplicationConfiguration(String[] args, boolean keepLinkReport,
boolean keepSizeReport, Logger logger, PathResolver resolver,
MimeMappings mimeMappings)
{
return getApplicationConfiguration(args, keepLinkReport, keepSizeReport, logger, resolver, mimeMappings, true);
}
/**
*
* @param args
* @param logger
* @param mimeMappings
* @param processDefaults
* @return
*/
public static final OEMConfiguration getApplicationConfiguration(String[] args, boolean keepLinkReport, boolean keepSizeReport,
Logger logger, PathResolver resolver, MimeMappings mimeMappings,
boolean processDefaults)
{
if (!processDefaults)
{
return new OEMConfiguration(null, null);
}
// expect args to have --file-specs because we need it to find app-specific-config.xml.
OEMUtil.init(logger, mimeMappings, null, resolver, null);
try
{
ConfigurationBuffer cfgbuf = new ConfigurationBuffer(ApplicationCompilerConfiguration.class,
ApplicationCompilerConfiguration.getAliases());
cfgbuf.setDefaultVar(Mxmlc.FILE_SPECS);
DefaultsConfigurator.loadDefaults(cfgbuf);
Object obj = Mxmlc.processConfiguration(ThreadLocalToolkit.getLocalizationManager(),
"oem",
args,
cfgbuf,
ApplicationCompilerConfiguration.class,
Mxmlc.FILE_SPECS);
ApplicationCompilerConfiguration configuration = (ApplicationCompilerConfiguration) obj;
configuration.keepLinkReport(keepLinkReport);
configuration.keepSizeReport(keepSizeReport);
return new OEMConfiguration(cfgbuf, configuration);
}
catch (ConfigurationException ex)
{
Mxmlc.processConfigurationException(ex, "oem");
return null;
}
catch (flex.messaging.config.ConfigurationException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
catch (IOException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
}
/**
*
* @param args
* @param logger
* @param mimeMappings
* @return
*/
public static final OEMConfiguration getLibraryConfiguration(String[] args, boolean keepLinkReport,
boolean keepSizeReport, Logger logger, PathResolver resolver,
MimeMappings mimeMappings)
{
return getLibraryConfiguration(args, keepLinkReport, keepSizeReport, logger, resolver, mimeMappings, true);
}
/**
*
* @param args
* @param logger
* @param mimeMappings
* @param processDefaults
* @return
*/
public static final OEMConfiguration getLibraryConfiguration(String[] args, boolean keepLinkReport,
boolean keepSizeReport, Logger logger,
PathResolver resolver, MimeMappings mimeMappings,
boolean processDefaults)
{
if (!processDefaults)
{
return new OEMConfiguration(null, null);
}
// expect no SWC inputs in args.
OEMUtil.init(logger, mimeMappings, null, resolver, null);
try
{
ConfigurationBuffer cfgbuf = new ConfigurationBuffer(LibraryCompilerConfiguration.class,
LibraryCompilerConfiguration.getAliases());
DefaultsConfigurator.loadOEMCompcDefaults( cfgbuf );
Object obj = Mxmlc.processConfiguration(ThreadLocalToolkit.getLocalizationManager(),
"oem",
args,
cfgbuf,
LibraryCompilerConfiguration.class,
null);
LibraryCompilerConfiguration configuration = (LibraryCompilerConfiguration) obj;
configuration.keepLinkReport(keepLinkReport);
configuration.keepSizeReport(keepSizeReport);
return new OEMConfiguration(cfgbuf, configuration);
}
catch (ConfigurationException ex)
{
Mxmlc.processConfigurationException(ex, "oem");
return null;
}
catch (flex.messaging.config.ConfigurationException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
catch (IOException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
}
/**
*
* @param args
* @param logger
* @param mimeMappings
* @return
*/
public static final OEMConfiguration getLinkerConfiguration(String[] args, boolean keepLinkReport, boolean keepSizeReport,
Logger logger, MimeMappings mimeMappings,
PathResolver resolver,
flex2.compiler.common.Configuration c,
Set newLinkerOptions, Set<String> includes, Set<String> excludes)
{
OEMUtil.init(logger, mimeMappings, null, resolver, null);
try
{
ConfigurationBuffer cfgbuf = new ConfigurationBuffer(LinkerConfiguration.class,
LinkerConfiguration.getAliases());
DefaultsConfigurator.loadDefaults(cfgbuf);
Object obj = Mxmlc.processConfiguration(ThreadLocalToolkit.getLocalizationManager(),
"oem",
args,
cfgbuf,
LinkerConfiguration.class,
null);
LinkerConfiguration configuration = (LinkerConfiguration) obj;
configuration.setOriginalConfiguration(c, newLinkerOptions, includes, excludes);
configuration.keepLinkReport(keepLinkReport);
configuration.keepSizeReport(keepSizeReport);
return new OEMConfiguration(cfgbuf, configuration);
}
catch (ConfigurationException ex)
{
Mxmlc.processConfigurationException(ex, "oem");
return null;
}
catch (flex.messaging.config.ConfigurationException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
catch (IOException ex)
{
ThreadLocalToolkit.logError(ex.getMessage());
return null;
}
}
public static final ConfigurationBuffer getCommandLineConfigurationBuffer(Logger logger, PathResolver resolver, String[] args)
{
ConfigurationBuffer cfgbuf = null;
try
{
OEMUtil.init(logger, null, null, resolver, null);
cfgbuf = new ConfigurationBuffer(CommandLineConfiguration.class, CommandLineConfiguration.getAliases());
SystemPropertyConfigurator.load( cfgbuf, "flex" );
CommandLineConfigurator.parse( cfgbuf, null, args);
}
catch (ConfigurationException ex)
{
ThreadLocalToolkit.log(ex);
cfgbuf = null;
}
return cfgbuf;
}
public static final ConfigurationBuffer getCompcConfigurationBuffer(Logger logger, PathResolver resolver, String[] args)
{
ConfigurationBuffer cfgbuf = null;
try
{
OEMUtil.init(logger, null, null, resolver, null);
cfgbuf = new ConfigurationBuffer(CompcConfiguration.class, CompcConfiguration.getAliases());
SystemPropertyConfigurator.load( cfgbuf, "flex" );
CommandLineConfigurator.parse( cfgbuf, null, args);
}
catch (ConfigurationException ex)
{
ThreadLocalToolkit.log(ex);
cfgbuf = null;
}
return cfgbuf;
}
public static String[] trim(String[] args, ConfigurationBuffer cfgbuf, Set excludes)
{
List<Object[]> positions = cfgbuf.getPositions();
List<Object> newArgs = new ArrayList<Object>();
for (int i = 0, length = positions.size(); i < length; i++)
{
Object[] a = positions.get(i);
String var = (String) a[0];
int iStart = ((Integer) a[1]).intValue(), iEnd = ((Integer) a[2]).intValue();
if (!excludes.contains(var))
{
for (int j = iStart; j < iEnd; j++)
{
newArgs.add(args[j]);
}
}
}
args = new String[newArgs.size()];
newArgs.toArray(args);
return args;
}
public static final Logger getLogger(Logger logger, List<Message> messages)
{
return new BuilderLogger(logger == null ? new OEMConsole() : logger, messages);
}
public static final String formatConfigurationBuffer(ConfigurationBuffer cfgbuf)
{
return FileConfigurator.formatBuffer(cfgbuf, "flex-config",
OEMUtil.setupLocalizationManager(), "flex2.configuration");
}
/**
*
* @param configuration
* @return
*/
public static final Map getLicenseMap(ToolsConfiguration configuration)
{
return configuration.getLicensesConfiguration().getLicenseMap();
}
public static final void setGeneratedDirectory(CompilerConfiguration compilerConfig, File output)
{
if (compilerConfig.keepGeneratedActionScript())
{
File canonical = FileUtils.canonicalFile( output );
if (canonical != null)
output = canonical;
String parent = output.getParent();
String generated = null;
if (parent == null)
{
generated = new File( "generated" ).getAbsolutePath();
}
else
{
generated = FileUtils.addPathComponents( parent, "generated", File.separatorChar );
}
compilerConfig.setGeneratedDirectory( generated );
new File( generated ).mkdirs();
}
}
/**
* Save the compiler units signature checksums to ApplicationData.
* These will be used by the next compilation to determine if we
* can do an incremental compiler or if a full recompilation is required.
*
* @param units - compilation units, may be null
* @param data - application data, may not be null
*/
public static void saveSignatureChecksums(List units, ApplicationData data,
flex2.compiler.common.Configuration config)
{
if (!config.isSwcChecksumEnabled())
{
data.swcDefSignatureChecksums = null;
return;
}
if (units != null)
{
data.swcDefSignatureChecksums = new HashMap<QName, Long>();
for (Iterator iter = units.iterator(); iter.hasNext();)
{
CompilationUnit unit = (CompilationUnit)iter.next();
Source source = unit == null ? null : unit.getSource();
if (source != null && source.isSwcScriptOwner() && !source.isInternal())
{
addSignatureChecksumToData(data, unit);
}
}
}
}
/**
* @param swcContext context, may not be null
* @param data - application data, may not be null
*/
public static void saveSwcFileChecksums(CompilerSwcContext swcContext, ApplicationData data,
flex2.compiler.common.Configuration config)
{
if (!config.isSwcChecksumEnabled())
{
data.swcFileChecksums = null;
return;
}
data.swcFileChecksums = new HashMap<String, Long>();
for (Iterator iter = swcContext.getFiles().entrySet().iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry)iter.next();
String filename = (String)entry.getKey();
VirtualFile file = (VirtualFile)entry.getValue();
data.swcFileChecksums.put(filename, new Long(file.getLastModified()));
}
for (VirtualFile themeStyleSheet : swcContext.getThemeStyleSheets())
{
data.swcFileChecksums.put(themeStyleSheet.getName(),
new Long(themeStyleSheet.getLastModified()));
}
}
/**
* Loop thru the saved signature checksums in the application data and compare them with
* the signature checksums in the swc context.
*
* @param data application data from a previous compile. May not be null.
* @param swcContext swc context
* @return true if all the signature checksums in application data match the checksums
* in the swc context.
*/
public static boolean isRecompilationNeeded(ApplicationData data,
CompilerSwcContext swcContext,
OEMConfiguration config)
{
int checksum = OEMUtil.calculateChecksum(data, swcContext, config);
// if the checksum from last time and the current checksum do not match,
// then we need to recompile.
if (checksum != data.checksum)
{
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: calculated checksum differs from last checksum, recompile");
}
data.checksum = checksum;
return true;
}
// if we got here and swc checksums are disabled, then
// the checksums are equal and a recompilation is not needed.
// Otherwise continue on and compare the swc checksums.
if (!config.configuration.isSwcChecksumEnabled())
{
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: checksums equal, swc-checksum disabled, incremental compile");
}
return false;
}
Map signatureChecksums = data.swcDefSignatureChecksums;
if (signatureChecksums == null)
{
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: checksums equal, signatureChecksums is null, incremental compile");
}
}
else
{
for (Iterator iter = signatureChecksums.entrySet().iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry)iter.next();
// lookup definition in swc context
QName qName = (QName) entry.getKey();
Long dataSignatureChecksum = (Long)entry.getValue();
Long swcSignatureChecksum = swcContext.getChecksum(qName);
if (swcSignatureChecksum == null && qName != null)
{
Source source = swcContext.getSource(qName.getNamespace(), qName.getLocalPart());
if (source != null)
{
swcSignatureChecksum = new Long(source.getLastModified());
}
}
if (Trace.swcChecksum)
{
if (dataSignatureChecksum == null)
{
throw new IllegalStateException("dataSignatureChecksum should never be null");
}
}
if (dataSignatureChecksum != null && swcSignatureChecksum == null)
{
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: signature checksums not equal, recompile");
Trace.trace("compare " + entry.getKey());
Trace.trace("data = " + dataSignatureChecksum);
Trace.trace("swc = " + swcSignatureChecksum);
}
return true;
}
if (dataSignatureChecksum != null)
{
if (dataSignatureChecksum.longValue() != swcSignatureChecksum.longValue())
{
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: signature checksums not equal, recompile");
Trace.trace("compare " + entry.getKey());
Trace.trace("data = " + dataSignatureChecksum);
Trace.trace("swc = " + swcSignatureChecksum);
}
return true;
}
}
else {
// dataSignatureChecksum should never be null, but if it is then recompile.
return true;
}
}
}
boolean result = !areSwcFileChecksumsEqual(data, swcContext);
if (Trace.swcChecksum)
{
Trace.trace("isRecompilationNeeded: " + (result ? "recompile" : "incremental compile"));
}
return result;
}
/**
* Calculate the data checksum.
*
* @param data application data from a previous compile. If a recompilation is
* needed, data.checksum is updated with the new checksum. May not be null.
* @param swcContext swc context
* @return true if all the signature checksums in application data match the checksums
* in the swc context.
*/
public static int calculateChecksum(ApplicationData data,
CompilerSwcContext swcContext,
OEMConfiguration config)
{
int checksum = config.cfgbuf.checksum_ts();
// if swc checksums are disabled or there are no checksums to compare, then
// include the swc timestamp as part of the checksum.
if (!config.configuration.isSwcChecksumEnabled() ||
data.swcDefSignatureChecksums == null ||
data.swcDefSignatureChecksums.size() == 0)
{
checksum += swcContext.checksum();
}
return checksum;
}
/**
* Add all top level definitions to ApplicationData
* @param swcDefSignatureChecksums
* @param unit
* @param signatureChecksum
*/
private static void addSignatureChecksumToData(ApplicationData data, CompilationUnit unit)
{
Long signatureChecksum = unit.getSignatureChecksum();
if (signatureChecksum == null)
{
SwcScript script = (SwcScript) unit.getSource().getOwner();
signatureChecksum = new Long(script.getLastModified());
}
if (data.swcDefSignatureChecksums != null)
{
for (Iterator iter = unit.topLevelDefinitions.iterator(); iter.hasNext();)
{
QName qname = (QName) iter.next();
data.swcDefSignatureChecksums.put(qname, signatureChecksum);
}
}
}
/**
* Test if the files in the compiler swc context have changed since the last compile.
*
* @param data - application data, may not be null
* @param swcContext context, may not be null
* @return true it the swc files compiled with last time are the same as this time.
*/
private static boolean areSwcFileChecksumsEqual(ApplicationData data,
CompilerSwcContext swcContext)
{
if (data.swcFileChecksums == null)
{
if (Trace.swcChecksum)
{
Trace.trace("areSwcFileChecksumsEqual: no file checksum map, not equal");
}
return false;
}
Map swcFiles = swcContext.getFiles();
for (VirtualFile themeStyleSheet : swcContext.getThemeStyleSheets())
{
swcFiles.put(themeStyleSheet.getName(), themeStyleSheet);
}
Set dataSet = data.swcFileChecksums.entrySet();
if (swcFiles.size() < dataSet.size())
{
if (Trace.swcChecksum)
{
Trace.trace("areSwcFileChecksumsEqual: less files than before, not equal");
}
return false;
}
for (Iterator iter = dataSet.iterator(); iter.hasNext();)
{
Map.Entry entry = (Map.Entry)iter.next();
String filename = (String)entry.getKey();
// When we are reusing cached SWC's, the catalog.xml and
// library.swf are updated each time we save the
// SwcDynamicArchive, but we don't want to reload the SWC
// from disk, because all the compilation units
// represented by catalog.xml should be cached. If any of
// the cached CompilationUnit's becomes out of data,
// CompilerAPI.validateCompilationUnits() will handle
// removing the cached CompilationUnit, which will cause
// it to be reloaded from disk.
if (!filename.equals(Swc.CATALOG_XML) &&
!filename.equals(Swc.LIBRARY_SWF))
{
Long dataFileLastModified = (Long)entry.getValue();
Long swcFileLastModified = null;
VirtualFile swcFile = (VirtualFile)swcFiles.get(filename);
if (swcFile != null)
{
swcFileLastModified = new Long(swcFile.getLastModified());
}
if (!dataFileLastModified.equals(swcFileLastModified))
{
if (Trace.swcChecksum)
{
Trace.trace("areSwcFileChecksumsEqual: not equal");
Trace.trace("filename = " + filename);
Trace.trace("last modified1 = " + dataFileLastModified);
Trace.trace("last modified2 = " + swcFileLastModified);
}
return false;
}
}
}
if (Trace.swcChecksum)
{
Trace.trace("areSwcFileChecksumsEqual: equal");
}
return true;
}
}