blob: 5cfd2d2784e1a5af819e587912cfad108bcc2b53 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.tree.mxml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.royale.compiler.asdoc.IASDocComment;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.IMetaInfo;
import org.apache.royale.compiler.constants.INamespaceConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IClassDefinition.ClassClassification;
import org.apache.royale.compiler.definitions.metadata.IMetaTag;
import org.apache.royale.compiler.internal.definitions.ClassDefinition;
import org.apache.royale.compiler.internal.mxml.MXMLDialect;
import org.apache.royale.compiler.internal.mxml.StateDefinition;
import org.apache.royale.compiler.internal.mxml.StateGroupDefinition;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.MXMLFileScope;
import org.apache.royale.compiler.internal.scopes.TypeScope;
import org.apache.royale.compiler.internal.tree.as.ImportNode;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.mxml.IStateDefinition;
import org.apache.royale.compiler.mxml.IStateGroupDefinition;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.metadata.IMetaTagsNode;
import org.apache.royale.compiler.tree.mxml.IMXMLClassDefinitionNode;
import org.apache.royale.compiler.tree.mxml.IMXMLDeclarationsNode;
import org.apache.royale.compiler.tree.mxml.IMXMLEventSpecifierNode;
import org.apache.royale.compiler.tree.mxml.IMXMLFileNode;
import org.apache.royale.compiler.tree.mxml.IMXMLInstanceNode;
import org.apache.royale.compiler.tree.mxml.IMXMLMetadataNode;
import org.apache.royale.compiler.tree.mxml.IMXMLNode;
import org.apache.royale.compiler.tree.mxml.IMXMLPropertySpecifierNode;
import org.apache.royale.compiler.tree.mxml.IMXMLReparentNode;
import org.apache.royale.compiler.tree.mxml.IMXMLScriptNode;
import org.apache.royale.compiler.tree.mxml.IMXMLSpecifierNode;
import org.apache.royale.compiler.tree.mxml.IMXMLStateNode;
import org.apache.royale.compiler.tree.mxml.IMXMLStyleSpecifierNode;
import com.google.common.collect.ArrayListMultimap;
import static org.apache.royale.compiler.mxml.IMXMLLanguageConstants.*;
/**
* {@code MXMLClassDefinitionNode} represents an MXML tag which defines a new
* class. It might be the document tag, or the tag inside a <Component>
* tag, or the tag inside a <Definition> tag.
*/
public class MXMLClassDefinitionNode extends MXMLClassReferenceNodeBase
implements IMXMLClassDefinitionNode, IScopedNode
{
/**
* Constructor
*
* @param parent The parent node of this node, or <code>null</code> if there
* is no parent.
*/
MXMLClassDefinitionNode(NodeBase parent)
{
super(parent);
}
/**
* The class that this node is defining.
*/
private IClassDefinition classDefinition;
/**
* The interfaces that the class being defined claims it implements.
*/
@SuppressWarnings("unused")
private String[] implementedInterfaces;
/**
* The scope inside this class.
*/
private IASScope scope;
/**
* The project for this class.
*/
private RoyaleProject project;
/**
* A map mapping an id to the instance node with that id.
*/
private Map<String, IMXMLInstanceNode> idMap;
/**
* A map mapping the name of a state defined within this class to the state
* node with that name.
*/
private Map<String, StateDefinition> stateMap;
/**
* A map mapping the name of a state group defined within this class to a
* set of state names for the states contained by the group. Example: "odd"
* -> { "s1", "s3", "s5" }
*/
private Map<String, StateGroupDefinition> groupMap;
/**
* A list of all state-dependent nodes in this class, in tree order. This
* includes instance nodes with includeIn/excludeFrom, and
* property/style/event nodes with suffixes. This is a temporary list that
* is used to build stateDependentNodeMap and then freed.
*/
private List<IMXMLNode> stateDependentNodeList;
/**
* A map mapping the the name of a state defined within this class to a list
* of nodes (in tree order) that depend on that class. These node lists for
* each state are used by the code generator to generate the IOverride
* objects for each state.
*/
private ArrayListMultimap<String, IMXMLNode> stateDependentNodeMap;
/**
* The state name to which <code>currentState</code> should be initialized.
*/
private String initialState;
/**
* An incrementing counter for compiler-generated ids within this class.
*/
private int generatedIDCounter = 0;
/**
* A map mapping an instance node without an id to its autogenerated id.
*/
Map<IMXMLInstanceNode, String> generatedIDMap =
new HashMap<IMXMLInstanceNode, String>();
/**
* The child &lt;Metadata&gt; nodes.
*/
private IMXMLMetadataNode[] metadataNodes;
/**
* The child &lt;Script&gt; nodes.
*/
private IMXMLScriptNode[] scriptNodes;
/**
* The child &lt;Declarations&gt; nodes.
*/
private IMXMLDeclarationsNode[] declarationsNodes;
/**
* This definition link is used only by CodeModel. It is created and updated
* by {@code getAdapter()}.
*/
/**
* This counter keeps track of how many {@code MXMLComponentNode}s are
* inside this {@code MXMLClassDefinitionNode}. The counter is incorporated
* into the autogenerated name of the <code>&lt;fx:Component&gt;</code>
* class.
*/
private int componentCount = 0;
/**
* This flag keep track if there any data binding nodes in this class.
*/
private boolean hasDataBindings;
private IASDocComment asDocComment;
@Override
protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag,
MXMLNodeInfo info)
{
super.initializationComplete(builder, tag, info);
// Revisit all State nodes in this class after all the states are known,
// to determine the state groups and which states are in which groups.
reprocessStateNodes();
// Revisit all state-dependent nodes in this class after the states
// and state groups are known, to determine which of them depend on which state
// (since the code generator needs this information to create IOverride
// objects for each state).
reprocessStateDependentNodes(builder);
// Add the dependency between the class this node defines and its superclass,
// as expressed by this tag that created this node.
project = builder.getProject();
IClassDefinition classReference = getClassReference(project);
String qname = classReference.getQualifiedName();
builder.addDependency(qname, DependencyType.INHERITANCE);
}
@Override
protected void processTagSpecificAttribute(MXMLTreeBuilder builder, IMXMLTagData tag,
IMXMLTagAttributeData attribute,
MXMLNodeInfo info)
{
if (attribute.isSpecialAttribute(ATTRIBUTE_IMPLEMENTS))
{
// Keep track of the specified interfaces as an array of Strings,
// for use by the compiler.
String rawValue = attribute.getRawValue();
if (rawValue != null)
implementedInterfaces = attribute.getMXMLDialect().splitAndTrim(rawValue);
// For CodeModel's use, also keep track of them as children
// of an {@code MXMLImplementsNode}.
MXMLImplementsNode interfaceNode = new MXMLImplementsNode(this);
interfaceNode.initializeFromAttribute(builder, attribute);
info.addChildNode(interfaceNode);
// TODO Report problems if the interfaces don't exist
// Later we also have report a problems for each interface method
// that isn't implemented.
}
else
{
super.processTagSpecificAttribute(builder, tag, attribute, info);
}
}
@Override
protected void processChildTag(MXMLTreeBuilder builder, IMXMLTagData tag,
IMXMLTagData childTag,
MXMLNodeInfo info)
{
MXMLFileScope fileScope = builder.getFileScope();
project = builder.getProject();
MXMLNodeBase childNode = null;
if (fileScope.isDeclarationsTag(childTag))
{
childNode = new MXMLDeclarationsNode(this);
}
else if ((fileScope.isScriptTag(childTag)) &&
// Our base class will handle script tags for MXML 2009 and below.
// Beginning with MXML 2012, only class definition tags may have script
// tags as children.
(builder.getMXMLDialect().isEqualToOrAfter(MXMLDialect.MXML_2012)))
{
childNode = new MXMLScriptNode(this);
}
else if (fileScope.isStyleTag(childTag))
{
childNode = new MXMLStyleNode(this);
}
else if (fileScope.isMetadataTag(childTag))
{
childNode = new MXMLMetadataNode(this);
}
else if (fileScope.isBindingTag(childTag))
{
childNode = new MXMLBindingNode(this);
this.setHasDataBindings(); // we must have some, if we made this node
}
else
{
super.processChildTag(builder, tag, childTag, info);
}
if (childNode != null)
{
childNode.initializeFromTag(builder, childTag);
info.addChildNode(childNode);
}
}
@Override
public ASTNodeID getNodeID()
{
return ASTNodeID.MXMLClassDefinitionID;
}
@Override
public String getPackageName()
{
// getPackageName() in NodeBase expects to find an IPackageNode.
// MXML trees don't have package nodes, so instead we just ask
// the corresponding IClassDefinition for its package name.
return classDefinition.getPackageName();
}
@Override
public IClassDefinition getClassDefinition()
{
return classDefinition;
}
/**
* Sets the definition of the class defined by this node.
*
* @param classDefinition An {@code IClassDefinition} object for the class.
*/
void setClassDefinition(IClassDefinition classDefinition)
{
this.classDefinition = classDefinition;
setScope((TypeScope)classDefinition.getContainedScope());
}
/**
* Adds an entry to this class's id map, which maps an id to the node with
* that id.
* <p>
* If a previously processed tag had the same id, the specified node is not
* added to the map, and the old node is returned; otherwise
* <code>null</code> is returned.
*
* @param node An {@code IMXMLInstanceNode} with an id.
* @return The {@code IMXMLInstanceNode}, if any, that already has the same
* id.
*/
IMXMLInstanceNode addNodeWithID(String id, IMXMLInstanceNode node)
{
if (idMap == null)
idMap = new HashMap<String, IMXMLInstanceNode>();
return idMap.put(id, node);
}
@Override
public IMXMLInstanceNode getNodeWithID(String id)
{
return idMap != null ? idMap.get(id) : null;
}
/*
* Notes about MXML States Each component within a document has its own set
* of states and state groups, so these are managed by class definition
* nodes. Only classes that implement mx.core.IStateClient2 can have states.
* State tags can occur wherever a value of type mx.states.State would be
* allowed. Usually <State> tags are written as the value of the <states>
* property of the UIComponent-derived component that they're defining. But
* they could also go into <Declarations>, or be used as other property
* values. In MXML 2006, <mx:State> is a normal instance tag whose runtime
* behavior is to apply a particular state. You specify an array of
* IOverride objects to be executed when the state is entered by setting its
* 'overrides' property (which is the default property). Various
* implementations of IOverride handle setting properties and styles, and
* adding event handlers, on specified objects, or creating instances of
* objects. For example, you might write <mx:State name="s1">
* <mx:SetProperty target="b1" name="label" value="OK"/> <mx:SetStyle
* target="b1" name="color" value="0xFFFFFF"/> </mx:State> to change the
* label and color of Button b1. MXML 2009 introduced state-suffix notation
* (such as label.s1="OK or <mx:label.s1>OK<mx:label.s1> for specifying
* state-dependent properties/styles/events, and includeIn/excludeFrom
* attributes for specifying instances that exist only in particular states.
* These two MXML features end up autogenerating the override objects for
* the states, and you can no longer specify the overrides explicitly in
* MXML. Consider the following example: <s:Application ...> <s:Button
* id="b1" label="OK"/> <s:Button id="b2" label.even="Foo" label.odd="Bar"/>
* <s:Button id="b3" includeIn="odd,s4"/> <s:Button id="b4"
* excludeFrom="even,s3"/> <s:states> <s:State name="s1" groups="all,odd"/>
* <s:State name="s2" groups="all,even"/> <s:State name="s3"
* groups="all,odd"/> <s:State name="s4" groups="all,even"/> <s:State
* name="s5" groups="all,odd"/> </s:state> </s:Application> The class
* defined by the <s:Application> tag has five states (named "s1", "s2",
* "s3", "s4", and "s5") and three state groups (named "all", "odd", and
* "even".) Note that state groups are defined implicitly by being mentioned
* on <State> tags. Also, the names of states and state groups must be
* distinct. The members of the three groups are: all: s1, s2, s3, s4, s5
* odd: s1, s3, s5 even: s4, s4 Button b1 and its label property are not
* state-dependent. Button b2 is not a state-dependent instance, but it has
* a state-dependent label property. Buttons b3 and b4 are state-dependent
* instances; b3 exists in states "s1", "s3", "s4", "s5" but not in "s2"; b4
* exists in states "s1" and "s5" but not in "s2", "s3", or "s4". As tree
* nodes are created, their MXMLClassDefinitionNode keeps track of <State>
* nodes and any state-dependent node (i.e., one with includeIn/excludeFrom
* or a suffix). After all <State> nodes have been discovered, we reprocess
* them to determine all the state groups. After we know the names of all
* the states and state groups, we reprocess the state-dependent nodes to
* check that they specify valid states or state groups and determine what
* states they belong to. We build lists of state-dependent nodes for each
* state, like this: s1 MXMLPropertyNode for attribute label.odd="Bar" on
* Button b2 MXMLInstanceNode for Button b3 MXMLInstanceNode for Button b4
* s2 MXMLPropertyNode for attribute label.even="Foo" on Button b2 s3
* MXMLPropertyNode for attribute label.odd="Bar" on Button b2
* MXMLInstanceNode for Button b3 s4 MXMLPropertyNode for attribute
* label.even="Foo" on Button b2 MXMLInstanceNode for Button b3 s5
* MXMLPropertyNode for attribute label.odd="Bar" on Button b2
* MXMLInstanceNode for Button b3 MXMLInstanceNode for Button b4 The code
* generator then uses these lists to generate the override objects that
* each state object applies.
*/
@Override
public Set<String> getStateNames()
{
if (stateMap == null)
return new HashSet<String>(0);
return stateMap.keySet();
}
@Override
public Set<IStateDefinition> getStates()
{
if (stateMap == null)
return new HashSet<IStateDefinition>(0);
Set<IStateDefinition> states = new HashSet<IStateDefinition>(stateMap.size());
states.addAll(stateMap.values());
return states;
}
/**
* Gets the state node that defines a specified state.
*
* @param stateName A state name.
* @return The {code IMXMLStateNode} with that name.
*/
public IMXMLStateNode getStateNode(String stateName)
{
if (stateMap == null)
return null;
return stateMap.get(stateName).getNode();
}
/**
* Gets the state definition that defines a specified state.
*
* @param stateName A state name
* @return The {code IStateDefinition} with that name
*/
public IStateDefinition getStateByName(String stateName)
{
if (stateMap == null)
return null;
return stateMap.get(stateName);
}
/**
* Gets the state group that defines a specified state group.
*
* @param groupName A state group name
* @return The {code IStateGroup} with that name
*/
public IStateGroupDefinition getStateGroupByName(String groupName)
{
if (groupMap == null)
return null;
return groupMap.get(groupName);
}
/**
* Determines whether a string is the name of a state of this class.
*
* @param stateName A String that might be a state name.
* @return <code>true</code> if it is a state name.
*/
public boolean isState(String stateName)
{
if (stateMap == null)
return false;
return stateMap.containsKey(stateName);
}
@Override
public Set<String> getStateGroupNames()
{
if (groupMap == null)
return new HashSet<String>(0);
return groupMap.keySet();
}
@Override
public Set<IStateGroupDefinition> getStateGroups()
{
if (groupMap == null)
return new HashSet<IStateGroupDefinition>(0);
Set<IStateGroupDefinition> groups = new HashSet<IStateGroupDefinition>(groupMap.size());
groups.addAll(groupMap.values());
return groups;
}
private String[] getStatesInGroup(String groupName)
{
return groupMap.get(groupName).getIncludedStates();
}
/**
* Determines whether a string is the name of a state group of this class.
*
* @param s A String that might be a state group name.
* @return <code>true</code> if it is a state group name.
*/
private boolean isStateGroup(String groupName)
{
if (groupMap == null)
return false;
return groupMap.containsKey(groupName);
}
@Override
public List<IMXMLNode> getNodesDependentOnState(String stateName)
{
return stateDependentNodeMap != null ? stateDependentNodeMap.get(stateName) : null;
}
@Override
public List<IMXMLNode> getAllStateDependentNodes()
{
return stateDependentNodeList;
}
@Override
public String getInitialState()
{
return initialState;
}
/**
* Iterates over the State nodes to determine the state groups defined
* within this class. This initializes the stateGroupMap that's used by
* getStateGroups(), getStatesInGroup(), and isStateGroup().
*/
private void reprocessStateNodes()
{
Set<String> states = getStateNames();
if (states == null)
return;
for (String state : states)
{
IMXMLStateNode stateNode = getStateNode(state);
String[] stateGroups = stateNode.getStateGroups();
if (stateGroups != null)
{
for (String stateGroup : stateGroups)
{
addStateToStateGroup(stateGroup, state);
}
}
}
}
private void addStateToStateGroup(String groupName, String stateName)
{
if (groupMap == null)
groupMap = new HashMap<String, StateGroupDefinition>();
StateDefinition state = stateMap.get(stateName);
StateGroupDefinition group = groupMap.get(groupName);
if (group == null)
{
group = new StateGroupDefinition(groupName, getDefinition());
groupMap.put(groupName, group);
}
state.addGroup(group);
group.addState(state);
}
/**
* Determines which nodes depend on which states. Instance nodes depend on
* the states implied by their includeIn and excludeFrom attributes.
* Property, style, and event nodes depend on the states implied by their
* suffix (as in label.up="OK").
*/
private void reprocessStateDependentNodes(MXMLTreeBuilder builder)
{
if (stateDependentNodeList == null)
return;
// Flags that keep track of whether we have various kinds of stateful nodes.
// Each kind introduce particular dependencies on various runtime classes.
boolean haveInstanceOverride = false;
boolean havePropertyOverride = false;
boolean haveStyleOverride = false;
boolean haveEventOverride = false;
// Iterate over all instance nodes that have includeIn or excludeFrom,
// and all property/style/event nodes that have a suffix.
for (IMXMLNode node : stateDependentNodeList)
{
if (node instanceof IMXMLInstanceNode || node instanceof IMXMLReparentNode)
{
haveInstanceOverride = true;
// TODO Consider introducing an interface
// containing getIncludeIn() and getExcludeFrom()
// to make IMXMLInstanceNode and IMXMLReparentNode
// look alike here.
String[] includeIn = null;
if (node instanceof IMXMLInstanceNode)
includeIn = ((IMXMLInstanceNode)node).getIncludeIn();
else if (node instanceof IMXMLReparentNode)
includeIn = ((IMXMLReparentNode)node).getIncludeIn();
String[] excludeFrom = null;
if (node instanceof IMXMLInstanceNode)
excludeFrom = ((IMXMLInstanceNode)node).getExcludeFrom();
else if (node instanceof IMXMLReparentNode)
excludeFrom = ((IMXMLReparentNode)node).getExcludeFrom();
// Determine which states contain this instance
// based on includeIn or excludeFrom.
Set<String> statesContainingInstance = null;
if (includeIn != null)
statesContainingInstance = processIncludeIn(includeIn);
else if (excludeFrom != null)
statesContainingInstance = processExcludeFrom(excludeFrom);
// Add the node to each of those state's list of state-dependent nodes.
if (statesContainingInstance != null)
{
for (String state : statesContainingInstance)
{
addStateDependentNode(state, node);
}
}
}
else if (node instanceof IMXMLSpecifierNode)
{
// havePropertyOverride must be last because
// IMXMLStyleSpecifierNode extends IMXMLPropertySpecifierNode
if (node instanceof IMXMLStyleSpecifierNode)
haveStyleOverride = true;
else if (node instanceof IMXMLEventSpecifierNode)
haveEventOverride = true;
else if (node instanceof IMXMLPropertySpecifierNode)
havePropertyOverride = true;
String suffix = ((IMXMLSpecifierNode)node).getSuffix();
if (isState(suffix))
{
// For a specifier node like label.s1="OK",
// add the node to the s1's list of state-dependent nodes.
addStateDependentNode(suffix, node);
}
else if (isStateGroup(suffix))
{
// For a specifier node like label.g1="OK",
// add the node to the state-dependent node list
// of each state in group g1.
for (String state : getStatesInGroup(suffix))
{
addStateDependentNode(state, node);
}
}
}
}
RoyaleProject project = builder.getProject();
if (haveInstanceOverride)
builder.addExpressionDependency(project.getInstanceOverrideClass());
if (havePropertyOverride)
builder.addExpressionDependency(project.getPropertyOverrideClass());
if (haveStyleOverride)
builder.addExpressionDependency(project.getStyleOverrideClass());
if (haveEventOverride)
builder.addExpressionDependency(project.getEventOverrideClass());
}
private Set<String> processIncludeIn(String[] includeIn)
{
// Start with an empty set.
Set<String> applicableStates = new HashSet<String>();
// Add the included states or groups of states.
for (String item : includeIn)
{
if (isState(item))
{
applicableStates.add(item);
}
else if (isStateGroup(item))
{
for (String state : getStatesInGroup(item))
{
applicableStates.add(state);
}
}
}
return applicableStates;
}
private Set<String> processExcludeFrom(String[] excludeFrom)
{
// Start with the set of all states.
Set<String> applicableStates = new HashSet<String>();
for (String state : getStateNames())
{
applicableStates.add(state);
}
// Remove the excluded states or groups of states.
for (String item : excludeFrom)
{
if (isState(item))
{
applicableStates.remove(item);
}
else if (isStateGroup(item))
{
for (String state : getStatesInGroup(item))
{
applicableStates.remove(state);
}
}
}
return applicableStates;
}
public void generateID(IMXMLInstanceNode instanceNode)
{
if (instanceNode != null && instanceNode.getID() == null)
{
if (generatedIDMap.containsKey(instanceNode))
return;
String id = project.getGeneratedIDBase() + generatedIDCounter++;
generatedIDMap.put(instanceNode, id);
}
}
void addStateDependentNode(MXMLTreeBuilder builder, IMXMLNode node)
{
if (stateDependentNodeList == null)
stateDependentNodeList = new ArrayList<IMXMLNode>();
stateDependentNodeList.add(node);
// The codegen for a state-dependent instance node will require
// an autogenerated id if that node doesn't have a specified id.
// The codegen for a state-dependent property/style/event node
// will require an autogenerated id for the target instance node
// if the instance node doesn't have a specified id.
IMXMLInstanceNode instanceNode = null;
if (node instanceof IMXMLInstanceNode)
instanceNode = (IMXMLInstanceNode)node;
// in case of root node, parent won't be IMXMLInstanceNode
else if (node instanceof IMXMLSpecifierNode && node.getParent() instanceof IMXMLInstanceNode)
instanceNode = (IMXMLInstanceNode)node.getParent();
generateID(instanceNode);
}
@Override
public String getGeneratedID(IMXMLInstanceNode instanceNode)
{
return generatedIDMap.get(instanceNode);
}
private void addStateDependentNode(String state, IMXMLNode node)
{
if (stateDependentNodeMap == null)
stateDependentNodeMap = ArrayListMultimap.create();
stateDependentNodeMap.put(state, node);
}
/**
* Adds an entry to this class's state map, which maps state names to state
* nodes.
* <p>
* If a previously processed state node had the same name, the specified
* node is not added to the map, and the old node is returned; otherwise
* <code>null</code> is returned.
*
* @param node An {@code IMXMLStateNode} with an name.
* @return The {@code IMXMLStateNode}, if any, that already has the same
* name.
*/
IMXMLStateNode addStateNode(IMXMLStateNode node)
{
if (stateMap == null)
{
stateMap = new HashMap<String, StateDefinition>();
// The first state we find is the initial state.
// TODO Should it be the first one in the 'states' property?
initialState = node.getStateName();
}
StateDefinition oldState = stateMap.put(node.getStateName(),
(StateDefinition)node.getDefinition());
return oldState != null ? oldState.getNode() : null;
}
@Override
public IMXMLMetadataNode[] getMetadataNodes()
{
return metadataNodes;
}
@Override
public IMXMLScriptNode[] getScriptNodes()
{
return scriptNodes;
}
@Override
public IMXMLDeclarationsNode[] getDeclarationsNodes()
{
return declarationsNodes;
}
@Override
void setChildren(IMXMLNode[] children)
{
super.setChildren(children);
if (children != null)
{
List<IMXMLMetadataNode> metadataNodes = new ArrayList<IMXMLMetadataNode>();
List<IMXMLScriptNode> scriptNodes = new ArrayList<IMXMLScriptNode>();
List<IMXMLDeclarationsNode> declarationsNodes = new ArrayList<IMXMLDeclarationsNode>();
for (IMXMLNode child : children)
{
if (child instanceof IMXMLMetadataNode)
metadataNodes.add((IMXMLMetadataNode)child);
else if (child instanceof IMXMLScriptNode)
scriptNodes.add((IMXMLScriptNode)child);
else if (child instanceof IMXMLDeclarationsNode)
declarationsNodes.add((IMXMLDeclarationsNode)child);
}
this.metadataNodes = metadataNodes.toArray(new IMXMLMetadataNode[0]);
this.scriptNodes = scriptNodes.toArray(new IMXMLScriptNode[0]);
this.declarationsNodes = declarationsNodes.toArray(new IMXMLDeclarationsNode[0]);
}
}
@Override
public IScopedNode getScopedNode()
{
return this;
}
@Override
public IASScope getScope()
{
return scope;
}
/**
* Sets the class scope.
*
* @param scope A {@code TypeScope} object for the scope contained by the
* class defined by this tag.
*/
public void setScope(TypeScope scope)
{
this.scope = scope;
}
@Override
public void getAllImports(Collection<String> imports)
{
ArrayList<IImportNode> importNodes = new ArrayList<IImportNode>();
getAllImportNodes(importNodes);
for (IImportNode importNode : importNodes)
imports.add(importNode.getImportName());
}
@Override
public void getAllImportNodes(Collection<IImportNode> imports)
{
// Add implicit import nodes created by MXML tags.
// The implicit imports for each MXML class are stored on its ClassDefinition.
IMXMLFileNode fileNode = (IMXMLFileNode)getAncestorOfType(IMXMLFileNode.class);
ICompilerProject project = fileNode.getCompilerProject();
for (String qname : ((ClassDefinition)classDefinition).getImplicitImports())
{
imports.add(new MXMLImplicitImportNode(project, qname));
}
// Add the implicit import nodes associated with the file node.
fileNode.getAllImportNodes(imports);
// Add the explicit import nodes inside of <Script> tags.
for (IMXMLScriptNode scriptNode : getScriptNodes())
{
for (IASNode node : scriptNode.getASNodes())
{
if (node instanceof ImportNode)
imports.add((IImportNode)node);
else
((NodeBase)node).collectImportNodes(imports);
}
}
}
@Override
public IExpressionNode getNameExpressionNode()
{
// The name is determined by the file, not by anything in the file.
return null;
}
@Override
public int getNameStart()
{
// The name is determined by the file, not by anything in the file.
return -1;
}
@Override
public int getNameEnd()
{
// The name is determined by the file, not by anything in the file.
return -1;
}
@Override
public int getNameAbsoluteStart()
{
return getNameStart();
}
@Override
public int getNameAbsoluteEnd()
{
return getNameEnd();
}
@Override
public String getQualifiedName()
{
return classDefinition.getQualifiedName();
}
@Override
public String getShortName()
{
return classDefinition.getBaseName();
}
@Override
public boolean hasModifier(ASModifier modifier)
{
return classDefinition.hasModifier(modifier);
}
@Override
public boolean hasNamespace(String namespace)
{
return getNamespace().equals(namespace);
}
@Override
public String getNamespace()
{
return INamespaceConstants.public_;
}
@Override
public boolean isImplicit()
{
return false;
}
@Override
public IMetaTagsNode getMetaTags()
{
// TODO Auto-generated method stub
return null;
}
@Override
public IMetaInfo[] getMetaInfos()
{
return classDefinition.getAllMetaTags();
}
@Override
public IClassDefinition getDefinition()
{
return classDefinition;
}
/**
* Used only for debugging.
*/
@SuppressWarnings("unused")
private void dumpStateDependentNodes()
{
Set<String> states = getStateNames();
if (states == null)
return;
for (String state : states)
{
System.out.println("State " + state);
List<IMXMLNode> nodes = getNodesDependentOnState(state);
if (nodes != null)
{
for (IMXMLNode node : nodes)
{
System.out.println(" " + node);
}
}
}
}
@Override
public String getBaseClassName()
{
return getDefinition().getBaseClassAsDisplayString();
}
@Override
public String[] getImplementedInterfaces()
{
return getDefinition().getImplementedInterfacesAsDisplayStrings();
}
@Override
public IMetaTag[] getMetaTagsByName(String name)
{
return getDefinition().getMetaTagsByName(name);
}
@Override
public ClassClassification getClassClassification()
{
return ClassClassification.PACKAGE_MEMBER;
}
@Override
public IASDocComment getASDocComment()
{
return asDocComment;
}
@Override
public boolean hasExplicitComment()
{
return asDocComment != null;
}
/**
* Notifies this class definition node that an "inner" <fx:Component> has
* been found. In response, this methods returns an autogenerated name for
* the component class.
*/
String addComponent()
{
// Return a string such as "MyAppInnerClass0" or "MyCompInnerClass2InnerClass1".
StringBuilder sb = new StringBuilder();
sb.append(getShortName());
sb.append("InnerClass");
sb.append(componentCount);
componentCount++;
return sb.toString();
}
@Override
public boolean needsDescriptor()
{
return isContainer();
}
@Override
public boolean needsDocumentDescriptor()
{
return isContainer();
}
public void setHasDataBindings()
{
hasDataBindings = true;
}
@Override
public boolean getHasDataBindings()
{
return hasDataBindings;
}
public void setASDocComment(IASDocComment ref)
{
asDocComment = ref;
}
}