| /* |
| * |
| * 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.codegen.databinding; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| |
| import org.apache.royale.compiler.common.DependencyType; |
| import org.apache.royale.compiler.constants.IASKeywordConstants; |
| import org.apache.royale.compiler.definitions.IAccessorDefinition; |
| import org.apache.royale.compiler.definitions.IConstantDefinition; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.IVariableDefinition; |
| import org.apache.royale.compiler.definitions.references.INamespaceReference; |
| import org.apache.royale.compiler.definitions.references.IReference; |
| import org.apache.royale.compiler.definitions.references.ReferenceFactory; |
| import org.apache.royale.compiler.internal.as.codegen.InstructionListNode; |
| import org.apache.royale.compiler.internal.as.codegen.MXMLClassDirectiveProcessor; |
| import org.apache.royale.compiler.internal.definitions.ClassDefinition; |
| import org.apache.royale.compiler.internal.definitions.NamespaceDefinition; |
| import org.apache.royale.compiler.internal.scopes.ASScope; |
| import org.apache.royale.compiler.internal.scopes.TypeScope; |
| import org.apache.royale.compiler.internal.tree.as.IdentifierNode; |
| import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode; |
| import org.apache.royale.compiler.internal.tree.as.NodeBase; |
| import org.apache.royale.compiler.projects.ICompilerProject; |
| 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.IFunctionCallNode; |
| import org.apache.royale.compiler.tree.as.IIdentifierNode; |
| import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLArrayNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLBindingAttributeNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLClassDefinitionNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLClassReferenceNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLConcatenatedDataBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLSingleDataBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLDataBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLExpressionNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLInstanceNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLModelPropertyNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLPropertySpecifierNode; |
| |
| /** |
| * data that describes a single databinding expression. |
| * This data is put together during an analysis pass, then used for codegen. |
| */ |
| public class BindingInfo implements Comparable<BindingInfo> |
| { |
| /** |
| * This form of the constructor is used for the "normal" case, |
| * like <s:Button label="{foo}"/> |
| * |
| * In this case the node MUST be either a MXMLDataBindingNode or |
| * MXMLConcatenatedDataBindingNode |
| */ |
| public BindingInfo(IMXMLDataBindingNode dbnode, int index, MXMLClassDirectiveProcessor host) |
| { |
| this.index = index; |
| node = dbnode; |
| |
| // look at the node we are passed, and expand it out to all |
| // of its expression children |
| expressionNodesForGetter = new LinkedList<IExpressionNode>(); |
| if (dbnode instanceof IMXMLSingleDataBindingNode) |
| { |
| expressionNodesForGetter.add( ((IMXMLSingleDataBindingNode) dbnode).getExpressionNode()); |
| } |
| else if (dbnode instanceof IMXMLConcatenatedDataBindingNode) |
| { |
| for (int childIndex=0; childIndex < dbnode.getChildCount(); ++childIndex) |
| { |
| IASNode child = dbnode.getChild(childIndex); |
| if (child instanceof IMXMLSingleDataBindingNode) |
| { |
| expressionNodesForGetter.add( ((IMXMLSingleDataBindingNode) child).getExpressionNode()); |
| } |
| else if (child instanceof IExpressionNode) |
| { |
| expressionNodesForGetter.add( (IExpressionNode)child); |
| } |
| else |
| { |
| assert false; |
| } |
| } |
| } |
| else |
| { |
| assert false; |
| } |
| |
| // now attempt to make a destination function and a destination string |
| // for the binding. |
| expressionNodeForSetter = BindingDestinationMaker.makeDestinationFunctionInstructionList(dbnode, host); |
| destinationString = findDestinationString(dbnode, host); |
| |
| finishInit(host); |
| } |
| |
| |
| |
| /** |
| * Constructor for use with a BindingNode. The binding node specifies |
| * both the source and destination expressions explicitly. |
| * Usually these come from either the <fx:Binding> tag, or as an implementation detail |
| * of the <fx:XML> tag |
| * |
| * @param reverseSourceAndDest - if true, analyze the binding as if the source was the destination |
| * and the destination was the source |
| */ |
| public BindingInfo( |
| IMXMLBindingNode bindingNode, |
| int index, |
| MXMLClassDirectiveProcessor host, |
| boolean reverseSourceAndDest) |
| { |
| this.index = index; |
| node = bindingNode; |
| |
| IExpressionNode destinationNode = null; |
| expressionNodesForGetter = new LinkedList<IExpressionNode>(); |
| // look at the node we are passed, and expand it out to all |
| // of its expression children |
| |
| if (!reverseSourceAndDest) |
| { |
| expressionNodesForGetter.add( bindingNode.getSourceAttributeNode().getExpressionNode()); |
| destinationNode = bindingNode.getDestinationAttributeNode().getExpressionNode(); |
| } |
| else |
| { |
| expressionNodesForGetter.add( bindingNode.getDestinationAttributeNode().getExpressionNode()); |
| destinationNode = bindingNode.getSourceAttributeNode().getExpressionNode(); |
| } |
| expressionNodeForSetter = destinationNode; |
| |
| // We still need a dest string even if we have a function *sigh* |
| // The binding manager requires this, as it uses it to identify bindings |
| |
| destinationString = findDestinationString(destinationNode, host); |
| |
| assert expressionNodeForSetter != null; // we should always be able to make a destination function |
| |
| finishInit(host); |
| } |
| |
| /** common code used by all the constructors |
| */ |
| private void finishInit(MXMLClassDirectiveProcessor host) |
| { |
| ClassDefinition classDef = host.getClassDefinition(); |
| ASScope classScope = classDef.getContainedScope(); |
| analyzeExpression(host.getProject(), classScope); |
| |
| // TODO: we should be able to assert that we make a dest string, becuase |
| // in general the binding manager needs one, even if we have a dest func. |
| // HOWEVER - we don't always generate on now, and it seems OK. |
| } |
| |
| |
| // ------------ private vars ------------ |
| private final List<IExpressionNode> expressionNodesForGetter; |
| private String destinationString; |
| final int index; // the _bindings array index for this binding |
| private boolean isSimplePublicProperty; |
| private String sourceString; |
| private int twoWayCounterpart = -1; // index of two way counterpart, or -1 |
| public IMXMLNode node; |
| public ClassDefinition classDef; // non-null if binding to static const or var |
| |
| // The expression node that represents the destination |
| // this is used for more complex destinations, like inside an XML object |
| // where the destination could be something like: |
| // myXML.a[0].text()[0] |
| // that can't be easily codegen'ed from a String |
| private IExpressionNode expressionNodeForSetter; |
| |
| |
| // ------------ public methods ------------ |
| public int getIndex() |
| { |
| assert index >= 0; |
| return index; |
| } |
| |
| public int getTwoWayCounterpart() |
| { |
| return twoWayCounterpart; |
| } |
| |
| public void setTwoWayCounterpart(int twoWayCounterparterpart) |
| { |
| this.twoWayCounterpart = twoWayCounterparterpart; |
| } |
| /** |
| * |
| * @return the nodes for the expressions in between the { } curlies, or null if no getter is needed |
| * |
| */ |
| public List<IExpressionNode> getExpressionNodesForGetter() |
| { |
| return expressionNodesForGetter; |
| } |
| |
| /** |
| * Get the IExpressionNode that represents the destination |
| */ |
| public IExpressionNode getExpressionNodeForDestination() |
| { |
| return expressionNodeForSetter; |
| } |
| |
| /** |
| * @return the name of the binding destination property |
| */ |
| public String getDestinationString() |
| { |
| return destinationString; |
| } |
| |
| /** |
| * param the name of the binding destination property |
| */ |
| public void setDestinationString(String newDestString) |
| { |
| destinationString = newDestString; |
| } |
| |
| /** |
| * just for debugging |
| */ |
| @Override |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| sb.append( "BindingInfo #" + index + |
| " destStr:" + destinationString + |
| " srcStr:" + sourceString + "\n" |
| ); |
| if (expressionNodeForSetter != null) |
| { |
| sb.append(" destFunc "); |
| sb.append("node:\n"); |
| NodeBase n = (NodeBase) expressionNodeForSetter; |
| n.buildStringRecursive(sb, 3, false); |
| } |
| if (!expressionNodesForGetter.isEmpty()) |
| { |
| sb.append(" Getter Expressions:\n"); |
| for (IExpressionNode e: expressionNodesForGetter) |
| { |
| NodeBase n = (NodeBase)e; |
| n.buildStringRecursive(sb, 3, false); // put in the expression node with nice indenting |
| } |
| } |
| return sb.toString(); |
| } |
| |
| // ------------ private methods ------------ |
| |
| /** |
| * Synthesizes the "distinationString" argument for the PropertyWatcher constructor. |
| * for example, in <s:Button id="foo" bar="{goo}"/> |
| * the destination is "foo.bar" |
| */ |
| private static String findDestinationString(IASNode node, MXMLClassDirectiveProcessor host) |
| { |
| // get the parent of the node to figure out what kind of destination we are |
| String destString=null; |
| final IASNode parent = node.getParent(); |
| if (parent instanceof IMXMLPropertySpecifierNode) |
| { |
| destString = findDestinationStringFromPropertySpecifier((IMXMLPropertySpecifierNode)parent) ; |
| } |
| else if (parent instanceof IMXMLBindingAttributeNode) |
| { |
| destString = findDestinationStringFromBindingAttribute(node) ; |
| } |
| else if (parent instanceof IMXMLExpressionNode) |
| { |
| // We are an MXML primitive (like sf:String), so the dest string is just our ID |
| String id = ((IMXMLExpressionNode)parent).getEffectiveID(); |
| assert id != null; |
| |
| IMXMLClassReferenceNode propertyParent = (IMXMLClassReferenceNode) |
| parent.getAncestorOfType(IMXMLClassReferenceNode.class); |
| assert propertyParent != null; |
| if (propertyParent instanceof IMXMLClassDefinitionNode) |
| { |
| destString = "this." + id; |
| } |
| else |
| { |
| destString = id; |
| } |
| } |
| else if (parent instanceof IMXMLArrayNode) |
| { |
| // We are an MXML primitive, so the dest string is just our ID |
| String id = ((IMXMLArrayNode)parent).getEffectiveID(); |
| assert id != null; |
| |
| IMXMLClassReferenceNode propertyParent = (IMXMLClassReferenceNode) |
| parent.getAncestorOfType(IMXMLClassReferenceNode.class); |
| assert propertyParent != null; |
| if (propertyParent instanceof IMXMLClassDefinitionNode) |
| { |
| destString = "this." + id; |
| } |
| else |
| { |
| destString = id; |
| } |
| } |
| else if (parent instanceof IMXMLModelPropertyNode) |
| { |
| // For now, we are always making a destination function, so we |
| // don't need to make a string. |
| // We might want to do either/or, as a possible optimization |
| } |
| else |
| { |
| // there will be (presumably) some cases where we can't make a destination string. |
| // For now, however, any case where we fail to do so is probably a bug |
| System.err.println("findDestinationString can't parse parent: " + parent); |
| } |
| return destString; |
| } |
| |
| /** |
| * A typical tree shape for this case is: |
| * |
| * MXMLBindingNode twoWay="false" 31:2 loc: 900-948 abs: 900-948 null |
| * MXMLBindingAttributeNode "source" 31:14 loc: 912-924 abs: 912-924 null |
| * IdentifierNode "src" 31:22 loc: 920-923 abs: 920-923 null |
| * MXMLBindingAttributeNode "destination" 31:27 loc: 925-946 abs: 925-946 null |
| * MemberAccessExpressionNode "." 31:40 loc: 938-945 abs: 938-945 null |
| * IdentifierNode "b" 31:40 loc: 938-939 abs: 938-939 null |
| * IdentifierNode "label" 31:42 loc: 940-945 abs: 940-945 null |
| * |
| * Where the "node" parameter is MemberAccessExpressionNode "." 31:40 |
| * and the "parent" that got us here is MXMLBindingAttributeNode "destination" 31:27 |
| * @param node |
| * @return |
| */ |
| private static String findDestinationStringFromBindingAttribute(IASNode node) |
| { |
| String ret = null; |
| |
| // TODO: why not just remember the string in the node? |
| // TODO: This needs to be recursive in order to make destination strings from |
| // a.b.c.d |
| // For now we can only do x or x.y |
| |
| // More importantly, we need to make destination functions for the really gnarly cases. |
| // Once we do that, we may decide to remove this... |
| |
| if (node instanceof IMemberAccessExpressionNode) |
| { |
| IMemberAccessExpressionNode maNode = (IMemberAccessExpressionNode)node; |
| IExpressionNode left = maNode.getLeftOperandNode(); |
| IExpressionNode right = maNode.getRightOperandNode(); |
| if (left instanceof IIdentifierNode && right instanceof IIdentifierNode) |
| { |
| ret = ((IIdentifierNode) left).getName() + "." + ((IIdentifierNode) right).getName(); |
| } |
| else |
| { |
| // |
| // System.err.println("findDestinationStringFromBindingAttribute (1) can't parse " + node); |
| } |
| } |
| else if (node instanceof IIdentifierNode) |
| { |
| ret = ((IIdentifierNode) node).getName(); |
| } |
| else |
| { |
| if (!(node instanceof InstructionListNode)) |
| { |
| // |
| //System.err.println("findDestinationStringFromBindingAttribute (2) can't parse " + node); |
| } |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * A typical tree shape for this case is: |
| * |
| * MXMLInstanceNode "spark.components.Button" id="b2" 38:3 loc: 863-901 abs: 863-901 null |
| * MXMLPropertySpecifierNode "label" 38:21 loc: 881-899 abs: 881-899 null |
| * MXMLDataBindingNode "DataBinding" 38:28 loc: 888-898 abs: 888-898 D:\builder_trunk\compiler\org.apache.royale.compiler\generated\tests\scratch\mxmlfunctional\Binding3\bindtest3.mxml |
| * MemberAccessExpressionNode "." 38:29 loc: 889-897 abs: 889-897 null |
| * IdentifierNode "b1" 38:29 loc: 889-891 abs: 889-891 null |
| * IdentifierNode "label" 38:32 loc: 892-897 abs: 892-897 null |
| * |
| * where the "propertySpecifier" parameter to this function is MXMLPropertySpecifierNode "label" 38:21 |
| * |
| * and we discovered the property specifier because he is the parent of the node we |
| * are making the binding for, which is MXMLDataBindingNode "DataBinding" 38:28 |
| */ |
| private static String findDestinationStringFromPropertySpecifier(IMXMLPropertySpecifierNode propertySpecifier) |
| { |
| String ret = null; |
| IMXMLClassReferenceNode propertyParent = (IMXMLClassReferenceNode) |
| propertySpecifier.getAncestorOfType( IMXMLClassReferenceNode.class); |
| assert propertyParent != null; |
| |
| // If the property is a property of an instance, get id.name |
| if (propertyParent instanceof IMXMLInstanceNode) |
| { |
| IMXMLInstanceNode instanceNode = (IMXMLInstanceNode)propertyParent; |
| // get the effective id. If the component doesn't have an ID, then we will have already made up |
| // one. |
| String id = instanceNode.getEffectiveID(); |
| assert id != null; |
| assert instanceNode.getID()==null || id.equals(instanceNode.getID()); |
| ret = id + "." + propertySpecifier.getName(); |
| } |
| // If it's a property of the root, get this.name |
| else if (propertyParent instanceof IMXMLClassDefinitionNode) |
| { |
| ret = "this." + propertySpecifier.getName(); |
| } |
| else assert false; |
| return ret; |
| } |
| |
| @Override |
| public int compareTo(BindingInfo o) |
| { |
| return this.index - o.index; |
| } |
| |
| /** |
| * Is the binding source a simple public member variable? |
| */ |
| public boolean isSourceSimplePublicProperty() |
| { |
| return this.isSimplePublicProperty; |
| } |
| |
| /** |
| * The string that represents the binding source, but only for simple publics |
| * may return null if binding source is not simple public property |
| */ |
| public String getSourceString() |
| { |
| return sourceString; |
| } |
| |
| /** |
| * extract some information from the binding expression node, and store for later |
| * The information we are looking for is "is this a simple property, and if so what is it's name" |
| */ |
| private void analyzeExpression(ICompilerProject project, ASScope scope) |
| { |
| assert scope instanceof TypeScope; // we expect to get in the MXML document we are compiling |
| |
| // we there are multiple children, then we can't be a simple prop |
| if (expressionNodesForGetter.size() != 1) |
| return; |
| |
| IExpressionNode expressionNodeForGetter = expressionNodesForGetter.get(0); |
| //if it is a MemberAccessExpressionNode, and the first node is Identifier 'this' , then we will only examine right operand node |
| boolean hadThis = false; |
| if (expressionNodeForGetter instanceof MemberAccessExpressionNode) { |
| if (((MemberAccessExpressionNode) expressionNodeForGetter).getLeftOperandNode() instanceof IIdentifierNode |
| && ((IIdentifierNode) ((MemberAccessExpressionNode) expressionNodeForGetter).getLeftOperandNode()).getName().equals(IASKeywordConstants.THIS) |
| ) { |
| expressionNodeForGetter = ((MemberAccessExpressionNode) expressionNodeForGetter).getRightOperandNode(); |
| hadThis = true; |
| } |
| } |
| |
| |
| if (expressionNodeForGetter instanceof IIdentifierNode) |
| { |
| String name = ((IIdentifierNode)expressionNodeForGetter).getName(); |
| IReference ref = ReferenceFactory.lexicalReference(project.getWorkspace(), name); |
| ASScope resolutionScope = hadThis ? ((TypeScope) scope).getInstanceScope() : scope; |
| IDefinition def = ref.resolve(project, resolutionScope, DependencyType.EXPRESSION, false); |
| if (def instanceof IVariableDefinition) |
| { |
| // here we have decided that the binding expression is a variable |
| IVariableDefinition var = (IVariableDefinition)def; |
| INamespaceReference ns = var.getNamespaceReference(); |
| if (ns == NamespaceDefinition.getPublicNamespaceDefinition()) |
| { |
| // ok, our variable is public - let's take it |
| sourceString = def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| } |
| |
| if (def instanceof IConstantDefinition) { |
| IConstantDefinition cnst = (IConstantDefinition) def; |
| INamespaceReference ns = cnst.getNamespaceReference(); |
| if (ns == NamespaceDefinition.getPublicNamespaceDefinition()) |
| { |
| // ok, our constant is public - let's take it |
| sourceString = def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| } |
| } |
| else if (expressionNodeForGetter instanceof MemberAccessExpressionNode) |
| { |
| MemberAccessExpressionNode mae = (MemberAccessExpressionNode)expressionNodeForGetter; |
| IDefinition def; |
| if (hadThis) { |
| def = expressionNodesForGetter.get(0).resolve(project); |
| } |
| else def = mae.resolve(project); |
| if (def != null && def.isPublic() && |
| (def instanceof IAccessorDefinition || |
| def instanceof IConstantDefinition || |
| def instanceof IVariableDefinition)) |
| { |
| IExpressionNode leftSide = mae.getLeftOperandNode(); |
| if (leftSide instanceof IIdentifierNode) |
| { |
| IDefinition leftDef = leftSide.resolve(project); |
| if (leftDef.isPublic()) |
| { |
| if (leftDef instanceof ClassDefinition) |
| classDef = (ClassDefinition)leftDef; |
| sourceString = leftDef.getBaseName() + "." + def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| } |
| else if (leftSide instanceof MemberAccessExpressionNode) |
| { |
| IDefinition leftDef = leftSide.resolve(project); |
| if (leftDef.isPublic()) |
| { |
| if (leftDef instanceof ClassDefinition) |
| { |
| classDef = (ClassDefinition)leftDef; |
| sourceString = leftDef.getBaseName() + "." + def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| else |
| { |
| sourceString = buildChain((MemberAccessExpressionNode) leftSide); |
| if (sourceString != null) |
| { |
| sourceString += "." + def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| } |
| } |
| } |
| else if (leftSide instanceof IFunctionCallNode) |
| { |
| IFunctionCallNode fun = (IFunctionCallNode)leftSide; |
| IExpressionNode[] args = fun.getArgumentNodes(); |
| if (args.length == 1) |
| { |
| IExpressionNode arg = args[0]; |
| if (arg instanceof IIdentifierNode) |
| { |
| IDefinition argDef = arg.resolve(project); |
| if (argDef.isPublic()) |
| { |
| sourceString = argDef.getBaseName() + "." + def.getBaseName(); |
| isSimplePublicProperty = true; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private String buildChain(MemberAccessExpressionNode mae) |
| { |
| IExpressionNode left = mae.getLeftOperandNode(); |
| if (left.getNodeID() == ASTNodeID.IdentifierID) |
| { |
| IExpressionNode right = mae.getRightOperandNode(); |
| if (right.getNodeID() == ASTNodeID.IdentifierID) |
| { |
| return ((IdentifierNode)left).getName() + "." + ((IdentifierNode)right).getName(); |
| } |
| } |
| else if (left.getNodeID() == ASTNodeID.MemberAccessExpressionID) |
| { |
| IExpressionNode right = mae.getRightOperandNode(); |
| if (right.getNodeID() == ASTNodeID.IdentifierID) |
| { |
| String l = buildChain((MemberAccessExpressionNode)left); |
| if (l == null) return null; |
| return l + "." + ((IdentifierNode)right).getName(); |
| } |
| } |
| return null; |
| } |
| } |