/* * * 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.codegen.databinding; import static org.apache.flex.abc.ABCConstants.*; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.Vector; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.Label; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.abc.visitors.IABCVisitor; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.internal.abc.FunctionGeneratorHelper; import org.apache.flex.compiler.internal.as.codegen.CodeGeneratorManager; import org.apache.flex.compiler.internal.as.codegen.LexicalScope; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.projects.FlexProject; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.tree.as.LiteralNode; import org.apache.flex.compiler.mxml.IMXMLTypeConstants; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.tree.as.IExpressionNode; /** * A bunch of static, low level helpers for generating instruction lists * * In this case, low-level means that these utilities don't know anything about * our Databinding analysis strategy, what watchers are (other than some sdk class), directive processors etc.. * * Because of the "low level" nature of these utilties, they should generally take "simple types" * as arguments, with the exception is IASNodes. There is no need to be super strict about this, of course. * There are already cases here where we pass in BindingInfo rather that jumpthrough hoops (and CPU cycles) * to extract the simple types. */ public class BindingCodeGenUtils { private static final Name NAME_VOID = new Name(IASLanguageConstants.void_); // params for destination function, get set up fully in static block below. private static final Vector<Name> DEST_METHOD_PARAMS = new Vector<Name>(); static { // 1 arg, no type DEST_METHOD_PARAMS.add(null); } /** * emits the code for anonymous getter function, then adds to the instruction list for instantiating one onto the stack * @param ret The instruction list being built. * @param expressions will be codegened to do the bulk of the getter work * * * VM context: * one entry: nothing * exit: new function object for getter on TOS */ public static void generateGetter(IABCVisitor emitter, InstructionList ret, List<IExpressionNode> expressions, LexicalScope enclosing_scope) { // get a method info and a scope for the getter we will generate MethodInfo mi = createGetterMethodInfo(); mi.setMethodName("bind_getter"); log(ret, "making bind_getter"); // generate the getter function CodeGeneratorManager.getCodeGenerator().generateMXMLDataBindingGetterFunction(mi, expressions, enclosing_scope); // the instruction stream that will be executed by the ctor // needs to push the new function onto the stack ret.addInstruction(OP_newfunction, mi); } /** * emits the code for anonymous setter function, then adds to the instruction list for instantiating one onto the stack * @param ret The instruction list being built. * @param expression will be codegened to do the bulk of the setter work * * * VM context: * one entry: nothing * exit: new function object for getter on TOS */ public static void generateSetter( InstructionList ret, IExpressionNode expression, LexicalScope enclosing_scope ) { // get a method info and a scope for the getter we will generate MethodInfo mi = createSetterMethodInfo(); log(ret, "making bind_setter"); // After codegen of the binding expression, we just need to return it // TODO: add O_coerce InstructionList stuffToFollowExpression = new InstructionList(); stuffToFollowExpression.addInstruction(OP_returnvoid); List<IExpressionNode> exprs = new ArrayList<IExpressionNode>(); exprs.add(expression); // CG the function CodeGeneratorManager.getCodeGenerator().generateMXMLDataBindingSetterFunction(mi, expression, enclosing_scope); // the instruction stream that will be executed by the ctor // needs to push the new function onto the stack ret.addInstruction(OP_newfunction, mi); } /** * Generate a MethodInfo appropriate for an anonymous getter */ private static MethodInfo createGetterMethodInfo() { MethodInfo mi = new MethodInfo(); // TODO: consider making us a more useful name for these anonymous functions // For debugging with swfdump, you can uncomment the line below: // mi.setMethodName("binding_getter"); // Set return type as '*' mi.setReturnType( null); return mi; } /** * Generate a MethodInfo appropriate for an anonymous getter */ private static MethodInfo createSetterMethodInfo() { MethodInfo mi = new MethodInfo(); // TODO: consider making us a more useful name for these anonymous functions // For debugging with swfdump, you can uncomment the line below: // mi.setMethodName("binding_getter"); mi.setReturnType(NAME_VOID); mi.setParamTypes(DEST_METHOD_PARAMS); return mi; } /** * generate code to instantiate a binding object. The code to generate the source and destination functions * should be passed in in methodInstr - these instructions will be inserted in the appropriate place. * * exit: stack = binding object * * @param insns IL to add instructions to * @param project Project we are compiling in * @param destStr String representation of the destination expression * @param srcString String representation of the source expression * @param methodInstr IL with instructions to put the source function and destination function * on the stack. These will be inserted where they are needed - these will usually * be OP_newfunction, but could be anything that leaves the methods on the stack * */ public static void makeBinding(InstructionList insns, FlexProject project, String destStr, String srcString, InstructionList methodInstr) { log(insns, "making binding dest=" + destStr); /* instantiate Binding * public function Binding(document:Object, srcFunc:Function, destFunc:Function, destString:String, srcString:String = null) */ insns.addInstruction(OP_findpropstrict, project.getBindingClassName()); // stack: mx.Binding insns.addInstruction(OP_getlocal0); // stack: this, mx.Binding insns.addAll(methodInstr); // stack: destination_func, getter_func, this, mx.Binding pushString(insns, destStr != null ? destStr : ""); // stack: destStr, dest func, getter_func, this, mx.Binding, getters // push the source string, which may be null if (srcString != null) insns.addInstruction(OP_pushstring, srcString); else insns.addInstruction(OP_pushnull); // stack: sourceString, destStr, null, getter_func, this, mx.Binding, getters insns.addInstruction(OP_constructprop, new Object[] { project.getBindingClassName(), 5 }); // stack: binding } /** * thin wrapper around aet to prevent pushing null strings * * @param insns * @param string */ private static void pushString(InstructionList insns, String string) { assert string != null; insns.addInstruction(OP_pushstring, string); } /** * Generates the ABC to instantiate a PropertyWatcher * Can only generate one special case: * single event (Constructor actually takes an array of events. we could easily add that * no property getter function (we use null) * * Note that we are passing in references to BindingInfo. Although this somewhat breaks * the rules for our low-level CG helpers, it's the only convenient way to pass in the list * of indicies that we will need to prove to the property watcher ctor. * * * * VM context: * on entry: nothing on stack * local1 = this._bindings * exit: new property watcher on stack */ public static void makePropertyWatcher( boolean makeStaticWatcher, InstructionList insns, String propertyName, List<String> eventNames, List<BindingInfo> bindingInfo, MethodInfo propertyGetterFunction, FlexProject project ) { log(insns, "makePropertyWatchercg"); // check input assert propertyName != null; Name watcherClassName = makeStaticWatcher ? project.getStaticPropertyWatcherClassName() : project.getPropertyWatcherClassName(); insns.addInstruction(OP_findpropstrict, watcherClassName); // stack: mx.PropertyWatcher /* ctor arguments: PropertyWatcher( propertyName:String, events:Object, listeners:Array, propertyGetter:Function = null) */ pushString(insns, propertyName); // stack: propName, mx.PropertyWatcher // now make Object { eventName : true }, or null makeEventNameArray(insns, eventNames); // stack: {events}, propName, mx.PropertyWatcher makeArrayOfBindingsForWatcher(insns, bindingInfo); // stack: listeners[], {events}, propName, mx.PropertyWatcher if (propertyGetterFunction == null) { insns.addInstruction(OP_pushnull); // null is valid } else { insns.addInstruction(OP_newfunction, propertyGetterFunction); } // stack: propertyGetter, listeners[], {events}, propName, mx.PropertyWatcher insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, 4 }); // stack: watcher log(insns, "leave makePropertyWatchercg"); } /** * Generate instructions to instantiate an mx.binding.XMLWatcher * * @param insns The instruction list being built. * @param propertyName The name of a property. * @param bindingInfo The binding information for the property. * @param project The compiler project. */ public static void makeXMLWatcher( InstructionList insns, String propertyName, List<BindingInfo> bindingInfo, FlexProject project) { /* ctor arguments: XMLWatcher( (propertyName:String, listeners:Array) */ Name watcherClassName = project.getXMLWatcherClassName(); insns.addInstruction(OP_findpropstrict, watcherClassName); pushString(insns, propertyName); makeArrayOfBindingsForWatcher(insns, bindingInfo); insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, 2}); } /** * Given an event name, make Object { eventName: true } * TODO: support multiple names * * returns with new object on stack */ private static boolean makeEventNameArray(InstructionList insns, List<String> eventNames) { boolean isStyle = false; if (eventNames.isEmpty()) { insns.addInstruction(OP_pushnull); // null is acceptable for the events object (old compiler does this) } else if (eventNames.size() == 1 && eventNames.get(0).equals("isStyle")) { isStyle = true; insns.addInstruction(OP_pushnull); // null is acceptable for the events object (old compiler does this) } else { for (String eventName : eventNames) { insns.addInstruction(OP_pushstring, eventName); insns.addInstruction(OP_pushtrue); } insns.addInstruction(OP_newobject, eventNames.size()); } return isStyle; } /** * Generates the ABC to instantiate a FunctionReturnWatcher * * VM context: * on entry: nothing on stack * local1 = this._bindings * exit: new property watcher on stack */ public static void makeFunctionWatcher(InstructionList insns, FlexProject project, IABCVisitor emitter, String functionName, List<String> eventNames, List<BindingInfo> bindingInfo, IExpressionNode[] params) { log(insns, "enter makeFunctionWatchercg"); Name watcherClassName = project.getFunctionReturnWatcherClassName(); insns.addInstruction(OP_findpropstrict, watcherClassName); // stack: mx.binding.FunctionReturnWatcher /* FunctionReturnWatcher( functionName:String, document:Object, parameterFunction:Function, events:Object, listeners:Array, functionGetter:Function = null, isStyle:Boolean = false) */ // arg1: functionName pushString(insns, functionName); // stack: functionName, watcher class // arg2: document. which is "this" insns.addInstruction(OP_getlocal0); // stack: this, functionName, watcher class // arg3: parameterFunction makeParameterFunction(emitter, insns, params); // arg4: events boolean isStyle = makeEventNameArray(insns, eventNames); // arg5: listeners makeArrayOfBindingsForWatcher(insns, bindingInfo); // arg 6, propertyGetter. For now, we don't have one, so don't pass (it's optional) if (isStyle) { insns.addInstruction(OP_pushnull); insns.addInstruction(OP_pushtrue); } // now construct the wacher insns.addInstruction(OP_constructprop, new Object[] { watcherClassName, isStyle ? 7 : 5 }); log(insns, "leave makeFunctionWatchercg"); } /** * Generated the "parameter function" required by the FunctionReturnWatcher * constructor. * <p> * This function is like this: * * <pre> * function(): Array { return [param1, param2] }; * </pre> * * Since that is actually difficult to generate, we are instead generating a * simple function that throws an exception that we know will be caught * silently by the Flex SDK. * <p> * There may be some strange edge cases where this trick won't work, but it * will cover the majority of cases BETTER than doing the "right" thing. * * @param emitter {@link IABCVisitor} to which generated code is added. * @param ret {@link InstructionList} to which code that creates a function * closure for the new parameter function should be added. */ // version 2: just throw public static void makeParameterFunction(IABCVisitor emitter, InstructionList ret, IExpressionNode[] params) { //----------- step 1: build up a method info for the function // we are going to make a new func MethodInfo mi = new MethodInfo(); // TODO: consider making us a more useful name for these anonymous functions // For debugging with swfdump, you can un-comment the line below: //mi.setMethodName("parameterFunction"); mi.setReturnType( new Name(IASLanguageConstants.Array)); //---- generate code that throws the exception. // This is based on the code that came out of the old compiler InstructionList parameterFunctionBody = new InstructionList(); parameterFunctionBody.addInstruction(OP_getlocal0); parameterFunctionBody.addInstruction(OP_pushscope); if (params.length == 1 && params[0] instanceof LiteralNode) { parameterFunctionBody.addInstruction(OP_pushstring, ((LiteralNode)params[0]).getValue()); parameterFunctionBody.addInstruction(OP_newarray, 1); parameterFunctionBody.addInstruction(OP_returnvalue); } else { parameterFunctionBody.addInstruction(OP_findpropstrict, IMXMLTypeConstants.NAME_ARGUMENTERROR); // Make a new ArugmentError with correct ID parameterFunctionBody.addInstruction(OP_pushstring, "unimp param func"); parameterFunctionBody.pushNumericConstant(1069); parameterFunctionBody.addInstruction(OP_constructprop, new Object[] { IMXMLTypeConstants.NAME_ARGUMENTERROR, 2 }); // stack : ArgumentError parameterFunctionBody.addInstruction(OP_throw); } // now generate the function FunctionGeneratorHelper.generateFunction(emitter, mi, parameterFunctionBody); // the instruction stream that will be executed by the ctor // needs to push the new function onto the stack ret.addInstruction(OP_newfunction, mi); } /** * Set this to true so that the runtime trace diagnostic gets * re-routed to use SocketHelper. * * This is needed because most of our unit tests don't "see" regular traces, * because they use the SocketHelper */ private static boolean useSocketForTrace = false; // Must check in false /** * debugging function to all "trace" * * @param insns The instruction list being built. * @param string is a constant string to trace */ public static void trace(InstructionList insns, String string) { pushString(insns, string); trace(insns); insns.addInstruction(OP_pop); } /** * debugging function to call "trace" * * will use top of stack as string parameter, doesn't change stack depth * @param insns The instruction list being built. */ public static void trace(InstructionList insns) { insns.addInstruction(OP_dup); if (!useSocketForTrace) { System.out.println("** Warning: diagnostic trace not using socket. Will be invisible when running most unit tests"); insns.addInstruction(OP_findpropstrict, new Object[] { new Name("trace") } ); insns.addInstruction(OP_swap); insns.addInstruction(OP_callpropvoid, new Object[] { new Name("trace"), 1 } ); } else { insns.addInstruction(OP_findpropstrict, new Object[] { new Name("SocketHelper") } ); insns.addInstruction(OP_getproperty, new Object[] { new Name("SocketHelper") } ); insns.addInstruction(OP_swap); insns.addInstruction(OP_callpropvoid, new Object[] { new Name("trace"), 1 } ); } } /** * VM Context: * on entry: local1=_bindings * local2=_watchers * on exit: stack = array[ bindings ] */ private static void makeArrayOfBindingsForWatcher(InstructionList insns, List<BindingInfo> bindingInfos) { assert bindingInfos.size() > 0; // for now this is always true // in future....? for (BindingInfo bindingInfo : bindingInfos) { insns.addInstruction(OP_getlocal1); // stack: _bindings insns.pushNumericConstant(bindingInfo.getIndex()); // stack: index, _bindings insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP); // stack: _bindings[index] } // stack: _bindings[k], _bindings[x], ... insns.addInstruction(OP_newarray, bindingInfos.size()); // stack: array[ _bindings[0] ] } /** * Makes an AET name that represents all the open namespaces, but no "name" * use this when we need to lookup a property, but we aren't sure what namespace it's in. * example: this[propertyName] * @param project * @param scope * @return */ private static Name makeNameForPropertyLookup( ICompilerProject project, ASScope scope) { Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSet(project); ArrayList<Namespace> ns_set = new ArrayList<Namespace>(namespaceSet.size()); for (INamespaceDefinition namespace : namespaceSet) ns_set.add(((NamespaceDefinition)namespace).getAETNamespace()); int nameKind = CONSTANT_MultinameL; String name = null; // We are not providing a name here Name n = new Name(nameKind, new Nsset(ns_set), name); return n; } /** * generate this function: function(propertyName:String):* { return * target[propertyName]; } where target == "this" * * @param emitter The {@link IABCVisitor} that generated code is added to. * @param project The {@link ICompilerProject} in which code is being * generated. * @param scope The {@link ASScope} that contains the data binding * expression for which a getter is being generated. * @return the MethodInfo that we can use to refer to the generated function */ public static MethodInfo generatePropertyGetterFunction( IABCVisitor emitter, ICompilerProject project, ASScope scope ) { MethodInfo mi = new MethodInfo(); // TODO: consider making us a more useful name for these anonymous functions // For debugging with swfdump, you can un-comment the line below: //mi.setMethodName("propertyGetterFunction"); // Method returns "*", and takes one string parameter mi.setReturnType( null); Vector<Name> x = new Vector<Name>(); x.add( new Name(IASLanguageConstants.String)); mi.setParamTypes( x); Name magicName = makeNameForPropertyLookup(project, scope); InstructionList propertyGetterBody = new InstructionList(); // we are not a member, function, so local 0 does not have "this" // Make sure no-one accidently uses it propertyGetterBody.addInstruction(OP_kill, 0); // find "this" of class we are servicing by finding "someone" // who has the property we are looking for. The property name is in param 1 propertyGetterBody.addInstruction(OP_getlocal1); propertyGetterBody.addInstruction(OP_findproperty, magicName); // stack: this // get the name of the property we are supposed to find propertyGetterBody.addInstruction(OP_getlocal1); // stack: propertyName, this propertyGetterBody.addInstruction(OP_getproperty, magicName); // stack: this[propertyName] propertyGetterBody.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(emitter, mi, propertyGetterBody); return mi; } /** enumerate all the bindings we have created for this class, and execute each * of them so that initial values get assigned to binding targets. * * VM context: * one entry: nothing * while executing: * local1 = index (temporary local var) * local2 = this._bindings.length; * * exit: nothing */ public static InstructionList fireInitialBindings() { InstructionList insns = new InstructionList(); log(insns, "fireInitialBinding"); //----------------------------------------------------- // index = 0; insns.addInstruction(OP_pushbyte, 0); insns.addInstruction(OP_setlocal1); //-------------------------------------------------------- // size = _bindings.size insns.addInstruction(OP_getlocal0); // stack: this insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_BINDINGS); // stack: _bindings insns.addInstruction(OP_dup); // stack: _bindings, _bindings insns.addInstruction(OP_getproperty, new Name("length")); // stack: _bindings.length, _bindings insns.addInstruction(OP_setlocal2); // now stack: _bindings //---------------------- LOOP OVER BINDINGS ---------------- Label label = new Label(); insns.addInstruction(OP_label); insns.labelCurrent(label); insns.addInstruction(OP_dup); // stack: _bindings, _bindings insns.addInstruction(OP_getlocal1); // stack: index, _bindings, _bindings insns.addInstruction(OP_getproperty, IMXMLTypeConstants.NAME_ARRAYINDEXPROP); // stack: _bindings[index], _bindings // now call execute on TOS insns.addInstruction(OP_callpropvoid, IMXMLTypeConstants.ARG_EXECUTE); // stack: _bindings // increment index insns.addInstruction(OP_inclocal_i, 1); // if (index < length) goto label insns.addInstruction(OP_getlocal1); // stack: index, .... insns.addInstruction(OP_getlocal2); // stack: size, index, ... insns.addInstruction(OP_iflt, label); // stack: _bindings insns.addInstruction(OP_pop); // TODO: is this necessary? return insns; } public static final boolean doLog = false; // check in false, please public static void log(String s) { if (doLog) { System.out.println("MXMLBindingDirHelp: " + s); } } // this version logs to console, but also to ABC output (as debugfile) public static void log(InstructionList insns, String s) { assert s != null; if (doLog) { System.out.println("MXMLBindingDirHelp: " + s); insns.addInstruction(OP_debugfile, s); } } /** * This is a static utility class. It is a mistake to try and instantiate one. */ private BindingCodeGenUtils() {} }