/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.tree.mxml; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.io.FilenameUtils; import org.apache.flex.compiler.common.ISourceLocation; import org.apache.flex.compiler.common.SourceLocation; import org.apache.flex.compiler.internal.mxml.MXMLDialect; import org.apache.flex.compiler.internal.parsing.ISourceFragment; import org.apache.flex.compiler.internal.parsing.SourceFragment; import org.apache.flex.compiler.internal.parsing.as.ASParser; import org.apache.flex.compiler.internal.parsing.as.ASToken; import org.apache.flex.compiler.internal.parsing.as.IncludeHandler; import org.apache.flex.compiler.internal.parsing.as.OffsetLookup; import org.apache.flex.compiler.internal.parsing.as.StreamingASTokenizer; import org.apache.flex.compiler.internal.parsing.mxml.MXMLScopeBuilder; import org.apache.flex.compiler.internal.parsing.mxml.MXMLTokenizer; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.MXMLFileScope; import org.apache.flex.compiler.internal.semantics.PostProcessStep; import org.apache.flex.compiler.internal.tree.as.NodeBase; import org.apache.flex.compiler.internal.tree.as.ScopedBlockNode; import org.apache.flex.compiler.internal.workspaces.Workspace; import org.apache.flex.compiler.mxml.IMXMLData; import org.apache.flex.compiler.mxml.IMXMLTagAttributeData; import org.apache.flex.compiler.mxml.IMXMLNamespaceAttributeData; import org.apache.flex.compiler.mxml.IMXMLTagData; import org.apache.flex.compiler.mxml.IMXMLTextData; import org.apache.flex.compiler.mxml.IMXMLTextData.TextType; import org.apache.flex.compiler.mxml.IMXMLUnitData; import org.apache.flex.compiler.parsing.IASToken; import org.apache.flex.compiler.parsing.IMXMLToken; import org.apache.flex.compiler.parsing.MXMLTokenTypes; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.MXMLAttributeVersionProblem; import org.apache.flex.compiler.problems.MXMLEmptyAttributeProblem; import org.apache.flex.compiler.problems.MXMLOtherLanguageNamespaceProblem; import org.apache.flex.compiler.problems.MXMLPrivateAttributeProblem; import org.apache.flex.compiler.problems.MXMLUnexpectedAttributeProblem; import org.apache.flex.compiler.problems.MXMLUnexpectedTagProblem; import org.apache.flex.compiler.problems.MXMLUnexpectedTextProblem; import org.apache.flex.compiler.problems.MXMLUnknownNamespaceProblem; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.mxml.IMXMLClassDefinitionNode; import org.apache.flex.compiler.tree.mxml.IMXMLDocumentNode; import org.apache.flex.compiler.tree.mxml.IMXMLFileNode; import org.apache.flex.compiler.tree.mxml.IMXMLNode; import org.apache.flex.compiler.tree.mxml.IMXMLSpecifierNode; import org.apache.flex.utils.FilenameNormalization; /** * {@code MXMLNodeBase} is the abstract base class for all MXML nodes in an AST. * <p> * Although the MXML node interfaces (i.e., {@code IMXMLNode} and its * subinterfaces) support only read-only operations on the MXML AST, the MXML * node class contain the logic for building the tree node-by-node from the MXML * DOM. * <p> * For example, the processing of the <Script> tag is handled by the * {@code MXMLScriptNode} class, which encapsulates the logic that a * <Script> tag can only have namespace attributes and a * <code>source</code> attribute, and that it cannot have any child tags. * <p> * This base class handles some analysis that must be done on every kind of MXML * tag: * <ul> * <li>Walking the tag's attributes and its child units.</li> * <li>Noticing whether a namespace attribute defines an illegal language * namespace.</li> * <li>Noticing whether any tag or attribute uses an illegal language namespace. * </li> * <li>Noticing any private attributes. We will report a problem for these, * since developers who don't use them may prefer to catch them as warnings.</li> * </ul> * <p> * Problems at the level of incorrect XML are noticed before tree construction, * during MXML parsing. Such problems include: * <ul> * <li>Using an undefined prefix on a tag or attribute. * <li>Using an unrecognized entity. * </ul> * <p> * The default behavior implemented in this base class reports a problem for * every attribute (except for namespace and private ones) and every * non-whitespace text unit. In other words, by default it implements * "you can't put that here". It is the responsibility then of subclasses to * allow particular attributes and content units rather than disallow them. */ public abstract class MXMLNodeBase extends NodeBase implements IMXMLNode { // TODO Make this class package internal rather than public. /** * This tokenizer is used to implement * {@link #isValidActionScriptIdentifier()}. {@link StreamingASTokenizer} is * <b>not</b> thread-safe, so we need to make a thread-local copy for each * thread. */ private static final ThreadLocal<StreamingASTokenizer> asTokenizer = new ThreadLocal<StreamingASTokenizer>() { protected StreamingASTokenizer initialValue() { return new StreamingASTokenizer(); }; }; /** * This tokenizer is used to implement {@link #isValidXMLTagName()}. * {@link MXMLTokenizer} is <b>not</b> thread-safe, so we need to make a * thread-local copy for each thread. */ private static final ThreadLocal<MXMLTokenizer> mxmlTokenizer = new ThreadLocal<MXMLTokenizer>() { protected MXMLTokenizer initialValue() { return new MXMLTokenizer(); }; }; /** * Resolves the path specified in an MXML tag's <code>source</code> * attribute. * * @param builder The {@code MXMLTreeBuilder} object which is building this * MXML tree. * @param attribute The <code>source</code> attribute. * @return A resolved and normalized path to the external file. */ public static String resolveSourceAttributePath(MXMLTreeBuilder builder, IMXMLTagAttributeData attribute, MXMLNodeInfo info) { if (info != null) info.hasSourceAttribute = true; String sourcePath = attribute.getMXMLDialect().trim(attribute.getRawValue()); if (sourcePath.isEmpty() && builder != null) { ICompilerProblem problem = new MXMLEmptyAttributeProblem(attribute); builder.addProblem(problem); return null; } String resolvedPath; if ((new File(sourcePath)).isAbsolute()) { // If the source attribute specifies an absolute path, // it doesn't need to be resolved. resolvedPath = sourcePath; } else { // Otherwise, resolve it relative to the MXML file. String mxmlPath = attribute.getParent().getParent().getPath(); resolvedPath = FilenameUtils.getPrefix(mxmlPath) + FilenameUtils.getPath(mxmlPath); resolvedPath = FilenameUtils.concat(resolvedPath, sourcePath); } String normalizedPath = FilenameNormalization.normalize(resolvedPath); // Make the compilation unit dependent on the external file. if (builder != null) builder.getFileScope().addSourceDependency(normalizedPath); return normalizedPath; } /** * Determines whether the specified String is a single valid ActionScript * identifier, by tokenizing it. * * @param identifier A String to be tokenized. * @return <code>true</code> if the String tokenizes to a single identifier * token. */ protected static boolean isValidASIdentifier(String identifier) { IASToken[] tokens = asTokenizer.get().getTokens(identifier); return tokens != null && tokens.length == 1 && tokens[0].getType() == ASToken.TOKEN_IDENTIFIER; } /** * Determines whether the specified String is a valid XML tag name * by appending it to "<" and tokenizing it. * * @param tagName A String proposed as a tag name. * @return <code>true</code> if the String is a valid tag name. */ protected static boolean isValidXMLTagName(String tagName) { String s = "<" + tagName; IMXMLToken[] tokens = mxmlTokenizer.get().getTokens(s + "/>"); return tokens != null && tokens.length == 2 && tokens[0].getType() == MXMLTokenTypes.TOKEN_OPEN_TAG_START && tokens[0].getText().equals(s) && tokens[1].getType() == MXMLTokenTypes.TOKEN_EMPTY_TAG_END; } /** * Constructor. * * @param parent The parent node of this node, or <code>null</code> if there * is no parent. */ MXMLNodeBase(NodeBase parent) { this.parent = parent; } private boolean validForCodeGen = true; @Override public boolean isValidForCodeGen() { return validForCodeGen; } void markInvalidForCodeGen() { validForCodeGen = false; } /** * Initializes this MXML node from an MXML tag by processing the attribute * and content units of the tag. This processing sets the properties of this * node and creates the children of this node. The child nodes will * themselves be initialized from attributes or child tags; in this way, the * MXML AST will get constructed from the MXML DOM. * * @param builder The {@code MXMLTreeBuilder} object which is building this * MXML tree. * @param tag The MXML tag from which this MXML node is being created. */ protected void initializeFromTag(MXMLTreeBuilder builder, IMXMLTagData tag) { setLocation(tag); MXMLNodeInfo info = createNodeInfo(builder); // Process each attribute. for (IMXMLTagAttributeData attribute : tag.getAttributeDatas()) { processAttribute(builder, tag, attribute, info); } // Process each content unit. for (IMXMLUnitData unit = tag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) { processContentUnit(builder, tag, unit, info); } // Do any final processing. initializationComplete(builder, tag, info); } protected MXMLNodeInfo createNodeInfo(MXMLTreeBuilder builder) { return null; } /** * This method gives subclasses a chance to do final processing after * considering each attribute and content unit. * <p> * The base class version calls <code>adjustOffset</code> to translate the * node start and end offset from local to absolute offsets. */ protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag, MXMLNodeInfo info) { adjustOffsets(builder); } /** * Translates the node's start and end offset from local to absolute * offsets. */ public void adjustOffsets(MXMLTreeBuilder builder) { final MXMLFileScope fileScope = builder.getFileScope(); assert fileScope != null : "Expected MXMLFileScope."; final OffsetLookup offsetLookup = fileScope.getOffsetLookup(); assert offsetLookup != null : "Expected OffsetLookup on file scope."; final String path = getFileSpecification().getPath(); // TODO Get from builder? final int absoluteStart = offsetLookup.getAbsoluteOffset(path, getAbsoluteStart())[0]; final int absoluteEnd = offsetLookup.getAbsoluteOffset(path, getAbsoluteEnd())[0]; setStart(absoluteStart); setEnd(absoluteEnd); } @Override public IASNode getChild(int i) { // By default, MXML nodes have no children. // This base class does not provide child storage. return null; } @Override public int getChildCount() { // By default, MXML nodes have no children. // This base class does not provide child storage. return 0; } @Override public IMXMLClassDefinitionNode getClassDefinitionNode() { return (IMXMLClassDefinitionNode)getAncestorOfType(IMXMLClassDefinitionNode.class); } @Override public IMXMLDocumentNode getDocumentNode() { return (IMXMLDocumentNode)getAncestorOfType(IMXMLDocumentNode.class); } @Override public IMXMLFileNode getFileNode() { return (IMXMLFileNode)getAncestorOfType(IMXMLFileNode.class); } /** * Processes a single attribute during the initialization of this node. * <p> * The default behavior implemented in this base class is to simply call * <code>processNamespaceAttribute()</code> on namespace attributes, * <code>processPrivateAttribute()</code> on private attributes, and * <code>processTagSpecificAttribute()</code> on other attributes. * <p> * Subclass should not need to override this method, so it is private. * * @param builder The {@code MXMLTreeBuilder} object which is building this * MXML tree. * @param tag An {@code IMXMLTagData} object representing the tag. * @param attribute An {@code MXMLTagAttributeData} object representing the * attribute. */ private void processAttribute(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTagAttributeData attribute, MXMLNodeInfo info) { if (attribute instanceof IMXMLNamespaceAttributeData) processNamespaceAttribute(builder, tag, (IMXMLNamespaceAttributeData)attribute); else if (isPrivateAttribute(attribute)) processPrivateAttribute(builder, tag, attribute); else processTagSpecificAttribute(builder, tag, attribute, info); } /** * Processes a single namespace attribute such as xmlns:foo="..." or * xmlns="...". * <p> * The default behavior implemented in this base class is to check whether * the namespace URI is a language URI that is different from that of the * overall MXML document. (E.g., the document is in MXML 2009 but then you * use MXML 2006 somewhere inside.) * <p> * Subclasses do not need to override this method, so it is private. * * @param attribute An {@code MXMLNamespaceAttributeData} object * representing the attribute. */ private void processNamespaceAttribute(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLNamespaceAttributeData attribute) { String attributeURI = attribute.getNamespace(); IMXMLData mxmlData = attribute.getParent().getParent(); String languageURI = mxmlData.getMXMLDialect().getLanguageNamespace(); if (MXMLDialect.isLanguageNamespace(attributeURI) && !attributeURI.equals(languageURI)) { ICompilerProblem problem = new MXMLOtherLanguageNamespaceProblem(attribute); builder.addProblem(problem); } } /** * Determines whether the specified attribute is a private attribute. * <p> * MXML 2006 does not have the concept of private attributes. In MXML 2009, * a private attribute is one whose URI is neither the document's language * URI nor the URI of the attribute's tag. * <p> * * @param attribute An {@code MXMLNamespaceAttributeData} object * representing the attribute. * @return <code>true</code> if the attribute is a private attribute and * <code>false</code> otherwise. */ private static boolean isPrivateAttribute(IMXMLTagAttributeData attribute) { String attributeURI = attribute.getURI(); if (attributeURI == null) return false; String tagURI = attribute.getParent().getURI(); IMXMLData mxmlData = attribute.getParent().getParent(); MXMLDialect mxmlDialect = mxmlData.getMXMLDialect(); String languageURI = mxmlDialect.getLanguageNamespace(); boolean isPrivate = false; if (mxmlDialect.isEqualToOrAfter(MXMLDialect.MXML_2009)) { isPrivate = !attributeURI.equals(languageURI) && !attributeURI.equals(tagURI); } return isPrivate; } /** * Processes a single private attribute such as p="...". * <p> * The default behavior implemented in this base class is to report each * private attribute as a "problem". They're allowed by some versions of * MXML, but even in those versions developers may not want to use them and * would prefer to catch typographical mistakes that produce private * attributes. * <p> * Subclasses do not need to override this method so it is private. * * @param attribute An {@code MXMLAttributeData} object representing the * attribute. */ private void processPrivateAttribute(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTagAttributeData attribute) { ICompilerProblem problem = new MXMLPrivateAttributeProblem(attribute); builder.addProblem(problem); } /** * Processes a single tag-specific attribute (i.e., one that isn't allowed * on every tag, unlike a namespace attribute or a private attribute). * <p> * The attribute might be specifying a property, event, or style; or it * might be a special compile-time attribute such as 'id', 'includeIn', * 'excludeFrom', 'source', etc. A property/event/style attribute will cause * a child node to be added to this node; a compile-time attribute typically * will simply set a property of the node. * <p> * The default behavior implemented in this base class is to report each * tag-specific attribute as a problem. * <p> * Subclasses must override this method in order to allow tag-specific * attributes. * * @param attribute An {@code MXMLTagAttributeData} object representing the * attribute. */ protected void processTagSpecificAttribute(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTagAttributeData attribute, MXMLNodeInfo info) { ICompilerProblem problem = new MXMLUnexpectedAttributeProblem(attribute); builder.addProblem(problem); } /** * Processes a single content unit. A content unit can be a child tag, text * (including CDATA or an entity), a comment, or a processing instruction. * <p> * The default behavior implemented in this base class is simply to call * <code>processChildTag()</code> on each child tag, * <code>processChildTextUnit()</code> on each child text unit (including * whitespace, entities, comments, and processing instructions), and * <code>processChildDatabindingUnit()</code> on each child databinding * unit. * <p> * Subclasses do not need to override this method so it is private. * * @param unit An {@code MXMLUnitData} object representing the content unit. */ private void processContentUnit(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLUnitData unit, MXMLNodeInfo info) { if (unit instanceof IMXMLTagData) processChildTag(builder, tag, (IMXMLTagData)unit, info); else if (unit instanceof IMXMLTextData) processChildTextUnit(builder, tag, (IMXMLTextData)unit, info); } /** * Processes a single child tag. * * @param tag An {@code MXMLTagData} object representing the child tag. * <p> * The default behavior implemented in this base class is to report each * child tag as a problem. * <p> * Subclasses must override this method in order to allow the child tags * that they recognize. */ protected void processChildTag(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTagData childTag, MXMLNodeInfo info) { if (childTag.getURI() == null) builder.addProblem(new MXMLUnknownNamespaceProblem(childTag, childTag.getPrefix())); else builder.addProblem(new MXMLUnexpectedTagProblem(childTag)); } /** * Processes a single child text unit. * * @param text An {@code MXMLTextData} object representing the child text * unit. * <p> * The default behavior implemented in this base class is to ignore comments * and to call <code>processChildWhitespaceUnit()</code> if the text (which * might be an entity or a CDATA block) is all whitespace and * <code>processChildNonWhitespaceUnit()</code> otherwise. * <p> * Subclasses do not need to override this method so it is private. */ private void processChildTextUnit(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTextData text, MXMLNodeInfo info) { switch (text.getTextType()) { case TEXT: case WHITESPACE: case CDATA: { if (tag.getMXMLDialect().isWhitespace(text.getCompilableText())) processChildWhitespaceUnit(builder, tag, text, info); else processChildNonWhitespaceUnit(builder, tag, text, info); break; } case COMMENT: case ASDOC: { // ignore these break; } } } /** * Processes a single child text unit which is all whitespace. * <p> * The default behavior implemented in this base class is to ignore * whitespace. * <p> * Subclasses must override this method in order to disallow whitespace text * units. * * @param text An {@code MXMLTextData} object representing the child text * unit. */ protected void processChildWhitespaceUnit(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTextData text, MXMLNodeInfo info) { } /** * Processes a single child text unit which is not all whitespace. * <p> * The default behavior implemented in this base class is to report each * unit of non-whitespace as a problem. * <p> * Subclasses must override this method in order to allow the non-whitespace * text unit. * * @param text An {@code MXMLTextData} object representing the child text * unit. */ protected void processChildNonWhitespaceUnit(MXMLTreeBuilder builder, IMXMLTagData tag, IMXMLTextData text, MXMLNodeInfo info) { ICompilerProblem problem = new MXMLUnexpectedTextProblem(text); builder.addProblem(problem); } /** * Processes all the children of the given {@link IMXMLTagData} unit that are * {@link IMXMLTextData} nodes. Each node will be processes separately, in * the order in which the appear in the document. * <p> * This method is only used by MXML AST building. To parse an ActionScript * block for scope building, see {@link MXMLScopeBuilder#processScriptTag}. * * @param tag the {@link IMXMLTagData} to process * @return a {@link List} of {@link ScopedBlockNode}s for each * {@link IMXMLTextData} we encountered. */ public static List<ScopedBlockNode> processUnitAsAS( MXMLTreeBuilder builder, IMXMLTagData tag, String sourcePath, ASScope containingScope, PostProcessStep buildOrReconnect, IMXMLFileNode ancestorFileNode) { assert buildOrReconnect == PostProcessStep.POPULATE_SCOPE || buildOrReconnect == PostProcessStep.RECONNECT_DEFINITIONS; List<ScopedBlockNode> nodes = new ArrayList<ScopedBlockNode>(2); for (IMXMLUnitData unit = tag.getFirstChildUnit(); unit != null; unit = unit.getNextSiblingUnit()) { if (unit instanceof IMXMLTextData) { final IMXMLTextData mxmlTextData = (IMXMLTextData)unit; if (mxmlTextData.getTextType() != TextType.WHITESPACE) { final Workspace workspace = builder.getWorkspace(); final FlexProject project = builder.getProject(); final Collection<ICompilerProblem> problems = builder.getProblems(); final IncludeHandler includeHandler = new IncludeHandler(builder.getFileSpecificationGetter()); includeHandler.setProjectAndCompilationUnit(project, builder.getCompilationUnit()); final ScopedBlockNode node = ASParser.parseFragment2( mxmlTextData.getCompilableText(), sourcePath, mxmlTextData.getCompilableTextStart(), mxmlTextData.getCompilableTextLine() - 1, mxmlTextData.getCompilableTextColumn() - 1, problems, workspace, builder.getFileNode(), containingScope, project.getProjectConfigVariables(), EnumSet.of(PostProcessStep.CALCULATE_OFFSETS, buildOrReconnect), true, //follow includes includeHandler ); ((MXMLFileNode)ancestorFileNode).updateIncludeTreeLastModified(includeHandler.getLastModified()); nodes.add(node); } } } return nodes; } /** * Sets the start/end/line/column location information for this node. * * @param start The starting offset of this node. * @param end The ending offset of this node. * @param line The number of the line on which this node starts. * @param column This number of the column at which this node starts. */ public void setLocation(String sourcePath, int start, int end, int line, int column) { setSourcePath(sourcePath); setStart(start); setEnd(end); setLine(line); setColumn(column); } /** * Sets the start/end/line/column location information for this node. * * @param location A {@link SourceLocation} object. */ public void setLocation(ISourceLocation location) { String sourcePath = location.getSourcePath(); int start = location.getStart(); int end = location.getEnd(); int line = location.getLine(); int column = location.getColumn(); setLocation(sourcePath, start, end, line, column); } /** * Sets the start/end/line/column location information for this node. * * @param unit The MXML unit from which this node was created. */ protected void setLocation(IMXMLUnitData unit) { String sourcePath = unit.getSourcePath(); int start = unit.getAbsoluteStart(); int end; if (unit instanceof IMXMLTagData) { IMXMLTagData startTag = (IMXMLTagData)unit; IMXMLTagData endTag = startTag.findMatchingEndTag(); end = endTag != null ? endTag.getAbsoluteEnd() : startTag.getAbsoluteEnd(); } else { end = unit.getAbsoluteEnd(); } int line = unit.getLine(); int column = unit.getColumn(); setLocation(sourcePath, start, end, line, column); } /** * Sets the start/end/line/column location information for this node. * * @param attribute The MXML attribute from which this node was created. */ protected void setLocation(IMXMLTagAttributeData attribute) { String sourcePath = attribute.getSourcePath(); int start = attribute.getAbsoluteStart(); int end = attribute.getAbsoluteEnd(); int line = attribute.getLine(); int column = attribute.getColumn(); setLocation(sourcePath, start, end, line, column); } /** * Sets the start/end/line/column location information for this node so that * it spans a list of MXML content units. * * @param units A list of MXML content units. */ protected void setLocation(MXMLTreeBuilder builder, List<IMXMLUnitData> units) { int n = units.size(); IMXMLUnitData firstUnit = units.get(0); IMXMLUnitData lastUnit = units.get(n - 1); // we only store the open tags in the units // and the end offset should be the end of the last close tag // check this here and fetch the end tag if the last tag is an open and non-empty tag if (lastUnit instanceof IMXMLTagData && lastUnit.isOpenAndNotEmptyTag()) { IMXMLUnitData endTag = (IMXMLUnitData)((IMXMLTagData)lastUnit).findMatchingEndTag(); if (endTag != null) lastUnit = endTag; } String sourcePath = firstUnit.getSourcePath(); int start = firstUnit.getStart(); int end = lastUnit.getEnd(); int line = firstUnit.getLine(); int column = firstUnit.getColumn(); setLocation(sourcePath, start, end, line, column); adjustOffsets(builder); } /** * Accumulates source fragments that are produced by a text unit for later * processing. */ protected void accumulateTextFragments(MXMLTreeBuilder builder, IMXMLTextData text, MXMLNodeInfo info) { Collection<ICompilerProblem> problems = builder.getProblems(); ISourceFragment[] fragments = text.getFragments(problems); info.addSourceFragments(text.getSourcePath(), fragments); } /** * Processes an <code>includeIn="..."</code> or * <code>excludeFrom"..."</code> attribute on an instance tag or a * <code><Reparent></code> tag. */ protected String[] processIncludeInOrExcludeFromAttribute( MXMLTreeBuilder builder, IMXMLTagAttributeData attribute) { MXMLDialect mxmlDialect = builder.getMXMLDialect(); if (mxmlDialect == MXMLDialect.MXML_2006) { ICompilerProblem problem = new MXMLAttributeVersionProblem(attribute, attribute.getName(), "2009"); builder.addProblem(problem); return null; } MXMLClassDefinitionNode classNode = (MXMLClassDefinitionNode)getClassDefinitionNode(); classNode.addStateDependentNode(builder, this); return mxmlDialect.splitAndTrim(attribute.getRawValue()); } /** * MXML node classes can choose to create this object in * <code>createNodeInfo()</code>. It then gets passed to the various methods * involved in initializing one node, so that state can be conveyed between * these methods, rather than having this state stored in fields of the node * which would take up space after the node is completely initialized. * <p> * It is currently used accumulate child nodes and source fragments. */ protected static class MXMLNodeInfo { public MXMLNodeInfo(MXMLTreeBuilder builder) { this.builder = builder; } private MXMLTreeBuilder builder; private List<IMXMLNode> childNodeList = new ArrayList<IMXMLNode>(); private String sourcePath; private List<ISourceFragment> sourceFragmentList = new ArrayList<ISourceFragment>(); /** * A flag that keeps track of whether the tag had a <code>source</code> * attribute. */ public boolean hasSourceAttribute = false; /** * A flag that keeps track of whether the tag had content that would * conflict with the <code>source</code> attribute. For a Script, Style, * or String tag, this is non-whitespace text content. For an XML or * Model tag, this is a child tag. */ public boolean hasDualContent = false; /** * Set of specifiers */ protected Set<String> specifierSet = new HashSet<String>(0); public void addChildNode(IMXMLNode childNode) { childNodeList.add(childNode); if (childNode instanceof IMXMLSpecifierNode) { IMXMLSpecifierNode specifierNode = (IMXMLSpecifierNode)childNode; String suffix = specifierNode.getSuffix() != null ? specifierNode.getSuffix() : ""; specifierSet.add(specifierNode.getName() + '.' + suffix); } } public List<IMXMLNode> getChildNodeList() { return childNodeList; } public void addSourceFragments(String sourcePath, ISourceFragment[] sourceFragments) { this.sourcePath = sourcePath; for (ISourceFragment sourceFragment : sourceFragments) { sourceFragmentList.add(sourceFragment); } } public void clearFragments() { sourceFragmentList.clear(); } public String getSourcePath() { return sourcePath; } public ISourceFragment[] getSourceFragments() { final MXMLFileScope fileScope = builder.getFileScope(); final OffsetLookup offsetLookup = fileScope.getOffsetLookup(); assert offsetLookup != null : "Expected OffsetLookup on FileScope."; for (ISourceFragment fragment : sourceFragmentList) { int physicalStart = fragment.getPhysicalStart(); final int[] absoluteOffsets = offsetLookup.getAbsoluteOffset(sourcePath, physicalStart); ((SourceFragment)fragment).setPhysicalStart(absoluteOffsets[0]); } return sourceFragmentList.toArray(new ISourceFragment[0]); } public SourceLocation getSourceLocation() { int n = sourceFragmentList.size(); if (n == 0) return null; ISourceFragment firstFragment = sourceFragmentList.get(0); ISourceFragment lastFragment = sourceFragmentList.get(n - 1); int start = firstFragment.getPhysicalStart(); int end = lastFragment.getPhysicalStart() + lastFragment.getPhysicalText().length(); int line = firstFragment.getPhysicalLine(); int column = firstFragment.getPhysicalColumn(); return new SourceLocation(sourcePath, start, end, line, column); } /** * Check whether a specifier (attribute or child tag) with the same name and state already exist * @param name name of the specifier (attribute or child tag) * @param stateName name of the state * @return true, if the child already exist, false otherwise */ public boolean hasSpecifierWithName(String name, String stateName) { return specifierSet.contains(name + '.' + stateName); } } }