blob: aef1a4b0c072cf945ff5a303ea5044e4025e1919 [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.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.definitions.IParameterDefinition;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.definitions.mxml.MXMLEventHandlerScope;
import org.apache.royale.compiler.internal.parsing.as.ASParser;
import org.apache.royale.compiler.internal.parsing.as.IncludeHandler;
import org.apache.royale.compiler.internal.parsing.as.OffsetLookup;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.internal.tree.as.NodeBase;
import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.mxml.IMXMLTagAttributeData;
import org.apache.royale.compiler.mxml.IMXMLTagData;
import org.apache.royale.compiler.mxml.IMXMLTextData;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MXMLEmptyEventHandlerProblem;
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.IImportNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.mxml.IMXMLClassReferenceNode;
import org.apache.royale.compiler.tree.mxml.IMXMLEventSpecifierNode;
import org.apache.royale.compiler.tree.mxml.IMXMLFileNode;
/**
* {@code MXMLEventSpecifierNode} represents an MXML event attribute or event
* child tag.
* <p>
* Its child nodes are ActionScript nodes representing the statements to be
* executed.
* <p>
* For example, the MXML code
*
* <pre>
* &lt;s:Button click="doThis(); doThat()"/&gt;
* </pre>
*
* or
*
* <pre>
* &lt;s:Button&gt;
* &lt;s:click&gt;
* &lt;![CDATA[
* doThis();
* doThat();
* ]]&gt;
* &lt;/s:click&gt;
* &lt;/s:Button&gt;
* </pre>
*
* produces an AST of the form
*
* <pre>
* MXMLInstanceNode "spark.components.Button"
* MXMLEventSpecifierNode "click"
* FunctionCallNode
* IdentifierNode "doThis"
* ContainerNode
* FunctionCallNode
* IdentifierNode "doThat"
* ContainerNode
* </pre>
*/
class MXMLEventSpecifierNode extends MXMLSpecifierNodeBase
implements IMXMLEventSpecifierNode, IScopedNode
{
private static NodeBase[] NO_AS_NODES = new NodeBase[0];
/**
* Constructor
*
* @param parent The parent node of this node, or <code>null</code> if there
* is no parent.
*/
MXMLEventSpecifierNode(NodeBase parent)
{
super(parent);
// Create a handler scope to hold the 'event' parameter.
// Although it is attached to this node and not to the symbol table,
// it points upward to the class scope in the symbol table.
scope = new MXMLEventHandlerScope(this);
}
/**
* The child nodes, representing the ActionScript statements to be executed
* to handle the event.
*/
private NodeBase[] asNodes = NO_AS_NODES;
/**
* The scope contained by the autogenerated event handler method. The
* following eventParameter definition lives in this scope. Since this scope
* is only needed to process this AST, it is attached to the AST and not
* part of the symbol table. However, its containedScope points to the class
* scope in the symbol table.
*/
private final MXMLEventHandlerScope scope;
/**
* This override handles an event attribute such as
* click="doThis(); doThat()".
*/
@Override
protected void initializeFromAttribute(MXMLTreeBuilder builder,
IMXMLTagAttributeData attribute,
MXMLNodeInfo info)
{
super.initializeFromAttribute(builder, attribute, info);
final RoyaleProject project = builder.getProject();
final Workspace workspace = builder.getWorkspace();
final Collection<ICompilerProblem> problems = builder.getProblems();
setSourcePath(attribute.getParent().getParent().getFileSpecification().getPath());
final int startOffsetLocal = attribute.getValueStart();
final ASFileScope fileScope = builder.getFileScope();
final OffsetLookup offsetLookup = fileScope.getOffsetLookup();
assert offsetLookup != null : "Expected OffsetLookup on FileScope.";
final String filePath = getContainingFilePath();
final int[] absoluteOffsets =
offsetLookup.getAbsoluteOffset(filePath, startOffsetLocal);
final int startOffsetAbsolute = absoluteOffsets[0];
final IncludeHandler includeHandler =
IncludeHandler.createForASTBuilding(builder.getFileSpecificationGetter(), filePath, startOffsetLocal, startOffsetAbsolute);
includeHandler.setProjectAndCompilationUnit(project, builder.getCompilationUnit());
final String scriptFragment = attribute.getRawValue();
if (scriptFragment.isEmpty())
{
MXMLEmptyEventHandlerProblem problem = new MXMLEmptyEventHandlerProblem(attribute);
problems.add(problem);
}
final ScopedBlockNode node = ASParser.parseFragment2(
scriptFragment,
filePath,
0,
attribute.getValueLine(),
attribute.getValueColumn(),
problems,
workspace,
builder.getFileNode(),
scope,
project.getProjectConfigVariables(),
EnumSet.of(PostProcessStep.CALCULATE_OFFSETS, PostProcessStep.POPULATE_SCOPE),
true, //follow includes
includeHandler);
builder.getFileNode().updateIncludeTreeLastModified(includeHandler.getLastModified());
processHandlerCode(builder, Collections.singletonList(node));
}
/**
* This override is called on each non-whitespace unit of text inside an
* event tag, such as <click>doThis(); <!-- comment --> doThat();</click>
* which must compile as if it had been written <click>doThis();
* doThat();</click> All the text units will be processed later in
* initializationComplete().
*/
@Override
protected void processChildNonWhitespaceUnit(MXMLTreeBuilder builder, IMXMLTagData tag,
IMXMLTextData text,
MXMLNodeInfo info)
{
// Don't report non-whitespace as a problem.
}
/**
* This override is called on an event tag such as <click>doThis(); <!--
* comment --> doThat();</click>. It concatenates all the text units to get
* "doThis(); doThat();" and compiles that as the event handler.
*/
@Override
protected void initializationComplete(MXMLTreeBuilder builder, IMXMLTagData tag,
MXMLNodeInfo info)
{
String path = builder.getPath();
IMXMLFileNode fileNode = builder.getFileNode();
processHandlerCode(builder, processUnitAsAS(
builder, tag, path, scope, PostProcessStep.POPULATE_SCOPE, fileNode));
}
/**
* Helper method used by the two protected versions of initialize().
*
* @param scripts the {@link ScopedBlockNode} node that makes up the content
*/
private void processHandlerCode(MXMLTreeBuilder builder, List<ScopedBlockNode> scripts)
{
// Populate this handler scope with a definition for the 'event' parameter.
// The type of this parameter is determined by the [Event] metadata.
// For example, if the event is
// [Event(name="click", type="flash.events.MouseEvent")]
// then the type is flash.events.MouseEvent.
IReference typeRef = ((DefinitionBase)getDefinition()).getTypeReference();
scope.buildEventParameter(typeRef);
// Add an expression dependency on the event type.
// It doesn't need to be a signature dependency
// because autogenerated event handlers are inaccessible;
// they're either in a special private MXML namespace
// or they're public but have an 'illegal' name.
typeRef.resolve(builder.getProject(), scope, DependencyType.EXPRESSION, true);
// Make the statements inside the event handler the children of this node.
for (ScopedBlockNode script : scripts)
{
int n = script.getChildCount();
asNodes = new NodeBase[n];
for (int i = 0; i < n; i++)
{
NodeBase child = (NodeBase)script.getChild(i);
asNodes[i] = child;
child.setParent(this);
}
}
}
@Override
public ASTNodeID getNodeID()
{
return ASTNodeID.MXMLEventSpecifierID;
}
@Override
public IASNode getChild(int i)
{
return asNodes != null ? asNodes[i] : null;
}
@Override
public int getChildCount()
{
return asNodes != null ? asNodes.length : 0;
}
@Override
public IASNode[] getASNodes()
{
return asNodes;
}
@Override
public IASScope getScope()
{
return scope;
}
@Override
public void getAllImports(Collection<String> imports)
{
((MXMLClassDefinitionNode)getClassDefinitionNode()).getAllImports(imports);
}
@Override
public void getAllImportNodes(Collection<IImportNode> imports)
{
((MXMLClassDefinitionNode)getClassDefinitionNode()).getAllImportNodes(imports);
}
@Override
public IParameterDefinition getEventParameterDefinition()
{
final IParameterDefinition result = scope.getEventParameterDefinition();
assert result != null : "Even parameter definition should be built before it is accessed";
return result;
}
@Override
public boolean needsPublicHandler()
{
// The autogenerated event handler method must be public
// if it will be referenced in a UIComponentDescriptor.
return ((IMXMLClassReferenceNode)getParent()).needsDescriptor();
}
}