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