blob: 1bf6536d9354e10b501edf483785ab63e8b47caf [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 flex2.compiler.mxml;
import flash.util.StringUtils;
import flex2.compiler.CompilationUnit;
import flex2.compiler.CompilerBenchmarkHelper;
import flex2.compiler.CompilerContext;
import flex2.compiler.Logger;
import flex2.compiler.Source;
import flex2.compiler.SymbolTable;
import flex2.compiler.abc.AbcClass;
import flex2.compiler.as3.As3Compiler;
import flex2.compiler.as3.As3Configuration;
import flex2.compiler.as3.HostComponentExtension;
import flex2.compiler.as3.SyntaxTreeEvaluator;
import flex2.compiler.as3.binding.ClassInfo;
import flex2.compiler.as3.reflect.NodeMagic;
import flex2.compiler.io.FileUtil;
import flex2.compiler.io.TextFile;
import flex2.compiler.io.VirtualFile;
import flex2.compiler.mxml.analyzer.SyntaxAnalyzer;
import flex2.compiler.mxml.dom.*;
import flex2.compiler.mxml.lang.*;
import flex2.compiler.mxml.reflect.*;
import flex2.compiler.mxml.rep.DocumentInfo.NameInfo;
import flex2.compiler.mxml.rep.BindingExpression;
import flex2.compiler.mxml.rep.DocumentInfo;
import flex2.compiler.mxml.rep.Script;
import flex2.compiler.util.*;
import macromedia.asc.embedding.ConfigVar;
import macromedia.asc.util.ObjectList;
import org.apache.flex.forks.velocity.Template;
import org.apache.flex.forks.velocity.VelocityContext;
import org.apache.flex.forks.velocity.exception.MethodInvocationException;
import org.apache.flex.forks.velocity.exception.ParseErrorException;
import org.apache.flex.forks.velocity.exception.ResourceNotFoundException;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* The tasks of the InterfaceCompiler are:
*
* <p>1. Expose the public signature of our generated MXML class at the same point in the compiler workflow as a native
* AS class would. This means generating "public signature" AS code <strong>immediately</strong> (i.e., for consumption
* in analyze1..N). This public signature must contain:
*
* <br>-typed declarations for any MXML children with id attributes;
* <br>-user script code (which might also contain public declarations).
*
* <p>2. "Bring in" all types referred to by component tags, so that MXML DOM traversal, which happens in the
* ImplementationCompiler, has the information it needs to disambiguate child tags.
*
* <p>Example: if a &lt;Button/&gt; tag appears in the document, information contained in the definition for
* mx.controls.Button (assuming that's what &lt;Button/&gt; resolves to) will be necessary for disambiguating properties,
* styles, nested components, etc., in the associated MXML subtree.
*
* <p>The InterfaceCompiler's postprocess() phase uses (local class) DependencyAnalyzer to do this type registration.
*
* <p><strong>Note that not all necessary imports are registered here, only types needed to process the MXML DOM.</strong>
* For instance, the definitions specified by name for Class- and Function-typed properties and styles need to be imported,
* but they are not necessary for DOM processing. They're registered as imports later, <strong>during</strong> DOM
* processing, as the properties etc. are actually processed (see e.g. AbstractBuilder.TextParser's className() handler).
*
* <p>Why not register all such types here? Because values are subject to interpretation (e.g. @Embeds, or
* {bindings}, may appear as rvalues, and have different type implications), and all that sort of interpretation happens
* during DOM processing (by the Builders). Ordinary type registration happens naturally at that point; think of the
* registration that happens here as being "early" out of necessity, but as minimal as possible.
* <p>2a. update: the blanket early-class-import requirement due to style trimming has been removed. However, there are
* still early-import dependencies in the Builders (other than for DOM-walking), including some that may be unnecessary.
* One that definitely *is* and will remain necessary is due to generating classname-to-IFactory coercions: the AbstractBuilder
* code introspects the definition associated with the classname in order to generate automatic assignments of properties
* like outerDocument. Others may be brunable, including event type defs and generic imports of the classes assigned to
* Class-typed properties. TODO: review and narrow the set of import requests generated here to only those that are
* strictly necessary.
*
* <p>At the end of the InterfaceCompiler phases, the workflow switches to ImplementationCompiler for generation of the
* complete AS code. (As noted above, the additional dependencies outside those needed purely for the document's
* component tags, are detected and registered during that phase.)
*
* Changed to extend AbstractSubCompiler to clean up benchmarking code and enble
* embedded compiler benchmarking - bfrazer
*
* Changed class to public so that it can be used directly for mxml parsing by other tools. - gauravj
*/
// TODO enable auto-imports as specified by superclass metadata,
// e.g. [Event]. This will require sneaking code generation into the
// beginning of analyze1().
public class InterfaceCompiler extends flex2.compiler.AbstractSubCompiler implements MXMLNamespaces
{
// path to our velocity template
private static final String TemplatePath = InterfaceCompiler.class.getPackage().getName().replace('.', '/') + "/gen/";
// context attributes
private static final String AttrTypeRequests = "TypeRequests";
private static final String AttrInlineComponentSyntaxTree = "InlineComponentSyntaxTree";
private static final String DOCUMENT_NODE = "DocumentNode";
private static final String EMPTY_STRING = "";
private boolean processComments = false;
/**
*
*/
public InterfaceCompiler(final MxmlConfiguration mxmlConfiguration,
final As3Configuration ascConfiguration,
NameMappings mappings, boolean processComments)
{
this.mxmlConfiguration = mxmlConfiguration;
this.ascConfiguration = ascConfiguration;
this.nameMappings = mappings;
this.processComments = processComments;
mimeTypes = new String[]{MimeMappings.MXML};
asc = new As3Compiler(new As3Configuration()
{
public boolean optimize() { return true; }
public boolean verboseStacktraces() { return false; }
public boolean debug() { return false; }
public boolean strict() { return ascConfiguration.strict(); }
public int dialect() { return ascConfiguration.dialect(); }
public boolean adjustOpDebugLine() { return ascConfiguration.adjustOpDebugLine(); }
public boolean warnings() { return false; }
public boolean doc() { return false; }
public boolean getGenerateAbstractSyntaxTree() { return ascConfiguration.getGenerateAbstractSyntaxTree(); }
public String getEncoding() { return null; }
public boolean metadataExport() { return false; }
public boolean getAdvancedTelemetry() { return false; }
public boolean showDeprecationWarnings() { return false; }
public boolean warn_array_tostring_changes() { return false; }
public boolean warn_assignment_within_conditional() { return false; }
public boolean warn_bad_array_cast() { return false; }
public boolean warn_bad_bool_assignment() { return false; }
public boolean warn_bad_date_cast() { return false; }
public boolean warn_bad_es3_type_method() { return false; }
public boolean warn_bad_es3_type_prop() { return false; }
public boolean warn_bad_nan_comparison() { return false; }
public boolean warn_bad_null_assignment() { return false; }
public boolean warn_bad_null_comparison() { return false; }
public boolean warn_bad_undefined_comparison() { return false; }
public boolean warn_boolean_constructor_with_no_args() { return false; }
public boolean warn_changes_in_resolve() { return false; }
public boolean warn_class_is_sealed() { return false; }
public boolean warn_const_not_initialized() { return false; }
public boolean warn_constructor_returns_value() { return false; }
public boolean warn_deprecated_event_handler_error() { return false; }
public boolean warn_deprecated_function_error() { return false; }
public boolean warn_deprecated_property_error() { return false; }
public boolean warn_duplicate_argument_names() { return false; }
public boolean warn_duplicate_variable_def() { return false; }
public boolean warn_for_var_in_changes() { return false; }
public boolean warn_import_hides_class() { return false; }
public boolean warn_instance_of_changes() { return false; }
public boolean warn_internal_error() { return false; }
public boolean warn_level_not_supported() { return false; }
public boolean warn_missing_namespace_decl() { return false; }
public boolean warn_negative_uint_literal() { return false; }
public boolean warn_no_constructor() { return false; }
public boolean warn_no_explicit_super_call_in_constructor() { return false; }
public boolean warn_no_type_decl() { return false; }
public boolean warn_number_from_string_changes() { return false; }
public boolean warn_scoping_change_in_this() { return false; }
public boolean warn_slow_text_field_addition() { return false; }
public boolean warn_unlikely_function_value() { return false; }
public boolean warn_xml_class_has_changed() { return false; }
public ObjectList<ConfigVar> getDefine() { return ascConfiguration.getDefine(); }
public boolean keepEmbedMetadata() { return false; }
});
asc.addCompilerExtension(new HostComponentExtension(mxmlConfiguration.reportMissingRequiredSkinPartsAsWarnings()));
}
private As3Configuration ascConfiguration;
private MxmlConfiguration mxmlConfiguration;
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)
{
String componentName = source.getShortName();
if (!TextParser.isValidIdentifier(componentName))
{
CompilerMessage m = new InvalidComponentName(componentName);
m.setPath(source.getNameForReporting());
ThreadLocalToolkit.log(m);
}
return source;
}
/**
* Do basic MXML parse, generate AS containing public signature contributors, and parse that using our ASC instance.
* Result will be "outer" compilation unit, with the unit returned by ASC stashed in a context attribute.
*/
public CompilationUnit parse1(Source source, SymbolTable symbolTable)
{
CompilerContext context = new CompilerContext();
// 1. parse/analyze MXML, or retrieve preparsed DOM
// 2. add MXML syntax tree to a new CompilationUnit
DocumentNode app;
CompilationUnit unit;
Object preparsedSyntaxTree = source.getSourceFragment(AttrInlineComponentSyntaxTree);
if (preparsedSyntaxTree == null)
{
app = parseMXML(source);
if (app == null)
{
return null;
}
unit = source.newCompilationUnit(app, context);
// do more syntax checking, chase includes, etc.
app.analyze(new SyntaxAnalyzer(unit, mxmlConfiguration));
if (ThreadLocalToolkit.errorCount() > 0)
{
return null;
}
}
else
{
assert preparsedSyntaxTree instanceof DocumentNode : "bogus preparsed root node passed to InterfaceCompiler";
app = (DocumentNode) preparsedSyntaxTree;
unit = source.newCompilationUnit(app, context);
}
unit.getContext().setAttribute(DOCUMENT_NODE, app);
// start a new DocumentInfo. this will accumulate document state as compilation proceeds
DocumentInfo docInfo = createDocumentInfo(unit, app, source);
if (ThreadLocalToolkit.errorCount() > 0)
{
return null;
}
unit.getContext().setAttribute(MxmlCompiler.DOCUMENT_INFO, docInfo);
unit.topLevelDefinitions.add(new QName(docInfo.getPackageName(), docInfo.getClassName()));
transferDependencies(docInfo, unit.inheritance, unit.inheritanceHistory);
return unit;
}
public void parse2(CompilationUnit unit, SymbolTable symbolTable)
{
DocumentInfo docInfo = (DocumentInfo) unit.getContext().getAttribute(MxmlCompiler.DOCUMENT_INFO);
Source source = unit.getSource();
// get parsed superclass info - NOTE may be null in case of error
ClassInfo baseClassInfo = getClassInfo(source, symbolTable, docInfo.getQualifiedSuperClassName());
if (baseClassInfo == null && docInfo.getQualifiedSuperClassName() != null)
{
String qualifiedClassName = NameFormatter.toDot(docInfo.getPackageName(), docInfo.getClassName());
ThreadLocalToolkit.log(new BaseClassNotFound(qualifiedClassName, docInfo.getQualifiedSuperClassName()), source);
return;
}
// InterfaceAnalyzer will collect items to be included in generated interface code, and add them to info
InterfaceAnalyzer analyzer = new InterfaceAnalyzer(unit, docInfo, baseClassInfo, mxmlConfiguration.getGenerateAbstractSyntaxTree());
DocumentNode app = (DocumentNode) unit.getContext().getAttribute(DOCUMENT_NODE);
app.analyze(analyzer);
if (ThreadLocalToolkit.errorCount() > 0)
{
return;
}
// generate AS for the interface (i.e., public signature) of our class. This will include
// - superclass, interface and metadata declarations, as specified in the MXML
// - public var declarations for id-attributed children of the MXML
// - user-supplied script code
LineNumberMap map = new LineNumberMap(source.getName());
Source newSource;
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
newSource = generateSkeletonAST(docInfo, analyzer.bogusImports, source, symbolTable);
}
else
{
MxmlLogAdapter adapter = new MxmlLogAdapter(original, map);
adapter.addLineNumberMaps(unit.getSource().getSourceFragmentLineMaps());
ThreadLocalToolkit.setLogger(adapter);
newSource = generateSkeleton(docInfo, analyzer.bogusImports, map, source);
if (newSource == null)
{
ThreadLocalToolkit.setLogger(original);
return;
}
map.setNewName(newSource.getName());
}
// use ASC to produce new CU for generated interface source. Will be managed by "outer" MXML CU
CompilationUnit interfaceUnit = compileInterface(newSource, source, docInfo, map, symbolTable);
if (interfaceUnit != null)
{
// transfer includes from the interface unit to the real MXML unit
unit.getSource().addFileIncludes(interfaceUnit.getSource());
// InterfaceUnit, LineNumberMap are used in subsequent phases of InterfaceCompiler
unit.getContext().setAttribute(MxmlCompiler.LINE_NUMBER_MAP, map);
unit.getContext().setAttribute(MxmlCompiler.DELEGATE_UNIT, interfaceUnit);
Source.transferMetaData(interfaceUnit, unit);
// Source.transferAssets(interfaceUnit, unit);
// Source.transferGeneratedSources(interfaceUnit, unit);
}
else
{
ThreadLocalToolkit.setLogger(original);
return;
}
Source.transferInheritance(unit, interfaceUnit);
asc.parse2(interfaceUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
}
/**
* run asc.analyze1() on unit's private AS unit representing the public signature, shuttling results back to outer unit
*/
public void analyze1(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit interfaceUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Logger original = setLogAdapter(unit);
asc.analyze1(interfaceUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
Source.transferTypeInfo(interfaceUnit, unit);
Source.transferNamespaces(interfaceUnit, unit);
}
/**
* run asc.analyze1() on unit's private AS unit representing the public signature, shuttling results back to outer unit
*/
public void analyze2(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit interfaceUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Source.transferDependencies(unit, interfaceUnit);
Logger original = setLogAdapter(unit);
asc.analyze2(interfaceUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
Source.transferDependencies(interfaceUnit, unit);
}
public void analyze3(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit interfaceUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
// C: unit.importDefinitionStatements has no bogus statements
// interfaceUnit.importDefinitionStatements may have bogus statements.
QNameSet importDefinitionStatements = new QNameSet(interfaceUnit.importDefinitionStatements);
Source.transferDependencies(unit, interfaceUnit);
// C: But we tell asc that those bogus statements are legitimate.
// Don't try this in ImplementationCompiler!
interfaceUnit.importDefinitionStatements.addAll(importDefinitionStatements);
Logger original = setLogAdapter(unit);
asc.analyze3(interfaceUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
}
/**
* run asc.analyze1() on unit's private AS unit representing the public signature, shuttling results back to outer unit
*/
public void analyze4(CompilationUnit unit, SymbolTable symbolTable)
{
CompilationUnit interfaceUnit = (CompilationUnit) unit.getContext().getAttribute(MxmlCompiler.DELEGATE_UNIT);
Logger original = setLogAdapter(unit);
asc.analyze4(interfaceUnit, symbolTable);
ThreadLocalToolkit.setLogger(original);
for( Map.Entry<String, AbcClass> entry : interfaceUnit.classTable.entrySet() )
{
// Freeeze the clases generated for the interface file, so that we
// can still access them when we are building the generated file.
//System.out.println("Freezing " + entry.getKey());
AbcClass c = entry.getValue();
c.freeze();
}
Source.transferDependencies(interfaceUnit, unit);
Source.transferLoaderClassBase(interfaceUnit, unit);
Source.transferGeneratedSources(interfaceUnit, unit);
Source.transferClassTable(interfaceUnit, unit);
Source.transferStyles(interfaceUnit, unit);
}
public void generate(CompilationUnit unit, SymbolTable symbolTable)
{
// Source.transferBytecodes(interfaceUnit, unit);
}
/**
* Perform stepwise breadth-first traversal of MXML DOM to generate dependency information. At each step, the set
* <code>checkNodes</code> contains nodes that are known to represent component (not property) tags, for which the
* backing classdefs are known to be available in <code>symbolTable</code>.
* <p>
* On each such node N, we run the DependencyAnalyzer to collect the set of N's child
* component (not property) nodes. These are added to the new <code>checkNodes</code>, and names of their backing
* classes are added to the compilation unit's dependencies. As a result of the latter, the classdefs should be
* present in symbolTable by the next time postprocess() is called.
* <p>
* The DependencyAnalyzer will also accumulate information into DocumentInfo for later use, as a side effect of this
* traversal.
*/
public void postprocess(CompilationUnit unit, SymbolTable symbolTable)
{
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);
}
DocumentInfo info = (DocumentInfo)unit.getContext().getAttribute(MxmlCompiler.DOCUMENT_INFO);
@SuppressWarnings("unchecked")
Set<Node> checkNodes = (Set<Node>)unit.getContext().getAttribute(MxmlCompiler.CHECK_NODES);
@SuppressWarnings("unchecked")
Set<MultiName> allTypeRequests = (Set<MultiName>)unit.getContext().getAttribute(AttrTypeRequests);
if (checkNodes == null)
{
// first call to postprocess
// seed checkNodes with root node
checkNodes = new HashSet<Node>();
checkNodes.add(info.getRootNode());
// set up type request record - apparently unit.expressions gets scrubbed of multinames that fail to resolve?
allTypeRequests = new HashSet<MultiName>();
unit.getContext().setAttribute(AttrTypeRequests, allTypeRequests);
}
if (!checkNodes.isEmpty())
{
Set<Node> newCheckNodes = new HashSet<Node>();
Set<MultiName> newTypeRequests = new HashSet<MultiName>();
DependencyAnalyzer analyzer = new DependencyAnalyzer(unit, typeTable, info, newCheckNodes, newTypeRequests, allTypeRequests);
for (Iterator<Node> i = checkNodes.iterator(); i.hasNext(); )
{
i.next().analyze(analyzer);
}
unit.getContext().setAttribute(MxmlCompiler.CHECK_NODES, newCheckNodes);
if (!newTypeRequests.isEmpty())
{
// add new type requests to our memory list
allTypeRequests.addAll(newTypeRequests);
for (MultiName newTypeRequest : newTypeRequests)
{
unit.expressions.add(newTypeRequest);
}
}
}
}
private void transferDependencies(DocumentInfo docInfo, Set<Name> target, MultiNameMap history)
{
target.add(NameFormatter.toMultiName(docInfo.getQualifiedSuperClassName()));
Iterator iterator = docInfo.getInterfaceNames().iterator();
while (iterator.hasNext())
{
NameInfo nameInfo = (NameInfo) iterator.next();
target.add(NameFormatter.toMultiName(nameInfo.getName()));
}
}
/**
* parse MXML source into an DocumentNode
*/
// Changed method to public so that it can be used directly for mxml parsing by other tools (code coverage). - gauravj
public DocumentNode parseMXML(Source source)
{
DocumentNode app = null;
InputStream in = null;
try
{
in = new BufferedInputStream(source.getInputStream());
flex2.compiler.mxml.dom.MxmlScanner s = new flex2.compiler.mxml.dom.MxmlScanner(in, mxmlConfiguration.enableRuntimeDesignLayers(), processComments );
Parser p = new Parser(s);
MxmlVisitor v = new SyntaxTreeBuilder();
p.setVisitor(v);
app = (DocumentNode) p.parseApplication();
// check for MXML 1 namespace - check code is here only so it doesn't run on every node.
// NOTE: of course, you can place this namespace on *any* node, and use it as a legitimate manifest key.
// This error does a special check in the interests of detecting an obvious mistake early.
if (!checkMxmlNamespace(source, app))
{
app = null;
}
}
catch (ParseException ex)
{
Token token = ex.currentToken.next;
// Strip the unhelpful "was expecting ..." part of JavaCC exception message.
String msg = ex.getMessage();
int wasExpecting = msg.indexOf(System.getProperty("line.separator") + "Was expecting");
if (wasExpecting > 0)
{
msg = msg.substring(0, wasExpecting);
// experience tells us that msg is now "Encountered \"...\" at line X, column Y."
// Here we just want to reverse-engineer this so it can be localized, but we also fault to
// printing the unlocalized message if it's not the pattern above.
// Also note that we don't need to get the line/column info from the message; it's in token.
Pattern msgPatt = Pattern.compile("Encountered \"(.*)\" at line.*");
Matcher m = msgPatt.matcher(msg);
if (m.matches())
{
// convert to localized version
String parent = ex.currentToken.image;
String child = ex.currentToken.next.image;
msg = new InvalidToken(parent, child).getLocalizedMessage();
}
}
ThreadLocalToolkit.logError(source.getNameForReporting(), token.beginLine, token.beginColumn, msg);
app = null;
}
catch (Exception ex) // FileNotFoundException, IOException
{
String msg = ex.getMessage();
if (msg == null)
{
StringWriter stwriter = new StringWriter();
PrintWriter pw = new PrintWriter(stwriter);
ex.printStackTrace(pw);
msg = stwriter.toString();
}
ThreadLocalToolkit.logError(source.getNameForReporting(), msg);
app = null;
}
catch (ScannerError err)
{
String msg = err.getReason();
// We want a different error message here, so we check for the one that we'd like to change.
// The message is always in English.
if (msg.equals(flex2.compiler.mxml.dom.MxmlScanner.MarkupNotRecognizedInContent))
{
InvalidCharacterOrMarkup e = new InvalidCharacterOrMarkup();
e.setLine(err.getLineNumber());
e.setColumn(err.getColumnNumber());
ThreadLocalToolkit.log(e, source);
}
else if (msg.equals(flex2.compiler.mxml.dom.MxmlScanner.ReservedPITarget))
{
WhitespaceBeforePI e = new WhitespaceBeforePI();
e.setLine(err.getLineNumber());
e.setColumn(err.getColumnNumber());
ThreadLocalToolkit.log(e, source);
}
else if (msg.equals(flex2.compiler.mxml.dom.MxmlScanner.MarkupNotRecognizedInMisc))
{
InvalidMarkupAfterRootElement e = new InvalidMarkupAfterRootElement();
e.setLine(err.getLineNumber());
e.setColumn(err.getColumnNumber());
ThreadLocalToolkit.log(e, source);
}
else
{
// regexp-based msg traps for localization
Pattern msgPatt;
Matcher m;
// C: I'm not sure if the pattern matcher supports double-byte characters. Let's leave
// this one alone for now...
// 1. The element type "..." must be terminated by the matching end-tag "</...>"
msgPatt = Pattern.compile("The element type \"(.*)\" must be terminated by the matching end-tag \"(.*)\".");
m = msgPatt.matcher(msg);
if (m.matches())
{
msg = new MissingEndTag(m.group(1), m.group(2)).getLocalizedMessage();
}
// additional traps would go here... unless we can localize Xerces, which would be better
ThreadLocalToolkit.logError(source.getNameForReporting(), err.getLineNumber(), err.getColumnNumber(), msg);
}
app = null;
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch (IOException ex)
{
}
}
}
return app;
}
/**
*
*/
private boolean checkMxmlNamespace(Source source, Node node)
{
if (node.getNamespace().equals(MXML_1_NAMESPACE) || node.getNamespace().equals(MXML_2_NAMESPACE))
{
ThreadLocalToolkit.log(new WrongMXMLNamespace(node.getNamespace(), MXML_2009_NAMESPACE), source);
return false;
}
else
{
return true;
}
}
/**
* set up a DocumentInfo for the compilation:
* <li>- initializes everything immediately derivable from root node and source properties
* <li>- populates import names with initial set
* <p>InterfaceAnalyzer then adds additional stuff from the DOM, prior to interface codegen
*/
private DocumentInfo createDocumentInfo(CompilationUnit unit, DocumentNode app, Source source)
{
StandardDefs standardDefs = unit.getStandardDefs();
DocumentInfo info = new DocumentInfo(source.getNameForReporting(), standardDefs);
// set MXML root
info.setRootNode(app, app.beginLine);
// package/class is derived from source name and location
info.setClassName(source.getShortName());
info.setPackageName(source.getRelativePath().replace('/','.'));
// superclass is derived from root node name
// first, check to see if the base class is a local class
String superClassName = info.getLocalClass(app.getNamespace(), app.getLocalPart());
// otherwise, check the usual manifest mappings
if (superClassName == null)
superClassName = nameMappings.resolveClassName(app.getNamespace(), app.getLocalPart());
if (superClassName != null)
{
info.setQualifiedSuperClassName(NameFormatter.toDot(superClassName), app.beginLine);
}
else
{
ThreadLocalToolkit.log(new AnalyzerAdapter.CouldNotResolveToComponent(app.image), source, app.beginLine);
return null;
}
// interfaces specified by "implements" attribute.
// TODO "implements" is language def, it should be in a list of language constants somewhere
String interfaceNames = (String) app.getAttributeValue("implements");
if (interfaceNames != null)
{
StringTokenizer t = new StringTokenizer(interfaceNames, ",");
while (t.hasMoreTokens())
{
info.addInterfaceName(t.nextToken().trim(), app.getLineNumber("implements"));
}
}
// seed import name set with the unconditional imports present in all generated MXML classes
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
info.addSplitImportNames(StandardDefs.splitImplicitImports);
// See SDK-16946
if (info.getVersion() >= 4)
{
info.removeSplitImportName(NameFormatter.toDotStar(StandardDefs.PACKAGE_FLASH_FILTERS));
info.addSplitImportName(NameFormatter.toDotStar(StandardDefs.PACKAGE_MX_FILTERS),
StandardDefs.splitPackageMxFilters);
}
if (mxmlConfiguration.getCompilingForAIR())
{
info.addSplitImportNames(StandardDefs.splitAirOnlyImplicitImports);
}
info.addSplitImportNames(standardDefs.getSplitStandardMxmlImports());
}
else
{
info.addImportNames(StandardDefs.implicitImports, app.beginLine);
// See SDK-16946
if (info.getVersion() >= 4)
{
info.removeImportName(NameFormatter.toDotStar(StandardDefs.PACKAGE_FLASH_FILTERS));
info.addImportName(NameFormatter.toDotStar(StandardDefs.PACKAGE_MX_FILTERS), app.beginLine);
}
if (mxmlConfiguration.getCompilingForAIR())
{
info.addImportNames(StandardDefs.airOnlyImplicitImports, app.beginLine);
}
info.addImportNames(standardDefs.getStandardMxmlImports(), app.beginLine);
}
return info;
}
/**
*
*/
private static ClassInfo getClassInfo(Source source, SymbolTable symbolTable, String className)
{
// NOTE: make throwaway ASC context, since our real one isn't created until ASC parses the generated code.
macromedia.asc.util.Context tempContext = new macromedia.asc.util.Context(symbolTable.perCompileData);
tempContext.setScriptName(source.getName());
tempContext.setPath(source.getParent());
tempContext.setEmitter(symbolTable.emitter);
tempContext.setHandler(new As3Compiler.CompilerHandler());
symbolTable.perCompileData.handler = tempContext.getHandler();
return symbolTable.getTypeAnalyzer().analyzeClass(tempContext, new MultiName(NameFormatter.toColon(className)));
}
/**
* generate skeleton code based on information in DocumentInfo
*/
private Source generateSkeleton(DocumentInfo info, Set<String> bogusImports, LineNumberMap map, Source source)
{
//long start = System.currentTimeMillis();
String path = source.getName();
StandardDefs standardDefs = info.getStandardDefs();
String templateName = TemplatePath + standardDefs.getInterfaceDefTemplate();
Template template = VelocityManager.getTemplate(templateName);
if (template != null)
{
try
{
// create a velocity context
VelocityContext velocityContext = VelocityManager.getCodeGenContext();
// SourceCodeBuffer tracks line number change during codegen
SourceCodeBuffer out = new SourceCodeBuffer((int) source.size());
// create SourceCode wrappers for scripts
Set<SourceCode> scriptSet = new LinkedHashSet<SourceCode>();
for (Iterator iter = info.getScripts().iterator(); iter.hasNext(); )
{
Script script = (Script) iter.next();
if (!script.isEmbedded())
{
scriptSet.add(new SourceCode(script.getText(), script.getXmlLineNumber(), out, map));
}
else
{
// use Source.getName() to construct the new VirtualFile name
String n = source.getName().replace('\\', '/') + ":" + script.getXmlLineNumber() + "," + script.getEndXmlLineNumber();
VirtualFile f = new TextFile(script.getText(), n, source.getParent(), MimeMappings.AS, source.getLastModified());
// line number map is for error reporting, so the names must come from error reporting...
LineNumberMap m = new LineNumberMap(source.getNameForReporting(), n);
m.put(script.getXmlLineNumber(), 1, (script.getEndXmlLineNumber() - script.getXmlLineNumber()));
// C: add this so that when unexpected EOF occurs, (last line + 1) maps to the last line
// in the original XML Script block.
m.put(script.getEndXmlLineNumber(), script.getEndXmlLineNumber() - script.getXmlLineNumber() + 1, 1);
// 'n' must match 'n' in the include directive...
source.addSourceFragment(n, f, m);
// 'n' must match 'n' in the addSourceFragment call.
scriptSet.add(new SourceCode("include \"" + n + "\";", script.getXmlLineNumber(), out, map));
}
}
// create SourceCode wrappers for metadata entries
Set<SourceCode> metadataSet = new LinkedHashSet<SourceCode>();
for (Iterator iter = info.getMetadata().iterator(); iter.hasNext(); )
{
Script script = (Script)iter.next();
metadataSet.add(new SourceCode(script.getText(), script.getXmlLineNumber(), out, map));
}
// create SourceCode wrappers for variable declarations
Map<String, SourceCode> varDeclMap = new LinkedHashMap<String, SourceCode>();
for (Iterator iter = info.getVarDecls().values().iterator(); iter.hasNext(); )
{
DocumentInfo.VarDecl varDecl = (DocumentInfo.VarDecl)iter.next();
varDeclMap.put(varDecl.name, new SourceCode(varDecl.className, varDecl.line, out, map));
}
int superClassLineNumber = 1;
Set<SourceCode> importNameSet = new LinkedHashSet<SourceCode>();
for (Iterator i = info.getImportNames().iterator(); i.hasNext();)
{
DocumentInfo.NameInfo importName = (DocumentInfo.NameInfo) i.next();
importNameSet.add(new SourceCode(importName.getName(), importName.getLine(), out, map));
if (importName.getName().equals(info.getQualifiedSuperClassName()))
{
superClassLineNumber = importName.getLine();
}
}
for (Iterator<String> i = bogusImports.iterator(); i.hasNext();)
{
String importName = i.next();
importNameSet.add(new SourceCode(importName, 1, out, map));
}
Set<SourceCode> interfaceNameSet = new LinkedHashSet<SourceCode>();
for (Iterator i = info.getInterfaceNames().iterator(); i.hasNext();)
{
DocumentInfo.NameInfo interfaceName = (DocumentInfo.NameInfo) i.next();
interfaceNameSet.add(new SourceCode(interfaceName.getName(), interfaceName.getLine(), out, map));
}
// register values
velocityContext.put("imports", importNameSet);
velocityContext.put("variables", varDeclMap.entrySet());
velocityContext.put("scripts", scriptSet);
velocityContext.put("classMetaData", metadataSet);
velocityContext.put("bindingManagementVariables", FrameworkDefs.bindingManagementVars);
// C: should really give line number mappings to superclass name and interface names.
velocityContext.put("superClassName", new SourceCode(info.getQualifiedSuperClassName(), superClassLineNumber, out, map));
velocityContext.put("interfaceNames", interfaceNameSet);
velocityContext.put("className", info.getClassName());
velocityContext.put("packageName", info.getPackageName());
// run the template!
//long s2 = System.currentTimeMillis();
//VelocityManager.parseTime += s2 - start;
template.merge(velocityContext, out);
//VelocityManager.mergeTime += System.currentTimeMillis() - s2;
// Normalize line endings as a temporary work around for bug 149821
String generated = out.toString().replaceAll("\r\n", "\n");
String filename = writeGenerated(info, generated);
TextFile textFile = new TextFile(generated, filename, source.getParent(),
MimeMappings.AS, source.getLastModified());
return new Source(textFile, source);
}
catch (ResourceNotFoundException ex)
{
ThreadLocalToolkit.logError(path, FileUtil.getExceptionMessage(ex));
}
catch (ParseErrorException ex)
{
ThreadLocalToolkit.logError(path, FileUtil.getExceptionMessage(ex));
}
catch (MethodInvocationException ex)
{
ThreadLocalToolkit.logError(path, FileUtil.getExceptionMessage(ex));
}
catch (Exception ex)
{
ThreadLocalToolkit.logError(path, FileUtil.getExceptionMessage(ex));
}
}
return null;
}
private Source generateSkeletonAST(DocumentInfo info, Set<String> bogusImports,
Source source, SymbolTable symbolTable)
{
String fileName = MxmlCompiler.getGeneratedName(mxmlConfiguration, info.getPackageName(),
info.getClassName(), "-interface.as");
TextFile textFile = new TextFile(EMPTY_STRING, fileName, source.getName(), source.getParent(),
MimeMappings.MXML, source.getLastModified());
Source result = new Source(textFile, source);
InterfaceGenerator interfaceGenerator = new InterfaceGenerator(info, bogusImports,
symbolTable.perCompileData,
result,
symbolTable.emitter,
ascConfiguration.getDefine());
CompilerContext context = new CompilerContext();
context.setAscContext(interfaceGenerator.getContext());
Object syntaxTree = interfaceGenerator.getSyntaxTree();
result.newCompilationUnit(syntaxTree, context).setSyntaxTree(syntaxTree);
return result;
}
// TODO return real-looking filename only if file is actually created
private String writeGenerated(DocumentInfo info, String generated) throws IOException
{
String filename = MxmlCompiler.getGeneratedName(mxmlConfiguration, info.getPackageName(), info.getClassName(),
"-interface.as");
if (mxmlConfiguration.keepGeneratedActionScript())
{
new File(filename).getParentFile().mkdirs();
FileUtil.writeFile(filename, generated);
}
return filename;
}
/**
* compile generated interface code. returns new CU as returned by ASC
*/
private CompilationUnit compileInterface(Source newSource, Source origSource, DocumentInfo info,
LineNumberMap map, SymbolTable symbolTable)
{
// set the current logger to the one with line number mapping support
Logger original = ThreadLocalToolkit.getLogger();
if (mxmlConfiguration.getGenerateAbstractSyntaxTree())
{
Logger adapter = new AbstractSyntaxTreeLogAdapter(original);
ThreadLocalToolkit.setLogger(adapter);
}
else
{
MxmlLogAdapter adapter = new MxmlLogAdapter(original, map);
adapter.addLineNumberMaps(origSource.getSourceFragmentLineMaps());
ThreadLocalToolkit.setLogger(adapter);
}
CompilationUnit interfaceUnit = asc.parse1(newSource, symbolTable);
// performance: strip non-signature code from parse tree; it's not needed in the Interface pass
SyntaxTreeEvaluator.removeNonAPIContent(interfaceUnit);
// make sure management vars only occur once in class chain
SyntaxTreeEvaluator.stripRedeclaredManagementVars(interfaceUnit, info.getQName().toString(), symbolTable);
// metadata post-processing
if (!mxmlConfiguration.getGenerateAbstractSyntaxTree() &&
(interfaceUnit != null) && (info != null))
{
List md = info.getMetadata();
if (md.size() > 0)
{
// ensure <mx:Metadata> contains only [metadata] annotations
int[] beginLines = new int[md.size()], endLines = new int[md.size()];
for (int i = 0, size = md.size(); i < size; i++)
{
Script script = (Script) md.get(i);
beginLines[i] = script.getXmlLineNumber();
endLines[i] = script.getEndXmlLineNumber();
}
NodeMagic.metaDataOnly(interfaceUnit, map, beginLines, endLines);
}
}
if (interfaceUnit != null)
{
SyntaxTreeEvaluator.ensureMetaDataHasDefinition(interfaceUnit);
}
// reset the current logger to the original one...
ThreadLocalToolkit.setLogger(original);
return interfaceUnit;
}
// not needed (unless we are ever benchmarked as 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 embedded compiler as the "main" helper.
}
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);
MxmlLogAdapter adapter = new MxmlLogAdapter(original, map);
adapter.addLineNumberMaps(unit.getSource().getSourceFragmentLineMaps());
ThreadLocalToolkit.setLogger(adapter);
}
return original;
}
/**
* Walk MXML DOM, accumulating public signature (interface) info into associated DocumentInfo.
* <p><strong>NOTE: </strong>since user script blocks may contain elements of the public signature, they must be
* included in the initial "skeleton" codegen. Thus <strong>any implicit imports that are necessary to propertly
* compile user script blocks must be added to info.importNames as a result of this traversal.</strong>
*
* <p>Note: for complete results, analyze() must be initially invoked on an DocumentNode.
*/
private class InterfaceAnalyzer extends AnalyzerAdapter
{
private DocumentInfo docInfo;
private ClassInfo baseClassInfo;
private int repeaterNum;
private int innerClassCount = 0;
private Set<String> innerClassNames = new HashSet<String>();
private Set<String> bogusImports = new HashSet<String>();
private Set<DesignLayerNode> declaredLayers = new HashSet<DesignLayerNode>();
private boolean generateAst;
private InterfaceAnalyzer(CompilationUnit unit, DocumentInfo docInfo, ClassInfo baseClassInfo, boolean generateAst)
{
super(unit, null);
this.docInfo = docInfo;
this.baseClassInfo = baseClassInfo;
this.generateAst = generateAst;
}
// AnalyzerAdapter impl
public void analyze(Node node)
{
boolean inRepeater = false;
String className = nameMappings.resolveClassName(node.getNamespace(), node.getLocalPart());
if (className != null)
{
if (standardDefs.isRepeater(className))
{
repeaterNum++;
inRepeater = true;
}
}
registerVariableForId(node, null);
super.analyze(node);
if (inRepeater)
{
repeaterNum--;
}
// Handle top level design layer nodes.
if (node instanceof DocumentNode)
{
List<DesignLayerNode> layers = ((DocumentNode)node).layerDeclarationNodes;
for (Iterator<DesignLayerNode> i = layers.iterator(); i.hasNext();)
this.registerVariableForId(i.next(), null);
}
}
public void analyze(LayeredNode node)
{
analyze((Node) node);
// Process any associated design layers.
DesignLayerNode layerParent = node.getLayerParent();
while (layerParent != null)
{
if (!declaredLayers.contains(layerParent))
{
registerVariableForId(layerParent, null);
declaredLayers.add(layerParent);
}
layerParent = layerParent.getLayerParent();
}
}
public void analyze(ScriptNode node)
{
if (node.getSourceFile() == null)
{
CDATANode cdata = (CDATANode) node.getChildAt(0);
if (cdata != null)
{
Script script = new Script(cdata.image, cdata.beginLine, cdata.endLine);
script.setEmbeddedScript(true);
docInfo.addScript(script);
}
}
else
{
String source = (String) node.getAttributeValue("source");
if (source != null)
{
Script script = new Script("include \"" + source + "\";", node.beginLine);
docInfo.addScript(script);
}
}
}
public void analyze(MetaDataNode node)
{
CDATANode cdata = (CDATANode) node.getChildAt(0);
if (cdata != null)
{
Script script = new Script(cdata.image, cdata.beginLine, cdata.endLine);
docInfo.addMetadata(script);
}
}
public void analyze(ModelNode node)
{
registerVariableForId(node, NameFormatter.toDot(standardDefs.CLASS_OBJECTPROXY));
}
public void analyze(XMLNode node)
{
if (!node.isE4X())
{
// auto-import XMLUtil in generated source, if we're using XML tags with e4x=false.
docInfo.addImportName(NameFormatter.toDot(standardDefs.CLASS_XMLUTIL), node.beginLine);
}
registerVariableForId(node, NameFormatter.toDot(standardDefs.getXmlBackingClassName(node.isE4X())));
}
public void analyze(XMLListNode node)
{
registerVariableForId(node, NameFormatter.toDot(standardDefs.CLASS_XMLLIST));
}
public void analyze(ArrayNode node)
{
registerVariableForId(node, StandardDefs.CLASS_ARRAY);
super.analyze(node);
}
public void analyze(VectorNode node)
{
if (getLanguageAttribute(node, StandardDefs.PROP_TYPE) != null)
{
registerVariableForId(node, StandardDefs.CLASS_VECTOR);
super.analyze(node);
}
else
{
log(node, node.beginLine, new VectorTypeRequired());
}
}
public void analyze(BindingNode node)
{
}
public void analyze(PrivateNode node)
{
}
public void analyze(StringNode node)
{
registerVariableForId(node, null);
}
public void analyze(NumberNode node)
{
registerVariableForId(node, null);
}
public void analyze(IntNode node)
{
registerVariableForId(node, null);
}
public void analyze(UIntNode node)
{
registerVariableForId(node, null);
}
public void analyze(BooleanNode node)
{
registerVariableForId(node, null);
}
public void analyze(WebServiceNode node)
{
registerVariableForId(node, null);
super.analyze(node);
}
public void analyze(OperationNode node)
{
super.analyze(node);
}
public void analyze(HTTPServiceNode node)
{
registerVariableForId(node, null);
super.analyze(node);
}
public void analyze(RemoteObjectNode node)
{
registerVariableForId(node, null);
super.analyze(node);
}
public void analyze(InlineComponentNode node)
{
createInlineComponentUnit(node);
registerVariableForId(node, standardDefs.INTERFACE_IFACTORY);
// Note: do not traverse contents
}
public void analyze(DeclarationsNode node)
{
super.analyze(node);
}
public void analyze(LibraryNode node)
{
super.analyze(node);
}
public void analyze(DefinitionNode node)
{
createDefinitionUnit(node);
//Note: do not traverse contents here
}
public void analyze(StateNode node)
{
this.processStateNode(node);
registerVariableForId(node, null);
super.analyze(node);
}
protected void traverse(Node node)
{
for (int i = 0; i < node.getChildCount(); i++)
{
Node child = (Node) node.getChildAt(i);
child.analyze(this);
// We strip ScriptNode instances from the node tree as
// we process them, to avoid having to trip over them
// in later compilation stages (for example, when type
// checking value nodes).
if (child instanceof ScriptNode)
{
node.replaceNode(i, Collections.<Token>emptyList());
i -= 1;
}
}
}
private void registerVariableForId(Node node, String className)
{
String id = null;
int line = 0;
Attribute attr = getLanguageAttribute(node, StandardDefs.PROP_ID);
if (attr != null)
{
id = (String)attr.getValue();
line = attr.getLine();
}
registerVariable(node, id, line, className);
}
/**
* Note: if a variable is declared, an import for the declared type will be automatically added to docInfo.
* Caller only needs to explicitly add *other* imports that may be needed (e.g. XMLUtil for XMLNodes.)
*/
private void registerVariable(Node node, String id, int line, String className)
{
if (id != null)
{
if (!TextParser.isValidIdentifier(id))
{
log(node, line, new InvalidIdentifier(id));
}
else if (docInfo.containsVarDecl(id))
{
log(line, new IdentifierUsedMoreThanOnce(id));
}
else if (docInfo.getClassName().equals(id))
{
log(line, new IdentifierMatchesClassName(id));
}
else
{
if (className == null)
{
className = nameMappings.resolveClassName(node.getNamespace(), node.getLocalPart());
}
// We use Array as the type when inside a Repeater.
if (repeaterNum > 0)
{
className = SymbolTable.ARRAY;
}
if (className != null)
{
if (baseClassInfo == null ||
!(baseClassInfo.definesVariable(id) ||
baseClassInfo.definesGetter(id, true) ||
baseClassInfo.definesSetter(id, true)))
{
if (className.equals(StandardDefs.CLASS_VECTOR))
{
docInfo.addVectorVarDecl(id, line, (String) node.getAttribute(StandardDefs.PROP_TYPE).getValue());
}
else
{
docInfo.addVarDecl(id, NameFormatter.toDot(className), line);
}
}
else
{
// logInfo("base class '" + baseClassInfo.getClassName() + "' defines var/get/set '" + id +"', not declaring");
}
}
else
{
log(line, new CouldNotResolveToComponent(node.image));
}
}
}
else
{
// C: This else part tries to add import statements to the *-interface.as files.
// It's okay to add them, as long as we trick asc to believe that they're not
// bogus imports, even though some of them are in fact bogus. Please see
// InterfaceCompiler.analyze3() to find out how we trick asc.
if (className == null)
{
className = nameMappings.resolveClassName(node.getNamespace(), node.getLocalPart());
}
if (className != null && className.indexOf('*') == -1)
{
bogusImports.add(NameFormatter.toDot(className));
}
}
}
/**
*
*/
private void createInlineComponentUnit(InlineComponentNode node)
{
Node componentRoot = (Node) node.getChildAt(0);
// TODO central place for MXML language constants
String className = getInnerClassName(node.getAttribute(InlineComponentNode.CLASS_NAME_ATTR));
// qualify inline component classname with specifying component's package
QName classQName = new QName(docInfo.getPackageName(), className);
docInfo.addImportName(NameFormatter.toDot(classQName), node.beginLine);
// save classname to the inline component node
// TODO ideally, we could just convert this in-place to a ClassNode for downstream processing
// Good example of why an explicit type descriptor enum is generally more useful than subclassing
node.setClassQName(classQName);
// Create a new Source for the node.
VirtualFile virtualFile = new TextFile("", unit.getSource().getName() + "$" + className,
unit.getSource().getName(), unit.getSource().getParent(),
MimeMappings.MXML, unit.getSource().getLastModified());
Source source = new Source(virtualFile, unit.getSource(), className, false, false);
// Set the Source's syntax tree to the DocumentNode
// equivalent of the grandchild, so that the text
// representation won't have to be recreated and reparsed.
DocumentNode inlineDocumentNode =
DocumentNode.inlineDocumentNode(componentRoot.getNamespace(), componentRoot.getLocalPart(),
NameFormatter.toDot(docInfo.getPackageName(), docInfo.getClassName()));
inlineDocumentNode.beginLine = componentRoot.beginLine;
inlineDocumentNode.beginColumn = componentRoot.beginColumn;
inlineDocumentNode.endLine = componentRoot.endLine;
inlineDocumentNode.endColumn = componentRoot.endColumn;
inlineDocumentNode.image = componentRoot.image;
// Inline components are line inner classes, so there need to be suppressed in the asdoc.
if(generateAst)
{
inlineDocumentNode.comment = "<description><![CDATA[]]></description><private><![CDATA[]]></private>";
}
else
{
inlineDocumentNode.comment = "@private";
}
componentRoot.copy(inlineDocumentNode);
inlineDocumentNode.setLocalClassMappings(docInfo.getLocalClassMappings());
inlineDocumentNode.setLanguageNamespace(docInfo.getLanguageNamespace());
inlineDocumentNode.setVersion(docInfo.getVersion());
addExcludeClassNode(inlineDocumentNode, componentRoot);
source.addSourceFragment(AttrInlineComponentSyntaxTree, inlineDocumentNode, null);
unit.addGeneratedSource(classQName, source);
}
/**
* Adds ExcludeClass Metadata to inline_component nodes.
*/
private void addExcludeClassNode(DocumentNode inlineDocumentNode, Node componentRoot)
{
MetaDataNode inlineExcludeNode =
new MetaDataNode(componentRoot.getNamespace(), componentRoot.getLocalPart(), 0);
inlineExcludeNode.image = componentRoot.image;
CDATANode excludeTextNode = new CDATANode();
excludeTextNode.image = "[ExcludeClass]";
inlineExcludeNode.addChild(excludeTextNode);
inlineDocumentNode.addChild(inlineExcludeNode);
}
/**
* A Library Definition - equivalent to an inline private class
* definition that is dynamically generated and can be used elsewhere
* in the document by name.
*/
private void createDefinitionUnit(DefinitionNode node)
{
Node definitionRoot = (Node) node.getChildAt(0);
// We don't use getInnerClassName() here, because for
// definitions, it was deemed more important to make them
// document private than to be able to reference them from
// Script. When we have inner class support, we should be
// able to do both. See SDK-24229, SDK-24228, SDK-24224,
// and SDK-23662.
String nameAttr = (String)node.getAttributeValue(DefinitionNode.DEFINITION_NAME_ATTR);
String className = docInfo.getClassName() + "_definition" + (innerClassCount++);
// Qualify definition classname with language namespace
QName classQName = new QName(docInfo.getPackageName(), className);
// Register the local class mapping...
docInfo.addLocalClass(node.getNamespace(), nameAttr, classQName.toString());
// save classname to the inline component node
// TODO ideally, we could just convert this in-place to a ClassNode for downstream processing
// Good example of why an explicit type descriptor enum is generally more useful than subclassing
node.setName(classQName);
// Create a new Source for the node.
VirtualFile virtualFile = new TextFile("", unit.getSource().getName() + "$" + className,
unit.getSource().getName(), unit.getSource().getParent(),
MimeMappings.MXML, unit.getSource().getLastModified());
Source source = new Source(virtualFile, unit.getSource(), className, false, false);
// Set the Source's syntax tree to the DocumentNode
// equivalent of the grandchild, so that the text
// representation won't have to be recreated and reparsed.
DocumentNode definitionDocumentNode = new DocumentNode(definitionRoot.getNamespace(), definitionRoot.getLocalPart());
definitionDocumentNode.beginLine = definitionRoot.beginLine;
definitionDocumentNode.beginColumn = definitionRoot.beginColumn;
definitionDocumentNode.endLine = definitionRoot.endLine;
definitionDocumentNode.endColumn = definitionRoot.endColumn;
definitionDocumentNode.image = definitionRoot.image;
// anything defined using the Library Definition is only accesible within the class. So it should always have a private comment.
if(generateAst)
{
definitionDocumentNode.comment = "<description><![CDATA[]]></description><private><![CDATA[]]></private>";
}
else
{
definitionDocumentNode.comment = "@private";
}
definitionRoot.copy(definitionDocumentNode);
definitionDocumentNode.setLocalClassMappings(docInfo.getLocalClassMappings());
definitionDocumentNode.setLanguageNamespace(docInfo.getLanguageNamespace());
definitionDocumentNode.setVersion(docInfo.getVersion());
source.addSourceFragment(AttrInlineComponentSyntaxTree, definitionDocumentNode, null);
unit.addGeneratedSource(classQName, source);
}
private String getInnerClassName(Attribute attribute)
{
String result = null;
if (attribute != null)
{
result = (String) attribute.getValue();
// user-specified class name - must be unqualified
if (!TextParser.isValidIdentifier(result))
{
log(attribute.getLine(), new ClassNameInvalidActionScriptIdentifier());
result = null; // let compiler proceed with generated classname
}
else if (innerClassNames.contains(result))
{
log(attribute.getLine(), new ClassNameSpecifiedMoreThanOnce());
result = null; // let compiler proceed with generated classname
}
else
{
innerClassNames.add(result);
}
}
if (result == null)
{
result = docInfo.getClassName() + "InnerClass" + (innerClassCount++);
}
return result;
}
/**
* Validate all state and state group identifiers and registers each with our documentInfo.
*/
private void processStateNode(Node node)
{
// Register all state nodes found with our document, will be used for validation
// while processing state-specific nodes and attributes. Ensure that the state
// name is consistent with a valid Flex 4 state identifier.
Attribute stateNameAttr = getLanguageAttribute(node, StandardDefs.PROP_STATE_NAME);
String exclude = (String)getLanguageAttributeValue(node, StandardDefs.PROP_EXCLUDE_STATES);
String include = (String)getLanguageAttributeValue(node, StandardDefs.PROP_INCLUDE_STATES);
String stateName = null;
int line = 0;
if (stateNameAttr != null)
{
stateName = (String)stateNameAttr.getValue();
line = stateNameAttr.getLine();
}
if (TextParser.isValidStateIdentifier(stateName) || (getDocumentVersion() < 4) )
{
if (include == null && exclude == null)
{
docInfo.addStateName(stateName, line);
// Process all state groups referenced for this node.
String groups = (String)node.getAttributeValue(StandardDefs.PROP_STATE_GROUPS);
Collection<String> stateGroups = TextParser.parseStringList(groups);
for (Iterator<String> iter = stateGroups.iterator(); iter.hasNext(); )
{
String groupName = iter.next();
if (TextParser.isValidStateIdentifier(groupName))
docInfo.addStateGroup(groupName, stateName, line);
else
log(node, line, new InvalidIdentifier(groupName));
}
}
else
{
log(node, line, new InvalidStateAttributeUsage(node.getLocalPart()));
}
}
else
{
if (stateName == null)
log( node, line, new StateNameRequired());
else
log(node, line, new InvalidIdentifier(stateName));
}
}
protected int getDocumentVersion()
{
return docInfo.getVersion();
}
protected String getLanguageNamespace()
{
return docInfo.getLanguageNamespace();
}
}
/**
* Walk MXML (sub-)DOM, using already-resolved types to go as far into the tree as you can, examining children at
* each level. When a node is encountered whose backing class is unresolved, put node and classname on a request queue.
* The type will be added to the CU's type dependency set, and you'll be called back on the corresponding node after
* type resolution has been attempted.
*/
private class DependencyAnalyzer extends AnalyzerAdapter
{
private final TypeTable typeTable;
private final DocumentInfo info;
private final Set<Node> checkNodes;
private final Set<MultiName> newTypeRequests;
private final Set<MultiName> allTypeRequests;
private AttributeDependencyScanner attributeDependencyScanner;
private ChildNodeDependencyScanner childNodeDependencyScanner;
private ClassInitializerTextParser classInitializerTextParser;
private DependencyAnalyzer(CompilationUnit unit, TypeTable typeTable, DocumentInfo info,
Set<Node> checkNodes, Set<MultiName> newTypeRequests, Set<MultiName> allTypeRequests)
{
super(unit, null);
this.typeTable = typeTable;
this.info = info;
this.checkNodes = checkNodes;
this.newTypeRequests = newTypeRequests;
this.allTypeRequests = allTypeRequests;
this.attributeDependencyScanner = new AttributeDependencyScanner();
this.childNodeDependencyScanner = new ChildNodeDependencyScanner(typeTable);
this.classInitializerTextParser = new ClassInitializerTextParser();
}
// AnalyzerAdapter impl
public void analyze(Node node)
{
registerDependencies(node);
}
public void analyze(LayeredNode node)
{
analyze((Node) node);
}
public void analyze(CDATANode node) {}
public void analyze(StyleNode node)
{
// TODO register dependencies arising from e,g, object/inline styles
}
public void analyze(ScriptNode node) {}
public void analyze(MetaDataNode node) {}
public void analyze(ModelNode node)
{
requestType(standardDefs.CLASS_OBJECTPROXY, node);
}
public void analyze(XMLNode node)
{
if (!node.isE4X())
{
requestType(standardDefs.CLASS_XMLUTIL, node);
}
requestType(standardDefs.getXmlBackingClassName(node.isE4X()), node);
}
public void analyze(XMLListNode node)
{
requestType(standardDefs.CLASS_XMLLIST, node);
}
public void analyze(BindingNode node) {}
public void analyze(StringNode node) {}
public void analyze(NumberNode node) {}
public void analyze(IntNode node) {}
public void analyze(UIntNode node) {}
public void analyze(BooleanNode node) {}
public void analyze(WebServiceNode node)
{
registerDependencies(node);
}
public void analyze(StateNode node)
{
registerDependencies(node);
}
public void analyze(HTTPServiceNode node)
{
registerDependencies(node);
}
public void analyze(RemoteObjectNode node)
{
registerDependencies(node);
}
public void analyze(OperationNode node)
{
registerDependencies(node, standardDefs.getConvertedTagName(node));
}
public void analyze(RequestNode node) {}
public void analyze(MethodNode node)
{
registerDependencies(node, standardDefs.getConvertedTagName(node));
}
public void analyze(ArgumentsNode node) {}
public void analyze(InlineComponentNode node)
{
QName classQName = node.getClassQName();
if (classQName == null)
{
log(node, new InlineComponentInternalError());
}
else
{
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_CLASSFACTORY));
newTypeRequests.add(NameFormatter.toMultiName(classQName));
info.addImportName(NameFormatter.toDot(standardDefs.CLASS_CLASSFACTORY), node.beginLine);
info.addImportName(NameFormatter.toDot(classQName), node.beginLine);
}
}
public void analyze(LibraryNode node)
{
super.analyze(node);
}
public void analyze(VectorNode node)
{
String className = (String) getLanguageAttributeValue(node, StandardDefs.PROP_TYPE);
requestType(NameFormatter.toColon(className), node);
}
public void analyze(DefinitionNode node)
{
QName classQName = node.getName();
if (classQName == null)
{
log(node, new DefinitionNodeInternalError());
}
else
{
// Commenting out imports for Library Definitions until we
// solve why the transfer of dependencies does not happen
// correctly... also, these end up generated as local class
// definitions so the import appears unnecessary as the prototype
// is working.
//newTypeRequests.add(NameFormatter.toMultiName(classQName));
//info.addImportName(NameFormatter.toDot(classQName), node.beginLine);
}
}
public void analyze(DeclarationsNode node)
{
super.analyze(node);
}
public void analyze(ReparentNode node) {}
/**
* register dependencies on a class-backed node and its subtree.
*/
private void registerDependencies(Node node)
{
registerDependencies(node, node.getLocalPart());
}
/**
* register dependencies on a class-backed node and its subtree. (this entry point allows overriding node name -
* used when mapping 1.x node names to their associated 2.0 class names)
* <p>
* Per class comments, the logic here accounts for the situation where the compiler may not yet have attempted
* to resolve the backing class. In this instance, analysis is deferred (pending node and typename are added to
* their respective queues). DependencyAnalyzer will get reinvoked on the node after the compiler has attempted
* to resolve the pending types we specify here.
*/
private void registerDependencies(Node node, String localPart)
{
// Check local class mappings
String className = info.getLocalClass(node.getNamespace(), localPart);
// Then check manifest class mappings
if (className == null)
className = nameMappings.resolveClassName(node.getNamespace(), localPart);
if (className == null)
{
// e.g. may be missing from manifest
ThreadLocalToolkit.log(new CouldNotResolveToComponent(node.image), unit.getSource(), node.beginLine);
return;
}
Type type = requestType(className, node);
if (type != null)
{
// name resolves to type def - this gives us enough info to scan attributes and children:
// scan attributes - see note 2a at top of file
for (Iterator iter = node.getAttributeNames(); iter.hasNext() && ThreadLocalToolkit.errorCount() == 0; )
{
attributeDependencyScanner.invoke(node, type, (QName)iter.next());
}
// scan child nodes
childNodeDependencyScanner.scanChildNodes(node, type);
}
}
/**
*
*/
private Type requestType(String className, Node node)
{
// add to import list for later (implementation) codegen
info.addImportName(NameFormatter.toDot(className), node.beginLine);
// request type from typeTable - may or may not have been loaded already
Type type = typeTable.getType(className);
if (type == null)
{
// no type available for this name (yet) - need to register a dependency, or error if one
// has already been requested unsucessfully
MultiName multiName = NameFormatter.toMultiName(className);
if (allTypeRequests.contains(multiName))
{
// type has been requested already, and has failed to resolve
if (node instanceof VectorNode)
{
ThreadLocalToolkit.log(new CouldNotResolveToComponent(className), unit.getSource(), node.beginLine);
}
else
{
ThreadLocalToolkit.log(new CouldNotResolveToComponent(node.image), unit.getSource(), node.beginLine);
}
}
else
{
// queue up new entry for dependency registration
// System.out.println("@ registering dependency on " + multiName);
checkNodes.add(node);
newTypeRequests.add(multiName);
}
}
return type;
}
protected int getDocumentVersion()
{
return info.getVersion();
}
protected String getLanguageNamespace()
{
return info.getLanguageNamespace();
}
/**
* See note 2a at top of file.
*/
protected class AttributeDependencyScanner extends AttributeHandler
{
protected boolean isSpecial(String namespace, String localPart)
{
return false;
}
protected boolean processScopedNames()
{
return true;
}
protected void special(Type type, String namespace, String localPart) {}
protected void event(Event event)
{
requestEventType(event);
}
protected void states(Property property) {}
protected void property(Property property)
{
Type type = property.getType();
if (type.equals(type.getTypeTable().classType) || standardDefs.isInstanceGenerator(type))
{
classInitializerTextParser.parse(text, type);
}
}
protected void dynamicProperty(String name) {}
protected void effect(Effect effect) {}
protected void style(Style style) {}
protected void dynamicProperty(String name, String state) {}
protected void qualifiedAttribute(Node node, Type type, String namespace, String localPart) {}
protected void unknownNamespace(String namespace, String localPart) {}
protected void unknown(String namespace, String localPart) {}
}
/**
* See note 2a at top of file.
*/
protected class ChildNodeDependencyScanner extends ChildNodeHandler
{
protected ChildNodeDependencyScanner(TypeTable typeTable)
{
super(typeTable, MXMLNamespaces.FXG_2008_NAMESPACE.equals(info.getLanguageNamespace()));
}
private void scanGrandchildren()
{
for (Iterator iter = child.getChildIterator(); iter.hasNext(); )
((Node)iter.next()).analyze(DependencyAnalyzer.this);
}
private void scanChild()
{
child.analyze(DependencyAnalyzer.this);
}
// ChildNodeHandler impl
protected void event(Event event)
{
requestEventType(event);
scanGrandchildren();
}
protected void states(Property property)
{
// When supporting implicit generation of overrides (Flex 4 states syntax)
// we ensure the types are requested if we detect a states definition.
if (info.getVersion() >= 4)
{
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_SETPROPERTY));
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_SETEVENTHANDLER));
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_SETSTYLE));
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_ADDITEMS));
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.INTERFACE_IOVERRIDE));
scanGrandchildren();
}
else
{
// Otherwise treat in legacy fashion.
property(property);
}
}
protected void property(Property property)
{
Type type = property.getType();
if (type.equals(type.getTypeTable().classType) || standardDefs.isInstanceGenerator(type))
{
// TODO process <Class>name</Class>
CDATANode cdata = getTextContent(child.getChildren(), true);
if (cdata != null)
{
classInitializerTextParser.parse(cdata.image, type);
}
}
scanGrandchildren();
}
protected void effect(Effect effect)
{
scanGrandchildren();
}
protected void style(Style style)
{
scanGrandchildren();
}
protected void dynamicProperty(String name, String state)
{
if (parent instanceof DocumentNode)
{
// root is never dynamic, but if super is, then nested declarations will trigger a call to this handler.
// TODO mxml.lang.DocumentChildNodeHandler
scanChild();
}
else
{
scanGrandchildren();
}
}
protected void defaultPropertyElement(boolean locError)
{
scanChild();
}
protected void nestedDeclaration()
{
scanChild();
}
protected void textContent()
{
}
protected void languageNode()
{
scanChild();
}
}
/**
*
*/
protected void requestEventType(Event event)
{
if (event.getType() == null)
{
newTypeRequests.add(NameFormatter.toMultiName(event.getTypeName()));
}
}
/**
* here our only purpose is to pick up classnames. Everything else is ignored (including parse errors).
* See note 2a at top of file.
*/
protected class ClassInitializerTextParser extends TextParser
{
ClassInitializerTextParser()
{
super(typeTable);
}
public void parse(String text, Type type)
{
int flags = 0;
// We ignore binding syntax in FXG values
if (MXMLNamespaces.FXG_2008_NAMESPACE.equals(getLanguageNamespace()))
{
flags = flags | TextParser.FlagIgnoreBinding;
}
super.parse(text, type, null, flags);
}
protected BindingExpression parseBindingExpression(String text, int line)
{
return null;
}
// TextParser impl
public String contextRoot(String text)
{
return null;
}
public Object embed(String text, Type type)
{
if (standardDefs.isIFactory(type))
{
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_CLASSFACTORY));
}
return null;
}
public Object clear()
{
return null;
}
public Object resource(String text, Type type)
{
return null;
}
public Object bindingExpression(String converted)
{
return null;
}
public Object bindingExpression(String converted, boolean isTwoWay)
{
return null;
}
public Object percentage(String pct)
{
return null;
}
public Object array(Collection entries, Type arrayElementType)
{
return null;
}
public Object functionText(String text)
{
return null;
}
public Object className(String name, Type type)
{
if (standardDefs.isIFactory(type))
{
newTypeRequests.add(NameFormatter.toMultiName(standardDefs.CLASS_CLASSFACTORY));
}
newTypeRequests.add(NameFormatter.toMultiName(name));
return null;
}
public void error(int err, String text, Type type, Type arrayElementType)
{
}
}
}
/**
* wrapper class associates (string, source line number) with a destination line number set when
* the velocity template invokes toString. items must only be emitted once.
*/
private class SourceCode
{
private SourceCode(String text, int beginLine, SourceCodeBuffer out, LineNumberMap map)
{
this.text = text;
this.beginLine = beginLine;
this.out = out;
this.map = map;
newBeginLine = 0;
lineCount = StringUtils.countLines(text);
}
private String text;
private int beginLine, newBeginLine, lineCount;
private SourceCodeBuffer out;
private LineNumberMap map;
public String toString()
{
if (newBeginLine == 0)
{
newBeginLine = out.getLineNumber();
map.put(beginLine, newBeginLine, lineCount + 1);
// System.out.println("beginLine: " + beginLine + " newBeginLine: " + newBeginLine + " lineCount: " + lineCount);
}
else
{
throw new IllegalStateException("InterfaceGenerator: toString() was called more than once...");
}
return text;
}
}
// error messages
public static class InvalidCharacterOrMarkup extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -2984325226553564468L;
}
public static class WhitespaceBeforePI extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -7174283384299207618L;
}
public static class WrongMXMLNamespace extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 5565644959263774573L;
public WrongMXMLNamespace(String namespace1, String namespace2)
{
super();
this.namespace1 = namespace1;
this.namespace2 = namespace2;
}
public final String namespace1, namespace2;
}
public static class InvalidIdentifier extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 6629930451063326985L;
public InvalidIdentifier(String id)
{
super();
this.id = id;
}
public final String id;
}
public static class IdentifierUsedMoreThanOnce extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -5536047603734582126L;
public IdentifierUsedMoreThanOnce(String id)
{
super();
this.id = id;
}
public final String id;
}
public static class IdentifierMatchesClassName extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 3478059314461282575L;
public String name;
public IdentifierMatchesClassName (String name) { this.name = name; }
}
public static class ClassNameInvalidActionScriptIdentifier extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 480247425987577161L;
public ClassNameInvalidActionScriptIdentifier()
{
super();
}
}
public static class ClassNameSpecifiedMoreThanOnce extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 4442337674062750466L;
public ClassNameSpecifiedMoreThanOnce()
{
super();
}
}
public static class InlineComponentInternalError extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -1304020176499882673L;
public InlineComponentInternalError()
{
super();
}
}
public static class DefinitionNodeInternalError extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 2050837864240302889L;
public DefinitionNodeInternalError()
{
super();
}
}
public static class BaseClassNotFound extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -6143925391370661093L;
public String className, baseClassName;
public BaseClassNotFound(String className, String baseClassName)
{
super();
this.className = className;
this.baseClassName = baseClassName;
}
}
public static class InvalidComponentName extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -4650090768060530717L;
public String name;
public InvalidComponentName(String name)
{
this.name = name;
}
}
public static class InvalidToken extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -3327190119811022784L;
public final String parent;
public final String child;
public InvalidToken(String parent, String child)
{
this.parent = parent;
this.child = child;
}
}
public static class MissingEndTag extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = -6077307706546117384L;
public final String tag, endTag;
public MissingEndTag(String tag, String endTag) { this.tag = tag; this.endTag = endTag; }
}
public static class InvalidMarkupAfterRootElement extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 6607637220533252073L;
}
public static class StateNameRequired extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 6607637220522352073L;
}
public static class VectorTypeRequired extends CompilerMessage.CompilerError
{
private static final long serialVersionUID = 6607637220512352073L;
}
}