/* * Smart GWT (GWT for SmartClient) * Copyright 2008 and beyond, Isomorphic Software, Inc. * * Smart GWT is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 * is published by the Free Software Foundation. Smart GWT is also * available under typical commercial license terms - see * http://smartclient.com/license * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package com.smartgwt.client.bean; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.smartgwt.client.bean.BeanProperty; import com.smartgwt.client.widgets.BaseWidget; import com.smartgwt.client.widgets.Canvas; import com.smartgwt.client.util.SC; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.lang.annotation.ElementType; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // ================== // Technical Overview // ================== // // Here is a road-map to the mechanics of the whole bean-factory mechanism. // // The BeanFactory class is the main class. There are subclasses for several of // the core SmartGWT classes, starting with BeanFactoryForBaseWidget and // BeanFactoryForDataClass. There is then one subclass, and one instance, for // each BeanClass (which, for now, must be a subclass of BaseWidget or // DataClass). So, each subclass for a BeanClass is a singleton. // // The main interface is meant to be the static methods on BeanFactory, but // you can cache a BeanFactory instance and call the instance methods as well // (for a slight efficiency gain). // // You trigger the generation of the subclasses through mechanisms discussed // below. // // Each generated BeanFactory creates a corresponding SmartClient SGWTFactory // and stores it in the isc[] space. This allows the SmartClient side to deal // with SmartGWT classes through many usual SmartClient idioms. // // Each BeanFactory keeps a hash of BeanProperty objects, where each // BeanProperty represents one property. A property has a name, and may have // multiple BeanMethod objects (representing getters and setters). Multiple // setters would be overloaded methods, whereas multiple getters are such // things as getWidthAsString(). // // There are two big technical problems that need to be overcome to make this // all work (and lots of little ones). The first is that you can't normally // call a GWT function based on a run-time string -- you have to specify // functions at compile time. So how does BeanMethod actually call the // function? // // There are several approaches possible. You could iterate through // possible comparisons, by generating code like this: // // if (functionName == "getWidth") { // return bean.getWidth(); // } else if (functionName == "getHeight") { // return bean.getHeight(); // } ... // // However, that would be very inefficient, especially since there are so many // possible properties. // // A second way would be to generate an anonymous subclass for every method type, // something like this: // // new BeanMethod ("width") { // public Object getPropertyFromBean (BeanClass bean) { // return bean.getWidth(); // } // } // // This works, but it has a fair bit of overhead in the generated code size, // because it is literally generating at least one (anonymous) class for every // property, and each class has some boilerplate overhead. The next approach // reduces the generated code size by about one-third (in my testing). // // The approach actually used is to create a JSNI function for each getter and // setter. Conceptually, the generated code does something like this: // // public native Object callGetter (Object bean, String functionName) /*-{ // var functions = { // width: function (b) {return b.@com.smartgwt.widgets.Canvas::getWidth()();}, // height: function (b) {return b.@com.smartgwt.widgets.Canvas::getHeight()();} // ... // }; // return functions[functionName](bean); // }-*/; // // Though it's not implemented quite like that, because we would want to cache // the hash of the functions, and there are sometimes multiple methods per // function name. But you get the general idea -- experimentally, there is // about one-third less overhead than through the anonymous subclass approach. // // The other big technical problem is what to do about the argument types ... // that is, the types which setters expect, and the types which getters // provide. Thinking about it from a purely SmartGWT point of view, if the // property name is now dynamic and can be provided at run-time, it doesn't // make much sense to require that the caller know exactly what type is needed. // Of course, the SmartClient XML system can provide typed arguments for // properties, but even there we would always need to convert between int and // Integer and other numeric types. Also, where there are multiple overloaded // setters, we need to use some intelligence in picking the setter, depending // on what type of argument we've been provided. There are also various // complexities in transitioning data from SmartClient Javascript to SmartGWT // Java that need to be ironed out. // // That is what the BeanValueType class deals with. Essentially, it is meant to // be able to do any of the conversions we might need. Some of them are fairly // obvious, such as the conversion amongst numeric types and to and from Strings. // But it also handles conversion among array types, which is trickier (and // necessary, since all arrays provided from JavaScript are untyped as far as // GWT is concerned). It also deals with SmartGWT-specific conversions, like // the various ValueEnum types and conversions which are possible when we // know what the desired type is (for instance, converting a JavaScriptObject // which we know is a SmartClient Canvas to a SmartGWT Canvas). // // The general interface is in com.smartgwt.client.bean.BeanValueType, and the // various files in com.smartgwt.client.bean.types.* represent subclasses. // Some subclasses deal with a single type. Others are superclasses where we // need to generate a subclass for each type. Others can be used to deal with // more than one type. // // So, for setting a property, the basic steps are: // // 1. The BeanFactory static method finds the right BeanFactory for the object. // 2. The BeanFactory instance picks out the correct BeanProperty. // 3. The BeanProperty looks at its BeanMethods, and if more than one, uses // BeanValueType to determine which BeanMethod is best for the value supplied. // 4. The BeanProperty uses BeanValueType to convert the value to the type // that the selected BeanMethod needs. // 5. The BeanMethod uses JSNI to call the actual setter. /** * Utility class for creating objects and setting properties at run-time via * reflection-like mechanisms. * * <p>Subclasses must be generated for each {@link Class} you want to use. To trigger * the generation of subclasses, see the documentation on * {@link com.smartgwt.client.docs.Reflection registering classes for reflection}. * * <p>Once the appropriate subclass has been created, you can use the class via * the static methods. * * <p>For the moment, this class only works with subclasses of * {@link com.smartgwt.client.widgets.BaseWidget} or * {@link com.smartgwt.client.widgets.form.fields.FormItem}. */ public abstract class BeanFactory<BeanClass> { // -------------------- // Generator Interfaces // -------------------- /** * An interface which you can extend in order to register classes * with the <code>BeanFactory</code> reflection mechanism. * * <p><b>Note</b>: While this mechanism continues to work, there is * now an easier way to register classes for reflection, by annotating * them with the {@link BeanFactory.Generate} annotation. * * <p>In order to use a {@link BeanFactory} for a class, you need to * register it by generating a <code>BeanFactory</code> subclass for the * class. You can use {@link BeanFactory.CanvasMetaFactory} to scan the * class path and register every {@link Canvas} subclass (including your * custom subclasses), or use {@link BeanFactory.FormItemMetaFactory} to * regiser every {@link com.smartgwt.client.widgets.form.fields.FormItem} * subclass. However, if you know that you only need to register some * classes for reflection, then you can use * <code>BeanFactory.MetaFactory</code> instead (or, even more conveniently, * the {@link BeanFactory.Generate} annotation). * * <p>Usage of <code>BeanFactory.MetaFactory</code> is most easily explained * with an example. First, you define an interface. (Note that it can be an * inner interface.) * * <blockquote><pre> * public interface MyMetaFactory extends BeanFactory.MetaFactory { * BeanFactory<ListGrid> getListGridFactory(); * BeanFactory<TileGrid> getTileGridBeanFactory(); * }</pre></blockquote> * * ... and then you trigger the generation process: * * <blockquote><pre> * GWT.create(MyMetaFactory.class);</pre></blockquote> * * <p>Each function in the interface you define will result in the creation of * one <code>BeanFactory</code> ... so, in this case, we would end up with * bean factories for <code>ListGrid</code> and <code>TileGrid</code>. The rules * are as follows: * * <ol> * <li>The interface must extend <code>BeanFactory.MetaFactory</code></li> * <li>The return type for each function must be <code>BeanFactory</code>, * with a generic type that is the class you want the factory for.</li> * <li>The function must take no arguments.</li> * <li>The name of the function doesn't matter.</li> * </ol> * * <p>If you want, you can keep a reference to the results of the <code>GWT.create()</code>, * and call the functions: * * <blockquote><pre> * MyMetaFactory metaFactory = GWT.create(MyMetaFactory.class); * BeanFactory<TileGrid> myTileGridFactory = myMetaFactory.getTileGridBeanFactory();</pre></blockquote> * * <p>However, you don't have to do that ... you can ignore the results of <code>GWT.create()</code> * and just use the <code>BeanFactory</code> static API: * * <blockquote><pre> * GWT.create(MyMetaFactory.class); * Object myGrid = BeanFactory.newInstance("TileGrid"); * BeanFactory.setProperty(myGrid, "width", 207);</pre></blockquote> * * ... except that "TileGrid" would probably be a variable! * * <p>You can also use the generated classes in {@link com.smartgwt.client.docs.ComponentXML}, * by specifying the fully-qualified class name as the constructor for objects. * * <p>Note that the call to <code>GWT.create()</code> must occur at run-time before * the factories are used. However, you can modularize by creating some factories * first and other factories later, as long as each factory is created before * being used. * * @see BeanFactory.Generate */ public static interface MetaFactory { // One understands that a factory to generate factories is in danger of // being mocked. However, it really is the right way to do this in // GWT. // // The problem with using GWT.create to create the factory directly (e.g. // GWT.create(ListGrid.class)) is that then you have to register a // generator for ListGrid.class (or a superclass), and you can only have // one generator registered for that class. So, you can't generate anything // else for a ListGrid via that mechanism -- it's one shot. // // Also, the semantics are wrong. We're not generating the ListGrid class // -- we're generating a factory. Of course, it's legal to respond to // GWT.create(ListGrid.class) by generating something other than a // ListGrid class, but it is a bit odd. // // However, it would be tedious to have to define a separate interface for // each factory, so we instead define an interface for a second-level // factory -- a factory that can create factories. // // So, MetaFactory is just a marker -- by defining an interface // which extends it, you tell GWT to generate factories. // // Note that for most uses, BeanFactory.MetaFactory has now been superseded // by the more convenient BeanFactory.Generate annotation, but the older // method still works. } /** * Interface used to trigger the generation and registration of reflection * metadata for {@link com.smartgwt.client.widgets.Canvas} and all * its subclasses found in the classpath (including your custom subclasses). * * <p>Usage is to simply call <code>GWT.create(BeanFactory.CanvasMetaFactory.class);</code> * Note that the return value is not useful -- you would then simply use * the {@link BeanFactory} API, like so: * * <blockquote><pre> * GWT.create(BeanFactory.CanvasMetaFactory.class); * * String className = "com.smartgwt.client.widgets.Button"; * Canvas canvas = (Canvas) BeanFactory.newInstance(className); * * BeanFactory.setProperty(canvas, "title", "Hello World"); * BeanFactory.setProperty(canvas, "tooltip", "My tooltip"); * canvas.draw();</pre></blockquote> * * <p>Furthermore, the className can also be used as a Constructor in * {@link com.smartgwt.client.docs.ComponentXML Component XML}. * * <p>Alternatively if only specific <code>Canvas</code> types need to be * instantiated and configured dynamically, you can generate specific * factories by annotating classes with the {@link BeanFactory.Generate} * annotation instead. * * <p>If there are only a limited number of types which require dynamic * configuration, it will save code size to use the * <code>BeanFactory.Generate</code> annotation for those types. Once the * metadata is generated, GWT's opportunities to prune dead code are more * limited for those classes, since it cannot know what properties will be * set or retrieved at run-time. * * @see BeanFactory.Generate */ public static interface CanvasMetaFactory { } /** * Interface used to trigger the generation and registration of reflection * metadata for {@link com.smartgwt.client.widgets.form.fields.FormItem} and * all its subclasses found in the classpath (including your custom subclasses). * * <p>Usage is to simply call <code>GWT.create(BeanFactory.FormItemMetaFactory.class);</code> * Note that the return value is not useful -- you would then simply use * the {@link BeanFactory} API, like so: * * <blockquote><pre> * GWT.create(BeanFactory.FormItemMetaFactory.class); * * String className = "com.smartgwt.client.widgets.form.fields.TextItem"; * Object canvas = BeanFactory.newInstance(className); * * BeanFactory.setProperty(canvas, "title", "Hello World"); * BeanFactory.setProperty(canvas, "tooltip", "My tooltip"); * </pre></blockquote> * * <p>Furthermore, the className can also be used as a Constructor in * {@link com.smartgwt.client.docs.ComponentXML Component XML}. * * <p>Alternatively if only specific <code>FormItem</code> types need to be * instantiated and configured dynamically, you can generate specific * factories by annotating classes with the {@link BeanFactory.Generate} * annotation instead. * * <p>If there are only a limited number of types which require dynamic * configuration, it will save code size to use the * <code>BeanFactory.Generate</code> interface for those types. Once the * metadata is generated, GWT's opportunities to prune dead code are more * limited for those classes, since it cannot know what properties will be * set or retrieved at run-time. * * @see BeanFactory.Generate */ public static interface FormItemMetaFactory { } // This is a marker interface used internally to trigger processing of the // BeanFactory.Generate annotations, via a call to GWT.create in // com.smartgwt.client.SmartGwtEntryPoint. public static interface AnnotationMetaFactory { } // ----------- // Annotations // ----------- // We use annotations to indicate some metadata that the generator needs // when generating a BeanFactory. // An annotation used to indicate that a SmartGWT class is part of the // the framework. The generator picks this up when generating the // BeanFactory. @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface FrameworkClass {} // An annotation used to indicate the default scClassName that a class // will use. The generator picks this up when generating the BeanFactory. // Of course, the class will also need to implement getScClassName (if // it has a different scClassName than its superclass). But the generator // can't pick up the results of getScClassName at compile-time. So, we // need to indicate the default in the metadata. @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface ScClassName { String value(); } /** * Annotation which will trigger the generation of BeanFactories. * * <p>If no value is supplied, a BeanFactory will be generated for the class * that the annotation is applied to. So, if you have a custom class for which you would like to generate * a BeanFactory, you can just annotate it like this: * <blockquote><pre> * {@literal @}BeanFactory.Generate * public MyCanvas extends Canvas { * ... * }</pre></blockquote> * <p> * If you want to generate BeanFactories for framework classes, you can * supply a value for the annotation, where the value is an array * of class literals. For instance: * <blockquote><pre> * {@literal @}BeanFactory.Generate({ListGrid.class, TreeGrid.class}) * public interface EmptyInterface { * }</pre></blockquote> * <p> * Note that when supplying values for the annotation, the class you * annotate (here <code>EmptyInterface</code>) will <b>not</b> itself have * a BeanFactory generated for it. Thus, you can use an empty inner interface * for this purpose. * <p> * If you want to generate BeanFactories for all {@link Canvas} * subclasses or all {@link com.smartgwt.client.widgets.form.fields.FormItem} * subclasses, you can use {@link BeanFactory.CanvasMetaFactory} or * {@link BeanFactory.FormItemMetaFactory} instead. However, that is less * efficient if there are only a limited number of classes which need * BeanFactories. * * @see BeanFactory.CanvasMetaFactory * @see BeanFactory.FormItemMetaFactory */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface Generate { Class[] value() default {}; } // ------------------------------------------ // Static housekeeping properties and methods // ------------------------------------------ // The sgwtModule is a JavaScriptOject which we create and register with // SmartClient to provide functions that need to be handled on a per-module // basis. For each relevant SmartClient object that we tag with a __ref // property -- holding the GWT wrapper -- we also provide a __module // property that indicates which set of module functions can handle that // __ref. private static JavaScriptObject sgwtModule = exportSGWTModule(); private static native JavaScriptObject exportSGWTModule () /*-{ var module = { newInstance : $entry(@com.smartgwt.client.bean.BeanFactory::newInstance(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)), setProperty : $entry(function (obj, propName, value) { // We need to pre-convert the value to a Java object, so that // it can be a java.lang.Object when it reaches the JSNI glue code var javaValue = this.convertToJava(value); // Then set the property @com.smartgwt.client.bean.BeanFactory::setProperty(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)(obj, propName, javaValue); }), getProperty : $debox($entry(function (obj, propName) { // Get the Java property var javaValue = @com.smartgwt.client.bean.BeanFactory::getProperty(Ljava/lang/Object;Ljava/lang/String;)(obj, propName); // Convert it to a JavaScript value var jsArray = @com.smartgwt.client.bean.BeanValueType::wrapInJavascriptArray(Ljava/lang/Object;)(javaValue); // Dereference the wrapped array return jsArray[0]; })), getPropertyAsString : $debox($entry(@com.smartgwt.client.bean.BeanFactory::getPropertyAsString(Ljava/lang/Object;Ljava/lang/String;))), getAttributes : $entry(function (className) { // Get the Java attributes, which will be a String[] var javaValue = @com.smartgwt.client.bean.BeanFactory::getAttributes(Ljava/lang/String;)(className); // Convert it to a JavaScript value var jsArray = @com.smartgwt.client.bean.BeanValueType::wrapInJavascriptArray(Ljava/lang/Object;)(javaValue); // Dereference the wrapped array return jsArray[0]; }), getOrCreateJsObj : $entry(@com.smartgwt.client.bean.BeanFactory::getOrCreateJsObj(Ljava/lang/Object;)), setJsObj : $entry(@com.smartgwt.client.bean.BeanFactory::setJsObj(Ljava/lang/Object;Lcom/google/gwt/core/client/JavaScriptObject;)), getSGWTFactory : $entry(function (object) { var beanFactory = @com.smartgwt.client.bean.BeanFactory::getFactory(Ljava/lang/Object;)(object); if (beanFactory) { return beanFactory.@com.smartgwt.client.bean.BeanFactory::sgwtFactory; } else { return null; } }), getSGWTSuperClass : $entry(function (sgwtFactory) { var sc = sgwtFactory.beanFactory.@com.smartgwt.client.bean.BeanFactory::superclassFactory; if (sc) { return sc.@com.smartgwt.client.bean.BeanFactory::sgwtFactory; } else { return null; } }), getDefaultScClassName : $debox($entry(function (sgwtFactory) { return sgwtFactory.beanFactory.@com.smartgwt.client.bean.BeanFactory::getDefaultScClassName()(); })), isSGWTFrameworkClass : $debox($entry(function (sgwtFactory) { return sgwtFactory.beanFactory.@com.smartgwt.client.bean.BeanFactory::isFrameworkClass()(); })), // Performs basic conversion from a JavaScript type to an // equivalent Java value, without taking into account any // particularly desired Java types. This method may return a // JavaScriptObject -- either the value itself, or possibly a new // JavaScriptObject constructed from a config block. // // JSOHelper.convertToJava has some relevant code, but it // doesn't quite do what we want in some respects. In particular, // it converts Canvas instances to strings, which is not what we // want -- we leave any SmartClient instances as JavaScriptObjects // and wrap them in a BeanValueType later (if possible). Also, // if we get a config block with a _constructor, we construct the // object and make it a JavaScriptObject (rather than turning it // into a Map). // // Note that we need to define this function on the Javascript side, // because in development mode it's necessary to do the conversion // before we hit GWT's JSNI <-> Java glue code. Otherwise, we have no // way of properly declaring parameter types on the Java side. If // we use JavaScriptObject as the parameter type, the glue code // auto-converts strings to java.lang.String, which isn't castable to // JavaScriptObject. If we use Object, then the glue code doesn't // auto-convert primitive numbers, so we would need to take care of that // to avoid errors. None of that would be a problem if we had multiple // signatures for each parameter type ... but to have just one signature // we need to convert to Java objects before we hit the glue code. convertToJava : function (obj) { if (obj == null) return null; // If we're passed a real Java Object, just return it. if ($wnd.SmartGWT.isNativeJavaObject(obj)) return obj; // Use typeof to check for primitive types. This is similar to // what JSOHelper.convertToJavaType does, but just picks out // the primitive types and uses isA to discriminate among // others. switch (typeof obj) { case 'undefined': return null; break; case 'string': return obj; break; case 'number': if (obj.toString().indexOf('.') == -1) { if (obj <= @java.lang.Integer::MAX_VALUE && obj >= @java.lang.Integer::MIN_VALUE) { return @com.smartgwt.client.util.JSOHelper::toInteger(I)(obj); } else { return @com.smartgwt.client.util.JSOHelper::toLong(D)(obj); } } else { // Convert all non-integral numbers to Double, // rather than Float, in order to avoid loss of // precision in development mode. return @com.smartgwt.client.util.JSOHelper::toDouble(D)(obj); } break; case 'boolean': return @com.smartgwt.client.util.JSOHelper::toBoolean(Z)(obj); break; default: // If it's not a primitive type, then check various isA // possibilities if ($wnd.isc.isA.Date(obj)) { return @com.smartgwt.client.util.JSOHelper::toDate(D)(obj.getTime()); } if ($wnd.isc.isAn.Array(obj)) { var convertedArray = $wnd.Array.create(); // First, convert each member of the array, by calling // ourselves recursively for (var i = 0; i < obj.length; i++) { convertedArray[i] = this.convertToJava(obj[i]); } // Now we've converted all our members and we need to // return a Java array return @com.smartgwt.client.util.JSOHelper::convertToJavaObjectArray(Lcom/google/gwt/core/client/JavaScriptObject;)(convertedArray); } // If it's a SmartClient instance, then check for the __ref, // and return the SmartGWT object if it exists. Otherwise, just // return the SmartClient instance itself -- the specific // BeanValueType may be able to do further conversion. if ($wnd.isc.isAn.Instance(obj)) { if (obj[$wnd.isc.gwtRef]) { return obj[$wnd.isc.gwtRef]; } else { return obj; } } // Check if it is a Class object, for which // isc.isAn.Instance() returns false. We can't do any // meaningful conversion of Class objects, so we just // pass them through. Note that if a particular method // wants a String, BeanValueType will convert it later // to the class name. if ($wnd.isc.isA.ClassObject(obj)) return obj; if ($wnd.isc.isAn.Object(obj)) { // If it's a String object, use the primitive instead if (obj instanceof $wnd.String || obj instanceof String) return obj.toString(); // And check for Number or Boolean and call recursively if (obj instanceof $wnd.Number || obj instanceof Number || obj instanceof $wnd.Boolean || obj instanceof Boolean) { return this.convertToJava(obj.valueOf()); } // If it has a __ref, then just return that. if (obj[$wnd.isc.gwtRef]) { return obj[$wnd.isc.gwtRef]; } if (obj._constructor == 'DateRange') { return @com.smartgwt.client.widgets.form.fields.DateRangeItem::convertToDateRange(Lcom/google/gwt/core/client/JavaScriptObject;)(obj); } // If it's a config block with a _constructor, then // construct the SmartClient object and return it as a // JavaScriptObject. This is adapted from // Canvas.createCanvas (which isn't a class method, so we // can't call it from here). // // Note that we only are supporting a config block here -- // we're not dealing with autochildren or ID's specified as // strings (since, at this level, we don't know that that // is what is meant -- we would convert JavaScript strings // to Java strings). In principle, we could convert from // strings to Canvas via autochildren or ID's later on in // BeanValueTypes that take Canvii). // // An alternative would be to modify various methods (like // Layout.setMembers) so that they will take config blocks // (as the SmartClient equivalents do). However, this is // simpler for now. if (obj._constructor && $wnd.isc[obj._constructor]) { var cons = obj._constructor; delete obj._constructor; return $wnd.isc.ClassFactory.newInstance(cons, obj); } // If nothing else, convert the POJO to a Map var javaMap = @java.util.LinkedHashMap::new()(); // If it's a tree node, clean it up before converting // otherwise we may end up serializing out all parents and // children! if (obj._isc_tree != null || obj.$42c != null) { obj = $wnd.isc.Tree.getCleanNodeData(obj); } for (var fieldName in obj) { // This shouldn't really happen, but ... if (!$wnd.isc.isA.String(fieldName)) continue; // Don't convert the __module created by BeanFactory if (fieldName == $wnd.isc.gwtModule) continue; // If the field name is '__ref', the the value // is already a GWT java object reference. // Otherwise, we convert it by calling // ourselves recursively. var convertedVal = fieldName == $wnd.isc.gwtRef ? obj[fieldName] : this.convertToJava(obj[fieldName]); // And add it to the map @com.smartgwt.client.util.JSOHelper::doAddToMap(Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)(javaMap, fieldName, convertedVal); } return javaMap; } } // If we haven't returned anything yet, then we really don't know how // to convert the JavaScriptObject ... we may as well just return it. return obj; } }; return module; }-*/; public static JavaScriptObject getSGWTModule () { return sgwtModule; } // When creating a new instance via the BeanFactory, we may store some // properties here to be applied to the new instance. These properties are // picked up by the no-arg constructor, and then immediately cleared here // (so that they don't get picked up by any nested constructor calls). // // We do this instead of calling a constructor which takes properties, // because we don't want to force developers to implement such a // constructor in order to use the reflection mechanism. // // We can't simply apply the properties after the instance is constructed, // because the constructor may trigger getOrCreateJsObj(), and the // constructor on the SmartClient side may need the properties. However, we // do wait until the instance is fully constructed unless // getOrCreateJsObj() is called first. private static Map<String, Object> factoryProperties; public static Map<String, Object> getFactoryProperties () { return factoryProperties; } public static void clearFactoryProperties () { factoryProperties = null; factoryPropertiesClass = null; } // The class for which the factoryProperties are intended. Stashing this // helps us avoid the complication of triggering a static initializer, // which might itself create an instance before the instance for which the // properties are intended. By keeping track of the class for which the // properties are intended, we can limit the problem to static initializers // which create an instance of that very class. private static Class<?> factoryPropertiesClass; public static Class<?> getFactoryPropertiesClass () { return factoryPropertiesClass; } // The factories, hashed by the fully-qualified class name of the base class. private static Map<String, BeanFactory<?>> factoriesByName = new HashMap<String, BeanFactory<?>>(); // The factories, hashed by the base class literal. private static Map<Class<?>, BeanFactory<?>> factoriesByClass = new HashMap<Class<?>, BeanFactory<?>>(); // Note that we need to look up factories by class name and by class // literal to deal with two different scenarios. When we are creating a new // instance, and all we have is a class name, then we have to be able to // look up the class name. When we have an existing object and we need to // manipulate it, then we have to look up the class literal. The reason is // that there could be duplicate class names in separately loaded modules. // Thus, even if the class name matches, it may be that we should not // "claim" the object -- it may have been created in another module, which // was loaded separately. Even if the class in that model is compiled from // the same source as the class in this module, we may not be able to // manipulate it from this module, because it may have obfuscated // differently. // // Note also that the map we maintain here is only for this module -- this // class itself may be compiled into other modules loaded separately. The // only panoptic view of the factories is actually in the SmartClient // SGWTFactory class. // Note that this will only return factories defined in this GWT module ... // it is unaware of any other modules. public static BeanFactory<?> getFactory (String className) { return factoriesByName.get(className); } // Note that this will only return factories defined in this GWT module ... // it is unaware of any other modules. public static BeanFactory<?> getFactory (Class<?> klass) { return factoriesByClass.get(klass); } public static BeanFactory<?> getFactory (Object object) { // Overloading is based on compile-time types, so do some dynamic dispatch if (object instanceof Class) return getFactory((Class) object); if (object instanceof String) return getFactory((String) object); return getFactory(object.getClass()); } // Registers a factory for a base class. protected static void registerFactory (BeanFactory<?> factory) { // Bail out if a factory has already been registered for the class name final String beanClassName = factory.getBeanClassName(); if (factoriesByName.containsKey(beanClassName)) { SC.logWarn("BeanFactory for class has already been registered: " + beanClassName); return; } factoriesByName.put(beanClassName, factory); factoriesByClass.put(factory.getBeanClass(), factory); } // ----------------- // Static Public API // ----------------- // Note that all of these functions only deal with factories defined in a // single GWT module at a time. The only way to access factories in other // modules is through the SmartClient SGWTFactory side. But even if one did // so, all one could get would be opaque references that could only be // manipulated through the SGWTFactory, since code in this module would // have no idea how code in the other module had been obfuscated. If // desirable, it would be possible write a public API here to manipulate // such references through SGWTFactory. // Convenience functions for exceptions protected static IllegalStateException noFactoryException (String name) { return new IllegalStateException("No factory has been generated for class: " + name); } protected static IllegalStateException noFactoryException (Class<?> klass) { return noFactoryException(klass.getName()); } /** * Create an instance based on the provided class name. * * @param className the class name * @return a new instance of the class * @throws IllegalStateException If no factory has been generated for the className */ public static Object newInstance (String className) { // Note that we can't use the parameterized BeanClass type in the static method BeanFactory<?> factory = BeanFactory.getFactory(className); if (factory == null) { throw noFactoryException(className); } else { return factory.newInstance(); } } /** * Create an instance based on the provided class object. * * @param klass the class object * @return a new instance of the class * @throws IllegalStateException If no factory has been generated for the klass */ public static Object newInstance (Class<?> klass) { // Note that we can't use the parameterized BeanClass type in the static method BeanFactory<?> factory = BeanFactory.getFactory(klass); if (factory == null) { throw noFactoryException(klass); } else { return factory.newInstance(); } } /** * Create an instance based on the provided class name, and apply the * provided properties to it. * * The properties are applied after the instance is constructed with * its no-arg constructor, unless the constructor triggers a call to * getOrCreateJsObj(). In that case, the properties are applied before * calling getOrCreateJsObj(), to ensure that the jsObj gets all the * intended properties when it is constructed. * * @param className the class name * @param properties a JavaScriptObject whose key/value pairs represent * property names and values * @return a new instance of the class, with the properties applied * @throws IllegalStateException If no factory has been generated for the className */ public static Object newInstance (String className, JavaScriptObject properties) { BeanFactory<?> factory = BeanFactory.getFactory(className); if (factory == null) { throw noFactoryException(className); } else { return factory.newInstance(properties); } } /** * Create an instance based on the provided class object, and apply the * provided properties to it. * * The properties are applied after the instance is constructed with * its no-arg constructor, unless the constructor triggers a call to * getOrCreateJsObj(). In that case, the properties are applied before * calling getOrCreateJsObj(), to ensure that the jsObj gets all the * intended properties when it is constructed. * * @param klass the class object * @param properties a JavaScriptObject whose key/value pairs represent * property names and values * @return a new instance of the class, with the properties applied * @throws IllegalStateException If no factory has been generated for the class */ public static Object newInstance (Class<?> klass, JavaScriptObject properties) { BeanFactory<?> factory = BeanFactory.getFactory(klass); if (factory == null) { throw noFactoryException(klass); } else { return factory.newInstance(properties); } } /** * Create an instance based on the provided class name, and apply the * provided properties to it. * * The properties are applied after the instance is constructed with * its no-arg constructor, unless the constructor triggers a call to * getOrCreateJsObj(). In that case, the properties are applied before * calling getOrCreateJsObj(), to ensure that the jsObj gets all the * intended properties when it is constructed. * * @param className the class name * @param properties a Map whose key/value pairs represent * property names and values * @return a new instance of the class, with the properties applied * @throws IllegalStateException If no factory has been generated for the className */ public static Object newInstance (String className, Map<String, Object> properties) { BeanFactory<?> factory = BeanFactory.getFactory(className); if (factory == null) { throw noFactoryException(className); } else { return factory.newInstance(properties); } } /** * Create an instance based on the provided class object, and apply the * provided properties to it. * * The properties are applied after the instance is constructed with * its no-arg constructor, unless the constructor triggers a call to * getOrCreateJsObj(). In that case, the properties are applied before * calling getOrCreateJsObj(), to ensure that the jsObj gets all the * intended properties when it is constructed. * * @param klass the class object * @param properties a Map whose key/value pairs represent * property names and values * @return a new instance of the class, with the properties applied * @throws IllegalStateException If no factory has been generated for the class */ public static Object newInstance (Class<?> klass, Map<String, Object> properties) { BeanFactory<?> factory = BeanFactory.getFactory(klass); if (factory == null) { throw noFactoryException(klass); } else { return factory.newInstance(properties); } } /** * Sets a property of a bean to a value. * * @param bean The object whose property is to be set * @param property The name of the property * @param value The value to set * @throws IllegalStateException If no factory has been generated for the bean's class * @throws IllegalArgumentException If there is no appropriate setter for the value */ public static void setProperty (Object bean, String property, Object value) { // Note that we can't use the parameterized BeanClass type in the // static method. // // Also, we used to have a separate implementation for // JavaScriptObject values, but it turns out that doesn't work well in // hosted mode, since in hosted mode some JavaScript objects get // auto-converted to Java objects (e.g. strings) by the glue code, and // are thus not castable to JavaScriptObject. So, we now pre-convert // the value to a Java object on the JavaScript side, before we hit // the glue code. if (bean != null) { BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. factory.doSetProperty(bean, property, value); } } } /** * Applies a Map of property names and values to a bean. * * @param bean The object whose properties are to be set * @param properties A Map whose key/value pairs represent property names and values * @throws IllegalStateException If no factory has been generated for the bean's class * @throws IllegalArgumentException If there is no appropriate setter for a value */ public static void setProperties (Object bean, Map<String, Object> properties) { if (bean != null) { BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. factory.doSetProperties(bean, properties); } } } /** * Gets a property of a bean. * * @param bean The object whose property to get * @param property The name of the property * @throws IllegalStateException If no factory has been generated for the bean's class */ public static Object getProperty (Object bean, String property) { // Note that we can't use the parameterized BeanClass type in the static method if (bean == null) return null; BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. return factory.doGetProperty(bean, property); } } /** * Gets a property of a bean as a String. * * In simple cases, this will be equivalent to getProperty().toString(). * However, if there are multiple getters available, it will prefer getters * that actually return strings (e.g. getWidthAsString() would be preferred * to getWidth().toString()). * * @param bean The object whose property to get * @param property The name of the property * @throws IllegalStateException If no factory has been generated for the bean's class */ public static String getPropertyAsString (Object bean, String property) { // Note that we can't use the parameterized BeanClass type in the static method if (bean == null) return null; BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. return factory.doGetPropertyAsString(bean, property); } } /** * Gets an array of the names of the properties of a class. * * @param beanClassName The name of the class * @throws IllegalStateException If no factory has been generated for the class */ public static String[] getAttributes (String beanClassName) { BeanFactory<?> factory = BeanFactory.getFactory(beanClassName); if (factory == null) { throw noFactoryException(beanClassName); } else { return factory.getAttributes(); } } /** * Gets an array of the names of the properties of a class. * * @param beanClass The klass object * @throws IllegalStateException If no factory has been generated for the class */ public static String[] getAttributes (Class<?> beanClass) { BeanFactory<?> factory = BeanFactory.getFactory(beanClass); if (factory == null) { throw noFactoryException(beanClass); } else { return factory.getAttributes(); } } public static JavaScriptObject getOrCreateJsObj (Object bean) { // Note that we can't use the parameterized BeanClass type in the static method if (bean == null) return null; BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. return factory.doGetOrCreateJsObj(bean); } } public static void setJsObj (Object bean, JavaScriptObject jsObj) { // Note that we can't use the parameterized BeanClass type in the static method if (bean == null) return; BeanFactory<?> factory = BeanFactory.getFactory(bean.getClass()); if (factory == null) { throw noFactoryException(bean.getClass()); } else { // Need a different name for the instance method, since the signature // is the same. factory.doSetJsObj(bean, jsObj); } } /** * Indicates whether the class is defined by the SmartGWT framework. * * @param beanClass The Class object * @throws IllegalStateException If no factory has been generated for the class */ public static boolean isFrameworkClass (Class<?> beanClass) { BeanFactory<?> factory = BeanFactory.getFactory(beanClass); if (factory == null) { throw noFactoryException(beanClass); } else { return factory.isFrameworkClass(); } } /** * Gets the default scClassName for the class. * * @param beanClass The Class object * @throws IllegalStateException If no factory has been generated for the class */ public static String getDefaultScClassName (Class<?> beanClass) { BeanFactory<?> factory = BeanFactory.getFactory(beanClass); if (factory == null) { throw noFactoryException(beanClass); } else { return factory.getDefaultScClassName(); } } // ------------------------------ // Instance variables and methods // ------------------------------ // We use the superclassFactory to find properties in the superclasses // (rather than recording them redundantly). protected BeanFactory superclassFactory; // The generated subclass must populate the properties. protected Map<String, BeanProperty<BeanClass>> properties = new HashMap<String, BeanProperty<BeanClass>>(); // Our SGWTFactory (the SmartClient interface to the factory) protected JavaScriptObject sgwtFactory; // A cache for our attribute names, including those defined in superclasses private String[] attributeNames; /** * The constructor is protected because you should not create BeanFactory * instances directly. Instead, use {@link BeanFactory.MetaFactory}, * {@link BeanFactory.CanvasMetaFactory} or {@link BeanFactory.FormItemMetaFactory} * to create bean factories, and then use the static methods on * BeanFactory. * * @see BeanFactory.MetaFactory * @see BeanFactory.CanvasMetaFactory */ protected BeanFactory () { // Ensure that all the value types we use have been registered. registerValueTypes(); // Initialize the JSNI function array for the setters and getters, // and then initialilze the properties. for (BeanProperty<BeanClass> property : getProperties(getMethods())) { properties.put(property.getName(), property); } // Create the SmartClient SGWTFactory equivalent. Note that we need // this whether or not we're registering our class name with // SGWTFactory, since we need it to deal with any SGWT objects we // create. sgwtFactory = createSGWTFactory(); // This registers us locally, on the SmartGWT side, for this module BeanFactory.registerFactory(this); // See if we can get the superclass factory ... we don't want to // construct it if we don't have to ... Class superclass = getSuperclass(); if (superclass != null) { superclassFactory = getFactory(superclass); if (superclassFactory == null) { // Note that creating it will also register it, so next time // getFactory will find it. superclassFactory = createSuperclassFactory(); } } } // To be implemented by generated subclasses if it is appropriate to check // the superclass for properties. Can leave as null if generating for a base // class for which no further superclasses are relevant. protected Class getSuperclass () { return null; } // To be implemented by subclasses to actually create the superclass // factory. This will only be called if the factory is not already cached // (for instance, if GWT.create was called to create it, or it has already // been created by a different subclass). protected BeanFactory<?> createSuperclassFactory () { return null; } // To be implemented by subclasses to register any ValueTypes which they use protected abstract void registerValueTypes (); // This will be implemented by generated subclasses to set up all the // getters and setters for the BeanFactory, as a Javascript array of JSNI // functions, The reason we use JSNI functions is that we can then pass the // function around as a function pointer -- for instance, we can associate // it with a property as data, and call it. // // The "standard" GWT way to do that would be an anonymous subclass with a // callback function. That works, but it has a lot of overhead, since it // creates a class for every method. The size of the generated code ends up // approximately one-third larger that way. // // Ideally, we could simply get the function pointer for the *real* setter // or getter itself (rather than constructing a function that calls it). // However, that's hard to generate, because GWT will only generate the // obfuscated function name in JSNI in the context of calling the function. protected abstract JsArray<JavaScriptObject> getMethods (); // To be implemented by generated subclasses to return an array of properties. // Each property will use one or more methods from the JsArray generated by // getMethods(). protected abstract BeanProperty<BeanClass>[] getProperties (JsArray<JavaScriptObject> methods); // Creates the SmartClient SGWTFactory. Most of the callback functions are // actually the same for every SGWTFactory, and defined in the SGWTModule, // so we pass the reference to the module. private native JavaScriptObject createSGWTFactory () /*-{ return $wnd.isc.SGWTFactory.create({ beanClassName: this.@com.smartgwt.client.bean.BeanFactory::getBeanClassName()(), beanFactory: this, sgwtModule: @com.smartgwt.client.bean.BeanFactory::getSGWTModule()() }); }-*/; // Calls the SGWTFactory function which registers the class name in the // isc[] space, so that idioms like isc[className].create will work. The // generated code will call this if the factory has been explicitly created // (i.e. not just as a superclass of something which was explicitly // created.) public native void registerClassNameWithSGWTFactory () /*-{ this.@com.smartgwt.client.bean.BeanFactory::sgwtFactory.registerClassName(); }-*/; // Gets the base class for which we are the factory. Subclasses must // implement. public abstract Class<BeanClass> getBeanClass(); // Gets the name of the base class for which we are the factory. public String getBeanClassName () { return getBeanClass().getName(); } // Create an new instance of the factory's underlying class. // Must be generated for all subclasses. protected abstract BeanClass doNewInstance (); public BeanClass newInstance () { return newInstance((Map<String, Object>) null); } @SuppressWarnings("unchecked") public BeanClass newInstance (JavaScriptObject properties) { if (properties == null) return newInstance(); Object javaProperties = BeanValueType.convertToJava(properties); if (javaProperties instanceof Map) { return newInstance ((Map<String, Object>) javaProperties); } else { throw new IllegalArgumentException("properties were not a plain JavaScript object"); } } // All calls to the various overloaded newInstance methods will end up here public BeanClass newInstance (Map<String, Object> properties) { // Always provide the factoryCreated property, so that we can test // whether the properties were correctly applied. There is one scenario // in which the properties will be applied to the wrong object: where // (a) the BeanClass has a static initializer; (b) the static initializer // has not previously run; and (c) the static initializer creates an // instance of the BeanClass. We can't check for that scenario in // advance, but we can check afterwards and try to recover. if (properties == null) properties = new HashMap<String, Object>(); properties.put("factoryCreated", true); // Trigger any static initializers before stashing the properties. // This is a no-op unless the BeanClass defines a public static no-arg // method named beanFactoryInit(). Doing so avoids the problem // mentioned above re: static initializers. triggerStaticInitializers(); // Stash the provided properties. See comments above on factoryProperties factoryProperties = properties; factoryPropertiesClass = getBeanClass(); // Construct the new instance. Note that the BeanClass will apply the // properties if getOrCreateJsObj() is called before the constructor returns BeanClass instance = doNewInstance(); // Apply the properties supplied by BeanFactory, if they haven't // already been applied via getOrCreateJsObj(). applyFactoryProperties(instance); // If the BeanClass is abstract, doNewInstance will return null and we // don't need any further checking if (instance == null) return instance; // Check whether the properties were applied, by checking for the // factoryCreated property we always supply if (isFactoryCreated(instance)) { // It worked! return instance; } else { // In the static initializer scenario discussed above, we should be // able to make it work just by trying again ... since we've now // triggered the static initializer, the problem won't occur a // second time. Note that we can't just apply the properties now // to the already-created instance, because it may have already // called getOrCreateJsObj(), and may have needed the properties // then. We could possibly check whether the jsObj has been created ... // if not, we could just apply the properties now rather than // recreating the object. factoryProperties = properties; factoryPropertiesClass = getBeanClass(); instance = doNewInstance(); applyFactoryProperties(instance); if (isFactoryCreated(instance)) { // It worked the second time. Now, it's still a problem, // because the instance created by the static initializer will // have received some unwanted properties, and we don't know // what the effects of that are. Also, we've created two new instances // instead of one (since we had to retry). So, we still log an error // message. String beanClassName = getBeanClassName(); SC.logWarn( "The BeanFactory for " + beanClassName + " failed to apply properties " + "to a new instance on the first attempt, but succeeded on the second " + "attempt. A known cause for this is where " + beanClassName + " has a " + "static initializer, the static initializer has not run yet, and the " + "static initializer itself creates an instance of " + beanClassName + ". " + "To work around this problem, define a static no-arg public method within " + beanClassName + " named beanFactoryInit(). BeanFactory will call that function " + "in order to trigger static initialization before trying to create " + "a new instance. Or, you can do something else to ensure that the " + "static initializer is called before creating instances of " + beanClassName + " via BeanFactory." ); return instance; } else { // It failed again, so we better throw an exception throw new IllegalStateException("BeanFactory for " + getBeanClassName() + " failed to apply properties to a new instance."); } } } protected void triggerStaticInitializers () { // This is a no-op, unless a specific BeanClass defines a public static // no-arg method names beanFactoryInit() } // Sets a property of the bean to a value @SuppressWarnings("unchecked") public void doSetProperty (Object bean, String propertyName, Object value) { // Note that we don't want to use the parameterized BeanClass type // here, because we'd like to be able to call this from the static // method. BeanProperty<BeanClass> property = properties.get(propertyName); if (property == null) { // If we don't have it, check the superclass if (superclassFactory == null) { // If there is no superclassFactory, then set the property on // the underlying Javascript object directly. setJavascriptProperty((BeanClass) bean, propertyName, value); } else { // If we have a superclass, let it try superclassFactory.doSetProperty(bean, propertyName, value); } } else { // The type-cast should be safe, because we only get here once we've // picked the correct factory for the bean try { property.setProperty((BeanClass) bean, value); } catch (BeanProperty.NoSetterException e) { // There are some properties which have no setter on the SmartGWT // side (because they aren't meant to be set from user code), but // which do need to be set from framework code. So, if we have no // setter, we fall back on setting the property via Javascript. setJavascriptProperty((BeanClass) bean, propertyName, value); } } } public void doSetProperties (Object bean, Map<String, Object> properties) { for (Map.Entry<String, Object> entry : properties.entrySet()) { Object key = entry.getKey(); Object value = entry.getValue(); if (key instanceof String) { doSetProperty(bean, (String) key, value); } else { throw new IllegalArgumentException("a property name was not a String"); } } } public String[] getAttributes () { // We lazily create a cache for the return value. Note that this // assumes that the method won't be called too "early" -- that is, // before the BeanFactory is finished registering its properties and // superclass factories. That happens in the constructor, so it should // be a safe assumption. if (attributeNames == null) { // Construct a new set of strings out of our properties keySet, since // the keySet should not be mutated here. Set<String> attributes = new HashSet<String>(properties.keySet()); // Add property names from superclass factories BeanFactory<?> superclassIterator = superclassFactory; while (superclassIterator != null) { attributes.addAll(superclassIterator.properties.keySet()); superclassIterator = superclassIterator.superclassFactory; } // Supply the parameter to provide the array type attributeNames = attributes.toArray(new String[attributes.size()]); } return attributeNames; } @SuppressWarnings("unchecked") public Object doGetProperty (Object bean, String propertyName) { // Note that we don't want to use the parameterized BeanClass type // here, because we'd like to be able to call this from the static // method. BeanProperty<BeanClass> property = properties.get(propertyName); if (property == null) { // If we don't have the property, try a superclassFactory. if (superclassFactory == null) { // If there is no superclassFactory, then try to get the // property from the Javascript side. return getJavascriptProperty((BeanClass) bean, propertyName); } else { // Otherwise, let the superclass try return superclassFactory.doGetProperty(bean, propertyName); } } else { // The type-cast should be safe, since we only get here once we've // picked the correct factory. return property.getProperty((BeanClass) bean); } } @SuppressWarnings("unchecked") public String doGetPropertyAsString (Object bean, String propertyName) { // Note that we don't want to use the parameterized BeanClass type // here, because we'd like to be able to call this from the static // method. BeanProperty<BeanClass> property = properties.get(propertyName); if (property == null) { // If we don't have the property, try a superclassFactory. if (superclassFactory == null) { // If there is no superclassFactory, then try to get the // property from the Javascript side. Object jsProperty = getJavascriptProperty((BeanClass) bean, propertyName); return jsProperty == null ? null : jsProperty.toString(); } else { // Otherwise, let the superclass try return superclassFactory.doGetPropertyAsString(bean, propertyName); } } else { // The type-cast should be safe, since we only get here once we've // picked the correct factory. return property.getPropertyAsString((BeanClass) bean); } } // These are abstract because their implementation differs depending on the // BeanClass. // Sets a property on the underlying Javascript property directly. protected abstract void setJavascriptProperty (BeanClass bean, String propertyName, Object value); // Gets a property from the underlying JavaScript object protected abstract Object getJavascriptProperty (BeanClass bean, String propertyName); // Gets the uncerlying JavaScriptObject. public abstract JavaScriptObject doGetOrCreateJsObj (Object bean); // Sets the uncerlying JavaScriptObject. public abstract void doSetJsObj (Object bean, JavaScriptObject jsObj); // Whether the bean was created by a BeanFactory public abstract boolean isFactoryCreated (BeanClass bean); protected abstract void applyFactoryProperties (BeanClass bean); public boolean isFrameworkClass () { // default to false, so we only need to generate a method when it is true return false; } public String getDefaultScClassName () { // default to null return null; } }