/* * * 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.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.definitions.IParameterDefinition; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.mxml.MXMLEventHandlerScope; import org.apache.flex.compiler.internal.parsing.as.ASParser; import org.apache.flex.compiler.internal.parsing.as.IncludeHandler; import org.apache.flex.compiler.internal.parsing.as.OffsetLookup; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.scopes.ASFileScope; 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.IMXMLTagAttributeData; import org.apache.flex.compiler.mxml.IMXMLTagData; import org.apache.flex.compiler.mxml.IMXMLTextData; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.MXMLEmptyEventHandlerProblem; import org.apache.flex.compiler.scopes.IASScope; import org.apache.flex.compiler.tree.ASTNodeID; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IImportNode; import org.apache.flex.compiler.tree.as.IScopedNode; import org.apache.flex.compiler.tree.mxml.IMXMLClassReferenceNode; import org.apache.flex.compiler.tree.mxml.IMXMLEventSpecifierNode; import org.apache.flex.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 FlexProject 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(); } }