blob: 17654d2fb9a3ee281954a8cd8ed078f988845e08 [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;
import flash.swf.tags.DefineFont;
import flash.swf.tags.DefineTag;
import flash.util.StringJoiner;
import flex2.compiler.CompilationUnit;
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.SymbolTable;
import flex2.compiler.common.Configuration;
import flex2.compiler.common.MxmlConfiguration;
import flex2.compiler.i18n.I18nUtils;
import flex2.compiler.io.TextFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.swc.Digest;
import flex2.compiler.swc.Swc;
import flex2.compiler.swc.SwcException;
import flex2.compiler.swc.SwcScript;
import flex2.compiler.util.CompilerMessage;
import flex2.compiler.util.MimeMappings;
import flex2.compiler.util.MultiName;
import flex2.compiler.util.Name;
import flex2.compiler.util.NameFormatter;
import flex2.compiler.util.NameMappings;
import flex2.compiler.util.QName;
import flex2.compiler.util.ThreadLocalToolkit;
import java.io.File;
import java.util.*;
/**
* Compc specific PreLink implementation, which handles generating a
* root class, which handles registring embedded fonts and setting up
* for RSL's and resource bundles.
*/
public class CompcPreLink implements flex2.compiler.PreLink
{
private static final String DEFAULTS = "defaults";
private static final String DOT_CSS = ".css";
private boolean hidePotentialMissingSkinsWarning;
/**
* Constructor
*
* @param rbFiles
* @param defaults
* @param hidePotentialMissingSkinsWarning should only be set to true when called via asdoc.
*/
public CompcPreLink(Map<String, VirtualFile> rbFiles, List<String> defaults,
boolean hidePotentialMissingSkinsWarning)
{
this.rbFiles = rbFiles;
this.defaults = defaults;
this.hidePotentialMissingSkinsWarning = hidePotentialMissingSkinsWarning;
}
private Map<String, VirtualFile> rbFiles;
private List<String> defaults;
public boolean run(List<Source> sources, List<CompilationUnit> units,
FileSpec fileSpec, SourceList sourceList, SourcePath sourcePath, ResourceBundlePath bundlePath,
ResourceContainer resources, SymbolTable symbolTable, CompilerSwcContext swcContext,
NameMappings nameMappings, Configuration configuration)
{
postGenerateExtraSwcCode(sources, units, symbolTable, sourceList, sourcePath, bundlePath, resources, swcContext, configuration);
processResourceBundles(rbFiles, configuration, sources, defaults, symbolTable, bundlePath, swcContext);
// SDK-17411 - return false so that we run preLink only once for compc.
return false;
}
public void postRun(List<Source> sources, List<CompilationUnit> units,
ResourceContainer resources,
SymbolTable symbolTable,
CompilerSwcContext swcContext,
NameMappings nameMappings,
Configuration configuration)
{
int highestMinimumSupportedVersion = (MxmlConfiguration.EARLIEST_MAJOR_VERSION << 24);
boolean isMinimumSupportedVersionConfigured =
configuration.getCompilerConfiguration().getMxmlConfiguration().isMinimumSupportedVersionConfigured();
Set<String> processedSwcs = new HashSet<String>();
Set externs = configuration.getExterns();
for (CompilationUnit u : units)
{
Set<Name> dependencies = new HashSet<Name>();
dependencies.addAll(u.inheritance);
dependencies.addAll(u.namespaces);
dependencies.addAll(u.expressions);
dependencies.addAll(u.types);
for (Name name : dependencies)
{
if (name instanceof QName)
{
Source dependent = symbolTable.findSourceByQName((QName) name);
if ((dependent != null) && dependent.isSwcScriptOwner())
{
SwcScript swcScript = (SwcScript) dependent.getOwner();
Swc swc = swcScript.getLibrary().getSwc();
// Make sure each dependency's minimum
// supported version is less than or equal to
// the compatibility version.
if (highestMinimumSupportedVersion < swc.getVersions().getMinimumVersion() &&
configuration.getCompilerConfiguration().enableSwcVersionFiltering())
{
highestMinimumSupportedVersion = swc.getVersions().getMinimumVersion();
if (isMinimumSupportedVersionConfigured &&
configuration.getMinimumSupportedVersion() < highestMinimumSupportedVersion)
{
HigherMinimumSupportedVersionRequired message =
new HigherMinimumSupportedVersionRequired(swc.getLocation(),
swc.getVersions().getMinimumVersionString());
ThreadLocalToolkit.log(message, u.getSource());
}
}
}
}
}
// Warn about linked in dependent SWC's, which have style
// defaults. Linking in dependent SWC's pulls in
// definitions without their associated style defaults and
// their dependencies. This can lead to missing skins at
// runtime for applications, which compile against the
// output SWC. Merging the style defaults isn't enough,
// because styles can bring in additional dependencies, so
// in order to get a complete dependency set, we would
// have to compile the style defaults and our current
// design requires deferring that until application
// compile time. Therefore the best option is to
// recommend that the user put the dependent SWC in the
// external-library-path of the output SWC compilation and
// in the library-path of downstream application
// compilations.
Source source = u.getSource();
if (source.isSwcScriptOwner() &&
!PreLink.isCompilationUnitExternal(u, externs) &&
!source.isInternal() && !hidePotentialMissingSkinsWarning)
{
SwcScript swcScript = (SwcScript) source.getOwner();
Swc swc = swcScript.getLibrary().getSwc();
String location = swc.getLocation();
if (!processedSwcs.contains(location))
{
processedSwcs.add(location);
boolean foundDefaultsCss = false;
for (VirtualFile catalogFile : swc.getCatalogFiles().values())
{
String catalogFileName = catalogFile.getName();
int dollarSignIndex = catalogFileName.indexOf("$");
if ((dollarSignIndex != -1) &&
catalogFileName.startsWith(DEFAULTS, dollarSignIndex + 1) &&
catalogFileName.endsWith(DOT_CSS))
{
foundDefaultsCss = true;
}
}
if (foundDefaultsCss)
{
PotentialForMissingSkins message = new PotentialForMissingSkins(swc.getLocation());
ThreadLocalToolkit.log(message);
}
}
}
}
if (!isMinimumSupportedVersionConfigured)
{
configuration.getCompilerConfiguration().getMxmlConfiguration().setMinimumSupportedVersion(highestMinimumSupportedVersion);
}
}
private void postGenerateExtraSwcCode(List<Source> sources, List units, SymbolTable symbolTable, SourceList sourceList, SourcePath sourcePath,
ResourceBundlePath bundlePath, ResourceContainer resources, CompilerSwcContext swcContext, Configuration configuration)
{
LinkedList<DefineTag> fonts = new LinkedList<DefineTag>();
boolean isAccessible = configuration.getCompilerConfiguration().accessible();
Set<String> accessibilityList = new HashSet<String>();
Set<String> externs = configuration.getExterns();
ArrayList<Long> checksumList = new ArrayList<Long>(units.size());
for (int i = 0, size = units == null ? 0 : units.size(); i < size; i++)
{
CompilationUnit u = (CompilationUnit) units.get(i);
if (u != null && !PreLink.isCompilationUnitExternal(u, externs) &&
!u.getSource().isInternal())
{
if (isAccessible) {
Set<String> unitAccessibilityList = u.getAccessibilityClasses();
if (unitAccessibilityList != null)
{
accessibilityList.addAll(unitAccessibilityList);
}
}
if (u.hasAssets())
{
// don't add font assets for definitions that have been externed.
List<DefineFont> fontList = u.getAssets().getFonts();
if (fontList != null && !fontList.isEmpty())
{
fonts.addAll(fontList); // save for later...
}
}
if (u.getSignatureChecksum() != null)
checksumList.add(u.getSignatureChecksum());
}
}
if (accessibilityList.size() > 0)
{
for (Iterator it = accessibilityList.iterator(); it.hasNext();)
{
String className = (String) it.next();
MultiName mName = new MultiName(NameFormatter.retrievePackageName(className), NameFormatter.retrieveClassName(className));
flex2.compiler.CompilerAPI.resolveMultiName(mName, sources, sourceList, sourcePath, resources, swcContext, symbolTable);
}
}
// Sort the checksums to make sure the checksums are always in the same order.
// Later we will be creating a unique name for the root class from the digests
// of the checksums. That's why the order is important.
StringBuffer checksumBuffer = new StringBuffer();
Collections.sort(checksumList);
for (Iterator iter = checksumList.iterator(); iter.hasNext();)
{
Long checksum = (Long)iter.next();
checksumBuffer.append(checksum.longValue());
}
String uniqueRootClassName = new Digest().computeDigest(checksumBuffer.toString().getBytes());
codegenRootClass(sources, units, resources, swcContext, configuration, fonts, uniqueRootClassName);
}
private void processResourceBundles(Map<String, VirtualFile> rbFiles, Configuration configuration, List<Source> sources, List<String> defaults,
SymbolTable symbolTable, ResourceBundlePath bundlePath, CompilerSwcContext swcContext)
{
if (rbFiles != null && !configuration.generateRBList())
{
String[] locales = configuration.getCompilerConfiguration().getLocales();
Set<String> s = new TreeSet<String>();
Set externs = configuration.getExterns();
for (int i = 0, size = sources.size(); i < size; i++)
{
Source src = sources.get(i);
CompilationUnit unit = src == null ? null : src.getCompilationUnit();
if (unit != null && !PreLink.isCompilationUnitExternal(unit, externs) &&
!src.isInternal())
{
s.addAll(unit.resourceBundleHistory);
}
}
for (int i = 0, size = defaults == null ? 0 : defaults.size(); i < size; i++)
{
s.add(defaults.get(i));
}
for (Iterator i = s.iterator(); i.hasNext(); )
{
String rbName = NameFormatter.toColon((String) i.next());
QName qName = new QName(rbName);
VirtualFile[] files = bundlePath.findVirtualFiles(rbName);
if (files == null)
{
files = swcContext.getVirtualFiles(locales, qName.getNamespace(), qName.getLocalPart());
}
if (files == null)
{
// Handle Flex 2 style precompiled resource bundles.
QName precompiledQName = new QName(rbName + I18nUtils.CLASS_SUFFIX);
Source source = swcContext.getSource(precompiledQName.getNamespace(),
precompiledQName.getLocalPart());
if (source != null)
{
//FIXME I don't know if this logic is correct or possible.
// to my knowledge, getExterns() always returns String across the compiler
// so... are we toString()ing this? if so, let's change externs to Set<String>
// and call toString explicitely
externs.add(qName);
continue;
}
}
if (files == null && locales.length > 0)
{
ThreadLocalToolkit.log(new SwcException.NoResourceBundleSource(rbName));
}
for (int j = 0, size = files == null ? 0 : files.length; j < size; j++)
{
if (files[j] != null)
{
String ext = MimeMappings.getExtension(files[j].getMimeType());
String key = "locale/" + locales[j] + "/" + rbName.replace(':', '.').replace('.', '/') + ext;
rbFiles.put(key, files[j]);
}
}
if (files != null)
{
QName[] qNames = flex2.compiler.CompilerAPI.resolveResourceBundleName(rbName, sources, null, bundlePath, null, swcContext, symbolTable, locales);
configuration.addExterns(qNames);
}
}
}
}
/**
* Output code to create an RSL root class that is executed when the swf is loaded.
*
* @param sources
* @param units
* @param swcContext
* @param configuration
* @param fonts
* @param uniqueRootClassName - unique part of the root class name.
*/
private void codegenRootClass(List<Source> sources, List units, ResourceContainer resources,
CompilerSwcContext swcContext, Configuration configuration,
List<DefineTag> fonts, String uniqueRootClassName)
{
String rootClassName = "_" + uniqueRootClassName + "_";
String sourceText = null;
if (fonts.size() == 0)
{
rootClassName += "flash_display_Sprite";
sourceText = codegenRSLRootClass("flash.display.Sprite", rootClassName);
}
else
{
rootClassName += "mx_core_FlexModuleFactory";
sourceText = PreLink.codegenModuleFactory("flash.display.Sprite",
rootClassName,
null,
null,
null,
null,
null,
null,
null,
fonts,
null,
null,
null,
null,
configuration,
null,
swcContext,
true);
}
String generatedLoaderFile = rootClassName + ".as";
Source s = new Source(new TextFile(sourceText,
generatedLoaderFile,
null,
MimeMappings.getMimeType(generatedLoaderFile)),
"", rootClassName, null, false, false, false);
// C: It doesn't look like this Source needs any path resolution. null is fine...
s.setPathResolver(null);
sources.add(resources.addResource(s));
configuration.setRootClassName(rootClassName);
if (configuration.getCompilerConfiguration().keepGeneratedActionScript())
{
PreLink.saveGenerated(generatedLoaderFile, sourceText, configuration.getCompilerConfiguration().getGeneratedDirectory());
}
}
/**
*
* @param pathName
* @return filename of the swc with the extension removed and the integer
*/
private String getSwcClassName(String pathName)
{
File file = new File(pathName);
String fileName = file.getName();
int ext = fileName.lastIndexOf(".");
if (ext != -1)
{
fileName = fileName.substring(0, ext);
}
// replace non-word characters with an underscore.
fileName = fileName.replaceAll("[\\W]", "_");
return fileName;
}
/**
* Generate a root class for an RSL with wrapper calls to Security.allowDomain() and
* Security.allowInsecureDomain(). The purpose is to allow callers to trust the RSL SWFs
* in the same way they can trust an application swf.
*
* @param base The class root class extends.
* @param rootClassName
* @return The root class actionscript class definition as a String.
*/
private static String codegenRSLRootClass(String base,
String rootClassName)
{
String lineSep = System.getProperty("line.separator");
String[] codePieces = new String[]
{
"package", lineSep,
"{", lineSep, lineSep,
"import flash.display.Sprite;", lineSep,
"import flash.system.Security;", lineSep, lineSep,
"/**", lineSep,
" * @private", lineSep,
" */", lineSep,
"[ExcludeClass]", lineSep,
"public class ", rootClassName, lineSep,
" extends ", base, lineSep,
"{", lineSep,
" public function ", rootClassName, "()", lineSep,
" {", lineSep,
" super();", lineSep,
" }", lineSep, lineSep,
PreLink.codegenRSLSecurityWrapper(true, lineSep),
"}", lineSep, lineSep,
"}", lineSep,
};
return StringJoiner.join(codePieces, null);
}
public static class HigherMinimumSupportedVersionRequired extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -917715346261180364L;
public String swc;
public String swcMinimumSupportedVersion;
public HigherMinimumSupportedVersionRequired(String swc, String swcMinimumSupportedVersion)
{
this.swc = swc;
this.swcMinimumSupportedVersion = swcMinimumSupportedVersion;
}
}
public static class PotentialForMissingSkins extends CompilerMessage.CompilerWarning
{
private static final long serialVersionUID = -917715346261180365L;
public String swc;
public PotentialForMissingSkins(String swc)
{
this.swc = swc;
}
}
}