Package flex2.compiler.mxml.rep

Source Code of flex2.compiler.mxml.rep.BindingExpression

/*
*
*  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 flex2.compiler.mxml.rep;

import flex2.compiler.SymbolTable;
import flex2.compiler.as3.AbstractSyntaxTreeUtil;
import flex2.compiler.as3.binding.Watcher;
import flex2.compiler.mxml.builder.AbstractBuilder;
import flex2.compiler.mxml.lang.StandardDefs;
import flex2.compiler.mxml.reflect.Property;
import flex2.compiler.mxml.reflect.Style;
import flex2.compiler.mxml.reflect.Type;
import flex2.compiler.util.NameFormatter;
import macromedia.asc.parser.*;
import macromedia.asc.util.Context;
import macromedia.asc.util.IntegerPool;
import java.util.*;

/**
* A BindingExpression is used to store binding expressions (surprise!) when we come
* across them while parsing MXML.  As we go, we fill in the destination of each
* BindingExpression, and when we're done parsing we compile the source expression
* in order to figure out how to attach ActionScript watchers and binding objects.
*
* @author gdaniels
* @author mchotin
* @author preilly
*/
public class BindingExpression implements Comparable<BindingExpression>
{
    /** The source expression for this binding */
    private String sourceExpression;
    /** The destination Model of this binding */
    private Model destination;
    /** The destination property within the Model (numeric for Arrays) */
    private String destinationProperty;
    /** The destination style */
    private String destinationStyle;
    /** If destinationProperty is an array index, this is true.  Controlled by
     * calling setDestinationProperty(int).
     */
    private boolean arrayAccess = false;
    /**
     * The lvalue is the expression that can be used for the left side of the destination expression.
     * For destinationLiteral = false, destinationLValue == destinationProperty,
     * for destinationLiteral = true, destinationLValue is XML AS code while destinationProperty is dotted expression
     */
    private String destinationLValue;
    /**
     * The id of the binding expression, used for variable name.
     */
    private int id;
    /** Is this an XML attribute? */
    private boolean isDestinationXMLAttribute;
    /** Is this an XML node value? */
    private boolean isDestinationXMLNode;
    /** Is this XMLnode an E4X assignment? */
    private boolean isDestinationE4X;
    /** Is the destination a Model? */
    private boolean isDestinationObjectProxy;
    /**
     * Used to signal that the destination must be parsed.
     */
    private boolean isFromBindingNode;
    /**
     * Used to signal that a 2nd binding expression with source and
     * destinations reversed needs to be generated right before the
     * two-way bindings are resolved and that isTwoWayPrimary should
     * be set on the runtime Binding object.
     */
    private boolean isTwoWayPrimary;
    /** The line number where this binding expression was set up*/
    public int xmlLineNumber;

    private MxmlDocument mxmlDocument;

    private BindingExpression twoWayCounterpart;

  // namespace-aware e4x expressions need namespaces
  private Map<Integer, String> namespaces;

    private String sourceAsProperty;

  public BindingExpression(String bindingExpression, int xmlLineNumber, MxmlDocument mxmlDocument)
    {
        this.sourceExpression = bindingExpression;
    this.xmlLineNumber = xmlLineNumber;

    assert mxmlDocument != null;
    setMxmlDocument(mxmlDocument);
    }

    public int compareTo(BindingExpression bindingExpression)
    {
        int result = 0;

        if (id < bindingExpression.id)
        {
            result = -1;
        }
        else if (id > bindingExpression.id)
        {
            result = 1;
        }

        return result;
    }

    private void ensureHighestLevelModelDeclared(Model model)
    {
        if (!((model instanceof XML) ||
              (model instanceof AnonymousObjectGraph) ||
              model.equals(mxmlDocument.getRoot())) &&
            (model.getId() != null))
        {
            // This object needs to have an id at runtime, so instruct
            // SWCBuilder to emit one.
            mxmlDocument.ensureDeclaration(model);
        }
    }

    public boolean equals(Object object)
    {
        boolean result = false;

        if (object instanceof BindingExpression)
        {
            BindingExpression bindingExpression = (BindingExpression) object;

            if (bindingExpression.id == id)
            {
                result = true;
            }
        }

        return result;
    }

  public void setMxmlDocument(MxmlDocument mxmlDocument)
  {
    this.mxmlDocument = mxmlDocument;
    mxmlDocument.addBindingExpression(this);
  }

    public boolean isDestinationXMLAttribute()
    {
        return isDestinationXMLAttribute;
    }

    public boolean isDestinationXMLNode()
    {
        return isDestinationXMLNode;
    }

    public boolean isDestinationE4X()
    {
        return isDestinationE4X;
    }

    public boolean isDestinationObjectProxy()
    {
        return isDestinationObjectProxy;
    }

    public String getSourceAsProperty()
    {
        return sourceAsProperty;
    }

    public boolean isDestinationNonPublicProperty()
    {
      // We currently assume our destination is publicly accessible, and only
      // validate the simple property case.
      boolean result = false;
     
      if ((destination != null) && (destinationProperty != null && destinationStyle == null) &&
            !isArrayAccess() && !((isDestinationXMLAttribute || isDestinationXMLNode)))
      {
        Type type = destination.getType();
        Property property = type.getProperty(destinationProperty);
       
        if (property != null && !property.hasPublic())
          result = true;
      }
     
      return result;
    }
   
    public boolean isSourcePublicProperty()
    {
        String potentialProperty;
       
        if (sourceExpression.startsWith("(") && sourceExpression.endsWith(")"))
        {
            potentialProperty = sourceExpression.substring(1, sourceExpression.length() - 1);
        }
        else
        {
            potentialProperty = sourceExpression;
        }

        Type skeletonClass = mxmlDocument.getSkeletonClass();

        boolean result = false;

        if (potentialProperty.indexOf(":") == -1)
        {
            Property property = skeletonClass.getProperty(SymbolTable.publicNamespace, potentialProperty);

            // It looks like we are checking for "public" twice, but
            // the above call also returns properties in the unnamed
            // package, because they are both equivalent to an empty
            // string.
            if ((property != null) && property.hasPublic())
            {
                result = true;
                sourceAsProperty = potentialProperty;

                if (mxmlDocument.showDeprecationWarnings())
                {
                    AbstractBuilder.checkDeprecation(property, mxmlDocument.getSourcePath(), xmlLineNumber);
                }
            }
        }

        return result;
    }

    private static final String NODE_VALUE = "nodeValue".intern();

    public MemberExpressionNode generateDestinationAssignment(NodeFactory nodeFactory, String rvalue)
    {
        Node base = null;

        if (!((isDestinationXMLAttribute || isDestinationXMLNode)))
        {
            base = generateDestinationPathRoot(nodeFactory, false);
        }

        IdentifierNode identifier = null;

        if (destinationLValue != null)
        {
            identifier = nodeFactory.identifier(destinationLValue);
        }
        else if (destinationProperty != null)
        {
            identifier = nodeFactory.identifier(destinationProperty);
        }
        else if (destinationStyle != null)
        {
            identifier = nodeFactory.identifier(destinationStyle);
        }

        assert identifier != null;

        if (isDestinationXMLNode && !isDestinationE4X)
        {
            //buffer.append(".nodeValue");
            GetExpressionNode getExpression = nodeFactory.getExpression(identifier);
            base = nodeFactory.memberExpression(base, getExpression);
            identifier = nodeFactory.identifier(NODE_VALUE, false);
        }

        MemberExpressionNode memberExpression =
            AbstractSyntaxTreeUtil.generateGetterSelector(nodeFactory, rvalue, true);
        ArgumentListNode argumentList = nodeFactory.argumentList(null, memberExpression);
        SetExpressionNode selector = nodeFactory.setExpression(identifier, argumentList, false);

        if ((destination != null) &&
            (destinationProperty != null || destinationStyle != null) &&
            !isArrayAccess() &&
            !((isDestinationXMLAttribute || isDestinationXMLNode)))
        {
            selector.setMode(Tokens.DOT_TOKEN);
        }
        else if (isArrayAccess())
        {
            selector.setMode(Tokens.LEFTBRACKET_TOKEN);
        }

        return nodeFactory.memberExpression(base, selector);
    }

    private static final String SET_STYLE = "setStyle".intern();
    private static final String _SOURCE_FUNCTION_RETURN_VALUE = "_sourceFunctionReturnValue".intern();

    public MemberExpressionNode generateDestinationSetStyle(NodeFactory nodeFactory, String rvalue)
    {
        Node base = null;

        if (!((isDestinationXMLAttribute || isDestinationXMLNode)))
        {
            base = generateDestinationPathRoot(nodeFactory, false);
        }

        IdentifierNode identifier = nodeFactory.identifier(SET_STYLE, false);
        LiteralStringNode literalString = nodeFactory.literalString(destinationStyle);
        ArgumentListNode argumentList = nodeFactory.argumentList(null, literalString);
        MemberExpressionNode memberExpression =
            AbstractSyntaxTreeUtil.generateGetterSelector(nodeFactory, _SOURCE_FUNCTION_RETURN_VALUE, false);
        argumentList = nodeFactory.argumentList(argumentList, memberExpression);
        CallExpressionNode selector = (CallExpressionNode) nodeFactory.callExpression(identifier,
                                                                                      argumentList);
        selector.setRValue(false);

        return nodeFactory.memberExpression(base, selector);
    }

    /**
     * Sometimes the destination is not a member of the document, so
     * we have to climb the parent tree to find a parent that is.  Here is an example:
     *
     *   <mx:AreaChart>
     *    <mx:horizontalAxis>
     *      <mx:CategoryAxis dataProvider="{expenses}"/>
     *    </mx:horizontalAxis>
     *   </mx:AreaChart>
     *
     * For the above example the destination stack would be
     * ["_AreaChart1", "horizontalAxis"].
     */
    private Stack<Model> generateDestinationStack()
    {
        Stack<Model> destinationStack = new Stack<Model>();
        Model model = destination;

        while (model != null)
        {
            destinationStack.push(model);

            if ((model.getId() == null) || model.getIsAnonymous())
            {
                model = model.getParent();
            }
            else
            {
                break;
            }
        }

        return destinationStack;
    }

    public String getDestinationPath(boolean doXML)
    {
        StringBuilder buffer = new StringBuilder();

        // Always treat E4X as XML so the destinationLValue is used.
        if (isDestinationE4X)
        {
            doXML = true;           
        }
       
        if (!(doXML && (isDestinationXMLAttribute || isDestinationXMLNode)))
        {
            buffer.append( getDestinationPathRoot(false) );
        }

        if ((destination != null) &&
            (destinationProperty != null || destinationStyle != null) &&
            !isArrayAccess() &&
            !(doXML && (isDestinationXMLAttribute || isDestinationXMLNode)))
        {
            buffer.append(".");
        }

        if (isArrayAccess())
        {
            buffer.append("[");
        }

        if ((doXML || (!(isDestinationXMLAttribute || isDestinationXMLNode))) && (destinationLValue != null))
        {
            buffer.append(destinationLValue);
        }
        else if (destinationProperty != null)
        {
            buffer.append(destinationProperty);
        }
        else if (destinationStyle != null)
        {
            buffer.append(destinationStyle);
        }

        if (doXML && isDestinationXMLNode && !isDestinationE4X)
        {
            buffer.append(".nodeValue");
        }
        else if (isArrayAccess())
        {
            buffer.append("]");
        }

        return buffer.toString();
    }

    public String getDestinationPathRoot(boolean doRepeatable)
    {       
        if (destination == null)
        {
            return "";
        }

        StringBuilder destinationRoot = new StringBuilder();

        Stack<Model> destinationStack = generateDestinationStack();

        Model model = destinationStack.peek();
        ensureHighestLevelModelDeclared(model);

        boolean writeRepeaterIndices = doRepeatable;

        while (!destinationStack.isEmpty())
        {
            model = destinationStack.pop();

            if (model.equals(mxmlDocument.getRoot()))
            {
                destinationRoot.append("this");
            }
            else
            {
                String parentIndex = model.getParentIndex();

                if ((parentIndex != null) && (destinationRoot.length() > 0))
                {
                    destinationRoot.append("[");
                    destinationRoot.append(parentIndex);
                    destinationRoot.append("]");
                }
                else
                {
                    String id = model.getId();

                    if (id != null)
                    {
                        if (!model.getIsAnonymous())
                        {
                            mxmlDocument.ensureDeclaration(model);
                        }
                        destinationRoot.append(id);
                    }
                }
            }

            if (writeRepeaterIndices && isRepeatable())
            {
                for (int i = 0; i < model.getRepeaterLevel(); ++i)
                {
                    destinationRoot.append("[instanceIndices[");
                    destinationRoot.append(i);
                    destinationRoot.append("]]");
                }
                writeRepeaterIndices = false;
            }

            if (!destinationStack.isEmpty())
            {
                Model child = destinationStack.peek();
               
                if (child.getParentIndex() == null)
                {
                    destinationRoot.append(".");
                }
            }
        }
       
        return destinationRoot.toString();
    }

    public Node generateDestinationPathRoot(NodeFactory nodeFactory, boolean doRepeatable)
    {
        Node result = null;

        if (destination != null)
        {
            Stack<Model> destinationStack = generateDestinationStack();
            Model model = destinationStack.peek();
            ensureHighestLevelModelDeclared(model);

            boolean writeRepeaterIndices = doRepeatable;

            while (!destinationStack.isEmpty())
            {
                model = destinationStack.pop();

                if (model.equals(mxmlDocument.getRoot()))
                {
                    result = nodeFactory.thisExpression(-1);
                }
                else
                {
                    String parentIndex = model.getParentIndex();

                    if ((parentIndex != null) && (result != null))
                    {
                        //destinationRoot.append("[");
                        //destinationRoot.append(parentIndex);
                        //destinationRoot.append("]");
                        assert false;
                    }
                    else
                    {
                        String id = model.getId();

                        if (id != null)
                        {
                            if (!model.getIsAnonymous())
                            {
                                mxmlDocument.ensureDeclaration(model);
                            }
                            //destinationRoot.append(id);

                            if (result == null)
                            {
                                result = AbstractSyntaxTreeUtil.generateGetterSelector(nodeFactory, id, true);
                            }
                            else
                            {
                                assert false;
                            }
                        }
                    }
                }

                if (writeRepeaterIndices && isRepeatable())
                {
                    for (int i = 0; i < model.getRepeaterLevel(); ++i)
                    {
                        //destinationRoot.append("[instanceIndices[");
                        //destinationRoot.append(i);
                        //destinationRoot.append("]]");
                        assert false;
                    }
                    writeRepeaterIndices = false;
                }

                if (!destinationStack.isEmpty())
                {
                    Model child = destinationStack.peek();
               
                    if (child.getParentIndex() == null)
                    {
                        //destinationRoot.append(".");
                        assert false;
                    }
                }
            }
        }

        return result;
    }

    /**
     * Returns the type name of the destination.  This is used when
     * code generating the binding source and destination functions.
     * It's also used when code generating the document's imports.
     */
    public String getDestinationTypeName()
    {
        Type type = null;

        if ((destination != null) &&
            !(destination instanceof AnonymousObjectGraph) &&
            !(destination instanceof XML))
        {
            if (destinationProperty != null)
            {
                Type destinationType = destination.getType();

                if (!destinationType.getName().equals(mxmlDocument.getStandardDefs().CLASS_OBJECTPROXY))
                {
                    Property property = destinationType.getProperty(destinationProperty);

                    if (property != null)
                    {
                        type = property.getType();
                    }
                }
            }
            else if (destinationStyle != null)
            {
                Type destinationType = destination.getType();
                Style style = destinationType.getStyle(destinationStyle);

                if (style != null)
                {
                    type = style.getType();
                }
            }
            else
            {
                type = destination.getType();
            }
        }

        String result = SymbolTable.NOTYPE;

        if (type != null)
        {
            result = NameFormatter.toDot(type.getName());
        }

        return result;
    }

    public int getId()
    {
        return id;
    }

    public String getRepeatableSourceExpression()
    {
        String repeatableSourceExpression = sourceExpression;
        List repeaterParents = destination.getRepeaterParents();
        Iterator iterator = repeaterParents.iterator();

        while ( iterator.hasNext() )
        {
            Model repeater = (Model) iterator.next();
            int repeaterLevel = repeater.getRepeaterLevel();
            StringBuilder buffer = new StringBuilder();
            int i;

            for (i = 0; i < repeaterLevel; i++)
            {
                buffer.append("[instanceIndices[");
                buffer.append(i);
                buffer.append("]]");
            }

            buffer.append(".mx_internal::getItemAt(repeaterIndices[");
            buffer.append(i);
            buffer.append("])");

            repeatableSourceExpression = repeatableSourceExpression.replaceAll(repeater.getId() + "\\.currentItem",
                                                                               repeater.getId() + buffer.toString());

            repeatableSourceExpression = repeatableSourceExpression.replaceAll(repeater.getId() + "\\.currentIndex",
                                                                               "repeaterIndices[" + i + "]");
        }

        return repeatableSourceExpression;
    }

    public String getSourceExpression()
    {
        return sourceExpression;
    }

    public void setDestinationProperty(String destinationProperty)
    {
        this.destinationProperty = destinationProperty;
    }

    public void setDestinationProperty(int destinationProperty)
    {
        this.destinationProperty = Integer.toString(destinationProperty);
        arrayAccess = true;
    }

    public void setDestinationStyle(String destinationStyle)
    {
        this.destinationStyle = destinationStyle;
    }

    public String getDestinationStyle()
    {
        return destinationStyle;
    }

    public boolean isSimpleChain()
    {
        return (!isFromBindingNode() &&
                !isStyle() &&
                !isDestinationObjectProxy() &&
                getNamespaceDeclarations().equals("") &&
                (twoWayCounterpart == null) &&
                (getDestinationPath(false).indexOf("[") == -1));
    }

    public boolean isStyle()
    {
        return destinationStyle != null;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public void setDestinationXMLAttribute(boolean isDestinationXMLAttribute)
    {
        this.isDestinationXMLAttribute = isDestinationXMLAttribute;
    }

    public void setDestinationXMLNode(boolean isDestinationXMLNode)
    {
        this.isDestinationXMLNode = isDestinationXMLNode;
    }

    public void setDestinationE4X(boolean isDestinationE4X)
    {
        this.isDestinationE4X = isDestinationE4X;
    }

    public void setDestinationObjectProxy(boolean isDestinationObjectProxy)
    {
        this.isDestinationObjectProxy = isDestinationObjectProxy;
    }

    public String getDestinationProperty()
    {
        return destinationProperty;
    }

    public boolean isArrayAccess()
    {
        return arrayAccess;
    }

    public String getDestinationLValue()
    {
        return destinationLValue;
    }

    public void setDestinationLValue(String lvalue)
    {
        destinationLValue = lvalue;
    }

    public Model getDestination()
    {
        return destination;
    }

    public void setDestination(Model destination)
    {
        this.destination = destination;
        if (this.xmlLineNumber == 0)
        {
          // C: The destination xml line number may not be as accurate as the binding expression's original number...
          this.xmlLineNumber = destination.getXmlLineNumber();
        }
    }

    public boolean isRepeatable()
    {
        return ((destination != null) && (destination.getRepeaterLevel() > 0));
    }

    public int getRepeaterLevel(String var)
    {
        if (var.indexOf("[repeaterIndices") > -1)
        {
            var = var.substring(0, var.indexOf("["));
        }
       
        int repeaterLevel = -1;

        if (destination != null)
        {
            List repeaters = destination.getRepeaterParents();

            repeaterLevel = repeaters.size() - 1;

            for (; repeaterLevel >= 0; --repeaterLevel)
            {
                Model r = (Model) repeaters.get(repeaterLevel);
                if (var.equals(r.getId()))
                {
                    break;
                }
            }
        }

        return repeaterLevel;
    }

    public String getRepeaterId(int level)
    {
        Model repeater = (Model) destination.getRepeaterParents().get(level);
        return repeater.getId();
    }

    public BindingExpression getTwoWayCounterpart()
    {
        return twoWayCounterpart;
    }

    public void setTwoWayCounterpart(BindingExpression twoWayCounterpart)
    {
        this.twoWayCounterpart = twoWayCounterpart;
    }

  /**
   *
   */
  public int getXmlLineNumber()
  {
    return xmlLineNumber;
  }

  public void addNamespace(String nsUri, int i)
  {
    if (namespaces == null)
    {
      namespaces = new HashMap<Integer, String>();
    }
    namespaces.put(IntegerPool.getNumber(i), nsUri);
  }
 
  public Map<Integer, String> getNamespaces()
  {
      return namespaces;
  }

    /**
     * Combine the namespaces from all of the binding expressions.  Use the   
     * Integer for the key, since it is unique across all the binding expressions.   
     * @param bindingExpressions   
     * @return   
     */   
    public static String getAllBindingNamespaceDeclarations(List<BindingExpression> bindingExpressions)   
    {   
        Map<Integer, String> allNs = new HashMap<Integer, String>();   
      
        // Combine all the namespaces using the Integer as the unique key.   
        for (BindingExpression be : bindingExpressions)   
        {   
            allNs.putAll(be.getNamespaces());   
        }   
      
        return getNamespaceDeclarations(allNs);   
    }

  /**
   * Build the AS var Namespace declarations for the namespaces in the map.
   * @param namespaceMap
   * @return
   */
    public static String getNamespaceDeclarations(Map<Integer, String> namespaceMap)
    {
        if (namespaceMap != null)
        {
            StringBuilder b = new StringBuilder();
            for (Integer key : namespaceMap.keySet())
            {
                int k = key.intValue();
                String uri = namespaceMap.get(key);
                b.append("var ns").append(k).append(":Namespace = new Namespace(\"").append(uri).append("\");\n");
            }
            return b.toString();
        }
       
        return "";
    }
 
  public String getNamespaceDeclarations()
  {
      return getNamespaceDeclarations(namespaces);
  }

    // intern all identifier constants
    private static final String NAMESPACE = "Namespace".intern();

    public StatementListNode generateNamespaceDeclarations(Context context, StatementListNode statementList)
    {
        return generateNamespaceDeclarations(namespaces, context, statementList);
    }

    public static StatementListNode generateNamespaceDeclarations(
            Map<Integer, String> map, Context context,
            StatementListNode statementList)
    {
        StatementListNode result = statementList;

        if (map != null)
        {
            NodeFactory nodeFactory = context.getNodeFactory();

            for (Integer key : map.keySet())
            {
                String uri = map.get(key);
                IdentifierNode identifier = nodeFactory.identifier(NAMESPACE, false);
                LiteralStringNode literalString = nodeFactory.literalString(uri);
                ArgumentListNode argumentList = nodeFactory.argumentList(null, literalString);
                CallExpressionNode callExpression = (CallExpressionNode)nodeFactory.callExpression(identifier, argumentList);
                callExpression.is_new = true;
                callExpression.setRValue(false);
                MemberExpressionNode initializer = nodeFactory.memberExpression(null, callExpression);
                int k = key.intValue();
                VariableDefinitionNode variableDefinition = AbstractSyntaxTreeUtil.generateVariable(nodeFactory, ("ns" + k).intern(), NAMESPACE, false, initializer);
                result = nodeFactory.statementList(result, variableDefinition);
            }
        }

        return result;
    }

    public boolean isFromBindingNode()
    {
        return isFromBindingNode;
    }

    public void setFromBindingNode(boolean isFromBindingNode)
    {
        this.isFromBindingNode = isFromBindingNode;
    }

    public boolean isTwoWayPrimary()
    {
        return isTwoWayPrimary;
    }

    public void setTwoWayPrimary(boolean isTwoWayPrimary)
    {
        this.isTwoWayPrimary = isTwoWayPrimary;
    }
}
TOP

Related Classes of flex2.compiler.mxml.rep.BindingExpression

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.