| /* |
| * |
| * 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> |
| * <s:Button click="doThis(); doThat()"/> |
| * </pre> |
| * |
| * or |
| * |
| * <pre> |
| * <s:Button> |
| * <s:click> |
| * <![CDATA[ |
| * doThis(); |
| * doThat(); |
| * ]]> |
| * </s:click> |
| * </s:Button> |
| * </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(); |
| } |
| } |