blob: f074cb9c6190aa05d9d93b2ac13ca45938bd8a72 [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.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import macromedia.asc.util.ContextStatics;
import flash.localization.LocalizationManager;
import flash.swf.Frame;
import flash.swf.Movie;
import flash.swf.MovieDecoder;
import flash.swf.MovieEncoder;
import flash.swf.TagDecoder;
import flash.swf.TagEncoder;
import flash.swf.tags.DefineTag;
import flash.util.Trace;
import flex2.compiler.CompilerSwcContext;
import flex2.compiler.io.LocalFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.swc.SwcCache;
import flex2.compiler.util.MimeMappings;
import flex2.compiler.util.NameMappings;
import flex2.compiler.util.OrderedProperties;
import flex2.compiler.util.QName;
import flex2.compiler.util.SwcDependencyInfo;
import flex2.compiler.util.SwcDependencyUtil;
import flex2.compiler.util.SwcExternalScriptInfo;
import flex2.compiler.util.ThreadLocalToolkit;
import flex2.compiler.util.Benchmark.MemoryUsage;
import flex2.compiler.util.graph.Vertex;
import flex2.tools.oem.OEMException.CircularLibraryDependencyException;
import flex2.tools.oem.internal.OEMUtil;
/**
* A utility class, which supports querying for Application, Library,
* Component, and Script information, loading properties, optimizing,
* and querying dependency info.
* @version 3.0
*/
public class Toolkit
{
/**
*
* @param application
* @return
*/
public static ApplicationInfo getApplicationInfo(File application)
{
InputStream in = null;
ApplicationInfo info = null;
try
{
in = new BufferedInputStream(new FileInputStream(application));
Movie movie = new Movie();
new TagDecoder(in).parse(new MovieDecoder(movie));
info = new ApplicationInfoImpl(movie);
}
catch (IOException ex)
{
if (Trace.error)
{
ex.printStackTrace();
}
}
finally
{
try { if (in != null) in.close(); } catch (IOException ex) {}
}
return info;
}
/**
*
* @param library
* @return
*/
public static LibraryInfo getLibraryInfo(File library)
{
return getLibraryInfo(new File[] { library });
}
/**
*
* @param libraries
* @return
*/
public static LibraryInfo getLibraryInfo(File[] libraries)
{
return getLibraryInfo(libraries, false);
}
/**
*
* @param libraries
* @param includeBytecodes
* @return
*/
public static LibraryInfo getLibraryInfo(File[] libraries, boolean includeBytecodes)
{
LibraryInfo info = null;
try
{
OEMUtil.init(OEMUtil.getLogger(null, new ArrayList<Message>()), new MimeMappings(), null, null, null);
CompilerSwcContext swcContext = new CompilerSwcContext();
SwcCache cache = new SwcCache();
swcContext.load(toVirtualFiles(libraries),
new NameMappings(),
".properties",
cache);
info = new LibraryInfoImpl(swcContext, includeBytecodes);
swcContext.close();
}
catch (Throwable t)
{
if (Trace.error)
{
t.printStackTrace();
}
}
finally
{
OEMUtil.clean();
}
return info;
}
/**
* Converts a list of File(s) into a list of VirtualFile(s).
* The VirtualFile implementation is flex2.compiler.io.LocalFile.
*
* @param files
* @return
*/
private static VirtualFile[] toVirtualFiles(File[] files)
{
if (files == null) return null;
List<VirtualFile> vFiles = new ArrayList<VirtualFile>(files.length);
for (int i = 0; i < files.length; i++)
{
if (files[i] != null)
vFiles.add(new LocalFile(files[i]));
}
return vFiles.toArray(new VirtualFile[vFiles.size()]);
}
/**
* Creates a <code>java.util.Properties</code> object from an <code>UTF-8</code> encoded input stream.
*
* @param in <code>java.io.InputStream</code>
* @return an instance of <code>java.util.Properties</code>;
* <code>null</code> if <code>IOException</code> occurs.
*/
public static Properties loadProperties(InputStream in)
{
return loadProperties(in, "UTF-8");
}
/**
* Creates a <code>java.util.Properties</code> object from an <code>UTF-8</code> encoded .properties file.
*
* @param f an <code>UTF-8</code> encoded .properties file
* @return an instance of <code>java.util.Properties</code>;
* <code>null</code> if the file doesn't exist or if <code>IOException</code> occurs.
*/
public static Properties loadProperties(File f)
{
return loadProperties(f, "UTF-8");
}
/**
* Creates a <code>java.util.Properties</code> object from an <code>UTF-8</code> encoded .properties file.
*
* @param f an <code>UTF-8</code> encoded .properties file
* @param encoding character encoding
* @return an instance of <code>java.util.Properties</code>;
* <code>null</code> if the file doesn't exist or if <code>IOException</code> occurs.
*/
public static Properties loadProperties(File f, String encoding)
{
if (f != null && f.isFile())
{
try
{
return loadProperties(new FileInputStream(f), encoding);
}
catch (IOException ex)
{
return null;
}
}
else
{
return null;
}
}
private static Properties loadProperties(InputStream in, String encoding)
{
if (in != null)
{
try
{
OrderedProperties p = new OrderedProperties();
p.load(new BufferedReader(new InputStreamReader(in, encoding)));
return p;
}
catch (IOException ex)
{
return null;
}
}
else
{
return null;
}
}
/**
* Optimizes a SWF. This operation performs the following:
*
* <pre>
* 1. remove debug tags and opcodes
* 2. merge abc bytecodes
* 3. peephole optimization
* 4. remove unwanted metadata
* </pre>
*
* @param in a SWF input stream
* @param out a SWF output stream
* @return the number of bytes written to the output stream; <code>0</code> if the optimization fails.
*/
public static long optimize(InputStream in, OutputStream out)
{
try
{
return flex2.tools.WebTierAPI.optimize(in, out);
}
catch (IOException ex)
{
return 0;
}
}
/**
* Optimizes the library SWF. This operation performs the following:
*
* <pre>
* 1. remove debug tags and opcodes
* 2. merge abc bytecodes
* 3. peephole optimization
* 4. remove unwanted metadata, but preserve the metadata specified in the Library object
* </pre>
*
* This operation returns an optimized version of the library SWF. The SWF in the library
* remains unchanged.
*
* @param in a SWF input stream
* @param out a SWF output stream
* @return the number of bytes written to the output stream; <code>0</code> if the optimization fails.
*/
public static long optimize(Library lib, OutputStream out)
{
if (lib == null || lib.data == null || lib.data.movie == null) return 0;
try
{
TagEncoder handler = new TagEncoder();
MovieEncoder encoder = new MovieEncoder(handler);
encoder.export(lib.data.movie);
//TODO PERFORMANCE: A lot of unnecessary recopying here
ByteArrayOutputStream baos = new ByteArrayOutputStream();
handler.writeTo(baos);
return flex2.tools.WebTierAPI.optimize(new ByteArrayInputStream(baos.toByteArray()),
out,
lib.data.configuration);
}
catch (IOException ex)
{
return 0;
}
}
/**
*
*
*/
public static void printMemoryUsage()
{
MemoryUsage mem = new flex2.compiler.util.Benchmark().getMemoryUsageInBytes();
long mbHeapUsed = (mem.heap / 1048576);
long mbNonHeapUsed = (mem.nonHeap / 1048576);
System.out.println("Heap: " + mbHeapUsed + " Non-Heap: " + mbNonHeapUsed);
}
// added for FB code model
/**
* Returns a list filled with namespaces that should be automatically
* opened, based on the current target player, e.g. flash10, AS3.
*
* @param targetPlayerMajorVersion E.g. 9, 10, ...
* @return List<String> containing the namespaces
*/
public static List<String> getRequiredUseNamespaces(int targetPlayerMajorVersion)
{
return ContextStatics.getRequiredUseNamespaces(targetPlayerMajorVersion);
}
/**
* The types of dependency the compiler assigns to a symbol. The possible
* values are as follows:
*
* <ul>
* <li>INHERITANCE
* <li>NAMESPACE
* <li>SIGNATURE
* <li>EXPRESSION
* </ul>
*/
public enum DependencyType {
/**
* The class is used as a base class or is implemented by another
* class.
*/
INHERITANCE ("i"),
/**
* The symbol is a namespace.
*/
NAMESPACE ("n"),
/**
* The symbol is used in a function signature.
*/
SIGNATURE ("s"),
/**
* The symbol is used in a class or function.
*/
EXPRESSION ("e");
private final String dependency;
DependencyType(String dependency)
{
this.dependency = dependency;
}
/**
* @return A string that represents the dependency type.
*/
@Override
public String toString()
{
return dependency;
}
}
/**
* Get the dependency order of a given set of libraries.
*
* @param libraries The set of libraries to find the dependency information for. Each
* File in the list must be a library file or a directory of libraries files.
*
* @return An ordered list of library dependencies. Each String in the
* list is the location of a library in the file system. The first library in the list has no
* dependencies. Each library in the list has at least the same dependencies as its
* predecessor and may be dependent on its predecessor as well.
*/
public static List<String> getDependencyOrder(File[] libraries) throws CircularLibraryDependencyException
{
return getDependencyOrder(libraries, null);
}
/**
* Get the dependency order of a given set of libraries.
*
* @param libraries The set of libraries to find the dependency information for. Each
* File in the list must be a library file or a directory of libraries files.
* @param dependencySet The types of dependencies to consider when
* determining the dependency order. If this parameter is null or an empty set, then all
* dependencies will be considered.
*
* @return An ordered list of library dependencies. Each String in the
* list is the location of a library in the file system. The first library in the list has no
* dependencies. Each library in the list has at least the same dependencies as its
* predecessor and may be dependent on its predecessor as well.
*/
public static List<String> getDependencyOrder(File[] libraries,
EnumSet<DependencyType> dependencySet) throws CircularLibraryDependencyException
{
if (libraries == null)
return Collections.emptyList();
// Convert dependencies from an array of DependencyType to an
// array of String.
String[] stringDependencyTypes = dependencyEnumSetToStringArray(dependencySet);
SwcDependencyInfo info = SwcDependencyUtil.getSwcDependencyInfo(toVirtualFiles(libraries),
stringDependencyTypes,
true);
Set<Vertex<String, SwcExternalScriptInfo>> cycles = info.detectCycles();
if (cycles.size() > 0)
{
LocalizationManager i10n = ThreadLocalToolkit.getLocalizationManager();
if (i10n == null)
{
OEMUtil.setupLocalizationManager();
i10n = ThreadLocalToolkit.getLocalizationManager();
}
String message = i10n.getLocalizedTextString(new CircularLibraryDependencyException(null, null));
throw new CircularLibraryDependencyException(message,
SwcDependencyUtil.SetOfVertexToString(cycles));
}
return info.getSwcDependencyOrder();
}
/**
* Get the set of library dependencies of a given library.
*
* @param libraries The set of libraries need to resolve all the dependencies of the targetLibrary. Each
* File in the list must be a library file or a directory of libraries files.
* @param targetLibrary The libraries to find dependencies for.
* @param minimizeDependencySet If false, all of the libraries dependencies are returned. If true, the external script
* classes are reviewed. If the set of script classes resolved in a libraryA is a subset of the script
* classes resolved in libraryB, then libraryA will be removed as a dependency of targetLibrary.
* @return A set of Strings; where each String is the location of a library in the file system.
*/
public static Set<String> getLibraryDependencies(File[] libraries,
File targetLibrary,
boolean minimizeDependencySet) throws CircularLibraryDependencyException
{
return getLibraryDependencies(libraries, targetLibrary, minimizeDependencySet, null);
}
/**
* Get the set of library dependencies of a given library.
*
* @param libraries The set of libraries need to resolve all the dependencies of the targetLibrary. Each
* File in the list must be a library file or a directory of libraries files.
* @param targetLibrary The libraries to find dependencies for.
* @param minimizeDependencySet If false, all of the libraries dependencies are returned. If true, the external script
* classes are reviewed. If the set of script classes resolved in a libraryA is a subset of the script
* classes resolved in libraryB, then libraryA will be removed as a dependency of targetLibrary.
* @param dependencyTypes The types of dependencies to consider when
* determining the library's dependencies. If this parameter is null or an empty set, then all
* dependencies will be considered.
* @return A set of Strings; where each String is the location of a library in the file system.
*/
public static Set<String> getLibraryDependencies(File[] libraries,
File targetLibrary,
boolean minimizeDependencySet,
EnumSet<DependencyType> dependencySet) throws CircularLibraryDependencyException
{
if (libraries == null || targetLibrary == null)
return Collections.emptySet();
// Convert dependencies from an array of DependencyType to an
// array of String.
String[] stringDependencyTypes = dependencyEnumSetToStringArray(dependencySet);
SwcDependencyInfo info = SwcDependencyUtil.getSwcDependencyInfo(toVirtualFiles(libraries),
stringDependencyTypes,
minimizeDependencySet);
Set<Vertex<String, SwcExternalScriptInfo>> cycles = info.detectCycles();
if (cycles.size() > 0)
{
LocalizationManager i10n = ThreadLocalToolkit.getLocalizationManager();
if (i10n == null)
{
OEMUtil.setupLocalizationManager();
i10n = ThreadLocalToolkit.getLocalizationManager();
}
String message = i10n.getLocalizedTextString(new CircularLibraryDependencyException(null, null));
throw new CircularLibraryDependencyException(message,
SwcDependencyUtil.SetOfVertexToString(cycles));
}
VirtualFile virtualLibrary = new LocalFile(targetLibrary);
return info.getDependencies(virtualLibrary.getName());
}
/**
* Convert an EnumSet of DependencyType to an Array of Strings.
*
* @param dependencySet EnumSet of dependencies to convert.
* @return Array of Strings. Each string in the Array represents a type of
* dependency. Returns null if dependencySet is null or an empty set.
*/
private static String[] dependencyEnumSetToStringArray(EnumSet<DependencyType> dependencySet)
{
// Convert dependencies from an array of DependencyType to an
// array of String.
String[] stringDependencyTypes = null;
if (dependencySet != null && dependencySet.size() > 0)
{
int n = dependencySet.size();
int i = 0;
stringDependencyTypes = new String[n];
for (DependencyType dependency : dependencySet)
{
stringDependencyTypes[i++] = dependency.toString();
}
}
return stringDependencyTypes;
}
}
/**
*
*
*/
class ApplicationInfoImpl implements ApplicationInfo
{
ApplicationInfoImpl(Movie movie)
{
version = movie.version;
List frames = movie.frames;
Set<String> symbols = new TreeSet<String>();
for (int i = 0, size = frames == null ? 0 : frames.size(); i < size; i++)
{
Frame f = (Frame) frames.get(i);
for (Iterator j = f.exportIterator(); j.hasNext(); )
{
DefineTag t = (DefineTag) j.next();
if (t.name != null)
{
symbols.add(t.name);
}
}
}
symbols.toArray(symbolNames = new String[symbols.size()]);
}
private String[] symbolNames;
private int version;
public String[] getSymbolNames()
{
return symbolNames;
}
public int getSWFVersion()
{
return version;
}
}
/**
*
*
*/
class LibraryInfoImpl implements LibraryInfo
{
LibraryInfoImpl(CompilerSwcContext swcContext, boolean includeBytecodes)
{
List<QName> names = new ArrayList<QName>();
for (Iterator i = swcContext.getDefinitionIterator(); i.hasNext(); )
{
names.add((QName) i.next());
}
definitionNames = new String[names.size()];
for (int i = 0; i < definitionNames.length; i++)
{
definitionNames[i] = names.get(i).toString();
}
scripts = new TreeMap<String, Script>();
for (int i = 0; i < definitionNames.length; i++)
{
QName def = names.get(i);
Script s = swcContext.getScript(def, includeBytecodes);
scripts.put(def.toString(), s);
}
components = new TreeMap<String, Component>();
for (Iterator i = swcContext.getComponentIterator(); i.hasNext(); )
{
Component c = (Component) i.next();
components.put(c.getClassName(), c);
}
mappings = swcContext.getNameMappings();
fileNames = new TreeSet<String>(swcContext.getFiles().keySet());
}
private String[] definitionNames;
private Map<String, Script> scripts;
private Map<String, Component> components;
private NameMappings mappings;
private Set<String> fileNames;
public Component getComponent(String namespaceURI, String name)
{
return getComponent(mappings.lookupClassName(namespaceURI, name));
}
public Component getComponent(String definition)
{
return (definition != null) ? components.get(definition) : null;
}
public Iterator<Component> getComponents()
{
return components.values().iterator();
}
public String[] getDefinitionNames()
{
return definitionNames;
}
public Script getScript(String definition)
{
return scripts.get(definition);
}
public Iterator<Script> getScripts()
{
return scripts.values().iterator();
}
public Iterator<String> getFiles()
{
return fileNames.iterator();
}
}