blob: abfdbad9670a243f697666cf233f987d6d1cfaa7 [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 org.apache.royale.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.royale.abc.ABCConstants;
import org.apache.royale.abc.ABCEmitter;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.semantics.Nsset;
import org.apache.royale.compiler.problems.UnresolvedClassReferenceProblem;
import org.apache.commons.io.IOUtils;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.Multiname;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.embedding.IEmbedData;
import org.apache.royale.compiler.internal.abc.ClassGeneratorHelper;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.definitions.PackageDefinition;
import org.apache.royale.compiler.internal.embedding.EmbedData;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.projects.DefinitionPriority;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.resourcebundles.PropertiesFileParser;
import org.apache.royale.compiler.internal.resourcebundles.ResourceBundleUtils;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.scopes.PackageScope;
import org.apache.royale.compiler.internal.scopes.TypeScope;
import org.apache.royale.compiler.internal.tree.as.ClassReferenceNode;
import org.apache.royale.compiler.internal.tree.as.EmbedNode;
import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase;
import org.apache.royale.compiler.internal.tree.as.LiteralNode;
import org.apache.royale.compiler.internal.tree.properties.ResourceBundleEntryNode;
import org.apache.royale.compiler.internal.tree.properties.ResourceBundleFileNode;
import org.apache.royale.compiler.internal.units.requests.ABCBytesRequestResult;
import org.apache.royale.compiler.internal.units.requests.FileScopeRequestResultBase;
import org.apache.royale.compiler.internal.units.requests.SWFTagsRequestResult;
import org.apache.royale.compiler.internal.units.requests.SyntaxTreeRequestResult;
import org.apache.royale.compiler.problems.CodegenInternalProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.InternalCompilerProblem;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.targets.ITarget.TargetType;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IFileNodeAccumulator;
import org.apache.royale.compiler.units.requests.IABCBytesRequestResult;
import org.apache.royale.compiler.units.requests.IFileScopeRequestResult;
import org.apache.royale.compiler.units.requests.IOutgoingDependenciesRequestResult;
import org.apache.royale.compiler.units.requests.ISWFTagsRequestResult;
import org.apache.royale.compiler.units.requests.ISyntaxTreeRequestResult;
import org.apache.royale.swc.ISWCFileEntry;
import org.apache.royale.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) ? ((RoyaleProject)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 RoyaleProject getRoyaleProject()
{
return (RoyaleProject)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);
Set<IEmbedData> iembeds = new HashSet<IEmbedData>();
for (EmbedData embed : embeds)
iembeds.add(embed);
return new ABCBytesRequestResult(generatedBytes, problems.toArray(new ICompilerProblem[0]), iembeds);
}
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.
RoyaleProject project = getRoyaleProject();
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(), getRoyaleProject().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": "Royale",
* "version": "1.0",
* "motto": "Awesome '{0}' ever.",
* "classref": org.apache.royale.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)
{
RoyaleProject project = getRoyaleProject();
//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();
}
}