/* * * 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.as.codegen; import org.apache.flex.abc.ABCConstants; 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.semantics.PooledValue; import org.apache.flex.abc.visitors.ITraitVisitor; import org.apache.flex.abc.visitors.ITraitsVisitor; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.internal.abc.FunctionGeneratorHelper; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.scopes.ASScope; import java.util.Vector; import static org.apache.flex.abc.ABCConstants.CONSTANT_PackageNs; import static org.apache.flex.abc.ABCConstants.CONSTANT_PrivateNs; import static org.apache.flex.abc.ABCConstants.CONSTANT_Qname; import static org.apache.flex.abc.ABCConstants.OP_callproperty; import static org.apache.flex.abc.ABCConstants.OP_callpropvoid; import static org.apache.flex.abc.ABCConstants.OP_constructprop; import static org.apache.flex.abc.ABCConstants.OP_findpropstrict; import static org.apache.flex.abc.ABCConstants.OP_getlex; import static org.apache.flex.abc.ABCConstants.OP_getlocal; import static org.apache.flex.abc.ABCConstants.OP_getlocal0; import static org.apache.flex.abc.ABCConstants.OP_getlocal1; import static org.apache.flex.abc.ABCConstants.OP_getlocal2; import static org.apache.flex.abc.ABCConstants.OP_getlocal3; import static org.apache.flex.abc.ABCConstants.OP_getproperty; import static org.apache.flex.abc.ABCConstants.OP_iffalse; import static org.apache.flex.abc.ABCConstants.OP_ifstricteq; import static org.apache.flex.abc.ABCConstants.OP_pushstring; import static org.apache.flex.abc.ABCConstants.OP_returnvalue; import static org.apache.flex.abc.ABCConstants.OP_returnvoid; import static org.apache.flex.abc.ABCConstants.OP_setlocal2; import static org.apache.flex.abc.ABCConstants.OP_setproperty; import static org.apache.flex.abc.ABCConstants.TRAIT_Getter; import static org.apache.flex.abc.ABCConstants.TRAIT_Method; import static org.apache.flex.abc.ABCConstants.TRAIT_Setter; import static org.apache.flex.abc.ABCConstants.TRAIT_Var; /** * Contains helper methods to generate the appropriate code for classes and properties * marked bindable. */ public class BindableHelper { /** * Generate a synthetic getter for a var that is bindable. The generated getter will simply return the value of * the property. * @param classScope the scope of the class to declare the getter in * @param propName the name of the property - this is the original name of the bindable var * @param backingName the name of the property that the value is actually stored in * @param propType the type of the property * @return a ITraitVisitor for the getter */ static ITraitVisitor generateBindableGetter(LexicalScope classScope, Name propName, Name backingName, Name propType) { // Equivalent AS, member property: // // public function get propName():propType // { // return this.backingName; // } // // Equivalent AS, static property: // // public static function get propName():propType // { // return backingName; // } // MethodInfo mi = new MethodInfo(); mi.setMethodName(propName.getBaseName()); mi.setReturnType(propType); InstructionList insns = new InstructionList(3); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, backingName); insns.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); return classScope.traitsVisitor.visitMethodTrait(TRAIT_Getter, propName, 0, mi); } /** * Generate a synthetic setter for a var that is bindable. The generated setter will compare the old value * of the property using strict equality (===), and if the new value differs from the old value, the method will * dispatch a "propertyChange" event. The generated code is slightly different for static properties vs. instance * properties, but the behavior should be the same. * @param classScope the scope of the class to declare the getter in * @param propName the name of the property - this is the original name of the bindable var * @param backingName the name of the property that the value is actually stored in * @param propType the type of the property * @param varDef the Definition of the original property - used to determine static-ness. * @return a ITraitVisitor for the setter */ static ITraitVisitor generateBindableSetter(LexicalScope classScope, Name propName, Name backingName, Name propType, IDefinition varDef) { // Equivalent AS, member property: // // public function set propName(newValue:propType):void // { // var oldValue = backingName; // if(oldValue !== newValue ) // { // backingName = newValue; // if( this.hasEventListener("propertyChange") ) // this.dispatchEvent( mx.events.PropertyChangeEvent.createUpdateEvent(this, propName, oldValue, newValue) // } // return; // } // // Equivalent AS, static property: // // public function set propName(newValue:propType):void // { // var oldValue = backingName; // if(oldValue !== newValue ) // { // backingName = newValue; // if( staticEventDispatcher.hasEventListener("propertyChange") ) // staticEventDispatcher.dispatchEvent( mx.events.PropertyChangeEvent.createUpdateEvent(this, propName, oldValue, newValue) // } // return; // } // if( varDef != null ) { // Set up a dependency btwn the class the setter is being declared in, // and mx.events.PropertyChangeEvents. ASScope containingScope = (ASScope)varDef.getContainingScope(); containingScope.findPropertyQualified(classScope.getProject(), NamespaceDefinition.createPackagePublicNamespaceDefinition(NAMESPACE_MX_EVENTS.getName()), NAME_PROPERTY_CHANGE_EVENT.getBaseName(), DependencyType.EXPRESSION); // TODO: remove this once mxmlc pulls in the correct dependencies. // TODO: This should be a dependency of PropertyChangeEvent, but it doesn't get emitted into the swf // TODO: unless the dependency is added here. containingScope.findPropertyQualified(classScope.getProject(), NamespaceDefinition.createPackagePublicNamespaceDefinition(NAMESPACE_MX_EVENTS.getName()), NAME_PROPERTY_CHANGE_EVENT_KIND.getBaseName(), DependencyType.EXPRESSION); } MethodInfo mi = new MethodInfo(); mi.setMethodName(propName.getBaseName()); Vector<Name> paramTypes = new Vector<Name>(1); paramTypes.add(propType); mi.setParamTypes(paramTypes); mi.setReturnType(NAME_VOID); InstructionList insns = new InstructionList(32); // var oldValue = backingName; insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, backingName); insns.addInstruction(OP_setlocal2); // if( oldValue !== newValue ) insns.addInstruction(OP_getlocal2); insns.addInstruction(OP_getlocal1); Label tail = new Label(); insns.addInstruction(OP_ifstricteq, tail); // { // backingName = newValue insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_setproperty, backingName); // if( this.hasEventListener("propertyChange") ) insns.addInstruction(OP_getlocal0); if( varDef.isStatic() ) insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_pushstring, PROPERTY_CHANGE); insns.addInstruction(OP_callproperty, new Object[]{NAME_HAS_EVENT_LISTENER, 1}); insns.addInstruction(OP_iffalse, tail); // { // this.dispatchEvent( mx.events.PropertyChangeEvent.createUpdateEvent(this, propName, oldValue, newValue) ) insns.addInstruction(OP_getlocal0); if( varDef.isStatic() ) insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_getlex, NAME_PROPERTY_CHANGE_EVENT); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_pushstring, propName.getBaseName()); insns.addInstruction(OP_getlocal2); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_callproperty, new Object[]{NAME_CREATE_UPDATE_EVENT, 4}); insns.addInstruction(OP_callpropvoid, new Object[]{NAME_DISPATCH_EVENT, 1}); // } // } // return; insns.labelNext(tail); insns.addInstruction(OP_returnvoid); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); return classScope.traitsVisitor.visitMethodTrait(TRAIT_Setter, propName, 0, mi); } /** * Generate a property to hold the _bindingEventDispatcher, and generate code to initialize the property. * @param traits the ITraitVisitor to declare the property in. * @param isStatic true if this is for a static property, false if this is an instance property. * @return An InstructionList of instructions to initialize the property. */ static InstructionList generateBindingEventDispatcherInit(ITraitsVisitor traits, boolean isStatic) { // // Equivalent AS, instance property: // hidden-private var _bindingEventDispatcher:EventDispatcher = new EventDispatcher(this); // // Equivalent AS, static property: // static hidden-private var _bindingEventDispatcher:EventDispatcher = new EventDispatcher(); // traits.visitSlotTrait(TRAIT_Var, NAME_BINDING_EVENT_DISPATCHER, ITraitsVisitor.RUNTIME_SLOT, NAME_EVENT_DISPATCHER, LexicalScope.noInitializer); InstructionList bindingEventDispIsns = new InstructionList(5); bindingEventDispIsns.addInstruction(OP_getlocal0); // Get this bindingEventDispIsns.addInstruction(OP_findpropstrict, NAME_EVENT_DISPATCHER); int argCount; if( isStatic ) { argCount = 0; } else { bindingEventDispIsns.addInstruction(OP_getlocal0); // Get this argCount = 1; } bindingEventDispIsns.addInstruction(OP_constructprop, new Object[] {NAME_EVENT_DISPATCHER, argCount} ); // Construct an EventDispatcher, passing in this as the arg bindingEventDispIsns.addInstruction(OP_setproperty, NAME_BINDING_EVENT_DISPATCHER); return bindingEventDispIsns; } /** * Generate a getter method to return the static event dispatcher. * @param classScope the class to declare the getter in * @return A ITraitVisitor for the getter. */ static ITraitVisitor generateStaticEventDispatcherGetter(LexicalScope classScope) { // Equivalent AS: // // public static function get staticEventDispatcher():flash.events.IEventDispatcher // { // return ClassName._bindingEventDispatcher; // } // MethodInfo mi = new MethodInfo(); mi.setMethodName(NAME_STATIC_EVENT_DISPATCHER.getBaseName()); mi.setReturnType(NAME_IEVENT_DISPATCHER); InstructionList insns = new InstructionList(3); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); return classScope.traitsVisitor.visitMethodTrait(TRAIT_Getter, NAME_STATIC_EVENT_DISPATCHER, 0, mi); } /** * Generate an addEventListener method. * @param classScope the class to declare the method in */ static void generateAddEventListener(LexicalScope classScope) { // Generate an addEventListenerFunction // Equivalent AS: // // public function addEventListener(type:String, listener:Function, // useCapture:Boolean = false, // priority:int = 0, // weakRef:Boolean = false):void // { // _bindingEventDispatcher.addEventListener(type, listener, useCapture, // priority, weakRef); // } MethodInfo addEventInfo = new MethodInfo(); addEventInfo.setMethodName("addEventListener"); Vector<Name> paramTypes = new Vector<Name>(5); paramTypes.add(NAME_STRING); paramTypes.add(NAME_FUNCTION); paramTypes.add(NAME_BOOLEAN); paramTypes.add(NAME_INT); paramTypes.add(NAME_BOOLEAN); addEventInfo.setParamTypes(paramTypes); //addEventInfo.setFlags(ABCConstants.HAS_OPTIONAL); addEventInfo.addDefaultValue(new PooledValue(false)); addEventInfo.addDefaultValue(new PooledValue(0)); addEventInfo.addDefaultValue(new PooledValue(false)); addEventInfo.setReturnType(NAME_VOID); InstructionList addEventInsns = new InstructionList(10); addEventInsns.addInstruction(OP_getlocal0); addEventInsns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); addEventInsns.addInstruction(OP_getlocal1); addEventInsns.addInstruction(OP_getlocal2); addEventInsns.addInstruction(OP_getlocal3); addEventInsns.addInstruction(OP_getlocal, 4); addEventInsns.addInstruction(OP_getlocal, 5); addEventInsns.addInstruction(OP_callpropvoid, new Object[]{NAME_ADDEVENT_LISTENER, 5}); addEventInsns.addInstruction(OP_returnvoid); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), addEventInfo, addEventInsns); classScope.traitsVisitor.visitMethodTrait(TRAIT_Method, NAME_ADDEVENT_LISTENER, 0, addEventInfo); } /** * Generate a dispatchEventListener method. * @param classScope the class to declare the method in */ static void generateDispatchEvent(LexicalScope classScope) { // Generate a dispatchEvent function // Equivalent AS: // // public function dispatchEvent(event:flash.events.Event):Boolean // { // return _bindingEventDispatcher.dispatchEvent(event); // } MethodInfo mi = new MethodInfo(); mi.setMethodName(NAME_DISPATCH_EVENT.getBaseName()); Vector<Name> paramTypes = new Vector<Name>(5); paramTypes.add(NAME_FLASH_EVENT); mi.setParamTypes(paramTypes); mi.setReturnType(NAME_BOOLEAN); InstructionList insns = new InstructionList(8); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_callproperty, new Object[]{NAME_DISPATCH_EVENT, 1}); insns.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); classScope.traitsVisitor.visitMethodTrait(TRAIT_Method, NAME_DISPATCH_EVENT, 0, mi); } /** * Generate a hasEventListener method. * @param classScope the class to declare the method in */ static void generateHasEventListener(LexicalScope classScope) { // Equivalent AS: // // public function hasEventListener(type:String):Boolean // { // return _bindingEventDispatcher.hasEventListener(type); // } MethodInfo mi = new MethodInfo(); mi.setMethodName(NAME_HAS_EVENT_LISTENER.getBaseName()); Vector<Name> paramTypes = new Vector<Name>(5); paramTypes.add(NAME_STRING); mi.setParamTypes(paramTypes); mi.setReturnType(NAME_BOOLEAN); InstructionList insns = new InstructionList(10); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_callproperty, new Object[]{NAME_HAS_EVENT_LISTENER, 1}); insns.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); classScope.traitsVisitor.visitMethodTrait(TRAIT_Method, NAME_HAS_EVENT_LISTENER, 0, mi); } /** * Generate a removeEventListener method. * @param classScope the class to declare the method in */ static void generateRemoveEventListener(LexicalScope classScope) { // Equivalent AS: // // public function removeEventListener(type:String, // listener:Function, // useCapture:Boolean = false):void // { // _bindingEventDispatcher.removeEventListener(type, listener, useCapture); // } MethodInfo mi = new MethodInfo(); mi.setMethodName(NAME_REMOVE_EVENT_LISTENER.getBaseName()); Vector<Name> paramTypes = new Vector<Name>(5); paramTypes.add(NAME_STRING); paramTypes.add(NAME_FUNCTION); paramTypes.add(NAME_BOOLEAN); mi.setParamTypes(paramTypes); mi.setFlags(ABCConstants.HAS_OPTIONAL); mi.addDefaultValue(new PooledValue(false)); mi.setReturnType(NAME_VOID); InstructionList insns = new InstructionList(10); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_getlocal2); insns.addInstruction(OP_getlocal3); insns.addInstruction(OP_callpropvoid, new Object[]{NAME_REMOVE_EVENT_LISTENER, 3}); insns.addInstruction(OP_returnvoid); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); classScope.traitsVisitor.visitMethodTrait(TRAIT_Method, NAME_REMOVE_EVENT_LISTENER, 0, mi); } /** * Generate a willTrigger method. * @param classScope the class to declare the method in */ static void generateWillTrigger(LexicalScope classScope) { // Equivalent AS: // // public function willTrigger(type:String):Boolean // { // return _bindingEventDispatcher.willTrigger(type); // } MethodInfo mi = new MethodInfo(); mi.setMethodName(NAME_WILL_TRIGGER.getBaseName()); Vector<Name> paramTypes = new Vector<Name>(5); paramTypes.add(NAME_STRING); mi.setParamTypes(paramTypes); mi.setReturnType(NAME_BOOLEAN); InstructionList insns = new InstructionList(8); insns.addInstruction(OP_getlocal0); insns.addInstruction(OP_getproperty, NAME_BINDING_EVENT_DISPATCHER); insns.addInstruction(OP_getlocal1); insns.addInstruction(OP_callproperty, new Object[]{NAME_WILL_TRIGGER, 1}); insns.addInstruction(OP_returnvalue); FunctionGeneratorHelper.generateFunction(classScope.getEmitter(), mi, insns); classScope.traitsVisitor.visitMethodTrait(TRAIT_Method, NAME_WILL_TRIGGER, 0, mi); } /** * Return the AET Name to use for the backing property of a bindable var. This name will have the same simple * name as the one passed in, but will be in a special, hidden private namespace to avoid conflicts with other * properties. * @param propName the Name of the property to generate a hidden name for * @return the Name of the hidden property that will actually store the value */ static Name getBackingPropertyName(Name propName) { return new Name(CONSTANT_Qname, new Nsset(bindablePrivateNamespace), propName.getBaseName()); } /** * Return the AET Name to use for the backing property of a bindable var. This name will have the same simple * name as the one passed in, but will be in a special, hidden private namespace to avoid conflicts with other * properties. * @param propName the Name of the property to generate a hidden name for * @return the Name of the hidden property that will actually store the value */ static Name getBackingPropertyName(Name propName, String suffix) { return new Name(CONSTANT_Qname, new Nsset(bindablePrivateNamespace), propName.getBaseName() + suffix); } /** * The namespace to put compiler generated members into so that they do not conflict with any user defined * members. */ private static final Namespace bindablePrivateNamespace = new Namespace(CONSTANT_PrivateNs, ".BindableNamespace"); /** * The namespace to put compiler generated members into so that they do not conflict with any user defined * members. */ public static final NamespaceDefinition bindableNamespaceDefinition = NamespaceDefinition.createNamespaceDefinition(bindablePrivateNamespace); /** * The mx.events package namespace */ public static Namespace NAMESPACE_MX_EVENTS = new Namespace(CONSTANT_PackageNs, "mx.events"); // // Following Names are constants to use for various types & properties used in the code generated for Bindable // public static Name NAME_PROPERTY_CHANGE_EVENT = new Name(CONSTANT_Qname, new Nsset(NAMESPACE_MX_EVENTS), "PropertyChangeEvent"); public static String PROPERTY_CHANGE_EVENT = "mx.events.PropertyChangeEvent"; public static Name NAME_PROPERTY_CHANGE_EVENT_KIND = new Name(CONSTANT_Qname, new Nsset(NAMESPACE_MX_EVENTS), "PropertyChangeEventKind"); private static final Name NAME_CREATE_UPDATE_EVENT = new Name("createUpdateEvent"); private static final Name NAME_STRING = new Name(IASLanguageConstants.String); private static final Name NAME_FUNCTION = new Name(IASLanguageConstants.Function); private static final Name NAME_BOOLEAN = new Name(IASLanguageConstants.Boolean); private static final Name NAME_INT = new Name(IASLanguageConstants._int); private static final Name NAME_VOID = new Name(IASLanguageConstants.void_); public static Name NAME_EVENT = new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, "flash.events")), "Event"); public static Name NAME_FLASH_EVENT = new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, "flash.events")), "Event"); private static final Name NAME_ADDEVENT_LISTENER = new Name("addEventListener"); private static final Name NAME_DISPATCH_EVENT = new Name("dispatchEvent"); private static final Name NAME_HAS_EVENT_LISTENER = new Name("hasEventListener"); private static final Name NAME_REMOVE_EVENT_LISTENER = new Name("removeEventListener"); private static final Name NAME_WILL_TRIGGER = new Name("willTrigger"); public static Name NAME_EVENT_DISPATCHER = new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, "flash.events")), "EventDispatcher"); public static Name NAME_IEVENT_DISPATCHER = new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, "flash.events")), "IEventDispatcher"); private static final Name NAME_BINDING_EVENT_DISPATCHER = new Name(CONSTANT_Qname, new Nsset(bindablePrivateNamespace), "_bindingEventDispatcher"); private static final Name NAME_STATIC_EVENT_DISPATCHER = new Name("staticEventDispatcher"); public static String PROPERTY_CHANGE = "propertyChange"; public static String BINDABLE = "Bindable"; public static String STRING_EVENT = "flash.events.Event"; public static String STRING_EVENT_DISPATCHER = "flash.events.EventDispatcher"; public static String STRING_IEVENT_DISPATCHER = "flash.events.IEventDispatcher"; }