/* * 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.smartgwt.client.bean.types.*; import com.smartgwt.client.types.ValueEnum; import com.smartgwt.client.util.JSOHelper; import com.smartgwt.client.core.BaseClass; import com.smartgwt.client.core.DataClass; import com.smartgwt.client.core.Function; import com.smartgwt.client.core.RefDataClass; import com.smartgwt.client.data.Record; import com.smartgwt.client.data.RecordList; import com.smartgwt.client.data.RelativeDate; import com.smartgwt.client.data.ResultSet; import com.smartgwt.client.types.ValueEnum; import com.smartgwt.client.util.SC; import com.smartgwt.client.widgets.BaseWidget; import java.util.HashMap; import java.util.Map; import java.util.Date; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; // This is a superclass for generated classes that help deal with the value // types used by BeanFactories ... that is, the types of the parameters which // the setters take (as opposed to the types of the BeanFactories themselves). // // There are quite a few tasks which we need help with. Generally, these are // things you would normally implement in Java with reflection, but the // reflection capabilities in GWT are limited (for various reasons). // // 1. We need to do conversion from JavaScriptObjects supplied from the // SmartClient side to Java-oriented objects. The routines in JSOHelper are // close to what we need, but require some adaptation. (Note that this is now // mostly implemented in BeanFactory -- see the comments there). // // 2. We need to do conversion from Java-oriented objects to JavaScriptObjects // to supply back to SmartClient, or to set on config blocks. Again, the // routines in JSOHelper are close, but require some adaptation. // // 3. We need to be able to detect whether a supplied value is assignable to // the parameter type (for a setter). This is partly because we are ultimately // calling the setters via JSNI function pointers, so we need to check type // compatibility ourselves (rather than catching cast exceptions). // // 4. We need to be able to choose the best setter for a supplied value, where // multiple setters are available that deal with different types. // // In some cases, achieving these tasks requires generating a subclass -- in // other cases, we can write the subclass in advance. Generated subclasses are // required in these cases: // // 1. When a method signature is an interface, GWT doesn't provide a way at // run-time to know whether an arbtirary object implements that interface if // all we have is the Class object (since Class.isAssignableFrom is not // available, and instanceof requires the class literal). We can construct a // partial implementation of isAssignableFrom using getSuperclass, but that // doesn't help when it comes to interfaces. So, we need to generate a subclass // for any interface used as a parameter type. // // 2. In some cases, we can construct the parameter from a JavaScriptObject. // For instance, we can construct a Canvas from a SmartClient JSO. In those // cases, we need a generated newInstance function. // // 3. When parameters are arrays, we need to be able to do various things that // require a generated subclass. // // The generation of subclasses is handled by com.smartgwt.rebind.BeanValueType, // in conjunction with com.smartgwt.rebind.BeanClass. // // Finally, we provide routines that will convert a supplied Object to the // BeanValueType, if possible. That way, we don't have to ensure that the // provided type exactly matches the parameter type -- we can perform a variety // of logical conversions. /** * Class with static methods that help with various reflection-like tasks with * respect to values used by {@link BeanFactory BeanFactories} (that is, the * return types of getters, and the parameter types of setters). * * <p>The primary interface is through the static methods. In some cases, * generated subclasses are required in order to work around GWT's limited * reflection capabilities. Those classes are automatically generated by the by * the <code>BeanFactory</code> generator when necessary. If you want to use * <code>BeanValueType</code> outside of <code>BeanFactory</code>, then you'll * need to use the {@link BeanValueType.MetaFactory} interface to register * types. */ public abstract class BeanValueType<ValueType> { // The ValueType is the actual type required by the getter or setter. If // the ValueType would be primitive, then it is the equivalent boxed type // instead, since you can't use primitive types in generics. /** * Interface used to trigger the generation and registration of * reflection metadata for argument types. * * <p>In order to use {@link BeanValueType} to convert values to a type, you * need to create the correct kind of <code>BeanValueType</code> for each * type, which sometimes involves code generation. This is all automatic * for types required by {@link BeanFactory}. However, if want to use * <code>BeanValueType</code> outside of <code>BeanFactory</code>, then you * can use <code>BeanValueType.MetaFactory</code> for specific types. * * <p>Usage 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 BeanValueType.MetaFactory { * BeanValueType<Integer[]> getIntegerArrayValueType(); * BeanValueType<Canvas> findMeTheCanvasValueType(); * }</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 the <code>BeanValueType</code> for one type ... so, in this case, we * would end up with <code>BeanValueTypes</code> for Integer arrays and * Canvas. The rules are as follows: * * <ol> * <li>The interface must extend <code>BeanValueType.MetaFactory</code></li> * <li>The return type for each function must be <code>BeanValueType</code>, * with a generic type that is the type you want to be able to convert to.</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); * BeanValueType<Integer[]> integerArrayType = myMetaFactory.getIntegerArrayValueType(); * Integer[] intArray = integerArrayType.convertFrom(new String[] {"7", "8"});</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 {@link BeanValueType} static * API: * * <blockquote><pre> * GWT.create(MyMetaFactory.class); * Integer[] intArray = BeanValueType.convertFrom(Integer[].class, new String[] {"7", "8"});</pre></blockquote> * * <p>Note that the call to <code>GWT.create()</code> must occur at run-time * before the types are used. However, you can modularize by creating some * types first and others later, as long as each <code>BeanValueType</code> * is created before being used. * * <p>If you just need to convert to basic types -- int, long, float, * double, boolean, Integer, Long, Float, Double, Boolean and String, then * you can call {@link BeanValueType#registerBasicValueTypes()} instead of * using <code>GWT.create()</code>. */ public static interface MetaFactory { // See com.smartgwt.client.BeanFactory.MetaFactory for comments on this approach // to triggering GWT.create() } /** * An enum used to indicate how well a BeanValueType can handle a * conversion. * * Used to choose among multiple getters or setters, if available. */ public enum Convertability { // Note that the order is important, because we are comparing based on the // ordinalValue() /** * The value cannot be converted to the type. */ UNSUPPORTED, /** * The value can be converted to the type, but other types might handle it better.. */ SUPPORTED, /** * The value can be converted to the type, and this type is preferrable to others. */ PREFERRED, /** * The value is of this type, so conversion is exact. */ EXACT } // ------------------------- // Static Methods and Fields // ------------------------- // Our cache for the generated subclass objects. private static Map<Class, BeanValueType<?>> beanValueTypes = new HashMap<Class, BeanValueType<?>>(); // The BeanMethods look up their BeanValueType in the cache, based on their // valueType. Note that I can't equate the two <?>'s, because the primitive // value types have BeanValueTypes with a boxed ValueType. public static BeanValueType<?> getBeanValueType (Class<?> valueType) { return beanValueTypes.get(valueType); } // Each subclass of BeanValueType has a static registerValueType method. // The BeanFactory generator will take note of which BeanValueTypes // actually get used, and make sure that each of them is registered. That // way, we avoid referring to code that might otherwise be dead code. The // registerValueType methods will be called multiple times as various // BeanFactories initialize themselves. This is largely unaviodable, since // we don't have a panoptic view of all the BeanFactories that are going to // be generated at generation time -- thus, each generated BeanFactory has // to be complete in itself. However, we do have the registerValueType // methods check to see whether the valueType is already registered, so // that we don't construct a BeanValueType for the same valueType over and // over again. protected static void registerBeanValueType (BeanValueType beanValueType) { beanValueTypes.put(beanValueType.getValueType(), beanValueType); } /** * Registers <code>BeanValueTypes</code> for boolean, double, float, long, Boolean, * Double, Float, Long, Number, and String. * * <p>This is not automatic in order to allow for more dead-code elimination * if not being used. * * <p>If you need <code>BeanValueTypes</code> for other types, you can register * them via the {@link BeanValueType.MetaFactory} interface. */ public static void registerBasicValueTypes () { // Note that they will also register their primitive equivalents BooleanValueType.registerValueType(); DoubleValueType.registerValueType(); FloatValueType.registerValueType(); LongValueType.registerValueType(); IntegerValueType.registerValueType(); NumberValueType.registerValueType(); StringValueType.registerValueType(); } // Convenience method to create exception object private static IllegalArgumentException noBeanValueTypeException (Class<?> klass) { return new IllegalArgumentException("No BeanValueType subclass has been generated for " + klass.getName()); } /** * Can the value be assigned to the class? Or, to put it another way, * would <code>value instanceof Klass</code> return true (if we had * the class literal to work with)? * * @param klass The Class to be assigned to. * @param value The value to be assigned * @throws IllegalArgumentException If the Class represents an interface, * and no BeanValueType subclass has been * generated to handle it. */ public static boolean isAssignableFrom (Class<?> klass, Object value) { // null can be assigned to anything if (value == null) return true; BeanValueType beanValueType = getBeanValueType(klass); if (beanValueType == null) { if (klass.isInterface()) { // If it's an interface, then our only hope is to use the // generated subclass. So, throw an exception if we can't find // one. throw noBeanValueTypeException(klass); } else { // If it's not an interface, then we don't need a subclass ... // we can use the class objects. return isAssignableFrom(klass, value.getClass()); } } else { // If we have a subclass, just ask it. return beanValueType.isAssignableFrom(value); } } /** * Can objects of the possible sub-class be assigned to the possible super-class? * * <p>Like <code>Class.assignableFrom()</code>. * * @param possibleSuperclass The possible super-class, or lhs of the assignment * @param possibleSubclass The possible sub-class, or rhs of the assignment * @throws IllegalArgumentException If one of the classes is an interface and the * other is not. In that case, you need to use * {@link #isAssignableFrom(Class<?>, Object)} with * the value to be assigned. */ public static boolean isAssignableFrom (Class<?> possibleSuperclass, Class<?> possibleSubclass) { // Method overloading gets confused with null, since null doesn't // really have a type. We do get called with a null, when we really // want the other overloaded method. So, call it in that case. if (possibleSubclass == null) { return isAssignableFrom(possibleSuperclass, (Object) null); } // This method only works if both sides are interfaces, or neither side // is an interface. If only one side is an interface, then we can't // just walk the class hierarchy -- in fact, there is seemingly no way // to figure out whether a Class implements an Interface in GWT in the // abstract, aside from calling instanceof on a value. // // (In principle, we could, when generating the BeanValueType subclass // for the interface, generate a list of all classes which implement // the interface, and then use that to test with here. However, one // would have to check what effect that would have on dead code pruning // -- it would be unfortunate if that prevented otherwise dead code // from being pruned). if (possibleSuperclass.isInterface() != possibleSubclass.isInterface()) { throw new IllegalArgumentException( "Cannot use BeanValueType.isAssignableFrom(Class, Class) with interfaces, " + "unless both classes are interfaces. Use isAssignableFrom(Class, Object) instead." ); } // While covariant, arrays don't have each other as supertypes ... the // supertype is always Object. So, we need to adopt a different // strategy. if (possibleSuperclass.isArray()) { // If they are equal, we can quickly return true if (possibleSuperclass == possibleSubclass) return true; // If the possibleSubclass is not an array, then clearly false if (!possibleSubclass.isArray()) return false; // Otherwise, we need to check the component types return isAssignableFrom(possibleSuperclass.getComponentType(), possibleSubclass.getComponentType()); } // Otherwise, we can iterate through getSuperclass and see what we // find. Class<?> iterator = possibleSubclass; while (iterator != null) { if (iterator == possibleSuperclass) return true; iterator = iterator.getSuperclass(); } return false; } /** * Is the SmartClient object an instance (or subclass) of the SmartClient class? * * @param value The SmartClient object * @param scClassName The SmartClient class name (e.g. "ListGrid") */ public static native boolean isA (JavaScriptObject value, String scClassName) /*-{ if (!value) return false; if (!scClassName) return false; // Make sure the relevant isA function exists ... if ($wnd.isc.isA[scClassName]) { // Explicitly return true or false to avoid type problems return $wnd.isc.isA[scClassName](value) ? true : false; } else { return false; } }-*/; /** * Performs basic conversion from a JavaScriptObject to an equivalent Java * value, without taking into account any particularly desired Java types. * * <p>This is similar to {@link * com.smartgwt.client.util.JSOHelper#convertToJava(JavaScriptObject,boolean) * JSOHelper.convertToJava}, but has several differences which are needed * for {@link BeanFactory} to work properly. * * Note that this method may return a {@link JavaScriptObject} -- either * the value itself, or possibly a new <code>JavaScriptObject</code> * constructed from a config block. In those cases, it may be that {@link * #convertFrom(Class,Object) convertFrom()} can still do some <i>further</i> * conversion on the return value, since it can take into account * opportunities for conversion that are specific to the desired type. In * other words, this is a <i>generic</i> conversion function. * * @param object A javascript value * @return A generic conversion to an <code>Object</code> (which may still be * a <code>JavaScriptObject</code>). */ public static native Object convertToJava (JavaScriptObject obj) /*-{ // This is actually defined in the SGWTModule exported by BeanFactory, // because we need to be able to call it from there without hitting the // development-mode JSNI glue code first -- see the comments there. var sgwtModule = @com.smartgwt.client.bean.BeanFactory::getSGWTModule()(); return sgwtModule.convertToJava(obj); }-*/; /** * How well could {@link #convertFrom(Class, Object) convertFrom} convert the value to the klass? * * <p>This is used to choose the best setter amongst multiple setters, if there are multiple setters * available. * * @param klass The desired class to convert to. * @param value The value to be converted. * @return A constant indicating how well the conversion could be performed. * @throws IllegalArgumentException If there is no BeanValueType subclass for the klass */ public static Convertability convertabilityFrom (Class<?> klass, Object value) { BeanValueType beanValueType = getBeanValueType(klass); if (beanValueType == null) { throw noBeanValueTypeException(klass); } else { return beanValueType.convertabilityFrom(value); } } /** * Convert the value into an object of the class. * * <p>Note that not all conversions are actually supported ... you can check * with {@link #convertabilityFrom(Class,Object) convertabilityFrom()}. The target classes which are * supported are generally the classes which are used as value types in * properties of BeanFactories that have been generated. The values supported * depend on the target class. * * @param klass The class that you want to convert to. * @param value The value that you want to convert. * @return The converted value. * @throws IllegalArgumentException If there is no BeanValueType subclass for the klass, * or if the BeanValueType is unable to convert the value. */ public static Object convertFrom (Class<?> klass, Object value) { BeanValueType beanValueType = getBeanValueType(klass); if (beanValueType == null) { throw noBeanValueTypeException(klass); } else { return beanValueType.convertFrom(value); } } // Note that we could add more convertTo... and convertabilityTo... // methods, depending on what we need. But String is the only obvious need. public static Convertability convertabilityToString (Class<?> klass) { BeanValueType beanValueType = getBeanValueType(klass); if (beanValueType == null) { // It's always at least supported, in the sense that we can always call toString() return Convertability.SUPPORTED; } else { return beanValueType.convertabilityToString(); } } /** * Converts the value to a string. * * <p>Of course, you can always call {@link Object#toString()}. However, a * BeanValueType subclass might implement a different strategy. * * @param value The value to convert. * @return A string representing the value. */ @SuppressWarnings("unchecked") public static String convertToString (Object value) { if (value == null) return null; BeanValueType beanValueType = getBeanValueType(value.getClass()); if (beanValueType == null) { return value.toString(); } else { // doConvertToString takes a ValueType, but that should be correct // since we got the correct beanValueType return beanValueType.doConvertToString(value); } } /** * Converts the value to a JavaScriptObject, and wraps it in a JavaScript array. * The wrapping avoids problems with declaring a return type for generic * Javascript values. The array is always a JavaScriptObject. If the * converted value were returned directly, then in development mode it * could be auto-converted back to a {@link java.lang.String}, or a * primitive numeric type, causing type errors no matter how the return * value is declared here. * * @param value Value to convert * @return A JavaScriptObject wrapped in a Javascript array */ public static JavaScriptObject wrapInJavascriptArray (Object value) { return convertToJavaScriptArray(new Object[] {value}); } // We used to use JSOHelper.convertToJavaScriptArray here, but the // JSOHelper version does some things that don't work well in this context. // For instance, it converts Trees to multi-level Arrays, which is sensible // for many purposes, but we need to be able to round-trip from SC Tree -> // SGWT Tree -> SC Tree. public static JavaScriptObject convertToJavaScriptArray (Object[] array) { JavaScriptObject jsArray = JSOHelper.createJavaScriptArray(); for (int i = 0; i < array.length; i++) { Object val = array[i]; if (val == null) { JSOHelper.setArrayValue(jsArray, i, (JavaScriptObject) val); } else if (val instanceof String || val instanceof Character) { JSOHelper.setArrayValue(jsArray, i, val.toString()); } else if (val instanceof Number) { if (val instanceof Long) { JSOHelper.setArrayValue(jsArray, i, ((Long) val).longValue()); } else { JSOHelper.setArrayValue(jsArray, i, JSOHelper.doubleValue((Number) val)); } } else if (val instanceof Boolean) { JSOHelper.setArrayValue(jsArray, i, ((Boolean) val).booleanValue()); } else if (val instanceof Date) { JSOHelper.setArrayValue(jsArray, i, (Date) val); } else if (val instanceof ValueEnum) { JSOHelper.setArrayValue(jsArray, i, ((ValueEnum) val).getValue()); } else if (val instanceof JavaScriptObject) { JSOHelper.setArrayValue(jsArray, i, (JavaScriptObject) val); } else if (val instanceof DataClass) { JSOHelper.setArrayValue(jsArray, i, ((DataClass) val).getJsObj()); } else if (val instanceof BaseClass) { JSOHelper.setArrayValue(jsArray, i, ((BaseClass) val).getOrCreateJsObj()); } else if (val instanceof BaseWidget) { JSOHelper.setArrayValue(jsArray, i, ((BaseWidget) val).getOrCreateJsObj()); } else if (val instanceof Record) { JSOHelper.setArrayValue(jsArray, i, ((Record) val).getJsObj()); } else if (val.getClass().isArray()) { if (val instanceof Object[]) { // Recurse into our own version, rather than JSOHelper JSOHelper.setArrayValue(jsArray, i, convertToJavaScriptArray((Object[]) val)); } else if (val instanceof int[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((int[]) val)); } else if (val instanceof double[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((double[]) val)); } else if (val instanceof float[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((float[]) val)); } else if (val instanceof boolean[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((boolean[]) val)); } else if (val instanceof char[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((char[]) val)); } else if (val instanceof byte[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((byte[]) val)); } else if (val instanceof short[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((short[]) val)); } else if (val instanceof long[]) { JSOHelper.setArrayValue(jsArray, i, JSOHelper.convertToJavaScriptArray((long[]) val)); } else { assert false : val.getClass() + " should not be an array class."; JSOHelper.setArrayValue(jsArray, i, (JavaScriptObject) null); } } else if (val instanceof List) { // Recurse into our own version of convertToJavaScriptArray (and below) JSOHelper.setArrayValue(jsArray, i, convertToJavaScriptArray(((List<?>) val).toArray())); } else if (val instanceof Iterator) { List listVal = new ArrayList(); while (((Iterator) val).hasNext()) listVal.add(((Iterator) val).next()); JSOHelper.setArrayValue(jsArray, i, convertToJavaScriptArray(((List<?>) listVal).toArray())); } else if (val instanceof Set) { JSOHelper.setArrayValue(jsArray, i, convertToJavaScriptArray(((Set<?>) val).toArray())); } else if (val instanceof Map) { // Use our own version of convertMapToJavascriptObject JSOHelper.setArrayValue(jsArray, i, convertMapToJavascriptObject((Map) val)); } else { throw new IllegalArgumentException( "Could not convert from " + (val == null ? "null" : val.getClass().getName()) + " to JavaScriptObject" ); } } return jsArray; } // We used to rely on JSOHelper.convertMapToJavascriptObject, but we need // to make it use our convertToJavaScriptArray, among other small differences. public static JavaScriptObject convertMapToJavascriptObject (Map valueMap) { if (valueMap == null) return null; JavaScriptObject valueJS = JSOHelper.createObject(); for (Iterator iterator = valueMap.keySet().iterator(); iterator.hasNext();) { String key = (String) iterator.next(); if (key == null) { SC.logWarn("JSO::convertMapToJavascriptObject : Map contains null key - dropping this entry."); continue; } else if (key.equals("__ref")) { SC.logWarn("JSO::convertMapToJavascriptObject : skipping __ref in map"); continue; } else if (key.equals("__module")) { SC.logWarn("JSO::convertMapToJavascriptObject : skipping __module in map"); continue; } Object value = valueMap.get(key); if (value == null) { JSOHelper.setNullAttribute(valueJS, key); } else if (value instanceof JavaScriptObject) { JSOHelper.setAttribute(valueJS, key, (JavaScriptObject) value); } else if (value instanceof Date) { JSOHelper.setAttribute(valueJS, key, ((Date) value)); } else if (value instanceof Number) { JSOHelper.setAttribute(valueJS, key, JSOHelper.doubleValue((Number) value)); } else if (value instanceof String || value instanceof Character) { JSOHelper.setAttribute(valueJS, key, value.toString()); } else if (value instanceof Boolean) { JSOHelper.setAttribute(valueJS, key, ((Boolean) value).booleanValue()); } else if (value.getClass().isArray()) { if (value instanceof Object[]) { // Use our convertToJavaScriptArray JSOHelper.setAttribute(valueJS, key, convertToJavaScriptArray((Object[]) value)); } else if (value instanceof int[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((int[]) value)); } else if (value instanceof float[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((float[]) value)); } else if (value instanceof double[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((double[]) value)); } else if (value instanceof boolean[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((boolean[]) value)); } else if (value instanceof char[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((char[]) value)); } else if (value instanceof byte[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((byte[]) value)); } else if (value instanceof short[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((short[]) value)); } else if (value instanceof long[]) { JSOHelper.setAttribute(valueJS, key, JSOHelper.convertToJavaScriptArray((long[]) value)); } else { assert false : value.getClass() + " should not be an array class."; JSOHelper.setNullAttribute(valueJS, key); } } else if (value instanceof Map) { // Use our convertMapToJavascriptObject JavaScriptObject innerMapJS = convertMapToJavascriptObject((Map) value); JSOHelper.setAttribute(valueJS, key, innerMapJS); } else if (value instanceof List) { JSOHelper.setAttribute(valueJS, key, convertToJavaScriptArray(((List) value).toArray())); } else if (value instanceof Iterator) { List listVal = new ArrayList(); while (((Iterator) value).hasNext()) listVal.add(((Iterator) value).next()); JSOHelper.setAttribute(valueJS, key, convertToJavaScriptArray(((List<?>) listVal).toArray())); } else if (value instanceof Set) { JSOHelper.setAttribute(valueJS, key, convertToJavaScriptArray(((Set<?>) value).toArray())); } else if (value instanceof DataClass) { JSOHelper.setAttribute(valueJS, key, ((DataClass) value).getJsObj()); } else if (value instanceof BaseClass) { JSOHelper.setAttribute(valueJS, key, ((BaseClass) value).getOrCreateJsObj()); } else if (value instanceof BaseWidget) { JSOHelper.setAttribute(valueJS, key, ((BaseWidget) value).getOrCreateJsObj()); } else if (value instanceof Record) { JSOHelper.setAttribute(valueJS, key, ((Record) value).getJsObj()); } else { throw new IllegalArgumentException( "Could not convert from " + (value == null ? "null" : value.getClass().getName()) + " to JavaScriptObject" ); } } return valueJS; } /** * Converts the value to a JavaScriptObject. * Note that not all values will convert to JavaScriptObject, becuase * the JSNI glue code will auto-convert them back. This returns * values only when they really do convert to JavaScriptObject (so, * not strings, or numbers). * * @param value Value to convert * @return A JavaScriptObject */ public native static JavaScriptObject convertToJavaScriptObject (Object value) /*-{ // Convert the value and wrap it in a jsArray var jsArray = @com.smartgwt.client.bean.BeanValueType::wrapInJavascriptArray(Ljava/lang/Object;)(value); // We need to make sure that it can actually be returned as a JavaScriptObject var jsValue = jsArray[0]; if ($wnd.isc.isAn.Object(jsValue)) { return jsValue; } else { return null; } }-*/; // --------------------------- // Instance Fields and Methods // --------------------------- // We make the constructor protected, because construction should be // triggered by calling a registerValueType method in a subclass. protected BeanValueType () { } // Generated classes implement in order to return the value type. Note // that this would be the primitive type for primitive types (thus, the // type doesn't entirely correspond with the generic ValueType). protected abstract Class<?> getValueType (); // e.g. return java.lang.Map.class; // Generated classes implement protected abstract boolean isAssignableFrom (Object value); // e.g. return value == null || value instanceof java.lang.Map; // Value to use if supplied with a null value. Subclasses for primitive // types implement with an appropriate value (e.g. 0 or false etc.). protected ValueType nullValue () { return null; } // Note that we could add more convertTo... and convertabilityTo... // methods, depending on what we need. But String is the only obvious need. // How well can a value of this type be converted to a string? Of course, // any type can be converted to a string, but if we have a choice of getters, // then some may be better than others. protected Convertability convertabilityToString () { // At worst, it will be supported, since we can always call toString(). // Subclasses can return something else if they can do better. return Convertability.SUPPORTED; } // A default procedure ... subclasses may implement a better strategy. // Note that we have to use a distinct method name from the static method // convertToString, since the signature is the same. protected String doConvertToString (ValueType value) { return value == null ? null : value.toString(); } // How well can the specified value be converted to this type? Used by // BeanProperty to select among multiple available setters for a property. // We distinguish between EXACT, PREFERRED, SUPPORTED and UNSUPPORTED. In // theory, we could make even finer distinctions, if necessary. public Convertability convertabilityFrom (Object value) { if (value == null) { // With null, we prefer non-primitive setters if available, since // less conversion would be involved. if (getValueType().isPrimitive()) { return Convertability.SUPPORTED; } else { return Convertability.PREFERRED; } } else { // If not null, we don't have any default conversions (since we // can't always convert from String). So, subclasses have to // indicate what they can handle. return Convertability.UNSUPPORTED; } } // This is the main entry point for converting from objects to our value type. public ValueType convertFrom (Object value) { if (value == null) { return nullValue(); } else { throw conversionException(value); } } // Convenience function for conversion exceptions protected IllegalArgumentException conversionException (Object value) { return new IllegalArgumentException( "Could not convert from " + (value == null ? "null" : value.getClass().getName()) + " to " + getValueType().getName() ); } }