/* * * 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.units; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.ABCEmitter; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.compiler.problems.UnresolvedClassReferenceProblem; import org.apache.commons.io.IOUtils; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.Multiname; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference; import org.apache.flex.compiler.definitions.references.ReferenceFactory; import org.apache.flex.compiler.internal.abc.ClassGeneratorHelper; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.PackageDefinition; import org.apache.flex.compiler.internal.embedding.EmbedData; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.projects.DefinitionPriority; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.resourcebundles.PropertiesFileParser; import org.apache.flex.compiler.internal.resourcebundles.ResourceBundleUtils; import org.apache.flex.compiler.internal.scopes.ASFileScope; import org.apache.flex.compiler.internal.scopes.PackageScope; import org.apache.flex.compiler.internal.scopes.TypeScope; import org.apache.flex.compiler.internal.tree.as.ClassReferenceNode; import org.apache.flex.compiler.internal.tree.as.EmbedNode; import org.apache.flex.compiler.internal.tree.as.ExpressionNodeBase; import org.apache.flex.compiler.internal.tree.as.LiteralNode; import org.apache.flex.compiler.internal.tree.properties.ResourceBundleEntryNode; import org.apache.flex.compiler.internal.tree.properties.ResourceBundleFileNode; import org.apache.flex.compiler.internal.units.requests.ABCBytesRequestResult; import org.apache.flex.compiler.internal.units.requests.FileScopeRequestResultBase; import org.apache.flex.compiler.internal.units.requests.SWFTagsRequestResult; import org.apache.flex.compiler.internal.units.requests.SyntaxTreeRequestResult; import org.apache.flex.compiler.problems.CodegenInternalProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.InternalCompilerProblem; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.targets.ITarget.TargetType; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IFileNodeAccumulator; import org.apache.flex.compiler.units.requests.IABCBytesRequestResult; import org.apache.flex.compiler.units.requests.IFileScopeRequestResult; import org.apache.flex.compiler.units.requests.IOutgoingDependenciesRequestResult; import org.apache.flex.compiler.units.requests.ISWFTagsRequestResult; import org.apache.flex.compiler.units.requests.ISyntaxTreeRequestResult; import org.apache.flex.swc.ISWCFileEntry; import org.apache.flex.utils.FilenameNormalization; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; /** * This is a compilation unit that handles .properties file compilation. */ public class ResourceBundleCompilationUnit extends CompilationUnitBase { /** * Parent folder name for properties files in a swc. We read properties * files off of this folder while reading a swc and copy them into this * folder while writing a swc. */ public static final String LOCALE = "locale"; /** * Qualified bundle name for the properties file processed * by this compilation unit. */ private final String bundleNameInColonSyntax; /** * Locale of the properties file processed by this compilation unit or * <code>null</code> if the file is not locale dependent. */ private final String locale; /** * SWC entry for the properties file processed by this compilation unit if * it comes from a swc, <code>null</code> if it doesn't come from a SWC. */ private final ISWCFileEntry fileEntry; /** * Constructor. * * @param project project this compilation unit is associated with * @param path path of the properties file * @param basePriority base priority * @param qname qualified name for the properties file that will be * processed by this comp unit * @param locale the locale this compilation unit depends on or <code>null</code> * if the compilation unit is not locale dependent */ public ResourceBundleCompilationUnit(final CompilerProject project, final String path, final DefinitionPriority.BasePriority basePriority, final String qname, final String locale) { super(project, path, basePriority, getQnames(project, qname, locale)); this.locale = locale; this.bundleNameInColonSyntax = ResourceBundleUtils.convertBundleNameToColonSyntax(qname); this.fileEntry = null; } /** * Constructor. This constructor should be used for properties files that * come from a swc. * * @param project project this compilation unit is associated with * @param fileEntry swc entry for the properties file that will be processed * by this compilation unit * @param qname qualified name for the properties file that will be * processed by this comp unit * @param locale the locale this compilation unit depends on or <code>null</code> * if the compilation unit is not locale dependent */ public ResourceBundleCompilationUnit(final CompilerProject project, final ISWCFileEntry fileEntry, final String qname, final String locale) { super(project, fileEntry.getContainingSWCPath(), DefinitionPriority.BasePriority.LIBRARY_PATH, getQnames(project, qname, locale)); this.bundleNameInColonSyntax = ResourceBundleUtils.convertBundleNameToColonSyntax(qname); this.fileEntry = fileEntry; this.locale = locale; } /** * utility to get the qnames promised by this compilation unit */ private static Collection<String> getQnames(CompilerProject project, String qname, String locale) { //if this comp unit is not locale dependent, then create qnames for each locale project targets Collection<String> locales = (locale == null) ? ((FlexProject)project).getLocales() : Collections.<String>singleton(locale); // For each local we are using, add the qnames promised for that locale ArrayList<String> qnames = new ArrayList<String>(); for(String loc : locales) { String qualifiedName = ResourceBundleUtils.getQualifiedName(loc, qname); //determine qualified name qnames.add(qualifiedName); } return qnames; } @Override public UnitType getCompilationUnitType() { return UnitType.RESOURCE_UNIT; } /** * Returns the name of the bundle processed by this compilation unit. Bundle * name is the qualified name that is used when referencing this bundle in * action script. * * For qualified names, "colon syntax" is used such as foo.bar:xyz for * "../foo/bar/xyz.properties" file. * * @return the name of the bundle processed by this compilation unit */ public String getBundleNameInColonSyntax() { return bundleNameInColonSyntax; } /** * Returns the locale of the properties file associated with this * compilation unit depends on or <code>null</code> if the file is not * locale dependent. * * @return the locale this compilation unit depends on or <code>null</code> * if the compilation unit is not locale dependent. */ public String getLocale() { return locale; } /** * Returns the flex project that contains this compilation unit. * * @return the flex project. */ private FlexProject getFlexProject() { return (FlexProject)getProject(); } @Override protected ISyntaxTreeRequestResult handleSyntaxTreeRequest() throws InterruptedException { startProfile(Operation.GET_SYNTAX_TREE); try { getProject().clearScopeCacheForCompilationUnit(this); final Collection<ICompilerProblem> problems = new LinkedList<ICompilerProblem>(); PropertiesFileParser parser = new PropertiesFileParser(getProject().getWorkspace()); final ResourceBundleFileNode fileNode = parser.parse(getFileName(), this.locale, getFileReader(problems), problems); ASFileScope fileScope = createFileScope(fileNode); addScopeToProjectScope(new ASFileScope[] { fileScope }); return new SyntaxTreeRequestResult(fileNode, ImmutableSet.<String>of(), getRootFileSpecification().getLastModified(), problems); } finally { stopProfile(Operation.GET_SYNTAX_TREE); } } @Override protected IFileScopeRequestResult handleFileScopeRequest() throws InterruptedException { startProfile(Operation.GET_FILESCOPE); try { ISyntaxTreeRequestResult syntaxTreeResult = getSyntaxTreeRequest().get(); final ResourceBundleFileNode rootNode = (ResourceBundleFileNode)syntaxTreeResult.getAST(); IASScope fileScope = rootNode.getScope(); assert fileScope instanceof ASFileScope : "Expect ASFileScope as the top-level scope, but found " + fileScope.getClass(); return new FileScopeRequestResultBase(Collections.<ICompilerProblem> emptyList(), Collections.singleton(fileScope)) { @Override public IDefinition getMainDefinition(String qname) { assert qname != null : "Excpect QName."; for (final IDefinition def : definitions) { if (qname.equals(def.getQualifiedName())) { return def; } } return null; } }; } finally { stopProfile(Operation.GET_FILESCOPE); } } @Override protected IABCBytesRequestResult handleABCBytesRequest() throws InterruptedException { ISyntaxTreeRequestResult syntaxTreeResult = getSyntaxTreeRequest().get(); final IASNode rootNode = syntaxTreeResult.getAST(); startProfile(Operation.GET_ABC_BYTES); try { final Collection<ICompilerProblem> problems = new LinkedList<ICompilerProblem>(); final ABCEmitter emitter = new ABCEmitter(); // TODO: hook this up to something in the settings - how do we access settings from here? emitter.visit(ABCConstants.VERSION_ABC_MAJOR_FP10, ABCConstants.VERSION_ABC_MINOR_FP10); byte[] generatedBytes = null; try { for (IDefinition def : getDefinitionPromises()) { String qualifiedClassName = def.getQualifiedName(); String locale = ResourceBundleUtils.getLocale(qualifiedClassName); generateABCForBundle(emitter, (ResourceBundleFileNode)rootNode, qualifiedClassName, bundleNameInColonSyntax, locale, problems); } generatedBytes = emitter.emit(); } catch (Exception ex) { problems.add(new CodegenInternalProblem(rootNode, ex)); } Set<EmbedData> embeds = new HashSet<EmbedData>(); EmbedCompilationUnitFactory.collectEmbedDatas(getProject(), (IFileNodeAccumulator)rootNode, embeds, problems); return new ABCBytesRequestResult(generatedBytes, problems.toArray(new ICompilerProblem[0]), embeds); } finally { stopProfile(Operation.GET_ABC_BYTES); } } @Override protected ISWFTagsRequestResult handleSWFTagsRequest() throws InterruptedException { // Fulfill other requests before profiling this request. final IABCBytesRequestResult abc = getABCBytesRequest().get(); startProfile(Operation.GET_SWF_TAGS); try { String tagName = getDefinitionPromises().get(0).getQualifiedName(); return new SWFTagsRequestResult(abc.getABCBytes(), tagName, abc.getEmbeds()); } finally { stopProfile(Operation.GET_SWF_TAGS); } } @Override protected IOutgoingDependenciesRequestResult handleOutgoingDependenciesRequest () throws InterruptedException { final ResourceBundleFileNode fileNode = (ResourceBundleFileNode)getSyntaxTreeRequest().get().getAST(); startProfile(Operation.GET_SEMANTIC_PROBLEMS); try { Collection<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(); updateEmbedCompilationUnitDependencies(fileNode.getEmbedNodes(), problems); // Kick off code generation to add all the dependencies found by code generation. getABCBytesRequest().get(); //Add dependency to 'mx.resources.ResourceBundle' since we want it to be picked up. FlexProject project = getFlexProject(); IResolvedQualifiersReference resourceBundleClassRef = ReferenceFactory.packageQualifiedReference( getProject().getWorkspace(), project.getResourceBundleClass()); resourceBundleClassRef.resolve(project, this, DependencyType.INHERITANCE); return new IOutgoingDependenciesRequestResult() { @Override public ICompilerProblem[] getProblems() { return IOutgoingDependenciesRequestResult.NO_PROBLEMS; } }; } finally { stopProfile(Operation.GET_SEMANTIC_PROBLEMS); } } @Override public void waitForBuildFinish(final Collection<ICompilerProblem> problems, TargetType targetType) throws InterruptedException { assert problems != null : "Expected 'problems'. Do not ignore problems."; Collections.addAll(problems, getSyntaxTreeRequest().get().getProblems()); Collections.addAll(problems, getFileScopeRequest().get().getProblems()); Collections.addAll(problems, getOutgoingDependenciesRequest().get().getProblems()); //Properties files doesn't get compiled down to abc while generating a swc. //Therefore, do the following steps for only swf case. if (TargetType.SWF.equals(targetType)) { Collections.addAll(problems, getABCBytesRequest().get().getProblems()); Collections.addAll(problems, getSWFTagsRequest().get().getProblems()); } } @Override public void startBuildAsync(TargetType targetType) { getSyntaxTreeRequest(); getFileScopeRequest(); getOutgoingDependenciesRequest(); //Properties files doesn't get compiled down to abc while generating a swc. //Therefore, do the following steps for only swf case. if (TargetType.SWF.equals(targetType)) { getABCBytesRequest(); getSWFTagsRequest(); } } /** * Get the time-stamp of the properties file processed by this compilation * unit. * * @return time stamp in milliseconds from epoch time. */ public long getFileLastModified() { if (fileEntry != null) return fileEntry.getLastModified(); return getRootFileSpecification().getLastModified(); } /** * Returns the content of the file processed by this compilation unit. * * @return byte array that represents the content or <code>null</code> if * any problem occurs. */ public byte[] getFileContent(final Collection<ICompilerProblem> problems) { Reader reader = null; try { reader = getFileReader(problems); return IOUtils.toByteArray(reader); } catch (IOException ex) { problems.add(new InternalCompilerProblem(ex)); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { //ignore } } } return null; } /** * Returns the file name that should be dispayed with errors message generated from errors in the * properties file. * * @return File name as a string. */ private String getFileName() { if (fileEntry != null) return FilenameNormalization.normalize(getAbsoluteFilename() + ":" + fileEntry.getPath()); return getAbsoluteFilename(); } /** * Returns the {@link Reader} for the contents of file that is processed by * this compilation unit. * * @return file reader or <code>null</code> if any problem occurs. */ private Reader getFileReader(final Collection<ICompilerProblem> problems) { try { if (fileEntry != null) { InputStream in = fileEntry.createInputStream(); if (in != null) { return new InputStreamReader(in, "UTF-8"); } } else { return getRootFileSpecification().createReader(); } } catch (IOException ex) { problems.add(new InternalCompilerProblem(ex)); } return null; } /** * Create the file scope for this compilation unit. * * @param fileNode file node of this compilation unit * @return root file scope for this compilation unit */ private ASFileScope createFileScope(final ResourceBundleFileNode fileNode) { List<IDefinition> definitions = getDefinitionPromises(); ASFileScope fileScope = new ASFileScope(fileNode); String packageName = Multiname.getPackageNameForQName(definitions.get(0).getQualifiedName()); PackageScope packageScope = new PackageScope(fileScope, packageName); packageScope.setContainingScope(fileScope); PackageDefinition packageDefinition = new PackageDefinition(packageName); packageDefinition.setContainedScope(packageScope); fileScope.addDefinition(packageDefinition); for(IDefinition def : definitions) { Multiname mname = Multiname.crackDottedQName(getProject(), def.getQualifiedName()); INamespaceDefinition packageNS = Iterables.getOnlyElement(mname.getNamespaceSet()); ClassDefinition classDefinition = new ClassDefinition(mname.getBaseName(), (INamespaceReference)packageNS); IReference baseClass = ReferenceFactory.packageQualifiedReference(getProject().getWorkspace(), getFlexProject().getResourceBundleClass()); classDefinition.setBaseClassReference(baseClass); classDefinition.setExcludedClass(); TypeScope classScope = new TypeScope(packageScope, classDefinition); classScope.setContainingDefinition(classDefinition); classDefinition.setContainedScope(classScope); classDefinition.setupThisAndSuper(); packageScope.addDefinition(classDefinition); } return fileScope; } /** * Generates abc for this compilation unit and the specified locale. AS * equivalent of a generated class looks like this: * * ---------------- * package mypackage * { * import mx.resources.ResourceBundle; * * public class en_US$myfile_properties extends ResourceBundle * { * public function en_US$myfile_properties() { * super("en_US", "myfile"); * } * * override protected function getContent():Object { * return { * "name": "Falcon", * "version": "1.0", * "motto": "Awesome '{0}' ever.", * "classref": org.apache.flex.foo * "embededAsset" : embed_properties_awesome_jpg_1808423157 * }; * } * } * } * -------------- * * @param emitter emitter object to use to generate abc code * @param fileNode file node of this compilation unit * @param qualifiedClassName qualified name of the class to generate abc for * @param bundleName name of the properties file processed by this compilation unit * @param locale locale of the properties file * @param problems problems collection that is used to collect problems */ private void generateABCForBundle(final ABCEmitter emitter, final ResourceBundleFileNode fileNode, final String qualifiedClassName, final String bundleName, final String locale, final Collection<ICompilerProblem> problems) { FlexProject project = getFlexProject(); //this class extends "mx.resources.ResourceBundle" IResolvedQualifiersReference resourceBundleReference = ReferenceFactory.packageQualifiedReference( project.getWorkspace(), project.getResourceBundleClass()); //Create constructor instruction list InstructionList constructorInstructionList = new InstructionList(); constructorInstructionList.addInstruction(ABCConstants.OP_getlocal0); constructorInstructionList.addInstruction(ABCConstants.OP_pushstring, locale); constructorInstructionList.addInstruction(ABCConstants.OP_pushstring, bundleName); constructorInstructionList.addInstruction(ABCConstants.OP_constructsuper, 2); constructorInstructionList.addInstruction(ABCConstants.OP_returnvoid); IResolvedQualifiersReference mainClassRef = ReferenceFactory.packageQualifiedReference( project.getWorkspace(), qualifiedClassName); ClassGeneratorHelper classGen = new ClassGeneratorHelper(project, emitter, mainClassRef.getMName(), (ClassDefinition)resourceBundleReference.resolve(project), Collections.<Name> emptyList(), Collections.<Name> emptyList(), constructorInstructionList, true); //Create method body for getContents InstructionList bodyInstructionList = new InstructionList(); bodyInstructionList.addInstruction(ABCConstants.OP_getlocal0); bodyInstructionList.addInstruction(ABCConstants.OP_pushscope); //Create key value pair entries "key":"value" int entryCount = 0; for (int i = 0; i < fileNode.getChildCount(); i++) { IASNode node = fileNode.getChild(i); if (node instanceof ResourceBundleEntryNode) { entryCount++; ResourceBundleEntryNode entryNode = (ResourceBundleEntryNode)node; //push key bodyInstructionList.addInstruction(ABCConstants.OP_pushstring, entryNode.getKeyNode().getValue()); //push value ExpressionNodeBase valueNode = entryNode.getValueNode(); switch (valueNode.getNodeID()) { case LiteralStringID: bodyInstructionList.addInstruction(ABCConstants.OP_pushstring, ((LiteralNode)valueNode).getValue()); break; case ClassReferenceID: ClassReferenceNode crn = (ClassReferenceNode)valueNode; if (crn.getName() != null) { IResolvedQualifiersReference refClass = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), crn.getName()); if (refClass.resolve(project, crn.getASScope(), DependencyType.EXPRESSION, true) == null) { ICompilerProblem problem = new UnresolvedClassReferenceProblem(crn, crn.getName()); problems.add(problem); } } String className = crn.getName(); if(className == null) { bodyInstructionList.addInstruction(ABCConstants.OP_pushnull); } else { IResolvedQualifiersReference classRef = ReferenceFactory.packageQualifiedReference( project.getWorkspace(), className); bodyInstructionList.addInstruction(ABCConstants.OP_getlex, classRef.getMName()); } break; case EmbedID: EmbedNode embedNode = (EmbedNode)valueNode; try { String name = embedNode.getName(project, problems); IResolvedQualifiersReference embedClassRef = ReferenceFactory.packageQualifiedReference( project.getWorkspace(), name); bodyInstructionList.addInstruction(ABCConstants.OP_getlex, embedClassRef.getMName()); } catch (InterruptedException ex) { problems.add(new CodegenInternalProblem(embedNode, ex)); } break; default: //This shouldn't happen. Should we handle this case by collecting a problem? } } } bodyInstructionList.addInstruction(ABCConstants.OP_newobject, entryCount); bodyInstructionList.addInstruction(ABCConstants.OP_returnvalue); Name getContentsMethodName = new Name(ABCConstants.CONSTANT_Qname, new Nsset(classGen.getProtectedNamespace()), "getContent"); //Create getContents method classGen.addITraitsMethod(getContentsMethodName, Collections.<Name> emptyList(), new Name(IASLanguageConstants.Object), Collections.<Object> emptyList(), false, false, true, bodyInstructionList); classGen.finishScript(); } }