blob: ea82e090b924079f43545a33b287833694d2e14a [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.codegen.databinding;
import static org.apache.royale.abc.ABCConstants.*;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.MethodInfo;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.visitors.IABCVisitor;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.constants.IMetaAttributeConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.internal.as.codegen.MXMLClassDirectiveProcessor;
import org.apache.royale.compiler.internal.codegen.databinding.WatcherInfoBase.WatcherType;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.targets.RoyaleAppSWFTarget;
import org.apache.royale.compiler.internal.tree.as.BinaryOperatorAsNode;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode;
import org.apache.royale.compiler.mxml.IMXMLTypeConstants;
import org.apache.royale.compiler.targets.ISWFTarget;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.mxml.IMXMLBindingNode;
import org.apache.royale.compiler.tree.mxml.IMXMLDataBindingNode;
import org.apache.royale.compiler.workspaces.IWorkspace;
/**
* Keeps track of all the Data Bindings in an MXML file and helps with codegen
* for data binding.
*
* This class is closely linked with MXMLDocumentDirectiveProcessor - they both call each
* other and could be merged. They are separate just to manage size and complexity.
*
* TODO:
* Document the runtime dependencies on SDK
* add problem reporting.
*
* bind to function, This should be fixed now
* Cases not yet working:
* xml, xml list, array
*
* Improve code gen
* don't make getter functions when not needed
* share getters when possible
*
*
* known bugs:
* Generated traits not in mx_internal
*
*/
public class MXMLBindingDirectiveHelper
{
// Some AET names we use locally
private static final Name NAME_OBJTYPE = new Name(IASLanguageConstants.Object);
private static final Name NAME_ARRAYTYPE = new Name(IASLanguageConstants.Array);
public MXMLBindingDirectiveHelper(MXMLClassDirectiveProcessor ddp, IABCVisitor emitter)
{
host = ddp;
ddp.getProject().getBindingMap().put(ddp.getClassDefinition(), bindingDataBase);
this.emitter = emitter;
}
// -------------------- private fields ------------------------------------------------
private final MXMLClassDirectiveProcessor host;
private final IABCVisitor emitter;
// This helper will do all the analysis of the binding expressions
private BindingDatabase bindingDataBase = new BindingDatabase();
// If we generate a propertyGetter for use by PropertyWatchers, we
// store it here. It will remain null if we don't need one.
private MethodInfo propertyGetter = null;
//-------------------------- public methods -------------------------------------
/**
* host should visit all the databinding nodes before trying to codegen them
*/
public BindingInfo visitNode(IMXMLDataBindingNode node)
{
// analyze the node for later CG
return bindingDataBase.analyze(node, host.getProblems(), host);
}
/**
* Visit an IMXMLBindingNode - this node has explicit source and destination expressions
* instead of the destination being determined by the nodes location in the AST, as in the IMXMLInstanceNode
* version above
* @param node the Binding Node to vist
*/
public void visitNode( IMXMLBindingNode node )
{
bindingDataBase.analyzeBindingNode(node, host.getProblems(), host);
}
/**
* get the instruction list for code to be added to constructor
* to set up data binding. This is the main entry point for codegen of
* databindings
*
* This function must be called only once, after then entire document has been traversed.
* @return the {@link InstructionList} the instruction list for code to be added to constructor
*/
public InstructionList getConstructorCode()
{
if (bindingDataBase.getBindingInfo().isEmpty())
return null;
if (!host.getProject().getTargetSettings().getMxmlChildrenAsData())
if (!establishSDKDependencies())
return null;
bindingDataBase.finishAnalysis();
for (IClassDefinition ancestor: host.getClassDefinition().resolveAncestry(host.getProject())) {
if (ancestor.equals(host.getClassDefinition())) continue;
if (ancestor.getMetaTagByName(IMetaAttributeConstants.ATTRIBUTE_BINDINGS) != null) {
//System.out.println("Ancestor bindings for "+host.getClassDefinition().getQualifiedName()+" at "+ancestor.getQualifiedName());
bindingDataBase.setNearestAncestorWithBindings(ancestor.getQualifiedName());
break;
}
}
// Please leave this in here - it is a very commonly used diagnostic
// Just comment it out before checking
//System.out.println("db: " + bindingDataBase);
boolean isFlexSDK = false;
ISWFTarget target = host.getProject().getSWFTarget();
if (target instanceof RoyaleAppSWFTarget)
{
if (!((RoyaleAppSWFTarget)target).isFlexInfo())
{
makeSpecialMemberVariablesForBinding();
isFlexSDK = true;
}
else {
//if the variable is already declared on an ancestor, we should not redeclare it (as it is currently public)
//redeclaring it will essentially prevent the inherited value from being accessed at the current level (in swf)
//accessing the super class value allows 'nesting' which permits inheritance
if (!bindingDataBase.getHasAncestorBindings())
host.addVariableTrait(IMXMLTypeConstants.NAME_BINDINGS, NAME_ARRAYTYPE);
//this should already be set, the following may be able to be removed (tbc):
host.getClassDefinition().setRoyaleBindings();
}
}
else
{
if (host.getProject().resolveQNameToDefinition(host.getProject().getBindingManagerClass()) != null)
{
makeSpecialMemberVariablesForBinding();
isFlexSDK = true;
}
else{
//redeclaring it will essentially prevent the inherited value from being accessed at the current level (in swf)
if (!bindingDataBase.getHasAncestorBindings())
host.addVariableTrait(IMXMLTypeConstants.NAME_BINDINGS, NAME_ARRAYTYPE);
//this should already be set, the following may be able to be removed (tbc):
host.getClassDefinition().setRoyaleBindings();
}
}
if (host.getProject().getTargetSettings().getMxmlChildrenAsData())
return outputBindingInfoAsData(isFlexSDK);
InstructionList ret = new InstructionList();
makePropertyGetterIfNeeded();
ret.addAll(makeBindingsAndGetters());
// now nothing on the stack, _bindings has the array of bindings
ret.addAll(makeAllWatchers());
ret.addAll(BindingCodeGenUtils.fireInitialBindings());
return ret;
}
private InstructionList outputBindingInfoAsData(boolean isFlexSDK)
{
InstructionList ret = new InstructionList();
int propertyCount = 0;
if (!isFlexSDK && bindingDataBase.getHasAncestorBindings()) {
//add the ancestor bindings reference as the first item in this current _bindings array
//this approach permits binding evaluation to work recursively by checking the first element only
ret.addInstruction(OP_getlocal0);
// stack: ..., this
ret.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS);
//propertyCount needs to be incremented for the current array:
propertyCount++;
}
Set<BindingInfo> bindingInfo = bindingDataBase.getBindingInfo();
ret.pushNumericConstant(bindingInfo.size()); // number of bindings
propertyCount++;
for (BindingInfo bi : bindingInfo)
{
String s;
s = bi.getSourceString();
if (s == null && bi.isSourceSimplePublicProperty())
s = getSourceStringFromGetter(bi.getExpressionNodesForGetter());
if (s == null || s.length() == 0 || isFlexSDK)
{
BindingCodeGenUtils.generateGetter(emitter, ret, bi.getExpressionNodesForGetter(), host.getInstanceScope());
}
else if (s.contains(".") && !isFlexSDK)
{
String[] parts = s.split("\\.");
if (bi.classDef != null)
{
ret.addInstruction(OP_pushstring, bi.classDef.getQualifiedName());
ret.addInstruction(OP_pushstring, parts[1]);
}
else
{
for (String part : parts)
ret.addInstruction(OP_pushstring, part);
}
ret.addInstruction(OP_newarray, parts.length);
}
else
ret.addInstruction(OP_pushstring, s);
IExpressionNode destNode = bi.getExpressionNodeForDestination();
if (destNode != null)
BindingCodeGenUtils.generateSetter(ret, destNode, host.getInstanceScope());
else
ret.addInstruction(OP_pushnull);
s = bi.getDestinationString();
// in badly broken code, you can bind to a non-existing type
// and end up with null here
if (s == null)
s = "";
if (s.contains("."))
{
String[] parts = s.split("\\.");
for (String part : parts)
ret.addInstruction(OP_pushstring, part);
ret.addInstruction(OP_newarray, parts.length);
}
else
ret.addInstruction(OP_pushstring, s);
propertyCount += 3;
}
Set<Entry<Object, WatcherInfoBase>> watcherChains = bindingDataBase.getWatcherChains();
if (watcherChains != null)
{
for (Entry<Object, WatcherInfoBase> entry : watcherChains)
{
WatcherInfoBase watcherInfoBase = entry.getValue();
propertyCount += encodeWatcher(ret, watcherInfoBase);
}
}
ret.addInstruction(OP_newarray, propertyCount);
// now save array to _bindings property
ret.addInstruction(OP_getlocal0);
// stack : this, bindings
ret.addInstruction(OP_swap);
// stack : bindings, this
if (isFlexSDK)
ret.addInstruction(OP_callpropvoid, IMXMLTypeConstants.ARG_SETUPBINDINGS);
else
ret.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_BINDINGS);
return ret;
}
private int encodeWatcher(InstructionList ret, WatcherInfoBase watcherInfoBase)
{
ret.pushNumericConstant(watcherInfoBase.getIndex());
WatcherType type = watcherInfoBase.getType();
int propertyCount = 1;
if (type == WatcherType.FUNCTION)
{
ret.pushNumericConstant(0);
FunctionWatcherInfo functionWatcherInfo = (FunctionWatcherInfo)watcherInfoBase;
ret.addInstruction(OP_pushstring, functionWatcherInfo.getFunctionName());
InstructionList paramFunction = new InstructionList();
BindingCodeGenUtils.makeParameterFunction(emitter, paramFunction, functionWatcherInfo.params, host.getInstanceScope());
ret.addAll(paramFunction);
outputEventNames(ret, functionWatcherInfo.getEventNames());
outputBindings(ret, functionWatcherInfo.getBindings());
propertyCount += 5;
}
else if ((type == WatcherType.STATIC_PROPERTY) || (type == WatcherType.PROPERTY))
{
ret.pushNumericConstant(type == WatcherType.STATIC_PROPERTY ? 1 : 2);
PropertyWatcherInfo propertyWatcherInfo = (PropertyWatcherInfo)watcherInfoBase;
boolean makeStaticWatcher = (watcherInfoBase.getType() == WatcherType.STATIC_PROPERTY);
// round up the getter function for the watcher, or null if we don't need one
MethodInfo propertyGetterFunction = null;
if (watcherInfoBase.isRoot && !makeStaticWatcher)
{
propertyGetterFunction = this.propertyGetter;
}
else if (watcherInfoBase.isRoot && makeStaticWatcher)
{
// TOTO: implement getter func for static watcher.
}
ret.addInstruction(OP_pushstring, propertyWatcherInfo.getPropertyName());
outputEventNames(ret, propertyWatcherInfo.getEventNames());
outputBindings(ret, propertyWatcherInfo.getBindings());
if (propertyGetterFunction == null)
ret.addInstruction(OP_pushnull); // null is valid
else
ret.addInstruction(OP_newfunction, propertyGetterFunction);
if (type == WatcherType.STATIC_PROPERTY)
{
StaticPropertyWatcherInfo pwinfo = (StaticPropertyWatcherInfo)watcherInfoBase;
Name classMName = pwinfo.getContainingClass(host.getProject());
ret.addInstruction(OP_getlex, classMName);
propertyCount++;
}
propertyCount += 5;
}
else if (type == WatcherType.XML)
{
ret.pushNumericConstant(3);
XMLWatcherInfo xmlWatcherInfo = (XMLWatcherInfo)watcherInfoBase;
ret.addInstruction(OP_pushstring, xmlWatcherInfo.getPropertyName());
outputBindings(ret, xmlWatcherInfo.getBindings());
propertyCount += 3;
}
else assert false;
// then recurse into children
Set<Entry<Object, WatcherInfoBase>> children = watcherInfoBase.getChildren();
if (children != null)
{
int childCount = 0;
for ( Entry<Object, WatcherInfoBase> ent : children)
{
childCount += encodeWatcher(ret, ent.getValue());
}
ret.addInstruction(OP_newarray, childCount);
propertyCount++;
}
else
{
ret.addInstruction(OP_pushnull);
propertyCount++;
}
return propertyCount;
}
private String getSourceStringFromMemberAccessExpressionNode(MemberAccessExpressionNode node)
{
String s = "";
IExpressionNode left = node.getLeftOperandNode();
if (left instanceof FunctionCallNode) // probably a cast
{
IASNode child = ((FunctionCallNode)left).getArgumentsNode().getChild(0);
if (child instanceof IdentifierNode)
s = getSourceStringFromIdentifierNode((IdentifierNode)child);
else if (child instanceof MemberAccessExpressionNode)
s = getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)child);
}
else if (left instanceof MemberAccessExpressionNode)
s = getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)left);
else if (left instanceof IdentifierNode)
s = getSourceStringFromIdentifierNode((IdentifierNode)left);
else if (left instanceof BinaryOperatorAsNode)
{
left = (IExpressionNode)((BinaryOperatorAsNode)left).getChild(0);
if (left instanceof MemberAccessExpressionNode)
s = getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)left);
else if (left instanceof IdentifierNode)
s = getSourceStringFromIdentifierNode((IdentifierNode)left);
else
System.out.println("expected binding BinaryOperatorAsNode left node" + node.toString());
}
else
System.out.println("expected binding member access left node" + node.toString());
s += ".";
IExpressionNode right = node.getRightOperandNode();
if (right instanceof FunctionCallNode) // probably a cast
{
IASNode child = ((FunctionCallNode)right).getArgumentsNode().getChild(0);
if (child instanceof IdentifierNode)
s += getSourceStringFromIdentifierNode((IdentifierNode)child);
else if (child instanceof MemberAccessExpressionNode)
s += getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)child);
}
else if (right instanceof MemberAccessExpressionNode)
s += getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)right);
else if (right instanceof IdentifierNode)
s += getSourceStringFromIdentifierNode((IdentifierNode)right);
else
System.out.println("expected binding member access right node" + node.toString());
return s;
}
private String getSourceStringFromIdentifierNode(IdentifierNode node)
{
return node.getName();
}
private String getSourceStringFromGetter(List<IExpressionNode> nodes)
{
String s = "";
IExpressionNode node = nodes.get(0);
if (node instanceof MemberAccessExpressionNode)
{
s = getSourceStringFromMemberAccessExpressionNode((MemberAccessExpressionNode)node);
}
else if (node instanceof IdentifierNode)
{
s = ((IdentifierNode)node).getName();
}
return s;
}
private void outputEventNames(InstructionList ret, List<String> events)
{
if (events.size() > 1)
{
for (String event : events)
ret.addInstruction(OP_pushstring, event);
ret.addInstruction(OP_newarray, events.size());
}
else if (events.size() == 1)
ret.addInstruction(OP_pushstring, events.get(0));
else
ret.addInstruction(OP_pushnull);
}
private void outputBindings(InstructionList ret, List<BindingInfo> bindings)
{
if (bindings.size() > 1)
{
for (BindingInfo binding : bindings)
ret.pushNumericConstant(binding.getIndex());
ret.addInstruction(OP_newarray, bindings.size());
}
else if (bindings.size() == 1)
ret.pushNumericConstant(bindings.get(0).getIndex());
else
ret.addInstruction(OP_pushnull);
}
/**
* Generates all the Binding objects, and and "getter" functions required to implement them
* puts Binding into this._bindings
*/
private InstructionList makeBindingsAndGetters()
{
InstructionList insns = new InstructionList();
log(insns, "makeBindingsAndGetters");
// TODO: do we really want to push these all onto the stack, then make them into an array?
// we could make the array first, then add then all later.
for (BindingInfo bindingInfo : bindingDataBase.getBindingInfo())
{
makeBindingAndGetter(insns, bindingInfo);
}
// stack : binding.n, binding.n-1..., binding.0
// now turn bindings into array
insns.addInstruction(OP_newarray, bindingDataBase.getBindingInfo().size());
// stack : bindings[]
// now save array to _bindings property
insns.addInstruction(OP_getlocal0);
// stack : this, bindings
insns.addInstruction(OP_swap);
// stack : bindings, this
insns.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_BINDINGS);
// stack : <empty>
// Now that we have saved the _bindings array, do any needed
// two way binding hook up.
linkTwoWayCounterparts(insns);
log(insns, "leave makebindingsArray");
return insns;
}
/**
* Goes through the BindingInfo's looking for two way pairs that need to be hooked up.
* Generate the code to hook them up
*/
private void linkTwoWayCounterparts(InstructionList insns)
{
Map<Integer, Integer> pairs = bindingDataBase.getTwoWayBindingInfoPairs();
Set<Entry<Integer, Integer>> entries = pairs.entrySet();
for (Entry<Integer, Integer> pair : entries)
{
setTwoWayCounterpart(insns, pair.getKey(), pair.getValue()); // one way...
setTwoWayCounterpart(insns, pair.getValue(), pair.getKey()); // ... then the other
}
}
/**
* Generate code for _bindings[dest].twoWayCounterpart = _bindings[src];
*/
private void setTwoWayCounterpart(InstructionList insns, int dest, int src)
{
assert dest >= 0;
assert src >= 0;
assert src != dest;
insns.addInstruction(OP_getlocal0);
// stack: this
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS);
// stack: this.bindings
insns.pushNumericConstant(dest);
// stack: this.bindings, dest
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP);
// stack: bindings[dest]
insns.addInstruction(OP_getlocal0);
// stack: ..., this
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS);
// stack: ..., this.bindings
insns.pushNumericConstant(src);
// stack: ..., this.bindings, src
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP);
// stack: bindings[dest], bindings[src];
insns.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_TWOWAYCOUNTERPART);
// stack: <empty>
}
/**
* Generate the getter for a single binding object, then create the binding object
* using the getter as a ctor argument.
*
* On entry: nothing special
* On exit : binding(n) on stack
*/
private void makeBindingAndGetter(InstructionList insns, BindingInfo bindingInfo)
{
log(insns, "makeBindingAndGetter");
// IL to hold code for putting the source and dest funcs on the stack,
// we just add them in at the right place to avoid OP_swaps
InstructionList methodIsns = new InstructionList();
// Step 1: make getter and leave on stack
if (bindingInfo.isSourceSimplePublicProperty())
{
// Special case - we don't need a getter for the simplest case.
methodIsns.addInstruction(OP_pushnull);
}
else
{
BindingCodeGenUtils.generateGetter(
emitter,
methodIsns,
bindingInfo.getExpressionNodesForGetter(),
host.getInstanceScope());
}
// stack : getter
if(bindingInfo.getExpressionNodeForDestination() != null )
{
BindingCodeGenUtils.generateSetter(
methodIsns,
bindingInfo.getExpressionNodeForDestination(),
host.getInstanceScope()
);
}
else
{
methodIsns.addInstruction(OP_pushnull);
}
// Step 2: make binding
String destStr = bindingInfo.getDestinationString();
String srcStr = bindingInfo.getSourceString();
BindingCodeGenUtils.makeBinding(insns, host.getProject(), destStr, srcStr, methodIsns);
}
/** Since we use SDK classes, we need to establish the correct dependency
* so that they classes get linked in.
*
* @return true if we succeed
*/
private boolean establishSDKDependencies()
{
// List of SDK classed we depend on
// TODO: generated complete and minimal list dynamically.
RoyaleProject project = host.getProject();
String[] depends = {
project.getBindingClass(),
project.getPropertyWatcherClass(),
project.getStaticPropertyWatcherClass(),
project.getFunctionReturnWatcherClass(),
project.getXMLWatcherClass(),
project.getBindingManagerClass()
};
ASScope scope = bindingDataBase.getScope();
IWorkspace workspace = project.getWorkspace();
for (String depend : depends)
{
IResolvedQualifiersReference ref = ReferenceFactory.packageQualifiedReference(workspace, depend);
if (ref==null)
{
// TODO: generate compiler problem if we can't resolve these
assert false;
return false;
}
IDefinition def = ref.resolve(project, scope, DependencyType.EXPRESSION, false);
if (def==null)
{
// TODO: generate compiler problem if we can't resolve these
// CMP-886
assert false;
return false;
}
}
return true;
}
/**
* Creates all the watchers we need for the class we are CG'ing, setup of their parent child relationships,
* and saves them in this._watchers
*
* @return
*
* VM Context:
* during: local1=_bindings
* local2=_watchers
*/
private InstructionList makeAllWatchers()
{
InstructionList insns = new InstructionList();
log(insns, "makeWatchers");
if (BindingCodeGenUtils.doLog) log("db: " + bindingDataBase);
allocateNullWatchersArray(insns, bindingDataBase.getNumWatchers());
// set up the locals
insns.addInstruction(OP_getlocal0); // stack: this
insns.addInstruction(OP_dup); // this, this
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS); // stack: _bindings, this
insns.addInstruction(OP_setlocal1); // local1 = _bindings
// stack: this
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_WATCHERS); // stack: _watchers
insns.addInstruction(OP_setlocal2); // local2 = _watchers
Set<Entry<Object, WatcherInfoBase>> watcherChains = bindingDataBase.getWatcherChains();
if (watcherChains != null)
{
for (Entry<Object, WatcherInfoBase> ent : watcherChains)
{
makeWatcherChain(insns, ent.getValue());
}
}
log(insns, "leave makeWatchersArray");
return insns;
}
/**
* Generate ABC to allocate an array full of nulls, and assign to this._watchers
*/
private void allocateNullWatchersArray(InstructionList insns, int numWatchers)
{
log(insns, "allocateNullWatchersArray");
for (int i=0; i<numWatchers; ++i)
{
// TODO: should we emit a loop? this may use up too much stack!!
insns.addInstruction(OP_pushnull);
}
insns.addInstruction(OP_newarray, numWatchers);
// stack : watchers[]
// now save array to _watchers propery
insns.addInstruction(OP_getlocal0);
// stack : this, watchers
insns.addInstruction(OP_swap);
// stack : watchers, this
insns.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_WATCHERS);
// stack : <empty>
}
/**
* Does code-gen for:
* Instantate all of the watchers in the watcher chain
* Save references to created watchers in _watchers
* Calls any necessary initialization code on the created watchers, including:
* any necessary updateParent call
* any necessary addChild calls
* @param insns
* @param watcherChain
*/
private void makeWatcherChain(InstructionList insns, WatcherInfoBase watcherChain)
{
log(insns, "** makeWatcherChain " + watcherChain.getIndex());
if (watcherChain.getChildren() == null && watcherChain.getEventNames().isEmpty())
{
// TODO: we already made an array slot for this - probably shouldn't have
// also - what it the real algorithm here? don't we sometimes make these guys?
log("not making watcher for non-bindable");
if (watcherChain.getType() != WatcherType.FUNCTION)
return;
}
// first populate the _watcher array with the ones in this chain
makeWatcherAndChildren(insns, watcherChain);
// then call update parent on and add child as needed
watcherUpdateParent(insns, watcherChain);
watcherAddChildren(insns, watcherChain);
}
/**
* generates all of the watcher.addChild() called needed for a chain of watchers,
* and any other parent/child code
*
* typically this will look like:
* watchers[0].addChild( watchers[1] );
* watchers[1].addchild( watchers[2] );
*/
private void watcherAddChildren(InstructionList insns, WatcherInfoBase watcher)
{
Set<Entry<Object, WatcherInfoBase>> children = watcher.getChildren();
if (children == null) return;
for (Entry<Object, WatcherInfoBase> child : children)
{
watcherAddChild(insns, watcher, child.getValue()); // set up children for this watcher
watcherAddChildren(insns, child.getValue());
}
}
/* VM Context:
* entry: local1=_bindings
* local2=_watchers
*
* during:
* local3= parentWatcher
* local4=childWatcher
*/
private void watcherAddChild(InstructionList insns, WatcherInfoBase parent, WatcherInfoBase child)
{
log(insns, "add child. parent=" + parent.getIndex() + " child=" + child.getIndex());
// push parent onto stack
//------------ First, find the parent and child and put into local registers -----
//
insns.addInstruction(OP_getlocal2); // stack: _watchers
insns.pushNumericConstant(parent.getIndex());
// stack: parent_index, _watchers
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP);
// stack: _watchers[ParentIndex]
insns.addInstruction(OP_setlocal3);
// stack empty, local3 = parent
// push child onto stack
insns.addInstruction(OP_getlocal2); // stack: _watchers
insns.pushNumericConstant(child.getIndex());
// stack: child_index, _watchers
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP);
// stack: _watchers[child_index]
insns.addInstruction(OP_setlocal, 4);
// stack empty, local4 = child
//------------------------------------------------------------
// FunctionRetrunWatcher has a special field "parentWatcher" that must
// be set to the parent
//
if (child.getType() == WatcherType.FUNCTION)
{
insns.addInstruction(OP_getlocal, 4);
// stack: child
insns.addInstruction(OP_getlocal3);
// stack: parent, child
insns.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_PARENTWATCHER);
}
//------------------ make the addChild call
//
insns.addInstruction(OP_getlocal3);
// stack: parent
insns.addInstruction(OP_getlocal, 4);
// stack: child, parent
// call parent.addChild(child)
insns.addInstruction(OP_callpropvoid, IMXMLTypeConstants.ARG_ADDCHILD);
}
/* VM Context:
* entry: local1=_bindings
* local2=_watchers
*/
private void watcherUpdateParent(InstructionList insns, WatcherInfoBase watcherInfo)
{
assert(watcherInfo.getIndex() >= 0 && watcherInfo.getIndex() < bindingDataBase.getNumWatchers());
//---------------------------------------------------
// first, push watcher [n]
insns.addInstruction(OP_getlocal2); // stack: _watchers
insns.pushNumericConstant(watcherInfo.getIndex());
// stack: index, _watchers
// stack: watcher_index
insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP); // stack: _watchers[index]
switch (watcherInfo.getType())
{
case PROPERTY:
case FUNCTION:
// for regular property watcher, will will do updateParent(this)
insns.addInstruction(OP_getlocal0);
break;
case STATIC_PROPERTY:
{
// for static watcher, we pass the class object of the class that contains the static
StaticPropertyWatcherInfo pwinfo = (StaticPropertyWatcherInfo)watcherInfo;
Name classMName = pwinfo.getContainingClass(host.getProject());
insns.addInstruction(OP_getlex, classMName);
}
break;
default:
assert false;
}
//--------------------------------------------------------
// next: call watcher.updateparent (thing)
// stack : thing, watcher
insns.addInstruction(OP_callpropvoid, IMXMLTypeConstants.ARG_UPDATEPARENT);
}
/** Generates the abc for the watcher, assigns to _watchers entry, and does the same for its
* children by calling self recursively
*
* @param insns
* @param watcherInfo
*/
private void makeWatcherAndChildren(InstructionList insns, WatcherInfoBase watcherInfo)
{
log(insns, "makeWatchers for " + watcherInfo.getIndex());
makeWatcher(insns, watcherInfo); // make this one
// then recurse into children
Set<Entry<Object, WatcherInfoBase>> children = watcherInfo.getChildren();
if (children != null)
{
for ( Entry<Object, WatcherInfoBase> ent : children)
{
makeWatcherAndChildren(insns, ent.getValue());
}
}
}
/**
* Generates code to create single watcher,and assign the watcher to the _watchers array
*
* VM Context:
* on entry: local1=_bindings
* local2=_watchers
*/
private void makeWatcher(InstructionList insns, WatcherInfoBase watcherInfo)
{
log(insns, "makeWatcher slot " + watcherInfo.getIndex());
insns.addInstruction(OP_getlocal2); // stack: _watchers
insns.pushNumericConstant(watcherInfo.getIndex());
WatcherType type = watcherInfo.getType();
// stack: index, _watchers
if (type == WatcherType.FUNCTION)
{
FunctionWatcherInfo functionWatcherInfo = (FunctionWatcherInfo)watcherInfo;
BindingCodeGenUtils.makeFunctionWatcher(
insns,
host.getProject(),
emitter,
functionWatcherInfo.getFunctionName(),
functionWatcherInfo.getEventNames(),
functionWatcherInfo.getBindings(),
functionWatcherInfo.params,
host.getInstanceScope());
}
else if ((type == WatcherType.STATIC_PROPERTY) || (type == WatcherType.PROPERTY))
{
PropertyWatcherInfo propertyWatcherInfo = (PropertyWatcherInfo)watcherInfo;
boolean makeStaticWatcher = (watcherInfo.getType() == WatcherType.STATIC_PROPERTY);
// round up the getter function for the watcher, or null if we don't need one
MethodInfo propertyGetterFunction = null;
if (watcherInfo.isRoot && !makeStaticWatcher)
{
propertyGetterFunction = this.propertyGetter;
assert propertyGetterFunction != null;
}
else if (watcherInfo.isRoot && makeStaticWatcher)
{
// TOTO: implement getter func for static watcher.
}
BindingCodeGenUtils.makePropertyWatcher(
makeStaticWatcher,
insns,
propertyWatcherInfo.getPropertyName(),
propertyWatcherInfo.getEventNames(),
propertyWatcherInfo.getBindings(),
propertyGetterFunction,
host.getProject());
}
else if (type == WatcherType.XML)
{
XMLWatcherInfo xmlWatcherInfo = (XMLWatcherInfo)watcherInfo;
BindingCodeGenUtils.makeXMLWatcher(
insns,
xmlWatcherInfo.getPropertyName(),
xmlWatcherInfo.getBindings(),
host.getProject()
);
}
else assert false;
bindingDataBase._watchersCreated++; // note one more created.
// now save the newly created watcher back to the _watchers array
// stack : new_watcher, index, _watcher
insns.addInstruction(OP_setproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP);
}
/** SDK BindingsManager contract forces us to have some names member variables
*
*/
private void makeSpecialMemberVariablesForBinding()
{
// TODO: make these mx.internal
host.addVariableTrait(IMXMLTypeConstants.NAME_BINDINGSBYDESTINATION, NAME_OBJTYPE);
host.addVariableTrait(IMXMLTypeConstants.NAME_BINDINGSBEGINWITHWORD, NAME_OBJTYPE);
host.addVariableTrait(IMXMLTypeConstants.NAME_WATCHERS, NAME_ARRAYTYPE);
host.addVariableTrait(IMXMLTypeConstants.NAME_BINDINGS, NAME_ARRAYTYPE);
// We aren't initiaizing any of these. SDK seems to do if for us, so why bother?
}
private void makePropertyGetterIfNeeded()
{
assert propertyGetter == null;
if (bindingDataBase.getRequiresPropertyGetter())
{
propertyGetter = BindingCodeGenUtils.generatePropertyGetterFunction(
emitter,
host.getProject(),
bindingDataBase.getScope()
);
}
}
private static void log(String s)
{
BindingCodeGenUtils.log(s);
}
// this version logs to console, but also to ABC output (as debugfile)
private static void log(InstructionList insns, String s)
{
BindingCodeGenUtils.log(insns, s);
}
}