/* * * 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 org.apache.flex.compiler.internal.embedding; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.io.FilenameUtils; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition; import org.apache.flex.compiler.definitions.IFunctionDefinition.FunctionClassification; import org.apache.flex.compiler.definitions.IGetterDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.embedding.transcoders.DataTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.ImageTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.JPEGTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.MovieTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.PBJTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.SoundTranscoder; import org.apache.flex.compiler.internal.embedding.transcoders.TranscoderBase; import org.apache.flex.compiler.internal.embedding.transcoders.XMLTranscoder; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.projects.ASProject; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.projects.SourcePathManager; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.problems.EmbedInvalidAttributeValueProblem; import org.apache.flex.compiler.problems.EmbedNoSourceAttributeProblem; import org.apache.flex.compiler.problems.EmbedQualityValueProblem; import org.apache.flex.compiler.problems.EmbedScalingGridValueProblem; import org.apache.flex.compiler.problems.EmbedSourceAttributeDoesNotExistProblem; import org.apache.flex.compiler.problems.EmbedUnknownAttributeProblem; import org.apache.flex.compiler.problems.EmbedUnknownMimeTypeProblem; import org.apache.flex.compiler.problems.EmbedUnrecogniedFileTypeProblem; import org.apache.flex.compiler.problems.FontEmbeddingNotSupported; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.units.ICompilationUnit; import org.apache.flex.swc.ISWCFileEntry; import org.apache.flex.swf.ISWFConstants; import org.apache.flex.utils.FilenameNormalization; import org.apache.flex.utils.StringEncoder; /** * This is the main class which contains all information extracted from embed * meta data. */ public class EmbedData { private static final String EMBED_SWC_SEP = "|"; public static class SkinClassInfo { public boolean needsIBorder; public boolean needsIFlexDisplayObject; public boolean flexMovieClipOrSprite; public boolean needsBorderMetrics; public boolean needsMeasuredHeight; public boolean needsMeasuredWidth; public boolean needsMove; public boolean needsSetActualSize; protected SkinClassInfo(ICompilerProject project, ClassDefinition definition) { needsIBorder = !definition.isInstanceOf(TranscoderBase.CORE_PACKAGE + ".IBorder", project); needsIFlexDisplayObject = !definition.isInstanceOf(TranscoderBase.CORE_PACKAGE + ".IFlexDisplayObject", project); flexMovieClipOrSprite = definition.isInstanceOf(TranscoderBase.CORE_PACKAGE + ".FlexMovieClip", project) || definition.isInstanceOf(TranscoderBase.CORE_PACKAGE + ".FlexSprite", project); final INamespaceDefinition qualifier = definition.getNamespaceReference().resolveNamespaceReference(project); needsBorderMetrics = needsGetter(project, qualifier, definition, "borderMetrics"); needsMeasuredHeight = needsGetter(project, qualifier, definition, "measuredHeight"); needsMeasuredWidth = needsGetter(project, qualifier, definition, "measuredWidth"); needsMove = needsFunction(project, qualifier, definition, "move"); needsSetActualSize = needsFunction(project, qualifier, definition, "setActualSize"); } private static boolean needsGetter(ICompilerProject project, INamespaceDefinition qualifier, ClassDefinition classDefinition, String baseName) { final ASScope scope = classDefinition.getContainedScope(); IDefinition def = scope.getQualifiedPropertyFromDef(project, classDefinition, baseName, qualifier, false); return (def instanceof IGetterDefinition) ? false : true; } private static boolean needsFunction(ICompilerProject project, INamespaceDefinition qualifier, ClassDefinition classDefinition, String baseName) { final ASScope scope = classDefinition.getContainedScope(); IDefinition def = scope.getQualifiedPropertyFromDef(project, classDefinition, baseName, qualifier, false); if (def instanceof IFunctionDefinition) { FunctionClassification classification = ((IFunctionDefinition)def).getFunctionClassification(); return (classification == FunctionClassification.CLASS_MEMBER) ? false : true; } return false; } } public EmbedData(String containingSourceFilename, String specifiedQName) { this.containingSourceFilename = containingSourceFilename; this.attributes = new HashMap<EmbedAttribute, Object>(); this.specifiedName = specifiedQName; this.swcSource = null; this.skinClassInfo = null; } private final String containingSourceFilename; private final HashMap<EmbedAttribute, Object> attributes; private final String specifiedName; private TranscoderBase transcoder; private ISWCFileEntry swcSource; @SuppressWarnings("unused") private SkinClassInfo skinClassInfo; /** * Add an attribute * * @param project containing project * @param location source location of the attribute * @param key attribute key * @param value attribute value * @param problems any problems with the key or value * @return true if there was an error */ public boolean addAttribute(CompilerProject project, ISourceLocation location, String key, String value, Collection<ICompilerProblem> problems) { boolean hadError = false; try { // a null key means default to source, ie [Embed="image.png"] if (EmbedAttribute.SOURCE.equals(key) || key == null) { // put source resolution problems into a separate collection first // so that if we fail because there's an octothorpe, and resolve successfully // later on, we haven't created incorrect problems. List<ICompilerProblem> resolveProblems = new LinkedList<ICompilerProblem>(); String source = getResolvedSourcePath(project, location, value, resolveProblems); // could not resolve the source, so check for an octothorpe // which indicates a file within a SWC if (source == null) { int octothorpe = value.indexOf("#"); if (octothorpe != -1) { source = getResolvedSourcePath(project, location, value.substring(0, octothorpe), problems); String symbol = value.substring(octothorpe + 1); attributes.put(EmbedAttribute.SYMBOL, symbol); } else { problems.addAll(resolveProblems); } } if (source != null) { attributes.put(EmbedAttribute.SOURCE, source); // if we have a filename, but the mimeType hasn't been set yet, // set if from the filename, but override it later on if there // is an explicit mimeType, as that takes priority if (!attributes.containsKey(EmbedAttribute.MIME_TYPE)) { attributes.put(EmbedAttribute.MIME_TYPE, EmbedMIMEType.getMimeTypeFromFilename(source)); } } else { Collection<ICompilationUnit> referencingCUs = project.getCompilationUnits(containingSourceFilename); for (ICompilationUnit cu : referencingCUs) { project.addUnfoundReferencedSourceFileDependency(value, cu); } hadError = true; } } else if (EmbedAttribute.MIME_TYPE.equals(key)) { attributes.put(EmbedAttribute.MIME_TYPE, EmbedMIMEType.getMimeTypeFromMimeString(value)); } else if (EmbedAttribute.COMPRESSION.equals(key)) { attributes.put(EmbedAttribute.COMPRESSION, Boolean.parseBoolean(value)); } else if (EmbedAttribute.ENCODING.equals(key)) { attributes.put(EmbedAttribute.ENCODING, value); } else if (EmbedAttribute.EXPORT_SYMBOL.equals(key)) { attributes.put(EmbedAttribute.EXPORT_SYMBOL, value); } else if (EmbedAttribute.FLASH_TYPE.equals(key)) { attributes.put(EmbedAttribute.FLASH_TYPE, Boolean.parseBoolean(value)); } else if (EmbedAttribute.ORIGINAL.equals(key)) { attributes.put(EmbedAttribute.ORIGINAL, value); } else if (EmbedAttribute.QUALITY.equals(key)) { double doubleValue = Double.parseDouble(value); if (doubleValue < 0 || doubleValue > 100) { problems.add(new EmbedQualityValueProblem(location, doubleValue)); hadError = true; } else { Float floatValue = (float)(doubleValue / 100.0); attributes.put(EmbedAttribute.QUALITY, floatValue); } } else if (EmbedAttribute.SCALE_GRID_BOTTOM.equals(key)) { Integer intValue = Integer.parseInt(value); if (intValue.intValue() < 0) { problems.add(new EmbedScalingGridValueProblem(location, EmbedAttribute.SCALE_GRID_BOTTOM, intValue.intValue())); hadError = true; } else { intValue *= ISWFConstants.TWIPS_PER_PIXEL; attributes.put(EmbedAttribute.SCALE_GRID_BOTTOM, intValue); } } else if (EmbedAttribute.SCALE_GRID_LEFT.equals(key)) { Integer intValue = Integer.parseInt(value); if (intValue.intValue() < 0) { problems.add(new EmbedScalingGridValueProblem(location, EmbedAttribute.SCALE_GRID_LEFT, intValue.intValue())); hadError = true; } else { intValue *= ISWFConstants.TWIPS_PER_PIXEL; attributes.put(EmbedAttribute.SCALE_GRID_LEFT, intValue); } } else if (EmbedAttribute.SCALE_GRID_RIGHT.equals(key)) { Integer intValue = Integer.parseInt(value); if (intValue.intValue() < 0) { problems.add(new EmbedScalingGridValueProblem(location, EmbedAttribute.SCALE_GRID_RIGHT, intValue.intValue())); hadError = true; } else { intValue *= ISWFConstants.TWIPS_PER_PIXEL; attributes.put(EmbedAttribute.SCALE_GRID_RIGHT, intValue); } } else if (EmbedAttribute.SCALE_GRID_TOP.equals(key)) { Integer intValue = Integer.parseInt(value); if (intValue.intValue() < 0) { problems.add(new EmbedScalingGridValueProblem(location, EmbedAttribute.SCALE_GRID_TOP, intValue.intValue())); hadError = true; } else { intValue *= ISWFConstants.TWIPS_PER_PIXEL; attributes.put(EmbedAttribute.SCALE_GRID_TOP, intValue); } } else if (EmbedAttribute.SKIN_CLASS.equals(key)) { attributes.put(EmbedAttribute.SKIN_CLASS, value); if (value == null || value.length() == 0) { //problems.add(new EmbedNoSkinClassProblem(location)); hadError = true; } if (!attributes.containsKey(EmbedAttribute.MIME_TYPE)) { attributes.put(EmbedAttribute.MIME_TYPE, EmbedMIMEType.SKIN); } // resolve the skin class here, as need to resolve against // a specific project IDefinition skinSymbol = project.resolveQNameToDefinition(value); if (skinSymbol == null) { //problems.add(new EmbedNoSkinClassProblem(location)); hadError = true; } else { // set the file from which the symbol came from to the source // so we can still detect whether EmbedDatas are equal if // a symbol resolves to a different class depending on the project String source = skinSymbol.getContainingFilePath(); attributes.put(EmbedAttribute.SOURCE, source); assert (skinSymbol instanceof ClassDefinition); skinClassInfo = new SkinClassInfo(project, (ClassDefinition)skinSymbol); } } else if (EmbedAttribute.SMOOTHING.equals(key)) { attributes.put(EmbedAttribute.SMOOTHING, Boolean.parseBoolean(value)); } else if (EmbedAttribute.SYMBOL.equals(key)) { attributes.put(EmbedAttribute.SYMBOL, value); } else if (EmbedAttribute.ADV_ANTI_ALIASING.equals(key) || EmbedAttribute.EMBED_AS_CFF.equals(key) || EmbedAttribute.FONT_FAMILY.equals(key) || EmbedAttribute.FONT_NAME.equals(key) || EmbedAttribute.FONT_STYLE.equals(key) || EmbedAttribute.FONT_WEIGHT.equals(key) || EmbedAttribute.SYSTEM_FONT.equals(key) || EmbedAttribute.SOURCE_LIST.equals(key)) { // silently ignore these, as proper problem will be reported elsewhere } else { problems.add(new EmbedUnknownAttributeProblem(location, key)); hadError = true; } } catch (NumberFormatException e) { problems.add(new EmbedInvalidAttributeValueProblem(location, key, value)); hadError = true; } return hadError; } /** * Returns the value of an attribute. * * @param attribute An embed attribute. * @return value of an attribute. null if attribute does not exist */ public Object getAttribute(EmbedAttribute attribute) { return attributes.get(attribute); } /** * @return All attributes */ public EmbedAttribute[] getAttributes() { return attributes.keySet().toArray(new EmbedAttribute[attributes.size()]); } /** * @param project The compiler project. * @param location The source location. * @param problems The colleciton of compiler projects to which this method should add problems. * @return true if the transcoder was successfully constructed */ public boolean createTranscoder(CompilerProject project, ISourceLocation location, Collection<ICompilerProblem> problems) { // there should always be a source, with the exception of skin embedding, so don't // create a transcoder in this error state String source = (String)getAttribute(EmbedAttribute.SOURCE); if (source == null && getAttribute(EmbedAttribute.SKIN_CLASS) == null) { problems.add(new EmbedNoSourceAttributeProblem(location)); return false; } // also check that we have a mimetype set, as don't know what transcoder // to create without it! EmbedMIMEType mimeType = (EmbedMIMEType)getAttribute(EmbedAttribute.MIME_TYPE); if (mimeType == null) { problems.add(new EmbedUnrecogniedFileTypeProblem(location, source)); return false; } Workspace workspace = project.getWorkspace(); switch (mimeType) { case JPEG: case JPG: case PNG: case GIF: { Boolean compression = (Boolean)getAttribute(EmbedAttribute.COMPRESSION); Float quality = (Float)getAttribute(EmbedAttribute.QUALITY); if ((compression != null && compression == true) || quality != null) { transcoder = new JPEGTranscoder(this, workspace); } else { transcoder = new ImageTranscoder(this, workspace); } break; } case MP3: { transcoder = new SoundTranscoder(this, workspace); break; } case FLASH: { transcoder = new MovieTranscoder(this, workspace); break; } case PBJ: { transcoder = new PBJTranscoder(this, workspace); break; } case OCT_STRM: { transcoder = new DataTranscoder(this, workspace); break; } case XML: { transcoder = new XMLTranscoder(this, workspace); break; } case SKIN: { //transcoder = new SkinTranscoder(this, workspace, skinClassInfo); break; } case TTF: case TTC: case OTF: case FONT: case DFONT: { problems.add(new FontEmbeddingNotSupported(location)); transcoder = null; break; } case TEXT: case PROPERTIES: break; // don't need transcoder for text default: { problems.add(new EmbedUnknownMimeTypeProblem(location, mimeType)); transcoder = null; } } if (transcoder == null) return false; // there were problems with the transcoder because of attribute settings // so don't return it, and let the user deal with the errors if (!transcoder.analyze(location, problems)) { transcoder = null; return false; } return true; } /** * Returns the qname of the class generated from the EmbedData. The name * is guaranteed to be unique and not conflict with user space names, unless * a user defined class has been decorated with the embed metadata, in which * case, the users class name will be returned. * @return qname */ public String getQName() { if (specifiedName != null) return specifiedName; String source = (String)getAttribute(EmbedAttribute.SOURCE); if (swcSource != null) { source = EMBED_SWC_SEP.concat(source); source = swcSource.getContainingSWCPath().concat(source); } String filename = FilenameUtils.getName(source); filename = filename.replace(".", "_"); String qname = filename + "$" + StringEncoder.stringToMD5String(source); // add the transcoder hashCode to the end of the QName to ensure // two embed data's with the same source, but different attributes // don't clash qname += transcoder.hashCode(); return qname; } /** * Check if the generated class extends another * * @return true if another class is extended */ public boolean generatedClassExtendsAnother() { String baseClassQname = transcoder.getBaseClassQName(); if (baseClassQname.isEmpty()) return false; return true; } /** * Get the transcoder used by this embed. This can be null if there was * a problem with the Embed directive * * @return transcoder */ public final TranscoderBase getTranscoder() { return transcoder; } /** * * @return ISWCFileEntry entry to source asset contained within swc. null if not contained within SWC */ public final ISWCFileEntry getSWCSource() { return swcSource; } @Override public boolean equals(Object o) { assert (transcoder != null) : "equals called on EmbedData with null transcoder"; if (!(o instanceof EmbedData)) return false; // EmbedData's are considered equal if their transcoders are equal return transcoder.equals(((EmbedData)o).getTranscoder()); } @Override public int hashCode() { assert (transcoder != null) : "hashCode called on EmbedData with null transcoder"; return transcoder.hashCode(); } private String getResolvedSourcePath(ICompilerProject project, ISourceLocation location, String sourceValue, Collection<ICompilerProblem> problems) { if (sourceValue == null || sourceValue.isEmpty()) { problems.add(new EmbedNoSourceAttributeProblem(location)); return null; } Map<String,String> searchedLocations = new LinkedHashMap<String,String>(); String containingSourcePath = new File(containingSourceFilename).getParent(); String sourceFile = getResolvedSourcePath(project, containingSourcePath, sourceValue, searchedLocations); if (sourceFile == null) { problems.add(new EmbedSourceAttributeDoesNotExistProblem(location, sourceValue, searchedLocations)); } return sourceFile; } /** * Resolve the location to the requested embed asset filename based on the rules. * 1) Absolute filename * 2) relative to the containing source file * 3) the source path (if flash project) * 4) the library path (if flash project) * @param containingSourcePath * @param filename * @param searchedLocations A map of the locations searched for filename. The * key is the filename and the value is the id of the message format used to * format the filename. * @return The absolute path to the requested filename, or null if not found. */ private String getResolvedSourcePath(ICompilerProject project, String containingSourcePath, String filename, Map<String,String> searchedLocations) { // first check if absolute path String sourceFile = null; if (new File(filename).isAbsolute()) { searchedLocations.put(FilenameNormalization.normalize(filename), "QuotedPath"); sourceFile = SourcePathManager.getSourceFileInPath(null, filename); if (sourceFile != null) return sourceFile; } // not an absolute path, so try relative to the containing source if (containingSourcePath != null) { File file = new File(containingSourcePath, filename); searchedLocations.put(FilenameNormalization.normalize(file).getAbsolutePath(), "QuotedPath"); sourceFile = SourcePathManager.getSourceFileInPath(new File(containingSourcePath), filename); } if (sourceFile != null) return sourceFile; if (project instanceof ASProject) { sourceFile = getResolvedSourcePath((ASProject)project, filename, searchedLocations); } if (project instanceof FlexProject) { FlexProject flexProject = (FlexProject) project; String packagePath = null; if((containingSourcePath != null) && !flexProject.getSourcePath().isEmpty()) { for (File sourcePath : flexProject.getSourcePath()) { if (containingSourcePath.startsWith(sourcePath.getAbsolutePath())) { packagePath = containingSourcePath.substring(sourcePath.getAbsolutePath().length() + 1); } } } sourceFile = getResolvedSourcePath((FlexProject)project, filename, packagePath, searchedLocations); } return sourceFile; } private String getResolvedSourcePath(ASProject project, String filename, Map<String,String> searchedLocations) { // Only files that start with a leading "/" are resolved using the // source path. String sourceFile = null; boolean isAbsolute = filename.startsWith("/"); if (isAbsolute) { searchedLocations.put(filename.substring(1), "EmbedOnSourcePath"); sourceFile = project.getSourceFileFromSourcePath(filename.substring(1)); } if (sourceFile != null) return sourceFile; // Not in the source path, so finally look for the file within the libraries. // Absolute files are not looked up using the library path. if (!isAbsolute) { searchedLocations.put(filename, "EmbedOnLibraryPath"); swcSource = project.getSourceFileFromLibraryPath(filename); if (swcSource != null) { sourceFile = swcSource.getPath(); } } return sourceFile; } private String getResolvedSourcePath(FlexProject project, String filename, String packagePath, Map<String,String> searchedLocations) { // Only files that start with a leading "/" are resolved using the // source path. String sourceFile = null; boolean isAbsolute = filename.startsWith("/"); if (isAbsolute) { searchedLocations.put(filename.substring(1), "EmbedOnSourcePath"); sourceFile = project.getSourceFileFromSourcePath(filename.substring(1)); } if (sourceFile != null) return sourceFile; // Not in the source path, so finally look for the file within the libraries. // Absolute files are not looked up using the library path. if (!isAbsolute) { for(File sourceDirectory : project.getSourcePath()) { File potentialFile = sourceDirectory; if(packagePath != null) { potentialFile = new File(potentialFile, packagePath); } searchedLocations.put(filename, potentialFile.getAbsolutePath()); potentialFile = new File(potentialFile, filename); if(potentialFile.exists()) { return potentialFile.getAbsolutePath(); } } searchedLocations.put(filename, "EmbedOnLibraryPath"); swcSource = project.getSourceFileFromLibraryPath(filename); if (swcSource != null) { sourceFile = swcSource.getPath(); } } return sourceFile; } }