/*
*
* 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.compiler.mxml;
import flex2.compiler.*;
import flex2.compiler.as3.As3Compiler;
import flex2.compiler.as3.As3Configuration;
import flex2.compiler.as3.BytecodeEmitter;
import flex2.compiler.as3.EmbedExtension;
import flex2.compiler.as3.HostComponentExtension;
import flex2.compiler.as3.SignatureExtension;
import flex2.compiler.as3.StyleExtension;
import flex2.compiler.as3.binding.BindableExtension;
import flex2.compiler.as3.binding.DataBindingExtension;
import flex2.compiler.as3.managed.ManagedExtensionError;
import flex2.compiler.as3.SkinPartExtension;
import flex2.compiler.common.CompilerConfiguration;
import flex2.compiler.io.FileUtil;
import flex2.compiler.io.TextFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.mxml.builder.DocumentBuilder;
import flex2.compiler.mxml.dom.AnalyzerAdapter;
import flex2.compiler.mxml.dom.DocumentNode;
import flex2.compiler.mxml.dom.Node;
import flex2.compiler.mxml.gen.VelocityUtil;
import flex2.compiler.mxml.lang.StandardDefs;
import flex2.compiler.mxml.reflect.TypeTable;
import flex2.compiler.mxml.rep.DocumentInfo;
import flex2.compiler.mxml.rep.MxmlDocument;
import flex2.compiler.util.*;
import macromedia.asc.util.ContextStatics;
import org.apache.flex.forks.velocity.Template;
import org.apache.flex.forks.velocity.VelocityContext;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class handles the second pass of the mxml subcompiler. It
* generates a full implementation and generates byte code.
*
* @author Clement Wong
*
* Changed to extend AbstractSubCompiler to clean up benchmarking code and enable
* embedded compiler benchmarking - bfrazer
*/
class ImplementationCompiler extends flex2.compiler.AbstractSubCompiler
{
private static final String DOC_KEY = "doc";
private static final String COMMENTS_KEY = "processcomment";
private static final String CLASSDEF_TEMPLATE_PATH = "flex2/compiler/mxml/gen/";
private static final String EMPTY_STRING = "";
private boolean processComments = false;
public ImplementationCompiler(MxmlConfiguration mxmlConfiguration,
As3Configuration ascConfiguration,
NameMappings mappings, Transcoder[] transcoders, boolean processComments )
{
this.mxmlConfiguration = mxmlConfiguration;
this.ascConfiguration = ascConfiguration;
this.nameMappings = mappings;
this.processComments = processComments;
mimeTypes = new String[]{MimeMappings.MXML};
generateDocComments = ascConfiguration.doc();
// set up ASC and extensions -- mostly mirrors flex2.tools.WebTierAPI.getCompilers()
asc = new As3Compiler(ascConfiguration);
// signature generation should occur before other extensions can touch the syntax tree.
if ((ascConfiguration instanceof CompilerConfiguration)
// currently, both configs reference same object, and are CompilerConfigurations
&& !((CompilerConfiguration)ascConfiguration).getDisableIncrementalOptimizations())
{
// SignatureExtension was already initialized in flex2.tools.WebTierAPI.getCompilers()
asc.addCompilerExtension(SignatureExtension.getInstance());
}
String gendir = (mxmlConfiguration.keepGeneratedActionScript()? mxmlConfiguration.getGeneratedDirectory() : null);
asc.addCompilerExtension(new EmbedExtension(transcoders, gendir, mxmlConfiguration.showDeprecationWarnings()));
asc.addCompilerExtension(new StyleExtension());
// IMPORTANT!!!! The HostComponentExtension must run before the BindableExtension!!!!
asc.addCompilerExtension(new HostComponentExtension(mxmlConfiguration.reportMissingRequiredSkinPartsAsWarnings()));
asc.addCompilerExtension(new SkinPartExtension());
// add binding extension only when processComments is false.
if(!processComments)
{
asc.addCompilerExtension(new BindableExtension(gendir, mxmlConfiguration.getGenerateAbstractSyntaxTree(), false));
}
asc.addCompilerExtension(new DataBindingExtension(gendir, mxmlConfiguration.showBindingWarnings(),
mxmlConfiguration.getGenerateAbstractSyntaxTree(),
ascConfiguration.getDefine()));
asc.addCompilerExtension(new ManagedExtensionError());
// asc.addCompilerExtension(new flex2.compiler.util.TraceExtension());
}
private As3Configuration ascConfiguration;
private MxmlConfiguration mxmlConfiguration;
private boolean generateDocComments;
private NameMappings nameMappings;
private String[] mimeTypes;
private As3Compiler asc;
As3Compiler getASCompiler()
{
return asc;
}
public boolean isSupported(String mimeType)
{
for (int i = 0; i < mimeTypes.length; i++)
{
if (mimeTypes[i].equals(mimeType))
return true;
}
return false;
}
public String[] getSupportedMimeTypes()
{
return mimeTypes;
}
public Source preprocess(Source source)
{
return source;
}
/**
* Traverse the MXML DOM, building an MxmlDocument object. Then use that object to generate AS3 source code.
* Then parse that source.
* <p>Note that we're guaranteed to have all the types we need to walk the DOM, due to InterfaceCompiler's
* previous traversal which registered the necessary types as dependencies. However, it's still our responsibility
* to e.g. generate imports for Classes that are used in the generated program, and so on.
*/
public CompilationUnit parse1(Source source, SymbolTable symbolTable)
{
CompilationUnit unit = source.getCompilationUnit();
// use TypeTable to do the encapsulation - SymbolTable can be too low-level for MXML...
TypeTable typeTable = (TypeTable) symbolTable.getContext().getAttribute(MxmlCompiler.TYPE_TABLE);
if (typeTable == null)
{
typeTable = new TypeTable(symbolTable, nameMappings, unit.getStandardDefs(),
mxmlConfiguration.getThemeNames());
symbolTable.getContext().setAttribute(MxmlCompiler.TYPE_TABLE, typeTable);
}
/**
* Note: because of the way the Compiler framework works, if it's ever the case that <strong>every type request
* made by a given iteration of InterfaceCompiler.postprocess() fails to resolve, then postprocess() will not
* be reinvoked.<strong> Hence the following.
*/
if (hasUnresolvedNodes(unit))
{
return null;
}
DocumentNode app = (DocumentNode)unit.getSyntaxTree();
assert app != null;
DocumentInfo info = (DocumentInfo)unit.getContext().removeAttribute(MxmlCompiler.DOCUMENT_INFO);
assert info != null;
// build MxmlDocument from MXML DOM
MxmlDocument document = new MxmlDocument(unit, typeTable, info, mxmlConfiguration);
DocumentBuilder builder = new DocumentBuilder(unit, typeTable, mxmlConfiguration, document);
app.analyze(builder);
if (ThreadLocalToolkit.errorCount() > 0)
{
return null;
}
Source genSource;
CompilationUnit ascUnit;
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
genSource = generateImplementationAST(document,
symbolTable.perCompileData,
symbolTable.emitter);
// C: null out MxmlDocument after generation
document.getStylesContainer().setMxmlDocument(null);
document = null;
// C: MXML DOM no longer needed
unit.setSyntaxTree(null);
ascUnit = asc.parse1(genSource, symbolTable);
if (ThreadLocalToolkit.errorCount() > 0)
{
return null;
}
}
else
{
// generate AS3 code
VirtualFile genFile = generateImplementation(document);
// obtain the line number map...
DualModeLineNumberMap lineMap = document.getLineNumberMap();
if (genFile != null && ThreadLocalToolkit.errorCount() == 0)
{
genSource = new Source(genFile, source);
// C: I don't think this is necessary...
genSource.addFileIncludes(source);
}
else
{
return null;
}
// use MxmlLogAdapter to do filtering, e.g. -generated.as -> .mxml, as line -> mxml line, etc...
Logger adapter = new MxmlLogAdapter(original, lineMap);
ThreadLocalToolkit.setLogger(adapter);
// C: null out MxmlDocument after generation
document.getStylesContainer().setMxmlDocument(null);
document = null;
// C: MXML DOM no longer needed
unit.setSyntaxTree(null);
// 6. invoke asc
ascUnit = asc.parse1(genSource, symbolTable);
if (ThreadLocalToolkit.errorCount() > 0)
{
ThreadLocalToolkit.setLogger(original);
return null;
}
unit.getContext().setAttribute(MxmlCompiler.LINE_NUMBER_MAP, lineMap);
// set this so asc can use the line number map to re-map line numbers for debug-mode movies.
ascUnit.getContext().setAttribute(MxmlCompiler.LINE_NUMBER_MAP, lineMap);
}
ThreadLocalToolkit.setLogger(original);
unit.getContext().setAttribute(MxmlCompiler.DELEGATE_UNIT, ascUnit);
List bindingExpressions = (List) unit.getContext().getAttribute(CompilerContext.BINDING_EXPRESSIONS);
ascUnit.getContext().setAttribute(CompilerContext.BINDING_EXPRESSIONS, bindingExpressions);
unit.getSource().addFileIncludes(ascUnit.getSource());
Source.transferMetaData(ascUnit, unit);
Source.transferGeneratedSources(ascUnit, unit);
Source.transferDefinitions(ascUnit, unit);
Source.transferInheritance(ascUnit, unit);
Source.transferExpressions(ascUnit, unit);
// 7. return CompilationUnit
return unit;
}
public void parse2(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Source.transferInheritance(unit, ascUnit);
Logger original = setLogAdapter(unit);
asc.parse2(ascUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
Source.transferAssets(ascUnit, unit);
Source.transferGeneratedSources(ascUnit, unit);
}
public void analyze1(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Logger original = setLogAdapter(unit);
asc.analyze1(ascUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
Source.transferTypeInfo(ascUnit, unit);
Source.transferNamespaces(ascUnit, unit);
}
public void analyze2(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Source.transferDependencies(unit, ascUnit);
Logger original = setLogAdapter(unit);
asc.analyze2(ascUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
Source.transferDependencies(ascUnit, unit);
}
public void analyze3(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Source.transferDependencies(unit, ascUnit);
Logger original = setLogAdapter(unit);
asc.analyze3(ascUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
}
public void analyze4(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
}
else
{
LineNumberMap map = (LineNumberMap) unit.getContext().getAttribute(MxmlCompiler.LINE_NUMBER_MAP);
MxmlLogAdapter adapter = new MxmlLogAdapter(original, map);
adapter.setRenamedVariableMap( (Map) ascUnit.getContext().getAttribute(CompilerContext.RENAMED_VARIABLE_MAP) );
ThreadLocalToolkit.setLogger(adapter);
}
asc.analyze4(ascUnit, symbolTable);
if (ThreadLocalToolkit.errorCount() > 0)
{
ThreadLocalToolkit.setLogger(original);
return;
}
ThreadLocalToolkit.setLogger(original);
Source.transferExpressions(ascUnit, unit);
Source.transferMetaData(ascUnit, unit);
Source.transferLoaderClassBase(ascUnit, unit);
Source.transferClassTable(ascUnit, unit);
Source.transferStyles(ascUnit, unit);
}
public void generate(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit ascUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
}
else
{
LineNumberMap lineNumberMap = (LineNumberMap) unit.getContext().getAttribute(MxmlCompiler.LINE_NUMBER_MAP);
if (lineNumberMap instanceof DualModeLineNumberMap)
{
((DualModeLineNumberMap) lineNumberMap).flushTemp(); // flush all compile-error-only line number mappings
}
Logger adapter = new MxmlLogAdapter(original, lineNumberMap);
ThreadLocalToolkit.setLogger(adapter);
}
asc.generate(ascUnit, symbolTable);
if (ThreadLocalToolkit.errorCount() > 0)
{
ThreadLocalToolkit.setLogger(original);
return;
}
ThreadLocalToolkit.setLogger(original);
Source.transferGeneratedSources(ascUnit, unit);
Source.transferBytecodes(ascUnit, unit);
}
public void postprocess(CompilationUnit unit, SymbolTable symbolTable)
{
// This method is never called, because generate() always produces bytecode, which
// causes CompilerAPI.postprocess() to skip calling the flex2.compiler.mxml.MxmlCompiler
// postprocess() method.
}
/**
*
*/
private boolean hasUnresolvedNodes(CompilationUnit unit)
{
Set checkNodes = (Set)unit.getContext().removeAttribute(MxmlCompiler.CHECK_NODES);
if (checkNodes != null && !checkNodes.isEmpty())
{
for (Iterator iter = checkNodes.iterator(); iter.hasNext(); )
{
Node node = (Node)iter.next();
ThreadLocalToolkit.log(new AnalyzerAdapter.CouldNotResolveToComponent(node.image), unit.getSource());
}
}
return ThreadLocalToolkit.errorCount() > 0;
}
/**
*
*/
private final VirtualFile generateImplementation(MxmlDocument doc)
{
StandardDefs standardDefs = doc.getStandardDefs();
String classDefTemplate = CLASSDEF_TEMPLATE_PATH + standardDefs.getClassDefTemplate();
String classDefLibTemplate = CLASSDEF_TEMPLATE_PATH + standardDefs.getClassDefLibTemplate();
// load template
Template template = VelocityManager.getTemplate(classDefTemplate, classDefLibTemplate);
if (template == null)
{
ThreadLocalToolkit.log(new UnableToLoadTemplate(classDefTemplate));
return null;
}
// evaluate template against document
String genFileName = MxmlCompiler.getGeneratedName(mxmlConfiguration, doc.getPackageName(), doc.getClassName(),
"-generated.as");
Source source = doc.getCompilationUnit().getSource();
// C: I would like to guesstimate this number based on MXML component size...
SourceCodeBuffer out = new SourceCodeBuffer((int) (source.size() * 4));
try
{
DualModeLineNumberMap lineMap = new DualModeLineNumberMap(source.getNameForReporting(), genFileName);
doc.setLineNumberMap(lineMap);
VelocityUtil util = new VelocityUtil(CLASSDEF_TEMPLATE_PATH, mxmlConfiguration.debug(), out, lineMap);
VelocityContext vc = VelocityManager.getCodeGenContext(util);
vc.put(DOC_KEY, doc);
// pass whether to care for comments or not
vc.put(COMMENTS_KEY, processComments);
template.merge(vc, out);
}
catch (Exception e)
{
ThreadLocalToolkit.log(new CodeGenerationException(doc.getSourcePath(), e.getLocalizedMessage()));
return null;
}
// (flush and) return result
if (out.getBuffer() != null)
{
String code = out.toString();
if (mxmlConfiguration.keepGeneratedActionScript())
{
try
{
FileUtil.writeFile(genFileName, code);
}
catch (IOException e)
{
ThreadLocalToolkit.log(new VelocityException.UnableToWriteGeneratedFile(genFileName, e.getLocalizedMessage()));
}
}
// -generated.as should use the originating source timestamp
return new TextFile(code, genFileName, doc.getCompilationUnit().getSource().getParent(),
MimeMappings.AS, doc.getCompilationUnit().getSource().getLastModified());
}
else
{
return null;
}
}
private Source generateImplementationAST(MxmlDocument mxmlDocument, ContextStatics contextStatics,
BytecodeEmitter bytecodeEmitter)
{
String genFileName = MxmlCompiler.getGeneratedName(mxmlConfiguration, mxmlDocument.getPackageName(),
mxmlDocument.getClassName(), "-generated.as");
Source source = mxmlDocument.getCompilationUnit().getSource();
VirtualFile emptyFile = new TextFile(EMPTY_STRING, genFileName, source.getParent(), MimeMappings.AS,
System.currentTimeMillis());
Source result = new Source(emptyFile, source);
ImplementationGenerator implementationGenerator =
new ImplementationGenerator(mxmlDocument, generateDocComments, contextStatics,
result, bytecodeEmitter, ascConfiguration.getDefine(), processComments);
CompilerContext context = new CompilerContext();
context.setAscContext(implementationGenerator.getContext());
Object syntaxTree = implementationGenerator.getSyntaxTree();
result.newCompilationUnit(syntaxTree, context).setSyntaxTree(syntaxTree);
return result;
}
// error messages
public static class UnableToLoadTemplate extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 4986512756206073031L;
public UnableToLoadTemplate(String template)
{
this.template = template;
noPath();
}
public String template;
}
public static class CodeGenerationException extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -5873407973653883428L;
public CodeGenerationException(String template, String message)
{
super();
this.template = template;
this.message = message;
}
public final String template, message;
}
// not needed (we don't expect to ever be a "top level compiler"
public String getName()
{
assert(false);
return null;
}
/**
* pass down a benchmarker to we can account for time in asc compiler
*
*/
public void setHelper(CompilerBenchmarkHelper helper, boolean isEmb)
{
assert(isEmb); // we expect that people will pass down the embedded to us, since we are
// going to turn around and use is as the MAIN benchmarker for asc.
asc.setHelper(helper, false); // Here is the "tricky" bit: we are being passed the embedded helper,
// from above, but we turn anround and pass it to our bemcedded compiler as the "main" helper.
// This
}
private Logger setLogAdapter(CompilationUnit unit)
{
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
}
else
{
LineNumberMap map = (LineNumberMap) unit.getContext().getAttribute(MxmlCompiler.LINE_NUMBER_MAP);
Logger adapter = new MxmlLogAdapter(original, map);
ThreadLocalToolkit.setLogger(adapter);
}
return original;
}
}