Move AutoBean package to com.google.web.bindery.autobean package. http://code.google.com/p/google-web-toolkit/issues/detail?id=6253 Review at http://gwt-code-reviews.appspot.com/1414803 Patch by: bobv Review by: rjrjr git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10007 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml new file mode 100644 index 0000000..88b8981 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
@@ -0,0 +1,24 @@ +<?xml version="1.0" ?> +<!-- + Copyright 2010 Google Inc. Licensed under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with the + License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + or agreed to in writing, software distributed under the License is + distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +--> + +<!-- AutoBean framework --> +<module> + <inherits name="com.google.gwt.core.Core" /> + <inherits name="com.google.gwt.user.User" /> + <source path="gwt/client" /> + <source path="shared" /> + <super-source path="super" /> + <generate-with class="com.google.web.bindery.autobean.gwt.rebind.AutoBeanFactoryGenerator"> + <when-type-assignable class="com.google.web.bindery.autobean.shared.AutoBeanFactory" /> + </generate-with> +</module>
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java new file mode 100644 index 0000000..fc3a0cd --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java
@@ -0,0 +1,95 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.client.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.impl.EnumMap; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides base implementations of AutoBeanFactory methods. + */ +public abstract class AbstractAutoBeanFactory implements AutoBeanFactory, EnumMap { + + protected Map<Enum<?>, String> enumToStringMap; + // This map is almost always one-to-one + protected Map<String, List<Enum<?>>> stringsToEnumsMap; + private JsniCreatorMap creatorMap; + + public <T> AutoBean<T> create(Class<T> clazz) { + maybeInitializeCreatorMap(); + return creatorMap.create(clazz, this); + } + + public <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate) { + maybeInitializeCreatorMap(); + return creatorMap.create(clazz, this, delegate); + } + + /** + * EnumMap support. + */ + public <E extends Enum<?>> E getEnum(Class<E> clazz, String token) { + maybeInitializeEnumMap(); + List<Enum<?>> list = stringsToEnumsMap.get(token); + if (list == null) { + throw new IllegalArgumentException(token); + } + for (Enum<?> e : list) { + if (e.getDeclaringClass().equals(clazz)) { + @SuppressWarnings("unchecked") + E toReturn = (E) e; + return toReturn; + } + } + throw new IllegalArgumentException(clazz.getName()); + } + + /** + * EnumMap support. + */ + public String getToken(Enum<?> e) { + maybeInitializeEnumMap(); + String toReturn = enumToStringMap.get(e); + if (toReturn == null) { + throw new IllegalArgumentException(e.toString()); + } + return toReturn; + } + + protected abstract void initializeCreatorMap(JsniCreatorMap creatorMap); + + protected abstract void initializeEnumMap(); + + private void maybeInitializeCreatorMap() { + if (creatorMap == null) { + creatorMap = JsniCreatorMap.createMap(); + initializeCreatorMap(creatorMap); + } + } + + private void maybeInitializeEnumMap() { + if (enumToStringMap == null) { + enumToStringMap = new HashMap<Enum<?>, String>(); + stringsToEnumsMap = new HashMap<String, List<Enum<?>>>(); + initializeEnumMap(); + } + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java new file mode 100644 index 0000000..08c753c --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java
@@ -0,0 +1,161 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.client.impl; + +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.PropertyContext; +import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean; +import com.google.gwt.core.client.JavaScriptObject; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides base methods for generated implementations of PropertyContext. + */ +public final class ClientPropertyContext implements PropertyContext, CollectionPropertyContext, + MapPropertyContext { + + /** + * A reference to an instance setter method. + */ + public static final class Setter extends JavaScriptObject { + /** + * Create a trivial Setter that calls {@link AbstractAutoBean#setProperty()} + * . + */ + public static native Setter beanSetter(AbstractAutoBean<?> bean, String key) /*-{ + return function(value) { + bean.@com.google.web.bindery.autobean.shared.impl.AbstractAutoBean::setProperty(*)(key, value); + }; + }-*/; + + protected Setter() { + } + + public native void call(Object instance, Object value) /*-{ + this.call(instance, value); + }-*/; + } + + private final Object instance; + private final int[] paramCounts; + private final Class<?>[] paramTypes; + private final Setter setter; + private final Class<?> simpleType; + + public ClientPropertyContext(Object instance, Setter setter, Class<?> type) { + this.instance = instance; + this.setter = setter; + this.simpleType = type; + this.paramTypes = null; + this.paramCounts = null; + } + + public ClientPropertyContext(Object instance, Setter setter, Class<?>[] types, int[] paramCounts) { + this.instance = instance; + this.setter = setter; + this.simpleType = null; + this.paramTypes = types; + this.paramCounts = paramCounts; + + /* + * Verify input arrays of same length and that the total parameter count, + * plus one for the root type, equals the total number of types passed in. + */ + if (ClientPropertyContext.class.desiredAssertionStatus()) { + assert types.length == paramCounts.length : "Length mismatch " + types.length + " != " + + paramCounts.length; + int count = 1; + for (int i = 0, j = paramCounts.length; i < j; i++) { + count += paramCounts[i]; + } + assert count == types.length : "Mismatch in total parameter count " + count + " != " + + types.length; + } + } + + public void accept(ParameterizationVisitor visitor) { + traverse(visitor, 0); + } + + public boolean canSet() { + return setter != null; + } + + public Class<?> getElementType() { + if (paramTypes == null || paramTypes.length < 2) { + return null; + } + if (List.class.equals(paramTypes[0]) || Set.class.equals(paramTypes[0])) { + return paramTypes[1]; + } + return null; + } + + public Class<?> getKeyType() { + if (paramTypes == null || paramTypes.length < 3) { + return null; + } + if (Map.class.equals(paramTypes[0])) { + return paramTypes[1]; + } + return null; + } + + public Class<?> getType() { + return simpleType == null ? paramTypes[0] : simpleType; + } + + public Class<?> getValueType() { + if (paramTypes == null || paramTypes.length < 3) { + return null; + } + if (Map.class.equals(paramTypes[0])) { + return paramTypes[2]; + } + return null; + } + + public void set(Object value) { + setter.call(instance, value); + } + + private int traverse(ParameterizationVisitor visitor, int count) { + if (simpleType != null) { + visitor.visitType(simpleType); + visitor.endVisitType(simpleType); + return 0; + } + + Class<?> type = paramTypes[count]; + int paramCount = paramCounts[count]; + ++count; + if (visitor.visitType(type)) { + for (int i = 0; i < paramCount; i++) { + if (visitor.visitParameter()) { + count = traverse(visitor, count); + } + visitor.endVisitParameter(); + } + } + visitor.endVisitType(type); + return count; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java new file mode 100644 index 0000000..d6e4468 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java
@@ -0,0 +1,71 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.client.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; + +/** + * Used in prod-mode code to create instances of generated AutoBean subtypes via + * JSNI references to their constructor methods. + */ +public final class JsniCreatorMap extends JavaScriptObject { + public static JsniCreatorMap createMap() { + return JavaScriptObject.createObject().cast(); + } + + /* + * Structure is a string map of class literal names to the no-arg and one-arg + * constructors of a generated AutoBean subtype. + */ + protected JsniCreatorMap() { + } + + public void add(Class<?> clazz, JsArray<JavaScriptObject> constructors) { + assert constructors.length() == 2 : "Expecting two constructor references"; + set(clazz.getName(), constructors); + } + + public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory) { + JsArray<JavaScriptObject> arr = get(clazz.getName()); + if (arr != null && arr.get(0) != null) { + return invoke(arr.get(0), factory, null); + } + return null; + } + + public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory, Object delegate) { + JsArray<JavaScriptObject> arr = get(clazz.getName()); + if (arr != null) { + assert arr.get(1) != null : "No delegate-based constructor"; + return invoke(arr.get(1), factory, delegate); + } + return null; + } + + private native JsArray<JavaScriptObject> get(String key) /*-{ + return this[key]; + }-*/; + + private native <T> AutoBean<T> invoke(JavaScriptObject fn, Object arg1, Object arg2)/*-{ + return fn(arg1, arg2); + }-*/; + + private native void set(String key, JsArray<JavaScriptObject> arr) /*-{ + this[key] = arr; + }-*/; +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java new file mode 100644 index 0000000..33eba24 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java
@@ -0,0 +1,343 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.client.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.HasSplittable; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; +import com.google.gwt.core.client.GwtScriptOnly; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsonUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject. + * <p> + * A string value represented by a JsoSplittable can't use the string object + * directly, since {@code String.prototype} is overridden, so instead a + * temporary wrapper object is used to encapsulate the string data. + */ +@GwtScriptOnly +public final class JsoSplittable extends JavaScriptObject implements Splittable, HasSplittable { + private static boolean stringifyFastTested; + private static boolean stringifyFastResult; + + public static native JsoSplittable create() /*-{ + return {}; + }-*/; + + public static Splittable create(boolean value) { + return create0(value); + } + + public static Splittable create(double value) { + return create0(value); + } + + public static Splittable create(String value) { + return create0(value); + } + + public static native JsoSplittable createIndexed() /*-{ + return []; + }-*/; + + public static native JsoSplittable nullValue() /*-{ + return null; + }-*/; + + private static native Splittable create0(boolean object) /*-{ + return Boolean(object); + }-*/; + + private static native Splittable create0(double object) /*-{ + return Number(object); + }-*/; + + private static native Splittable create0(String object) /*-{ + return { + __s : object + }; + }-*/; + + private static native boolean isUnwrappedString(JavaScriptObject obj) /*-{ + return Object.prototype.toString.call(obj) == '[object String]'; + }-*/; + + private static boolean stringifyFastSupported() { + if (stringifyFastTested) { + return stringifyFastResult; + } + stringifyFastTested = true; + return stringifyFastResult = stringifyFastSupported0(); + } + + /** + * Test that the JSON api is available and that it does not add function + * objects to the output. The test for function objects is for old versions of + * Safari. + */ + private static native boolean stringifyFastSupported0() /*-{ + return $wnd.JSON && $wnd.JSON.stringify && $wnd.JSON.stringify({ + b : function() { + } + }) == '{}'; + }-*/; + + protected JsoSplittable() { + }; + + public native boolean asBoolean() /*-{ + return this == true; + }-*/; + + public native double asNumber() /*-{ + return Number(this); + }-*/; + + public void assign(Splittable parent, int index) { + if (isString()) { + assign0(parent, index, asString()); + } else { + assign0(parent, index, this); + } + } + + public void assign(Splittable parent, String index) { + if (isString()) { + assign0(parent, index, asString()); + } else { + assign0(parent, index, this); + } + } + + public native String asString() /*-{ + return this.__s; + }-*/; + + public Splittable deepCopy() { + return StringQuoter.split(getPayload()); + } + + public JsoSplittable get(int index) { + return getRaw(index); + } + + public JsoSplittable get(String key) { + return getRaw(key); + } + + public String getPayload() { + if (isString()) { + return JsonUtils.escapeValue(asString()); + } + if (stringifyFastSupported()) { + return stringifyFast(); + } + return stringifySlow(); + } + + public List<String> getPropertyKeys() { + List<String> toReturn = new ArrayList<String>(); + getPropertyKeys0(toReturn); + return Collections.unmodifiableList(toReturn); + } + + public native Object getReified(String key) /*-{ + return this.__reified && this.__reified[':' + key]; + }-*/; + + public Splittable getSplittable() { + return this; + } + + public native boolean isBoolean() /*-{ + return Object.prototype.toString.call(this) == '[object Boolean]'; + }-*/; + + public native boolean isFunction() /*-{ + return Object.prototype.toString.call(this) == '[object Function]'; + }-*/; + + public native boolean isIndexed() /*-{ + return Object.prototype.toString.call(this) == '[object Array]'; + }-*/; + + public boolean isKeyed() { + return this != NULL && !isString() && !isIndexed() && !isFunction(); + } + + public native boolean isNull(int index) /*-{ + return this[index] == null; + }-*/; + + public native boolean isNull(String key) /*-{ + return this[key] == null; + }-*/; + + public native boolean isNumber() /*-{ + return Object.prototype.toString.call(this) == '[object Number]'; + }-*/; + + public native boolean isReified(String key) /*-{ + return !!(this.__reified && this.__reified.hasOwnProperty(':' + key)); + }-*/; + + /** + * Returns whether or not the current object is a string-carrier. + */ + public native boolean isString() /*-{ + return this && this.__s != null; + }-*/; + + public native boolean isUndefined(String key) /*-{ + return this[key] === undefined; + }-*/; + + public native void setReified(String key, Object object) /*-{ + // Use a function object so native JSON.stringify will ignore + (this.__reified || (this.__reified = function() { + }))[':' + key] = object; + }-*/; + + public native void setSize(int size) /*-{ + this.length = size; + }-*/; + + public native int size() /*-{ + return this.length; + }-*/; + + private native void assign0(Splittable parent, int index, Splittable value) /*-{ + parent[index] = value; + }-*/; + + private native void assign0(Splittable parent, int index, String value) /*-{ + parent[index] = value; + }-*/; + + private native void assign0(Splittable parent, String index, Splittable value) /*-{ + parent[index] = value; + }-*/; + + private native void assign0(Splittable parent, String index, String value) /*-{ + parent[index] = value; + }-*/; + + private native void getPropertyKeys0(List<String> list) /*-{ + for (key in this) { + if (this.hasOwnProperty(key)) { + list.@java.util.List::add(Ljava/lang/Object;)(key); + } + } + }-*/; + + private native JsoSplittable getRaw(int index) /*-{ + _ = this[index]; + if (_ == null) { + return null; + } + if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) { + return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_); + } + return Object(_); + }-*/; + + private native JsoSplittable getRaw(String index) /*-{ + _ = this[index]; + if (_ == null) { + return null; + } + if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) { + return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_); + } + return Object(_); + }-*/; + + /** + * The test for {@code $H} removes the key in the emitted JSON, however making + * a similar test for {@code __reified} causes the key to be emitted with an + * explicit {@code null} value. + */ + private native String stringifyFast() /*-{ + return $wnd.JSON.stringify(this, function(key, value) { + if (key == "$H") { + return; + } + return value; + }); + }-*/; + + private String stringifySlow() { + StringBuilder sb = new StringBuilder(); + stringifySlow(sb); + return sb.toString(); + } + + private void stringifySlow(StringBuilder sb) { + if (this == NULL) { + sb.append("null"); + return; + } + if (isBoolean()) { + sb.append(asBoolean()); + return; + } + if (isNumber()) { + sb.append(asNumber()); + return; + } + if (isString()) { + sb.append(JsonUtils.escapeValue(asString())); + return; + } + if (isIndexed()) { + sb.append("["); + for (int i = 0, j = size(); i < j; i++) { + if (i > 0) { + sb.append(","); + } + get(i).stringifySlow(sb); + } + sb.append("]"); + return; + } + + sb.append("{"); + boolean needsComma = false; + for (String key : getPropertyKeys()) { + if (needsComma) { + sb.append(","); + } else { + needsComma = true; + } + JsoSplittable value = get(key); + if (!value.isFunction()) { + if ("$H".equals(key)) { + // Ignore hashcode + continue; + } + sb.append(JsonUtils.escapeValue(key)); + sb.append(":"); + value.stringifySlow(sb); + } + } + sb.append("}"); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java new file mode 100644 index 0000000..0ec39b4 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java
@@ -0,0 +1,726 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind; + +import com.google.web.bindery.autobean.gwt.client.impl.AbstractAutoBeanFactory; +import com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext; +import com.google.web.bindery.autobean.gwt.client.impl.JsniCreatorMap; +import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryMethod; +import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryModel; +import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanMethod; +import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanType; +import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean; +import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.OneShotContext; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.impl.WeakMapping; +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JEnumConstant; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameter; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.editor.rebind.model.ModelUtils; +import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; +import com.google.gwt.user.rebind.SourceWriter; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Generates implementations of AutoBeanFactory. + */ +public class AutoBeanFactoryGenerator extends Generator { + + private GeneratorContext context; + private String simpleSourceName; + private TreeLogger logger; + private AutoBeanFactoryModel model; + + @Override + public String generate(TreeLogger logger, GeneratorContext context, String typeName) + throws UnableToCompleteException { + this.context = context; + this.logger = logger; + + TypeOracle oracle = context.getTypeOracle(); + JClassType toGenerate = oracle.findType(typeName).isInterface(); + if (toGenerate == null) { + logger.log(TreeLogger.ERROR, typeName + " is not an interface type"); + throw new UnableToCompleteException(); + } + + String packageName = toGenerate.getPackage().getName(); + simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl"; + PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName); + if (pw == null) { + return packageName + "." + simpleSourceName; + } + + model = new AutoBeanFactoryModel(logger, toGenerate); + + ClassSourceFileComposerFactory factory = + new ClassSourceFileComposerFactory(packageName, simpleSourceName); + factory.setSuperclass(AbstractAutoBeanFactory.class.getCanonicalName()); + factory.addImplementedInterface(typeName); + SourceWriter sw = factory.createSourceWriter(context, pw); + for (AutoBeanType type : model.getAllTypes()) { + writeAutoBean(type); + } + writeDynamicMethods(sw); + writeEnumSetup(sw); + writeMethods(sw); + sw.commit(logger); + + return factory.getCreatedClassName(); + } + + /** + * Flattens a parameterized type into a simple list of types. + */ + private void createTypeList(List<JType> accumulator, JType type) { + accumulator.add(type); + JParameterizedType hasParams = type.isParameterized(); + if (hasParams != null) { + for (JClassType arg : hasParams.getTypeArgs()) { + createTypeList(accumulator, arg); + } + } + } + + private String getBaseMethodDeclaration(JMethod jmethod) { + // Foo foo, Bar bar, Baz baz + StringBuilder parameters = new StringBuilder(); + for (JParameter param : jmethod.getParameters()) { + parameters.append(",").append(ModelUtils.getQualifiedBaseSourceName(param.getType())).append( + " ").append(param.getName()); + } + if (parameters.length() > 0) { + parameters = parameters.deleteCharAt(0); + } + + StringBuilder throwsDeclaration = new StringBuilder(); + if (jmethod.getThrows().length > 0) { + for (JType thrown : jmethod.getThrows()) { + throwsDeclaration.append(". ").append(ModelUtils.getQualifiedBaseSourceName(thrown)); + } + throwsDeclaration.deleteCharAt(0); + throwsDeclaration.insert(0, "throws "); + } + String returnName = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType()); + assert !returnName.contains("extends"); + return String.format("%s %s(%s) %s", returnName, jmethod.getName(), parameters, + throwsDeclaration); + } + + /** + * Used by {@link #writeShim} to avoid duplicate declarations of Object + * methods. + */ + private boolean isObjectMethodImplementedByShim(JMethod jmethod) { + String methodName = jmethod.getName(); + JParameter[] parameters = jmethod.getParameters(); + switch (parameters.length) { + case 0: + return methodName.equals("hashCode") || methodName.equals("toString"); + case 1: + return methodName.equals("equals") + && parameters[0].getType().equals(context.getTypeOracle().getJavaLangObject()); + } + return false; + } + + private void writeAutoBean(AutoBeanType type) throws UnableToCompleteException { + PrintWriter pw = context.tryCreate(logger, type.getPackageNome(), type.getSimpleSourceName()); + if (pw == null) { + // Previously-created + return; + } + + ClassSourceFileComposerFactory factory = + new ClassSourceFileComposerFactory(type.getPackageNome(), type.getSimpleSourceName()); + factory.setSuperclass(AbstractAutoBean.class.getCanonicalName() + "<" + + type.getPeerType().getQualifiedSourceName() + ">"); + SourceWriter sw = factory.createSourceWriter(context, pw); + + writeShim(sw, type); + + // Instance initializer code to set the shim's association + sw.println("{ %s.set(shim, %s.class.getName(), this); }", WeakMapping.class.getCanonicalName(), + AutoBean.class.getCanonicalName()); + + // Only simple wrappers have a default constructor + if (type.isSimpleBean()) { + // public FooIntfAutoBean(AutoBeanFactory factory) {} + sw.println("public %s(%s factory) {super(factory);}", type.getSimpleSourceName(), + AutoBeanFactory.class.getCanonicalName()); + } + + // Wrapping constructor + // public FooIntfAutoBean(AutoBeanFactory factory, FooIntfo wrapped) { + sw.println("public %s(%s factory, %s wrapped) {", type.getSimpleSourceName(), + AutoBeanFactory.class.getCanonicalName(), type.getPeerType().getQualifiedSourceName()); + sw.indentln("super(wrapped, factory);"); + sw.println("}"); + + // public FooIntf as() {return shim;} + sw.println("public %s as() {return shim;}", type.getPeerType().getQualifiedSourceName()); + + // public Class<Intf> getType() {return Intf.class;} + sw.println("public Class<%1$s> getType() {return %1$s.class;}", ModelUtils.ensureBaseType( + type.getPeerType()).getQualifiedSourceName()); + + if (type.isSimpleBean()) { + writeCreateSimpleBean(sw, type); + } + writeTraversal(sw, type); + sw.commit(logger); + } + + /** + * For interfaces that consist of nothing more than getters and setters, + * create a map-based implementation that will allow the AutoBean's internal + * state to be easily consumed. + */ + private void writeCreateSimpleBean(SourceWriter sw, AutoBeanType type) { + sw.println("@Override protected %s createSimplePeer() {", type.getPeerType() + .getQualifiedSourceName()); + sw.indent(); + // return new FooIntf() { + sw.println("return new %s() {", type.getPeerType().getQualifiedSourceName()); + sw.indent(); + sw.println("private final %s data = %s.this.data;", Splittable.class.getCanonicalName(), type + .getQualifiedSourceName()); + for (AutoBeanMethod method : type.getMethods()) { + JMethod jmethod = method.getMethod(); + JType returnType = jmethod.getReturnType(); + sw.println("public %s {", getBaseMethodDeclaration(jmethod)); + sw.indent(); + switch (method.getAction()) { + case GET: { + String castType; + if (returnType.isPrimitive() != null) { + castType = returnType.isPrimitive().getQualifiedBoxedSourceName(); + // Boolean toReturn = getOrReify("foo"); + sw.println("%s toReturn = getOrReify(\"%s\");", castType, method.getPropertyName()); + // return toReturn == null ? false : toReturn; + sw.println("return toReturn == null ? %s : toReturn;", returnType.isPrimitive() + .getUninitializedFieldExpression()); + } else if (returnType.equals(context.getTypeOracle().findType( + Splittable.class.getCanonicalName()))) { + sw.println("return data.isNull(\"%1$s\") ? null : data.get(\"%1$s\");", method + .getPropertyName()); + } else { + // return (ReturnType) values.getOrReify(\"foo\"); + castType = ModelUtils.getQualifiedBaseSourceName(returnType); + sw.println("return (%s) getOrReify(\"%s\");", castType, method.getPropertyName()); + } + } + break; + case SET: + case SET_BUILDER: { + JParameter param = jmethod.getParameters()[0]; + // setProperty("foo", parameter); + sw.println("setProperty(\"%s\", %s);", method.getPropertyName(), param.getName()); + if (JBeanMethod.SET_BUILDER.equals(method.getAction())) { + sw.println("return this;"); + } + break; + } + case CALL: + // return com.example.Owner.staticMethod(Outer.this, param, + // param); + JMethod staticImpl = method.getStaticImpl(); + if (!returnType.equals(JPrimitiveType.VOID)) { + sw.print("return "); + } + sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(), + staticImpl.getName(), type.getSimpleSourceName()); + for (JParameter param : jmethod.getParameters()) { + sw.print(", %s", param.getName()); + } + sw.println(");"); + break; + default: + throw new RuntimeException(); + } + sw.outdent(); + sw.println("}"); + } + sw.outdent(); + sw.println("};"); + sw.outdent(); + sw.println("}"); + } + + /** + * Write an instance initializer block to populate the creators map. + */ + private void writeDynamicMethods(SourceWriter sw) { + List<JClassType> privatePeers = new ArrayList<JClassType>(); + sw.println("@Override protected void initializeCreatorMap(%s map) {", JsniCreatorMap.class + .getCanonicalName()); + sw.indent(); + for (AutoBeanType type : model.getAllTypes()) { + if (type.isNoWrap()) { + continue; + } + String classLiteralAccessor; + JClassType peer = type.getPeerType(); + String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName(); + if (peer.isPublic()) { + classLiteralAccessor = peerName + ".class"; + } else { + privatePeers.add(peer); + classLiteralAccessor = "classLit_" + peerName.replace('.', '_') + "()"; + } + // map.add(Foo.class, getConstructors_com_foo_Bar()); + sw.println("map.add(%s, getConstructors_%s());", classLiteralAccessor, peerName.replace('.', + '_')); + } + sw.outdent(); + sw.println("}"); + + /* + * Create a native method for each peer type that isn't public since Java + * class literal references are scoped. + */ + for (JClassType peer : privatePeers) { + String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName(); + sw.println("private native Class<?> classLit_%s() /*-{return @%s::class;}-*/;", peerName + .replace('.', '_'), peerName); + } + + /* + * Create a method that returns an array containing references to the + * constructors. + */ + String factoryJNIName = + context.getTypeOracle().findType(AutoBeanFactory.class.getCanonicalName()) + .getJNISignature(); + for (AutoBeanType type : model.getAllTypes()) { + String peerName = ModelUtils.ensureBaseType(type.getPeerType()).getQualifiedSourceName(); + String peerJNIName = ModelUtils.ensureBaseType(type.getPeerType()).getJNISignature(); + /*- + * private native JsArray<JSO> getConstructors_com_foo_Bar() { + * return [ + * BarProxyImpl::new(ABFactory), + * BarProxyImpl::new(ABFactory, DelegateType) + * ]; + * } + */ + sw.println("private native %s<%s> getConstructors_%s() /*-{", JsArray.class + .getCanonicalName(), JavaScriptObject.class.getCanonicalName(), peerName + .replace('.', '_')); + sw.indent(); + sw.println("return ["); + if (type.isSimpleBean()) { + sw.indentln("@%s::new(%s),", type.getQualifiedSourceName(), factoryJNIName); + } else { + sw.indentln(","); + } + sw.indentln("@%s::new(%s%s)", type.getQualifiedSourceName(), factoryJNIName, peerJNIName); + sw.println("];"); + sw.outdent(); + sw.println("}-*/;"); + } + } + + private void writeEnumSetup(SourceWriter sw) { + // Make the deobfuscation model + Map<String, List<JEnumConstant>> map = new HashMap<String, List<JEnumConstant>>(); + for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) { + List<JEnumConstant> list = map.get(entry.getValue()); + if (list == null) { + list = new ArrayList<JEnumConstant>(); + map.put(entry.getValue(), list); + } + list.add(entry.getKey()); + } + + sw.println("@Override protected void initializeEnumMap() {"); + sw.indent(); + for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) { + // enumToStringMap.put(Enum.FOO, "FOO"); + sw.println("enumToStringMap.put(%s.%s, \"%s\");", entry.getKey().getEnclosingType() + .getQualifiedSourceName(), entry.getKey().getName(), entry.getValue()); + } + for (Map.Entry<String, List<JEnumConstant>> entry : map.entrySet()) { + String listExpr; + if (entry.getValue().size() == 1) { + JEnumConstant e = entry.getValue().get(0); + // Collections.singletonList(Enum.FOO) + listExpr = + String.format("%s.<%s<?>> singletonList(%s.%s)", Collections.class.getCanonicalName(), + Enum.class.getCanonicalName(), e.getEnclosingType().getQualifiedSourceName(), e + .getName()); + } else { + // Arrays.asList(Enum.FOO, OtherEnum.FOO, ThirdEnum,FOO) + StringBuilder sb = new StringBuilder(); + boolean needsComma = false; + sb.append(String.format("%s.<%s<?>> asList(", Arrays.class.getCanonicalName(), Enum.class + .getCanonicalName())); + for (JEnumConstant e : entry.getValue()) { + if (needsComma) { + sb.append(","); + } + needsComma = true; + sb.append(e.getEnclosingType().getQualifiedSourceName()).append(".").append(e.getName()); + } + sb.append(")"); + listExpr = sb.toString(); + } + sw.println("stringsToEnumsMap.put(\"%s\", %s);", entry.getKey(), listExpr); + } + sw.outdent(); + sw.println("}"); + } + + private void writeMethods(SourceWriter sw) throws UnableToCompleteException { + for (AutoBeanFactoryMethod method : model.getMethods()) { + AutoBeanType autoBeanType = method.getAutoBeanType(); + // public AutoBean<Foo> foo(FooSubtype wrapped) { + sw.println("public %s %s(%s) {", method.getReturnType().getQualifiedSourceName(), method + .getName(), method.isWrapper() + ? (method.getWrappedType().getQualifiedSourceName() + " wrapped") : ""); + if (method.isWrapper()) { + sw.indent(); + // AutoBean<Foo> toReturn = AutoBeanUtils.getAutoBean(wrapped); + sw.println("%s toReturn = %s.getAutoBean(wrapped);", method.getReturnType() + .getParameterizedQualifiedSourceName(), AutoBeanUtils.class.getCanonicalName()); + sw.println("if (toReturn != null) {return toReturn;}"); + // return new FooAutoBean(Factory.this, wrapped); + sw.println("return new %s(%s.this, wrapped);", autoBeanType.getQualifiedSourceName(), + simpleSourceName); + sw.outdent(); + } else { + // return new FooAutoBean(Factory.this); + sw.indentln("return new %s(%s.this);", autoBeanType.getQualifiedSourceName(), + simpleSourceName); + } + sw.println("}"); + } + } + + private void writeReturnWrapper(SourceWriter sw, AutoBeanType type, AutoBeanMethod method) + throws UnableToCompleteException { + if (!method.isValueType() && !method.isNoWrap()) { + JMethod jmethod = method.getMethod(); + JClassType returnClass = jmethod.getReturnType().isClassOrInterface(); + AutoBeanType peer = model.getPeer(returnClass); + + sw.println("if (toReturn != null) {"); + sw.indent(); + sw.println("if (%s.this.isWrapped(toReturn)) {", type.getSimpleSourceName()); + sw.indentln("toReturn = %s.this.getFromWrapper(toReturn);", type.getSimpleSourceName()); + sw.println("} else {"); + sw.indent(); + if (peer != null) { + // toReturn = new FooAutoBean(getFactory(), toReturn).as(); + sw.println("toReturn = new %s(getFactory(), toReturn).as();", peer.getQualifiedSourceName()); + } + sw.outdent(); + sw.println("}"); + + sw.outdent(); + sw.println("}"); + } + // Allow return values to be intercepted + JMethod interceptor = type.getInterceptor(); + if (interceptor != null) { + // toReturn = FooCategory.__intercept(FooAutoBean.this, toReturn); + sw.println("toReturn = %s.%s(%s.this, toReturn);", interceptor.getEnclosingType() + .getQualifiedSourceName(), interceptor.getName(), type.getSimpleSourceName()); + } + } + + /** + * Create the shim instance of the AutoBean's peer type that lets us hijack + * the method calls. Using a shim type, as opposed to making the AutoBean + * implement the peer type directly, means that there can't be any conflicts + * between methods in the peer type and methods declared in the AutoBean + * implementation. + */ + private void writeShim(SourceWriter sw, AutoBeanType type) throws UnableToCompleteException { + // private final FooImpl shim = new FooImpl() { + sw.println("private final %1$s shim = new %1$s() {", type.getPeerType() + .getQualifiedSourceName()); + sw.indent(); + for (AutoBeanMethod method : type.getMethods()) { + JMethod jmethod = method.getMethod(); + String methodName = jmethod.getName(); + JParameter[] parameters = jmethod.getParameters(); + if (isObjectMethodImplementedByShim(jmethod)) { + // Skip any methods declared on Object, since we have special handling + continue; + } + + // foo, bar, baz + StringBuilder arguments = new StringBuilder(); + { + for (JParameter param : parameters) { + arguments.append(",").append(param.getName()); + } + if (arguments.length() > 0) { + arguments = arguments.deleteCharAt(0); + } + } + + sw.println("public %s {", getBaseMethodDeclaration(jmethod)); + sw.indent(); + + switch (method.getAction()) { + case GET: + /* + * The getter call will ensure that any non-value return type is + * definitely wrapped by an AutoBean instance. + */ + sw.println("%s toReturn = %s.this.getWrapped().%s();", ModelUtils + .getQualifiedBaseSourceName(jmethod.getReturnType()), type.getSimpleSourceName(), + methodName); + + // Non-value types might need to be wrapped + writeReturnWrapper(sw, type, method); + sw.println("return toReturn;"); + break; + case SET: + case SET_BUILDER: + // getWrapped().setFoo(foo); + sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName, + parameters[0].getName()); + // FooAutoBean.this.set("setFoo", foo); + sw.println("%s.this.set(\"%s\", %s);", type.getSimpleSourceName(), methodName, + parameters[0].getName()); + if (JBeanMethod.SET_BUILDER.equals(method.getAction())) { + sw.println("return this;"); + } + break; + case CALL: + // XXX How should freezing and calls work together? + // sw.println("checkFrozen();"); + if (JPrimitiveType.VOID.equals(jmethod.getReturnType())) { + // getWrapped().doFoo(params); + sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName, + arguments); + // call("doFoo", null, params); + sw.println("%s.this.call(\"%s\", null%s %s);", type.getSimpleSourceName(), methodName, + arguments.length() > 0 ? "," : "", arguments); + } else { + // Type toReturn = getWrapped().doFoo(params); + sw.println("%s toReturn = %s.this.getWrapped().%s(%s);", ModelUtils.ensureBaseType( + jmethod.getReturnType()).getQualifiedSourceName(), type.getSimpleSourceName(), + methodName, arguments); + // Non-value types might need to be wrapped + writeReturnWrapper(sw, type, method); + // call("doFoo", toReturn, params); + sw.println("%s.this.call(\"%s\", toReturn%s %s);", type.getSimpleSourceName(), + methodName, arguments.length() > 0 ? "," : "", arguments); + sw.println("return toReturn;"); + } + break; + default: + throw new RuntimeException(); + } + sw.outdent(); + sw.println("}"); + } + + // Delegate equals(), hashCode(), and toString() to wrapped object + sw.println("@Override public boolean equals(Object o) {"); + sw.indentln("return this == o || getWrapped().equals(o);"); + sw.println("}"); + sw.println("@Override public int hashCode() {"); + sw.indentln("return getWrapped().hashCode();"); + sw.println("}"); + sw.println("@Override public String toString() {"); + sw.indentln("return getWrapped().toString();"); + sw.println("}"); + + // End of shim field declaration and assignment + sw.outdent(); + sw.println("};"); + } + + /** + * Generate traversal logic. + */ + private void writeTraversal(SourceWriter sw, AutoBeanType type) { + List<AutoBeanMethod> referencedSetters = new ArrayList<AutoBeanMethod>(); + sw.println("@Override protected void traverseProperties(%s visitor, %s ctx) {", + AutoBeanVisitor.class.getCanonicalName(), OneShotContext.class.getCanonicalName()); + sw.indent(); + sw.println("%s bean;", AbstractAutoBean.class.getCanonicalName()); + sw.println("Object value;"); + sw.println("%s propertyContext;", ClientPropertyContext.class.getCanonicalName()); + // Local variable ref cleans up emitted js + sw.println("%1$s as = as();", type.getPeerType().getQualifiedSourceName()); + + for (AutoBeanMethod method : type.getMethods()) { + if (!method.getAction().equals(JBeanMethod.GET)) { + continue; + } + + AutoBeanMethod setter = null; + // If it's not a simple bean type, try to find a real setter method + if (!type.isSimpleBean()) { + for (AutoBeanMethod maybeSetter : type.getMethods()) { + boolean isASetter = + maybeSetter.getAction().equals(JBeanMethod.SET) + || maybeSetter.getAction().equals(JBeanMethod.SET_BUILDER); + if (isASetter && maybeSetter.getPropertyName().equals(method.getPropertyName())) { + setter = maybeSetter; + break; + } + } + } + + // The type of property influences the visitation + String valueExpression = + String.format("bean = (%1$s) %2$s.getAutoBean(as.%3$s());", AbstractAutoBean.class + .getCanonicalName(), AutoBeanUtils.class.getCanonicalName(), method.getMethod() + .getName()); + String visitMethod; + String visitVariable = "bean"; + if (method.isCollection()) { + visitMethod = "Collection"; + } else if (method.isMap()) { + visitMethod = "Map"; + } else if (method.isValueType()) { + valueExpression = String.format("value = as.%s();", method.getMethod().getName()); + visitMethod = "Value"; + visitVariable = "value"; + } else { + visitMethod = "Reference"; + } + sw.println(valueExpression); + + // Map<List<Foo>, Bar> --> Map, List, Foo, Bar + List<JType> typeList = new ArrayList<JType>(); + createTypeList(typeList, method.getMethod().getReturnType()); + assert typeList.size() > 0; + + /* + * Make the PropertyContext that lets us call the setter. We allow + * multiple methods to be bound to the same property (e.g. to allow JSON + * payloads to be interpreted as different types). The leading underscore + * allows purely numeric property names, which are valid JSON map keys. + */ + // propertyContext = new CPContext(.....); + sw.println("propertyContext = new %s(", ClientPropertyContext.class.getCanonicalName()); + sw.indent(); + // The instance on which the context is nominally operating + sw.println("as,"); + // Produce a JSNI reference to a setter function to call + { + if (setter != null) { + // Call a method that returns a JSNI reference to the method to call + // setFooMethodReference(), + sw.println("%sMethodReference(as),", setter.getMethod().getName()); + referencedSetters.add(setter); + } else { + // Create a function that will update the values map + // CPContext.beanSetter(FooBeanImpl.this, "foo"); + sw.println("%s.beanSetter(%s.this, \"%s\"),", ClientPropertyContext.Setter.class + .getCanonicalName(), type.getSimpleSourceName(), method.getPropertyName()); + } + } + if (typeList.size() == 1) { + sw.println("%s.class", ModelUtils.ensureBaseType(typeList.get(0)).getQualifiedSourceName()); + } else { + // Produce the array of parameter types + sw.print("new Class<?>[] {"); + boolean first = true; + for (JType lit : typeList) { + if (first) { + first = false; + } else { + sw.print(", "); + } + sw.print("%s.class", ModelUtils.ensureBaseType(lit).getQualifiedSourceName()); + } + sw.println("},"); + + // Produce the array of parameter counts + sw.print("new int[] {"); + first = true; + for (JType lit : typeList) { + if (first) { + first = false; + } else { + sw.print(", "); + } + JParameterizedType hasParam = lit.isParameterized(); + if (hasParam == null) { + sw.print("0"); + } else { + sw.print(String.valueOf(hasParam.getTypeArgs().length)); + } + } + sw.println("}"); + } + sw.outdent(); + sw.println(");"); + + // if (visitor.visitReferenceProperty("foo", value, ctx)) + sw.println("if (visitor.visit%sProperty(\"%s\", %s, propertyContext)) {", visitMethod, method + .getPropertyName(), visitVariable); + if (!method.isValueType()) { + // Cycle-detection in AbstractAutoBean.traverse + sw.indentln("if (bean != null) { bean.traverse(visitor, ctx); }"); + } + sw.println("}"); + // visitor.endVisitorReferenceProperty("foo", value, ctx); + sw.println("visitor.endVisit%sProperty(\"%s\", %s, propertyContext);", visitMethod, method + .getPropertyName(), visitVariable); + } + sw.outdent(); + sw.println("}"); + + for (AutoBeanMethod method : referencedSetters) { + JMethod jmethod = method.getMethod(); + assert jmethod.getParameters().length == 1; + + /*- + * Setter setFooMethodReference(Object instance) { + * return instance.@com.example.Blah::setFoo(Lcom/example/Foo;); + * } + */ + sw.println("public static native %s %sMethodReference(Object instance) /*-{", + ClientPropertyContext.Setter.class.getCanonicalName(), jmethod.getName()); + sw.indentln("return instance.@%s::%s(%s);", jmethod.getEnclosingType() + .getQualifiedSourceName(), jmethod.getName(), jmethod.getParameters()[0].getType() + .getJNISignature()); + sw.println("}-*/;"); + } + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java new file mode 100644 index 0000000..4069a57 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java
@@ -0,0 +1,99 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind.model; + +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; + +/** + * Represents a single method in an AutoBeanFactory interface. + */ +public class AutoBeanFactoryMethod { + /** + * Builds AutoBeanFactoryMethods. + */ + public static class Builder { + private AutoBeanFactoryMethod toReturn = new AutoBeanFactoryMethod(); + + public AutoBeanFactoryMethod build() { + try { + return toReturn; + } finally { + toReturn = null; + } + } + + public void setAutoBeanType(AutoBeanType type) { + toReturn.autoBeanType = type; + } + + public void setMethod(JMethod method) { + setName(method.getName()); + setReturnType(method.getReturnType().isClassOrInterface()); + if (method.getParameters().length == 1) { + setWrappedType(method.getParameters()[0].getType().isClassOrInterface()); + } + } + + public void setName(String name) { + toReturn.name = name; + } + + public void setReturnType(JClassType returnType) { + toReturn.returnType = returnType; + } + + public void setWrappedType(JClassType wrapped) { + toReturn.wrappedType = wrapped; + } + } + + private AutoBeanType autoBeanType; + private JClassType wrappedType; + private String name; + private JClassType returnType; + + private AutoBeanFactoryMethod() { + } + + public AutoBeanType getAutoBeanType() { + return autoBeanType; + } + + public String getName() { + return name; + } + + public JClassType getReturnType() { + return returnType; + } + + public JClassType getWrappedType() { + return wrappedType; + } + + public boolean isWrapper() { + return wrappedType != null; + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + return name; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java new file mode 100644 index 0000000..6122dfc --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java
@@ -0,0 +1,464 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind.model; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap; +import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JEnumConstant; +import com.google.gwt.core.ext.typeinfo.JEnumType; +import com.google.gwt.core.ext.typeinfo.JGenericType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JParameter; +import com.google.gwt.core.ext.typeinfo.JParameterizedType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.editor.rebind.model.ModelUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + */ +public class AutoBeanFactoryModel { + private static final JType[] EMPTY_JTYPE = new JType[0]; + + private final JGenericType autoBeanInterface; + private final JClassType autoBeanFactoryInterface; + private final Map<JEnumConstant, String> allEnumConstants = new LinkedHashMap<JEnumConstant, String>(); + private final List<JClassType> categoryTypes; + private final List<JClassType> noWrapTypes; + private final TreeLogger logger; + private final List<AutoBeanFactoryMethod> methods = new ArrayList<AutoBeanFactoryMethod>(); + private final List<JMethod> objectMethods; + private final TypeOracle oracle; + private final Map<JClassType, AutoBeanType> peers = new LinkedHashMap<JClassType, AutoBeanType>(); + private boolean poisoned; + + /** + * Accumulates bean types that are reachable through the type graph. + */ + private Set<JClassType> toCalculate = new LinkedHashSet<JClassType>(); + + public AutoBeanFactoryModel(TreeLogger logger, JClassType factoryType) + throws UnableToCompleteException { + this.logger = logger; + oracle = factoryType.getOracle(); + autoBeanInterface = oracle.findType(AutoBean.class.getCanonicalName()).isGenericType(); + autoBeanFactoryInterface = oracle.findType( + AutoBeanFactory.class.getCanonicalName()).isInterface(); + + /* + * We want to allow the user to override some of the useful Object methods, + * so we'll extract them here. + */ + JClassType objectType = oracle.getJavaLangObject(); + objectMethods = Arrays.asList( + objectType.findMethod("equals", new JType[] {objectType}), + objectType.findMethod("hashCode", EMPTY_JTYPE), + objectType.findMethod("toString", EMPTY_JTYPE)); + + // Process annotations + { + Category categoryAnnotation = factoryType.getAnnotation(Category.class); + if (categoryAnnotation != null) { + categoryTypes = new ArrayList<JClassType>( + categoryAnnotation.value().length); + processClassArrayAnnotation(categoryAnnotation.value(), categoryTypes); + } else { + categoryTypes = null; + } + + noWrapTypes = new ArrayList<JClassType>(); + noWrapTypes.add(oracle.findType(AutoBean.class.getCanonicalName())); + NoWrap noWrapAnnotation = factoryType.getAnnotation(NoWrap.class); + if (noWrapAnnotation != null) { + processClassArrayAnnotation(noWrapAnnotation.value(), noWrapTypes); + } + + ExtraEnums extraEnumsAnnotation = factoryType.getAnnotation(ExtraEnums.class); + if (extraEnumsAnnotation != null) { + for (Class<?> clazz : extraEnumsAnnotation.value()) { + JEnumType asEnum = oracle.findType(clazz.getCanonicalName()).isEnum(); + assert asEnum != null; + for (JEnumConstant value : asEnum.getEnumConstants()) { + allEnumConstants.put(value, AutoBeanMethod.getEnumName(value)); + } + } + } + } + + for (JMethod method : factoryType.getOverridableMethods()) { + if (method.getEnclosingType().equals(autoBeanFactoryInterface)) { + // Ignore methods in AutoBeanFactory + continue; + } + + JClassType returnType = method.getReturnType().isInterface(); + if (returnType == null) { + poison("The return type of method %s is a primitive type", + method.getName()); + continue; + } + + // AutoBean<FooIntf> blah() --> beanType = FooIntf + JClassType beanType = ModelUtils.findParameterizationOf( + autoBeanInterface, returnType)[0]; + if (beanType.isInterface() == null) { + poison("The %s parameterization is not an interface", + beanType.getQualifiedSourceName()); + continue; + } + + // AutoBean<FooIntf> blah(FooIntfSub foo) --> toWrap = FooIntfSub + JClassType toWrap; + if (method.getParameters().length == 0) { + toWrap = null; + } else if (method.getParameters().length == 1) { + toWrap = method.getParameters()[0].getType().isClassOrInterface(); + if (!beanType.isAssignableFrom(toWrap)) { + poison( + "The %s parameterization %s is not assignable from the delegate" + + " type %s", autoBeanInterface.getSimpleSourceName(), + toWrap.getQualifiedSourceName()); + continue; + } + } else { + poison("Unexpecetd parameters in method %s", method.getName()); + continue; + } + + AutoBeanType autoBeanType = getAutoBeanType(beanType); + + // Must wrap things that aren't simple interfaces + if (!autoBeanType.isSimpleBean() && toWrap == null) { + if (categoryTypes != null) { + poison("The %s parameterization is not simple and the following" + + " methods did not have static implementations:", + beanType.getQualifiedSourceName()); + for (AutoBeanMethod missing : autoBeanType.getMethods()) { + if (missing.getAction().equals(JBeanMethod.CALL) + && missing.getStaticImpl() == null) { + poison(missing.getMethod().getReadableDeclaration()); + } + } + } else { + poison("The %s parameterization is not simple, but the %s method" + + " does not provide a delegate", + beanType.getQualifiedSourceName(), method.getName()); + } + continue; + } + + AutoBeanFactoryMethod.Builder builder = new AutoBeanFactoryMethod.Builder(); + builder.setAutoBeanType(autoBeanType); + builder.setMethod(method); + methods.add(builder.build()); + } + + while (!toCalculate.isEmpty()) { + Set<JClassType> examine = toCalculate; + toCalculate = new LinkedHashSet<JClassType>(); + for (JClassType beanType : examine) { + getAutoBeanType(beanType); + } + } + + if (poisoned) { + die("Unable to complete due to previous errors"); + } + } + + public Collection<AutoBeanType> getAllTypes() { + return Collections.unmodifiableCollection(peers.values()); + } + + public List<JClassType> getCategoryTypes() { + return categoryTypes; + } + + public Map<JEnumConstant, String> getEnumTokenMap() { + return Collections.unmodifiableMap(allEnumConstants); + } + + public List<AutoBeanFactoryMethod> getMethods() { + return Collections.unmodifiableList(methods); + } + + public AutoBeanType getPeer(JClassType beanType) { + beanType = ModelUtils.ensureBaseType(beanType); + return peers.get(beanType); + } + + private List<AutoBeanMethod> computeMethods(JClassType beanType) { + List<JMethod> toExamine = new ArrayList<JMethod>(); + toExamine.addAll(Arrays.asList(beanType.getInheritableMethods())); + toExamine.addAll(objectMethods); + List<AutoBeanMethod> toReturn = new ArrayList<AutoBeanMethod>( + toExamine.size()); + for (JMethod method : toExamine) { + if (method.isPrivate()) { + // Ignore private methods + continue; + } + AutoBeanMethod.Builder builder = new AutoBeanMethod.Builder(); + builder.setMethod(method); + + // See if this method shouldn't have its return type wrapped + // TODO: Allow class return types? + JClassType classReturn = method.getReturnType().isInterface(); + if (classReturn != null) { + maybeCalculate(classReturn); + if (noWrapTypes != null) { + for (JClassType noWrap : noWrapTypes) { + if (noWrap.isAssignableFrom(classReturn)) { + builder.setNoWrap(true); + break; + } + } + } + } + + // GET, SET, or CALL + JBeanMethod action = JBeanMethod.which(method); + builder.setAction(action); + if (JBeanMethod.CALL.equals(action)) { + JMethod staticImpl = findStaticImpl(beanType, method); + if (staticImpl == null && objectMethods.contains(method)) { + // Don't complain about lack of implementation for Object methods + continue; + } + builder.setStaticImp(staticImpl); + } + + AutoBeanMethod toAdd = builder.build(); + + // Collect referenced enums + if (toAdd.hasEnumMap()) { + allEnumConstants.putAll(toAdd.getEnumMap()); + } + + // See if parameterizations will pull in more types + if (toAdd.isCollection()) { + maybeCalculate(toAdd.getElementType()); + } else if (toAdd.isMap()) { + maybeCalculate(toAdd.getKeyType()); + maybeCalculate(toAdd.getValueType()); + } + + toReturn.add(toAdd); + } + return toReturn; + } + + private void die(String message) throws UnableToCompleteException { + poison(message); + throw new UnableToCompleteException(); + } + + /** + * Find <code>Object __intercept(AutoBean<?> bean, Object value);</code> in + * the category types. + */ + private JMethod findInterceptor(JClassType beanType) { + if (categoryTypes == null) { + return null; + } + for (JClassType category : categoryTypes) { + for (JMethod method : category.getOverloads("__intercept")) { + // Ignore non-static, non-public methods + // TODO: Implement visibleFrom() to allow package-protected categories + if (!method.isStatic() || !method.isPublic()) { + continue; + } + + JParameter[] params = method.getParameters(); + if (params.length != 2) { + continue; + } + if (!methodAcceptsAutoBeanAsFirstParam(beanType, method)) { + continue; + } + JClassType value = params[1].getType().isClassOrInterface(); + if (value == null) { + continue; + } + if (!oracle.getJavaLangObject().isAssignableTo(value)) { + continue; + } + return method; + } + } + return null; + } + + /** + * Search the category types for a static implementation of an interface + * method. Given the interface method declaration: + * + * <pre> + * Foo bar(Baz baz); + * </pre> + * + * this will search the types in {@link #categoryTypes} for the following + * method: + * + * <pre> + * public static Foo bar(AutoBean<Intf> bean, Baz baz) {} + * </pre> + */ + private JMethod findStaticImpl(JClassType beanType, JMethod method) { + if (categoryTypes == null) { + return null; + } + + for (JClassType category : categoryTypes) { + // One extra argument for the AutoBean + JParameter[] methodParams = method.getParameters(); + int requiredArgs = methodParams.length + 1; + overload : for (JMethod overload : category.getOverloads(method.getName())) { + if (!overload.isStatic() || !overload.isPublic()) { + // Ignore non-static, non-public methods + continue; + } + + JParameter[] overloadParams = overload.getParameters(); + if (overloadParams.length != requiredArgs) { + continue; + } + + if (!methodAcceptsAutoBeanAsFirstParam(beanType, overload)) { + // Ignore if the first parameter is a primitive or not assignable + continue; + } + + // Match the rest of the parameters + for (int i = 1; i < requiredArgs; i++) { + JType methodType = methodParams[i - 1].getType(); + JType overloadType = overloadParams[i].getType(); + if (methodType.equals(overloadType)) { + // Match; exact, the usual case + } else if (methodType.isClassOrInterface() != null + && overloadType.isClassOrInterface() != null + && methodType.isClassOrInterface().isAssignableTo( + overloadType.isClassOrInterface())) { + // Match; assignment-compatible + } else { + // No match, keep looking + continue overload; + } + } + return overload; + } + } + return null; + } + + private AutoBeanType getAutoBeanType(JClassType beanType) { + beanType = ModelUtils.ensureBaseType(beanType); + AutoBeanType toReturn = peers.get(beanType); + if (toReturn == null) { + AutoBeanType.Builder builder = new AutoBeanType.Builder(); + builder.setOwnerFactory(this); + builder.setPeerType(beanType); + builder.setMethods(computeMethods(beanType)); + builder.setInterceptor(findInterceptor(beanType)); + if (noWrapTypes != null) { + for (JClassType noWrap : noWrapTypes) { + if (noWrap.isAssignableFrom(beanType)) { + builder.setNoWrap(true); + break; + } + } + } + toReturn = builder.build(); + peers.put(beanType, toReturn); + } + return toReturn; + } + + /** + * Enqueue a type in {@link #toCalculate} if {@link #peers} does not already + * contain an entry. + */ + private void maybeCalculate(JClassType type) { + if (type.isInterface() == null || ModelUtils.isValueType(oracle, type)) { + return; + } + if (!peers.containsKey(type)) { + toCalculate.add(type); + } + } + + private boolean methodAcceptsAutoBeanAsFirstParam(JClassType beanType, + JMethod method) { + JParameter[] params = method.getParameters(); + if (params.length == 0) { + return false; + } + JClassType paramAsClass = params[0].getType().isClassOrInterface(); + + // First parameter is a primitive + if (paramAsClass == null) { + return false; + } + + // Check using base types to account for erasure semantics + JParameterizedType expectedFirst = oracle.getParameterizedType( + autoBeanInterface, + new JClassType[] {ModelUtils.ensureBaseType(beanType)}); + return expectedFirst.isAssignableTo(paramAsClass); + } + + private void poison(String message, Object... args) { + logger.log(TreeLogger.ERROR, String.format(message, args)); + poisoned = true; + } + + private void processClassArrayAnnotation(Class<?>[] classes, + Collection<JClassType> accumulator) { + for (Class<?> clazz : classes) { + JClassType category = oracle.findType(clazz.getCanonicalName()); + if (category == null) { + poison("Could not find @%s type %s in the TypeOracle", + Category.class.getSimpleName(), clazz.getCanonicalName()); + continue; + } else if (!category.isPublic()) { + poison("Category type %s is not public", + category.getQualifiedSourceName()); + continue; + } else if (!category.isStatic() && category.isMemberType()) { + poison("Category type %s must be static", + category.getQualifiedSourceName()); + continue; + } + accumulator.add(category); + } + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java new file mode 100644 index 0000000..43763df --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java
@@ -0,0 +1,219 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind.model; + +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JEnumConstant; +import com.google.gwt.core.ext.typeinfo.JEnumType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracle; +import com.google.gwt.editor.rebind.model.ModelUtils; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Describes a method implemented by an AutoBean. + */ +public class AutoBeanMethod { + /** + * Creates AutoBeanMethods. + */ + public static class Builder { + private AutoBeanMethod toReturn = new AutoBeanMethod(); + + public AutoBeanMethod build() { + if (toReturn.action.equals(JBeanMethod.GET) + || toReturn.action.equals(JBeanMethod.SET) + || toReturn.action.equals(JBeanMethod.SET_BUILDER)) { + PropertyName annotation = toReturn.method.getAnnotation(PropertyName.class); + if (annotation != null) { + toReturn.propertyName = annotation.value(); + } else { + toReturn.propertyName = toReturn.action.inferName(toReturn.method); + } + } + + try { + return toReturn; + } finally { + toReturn = null; + } + } + + public void setAction(JBeanMethod action) { + toReturn.action = action; + } + + public void setMethod(JMethod method) { + toReturn.method = method; + TypeOracle oracle = method.getEnclosingType().getOracle(); + + JType returnType = method.getReturnType(); + toReturn.isValueType = ModelUtils.isValueType(oracle, returnType); + + if (!toReturn.isValueType) { + // See if it's a collection or a map + JClassType returnClass = returnType.isClassOrInterface(); + JClassType collectionInterface = oracle.findType(Collection.class.getCanonicalName()); + JClassType mapInterface = oracle.findType(Map.class.getCanonicalName()); + if (collectionInterface.isAssignableFrom(returnClass)) { + JClassType[] parameterizations = ModelUtils.findParameterizationOf( + collectionInterface, returnClass); + toReturn.elementType = parameterizations[0]; + maybeProcessEnumType(toReturn.elementType); + } else if (mapInterface.isAssignableFrom(returnClass)) { + JClassType[] parameterizations = ModelUtils.findParameterizationOf( + mapInterface, returnClass); + toReturn.keyType = parameterizations[0]; + toReturn.valueType = parameterizations[1]; + maybeProcessEnumType(toReturn.keyType); + maybeProcessEnumType(toReturn.valueType); + } + } else { + maybeProcessEnumType(returnType); + } + } + + public void setNoWrap(boolean noWrap) { + toReturn.isNoWrap = noWrap; + } + + public void setStaticImp(JMethod staticImpl) { + toReturn.staticImpl = staticImpl; + } + + /** + * Call {@link #processEnumType(JEnumType)} if {@code type} is a + * {@link JEnumType}. + */ + private void maybeProcessEnumType(JType type) { + assert type != null : "type == null"; + JEnumType enumType = type.isEnum(); + if (enumType != null) { + processEnumType(enumType); + } + } + + /** + * Adds a JEnumType to the AutoBeanMethod's enumMap so that the + * AutoBeanFactoryGenerator can embed extra metadata about the enum values. + */ + private void processEnumType(JEnumType enumType) { + Map<JEnumConstant, String> map = toReturn.enumMap; + if (map == null) { + map = toReturn.enumMap = new LinkedHashMap<JEnumConstant, String>(); + } + for (JEnumConstant e : enumType.getEnumConstants()) { + String name = getEnumName(e); + map.put(e, name); + } + } + } + + static String getEnumName(JEnumConstant e) { + String name; + PropertyName annotation = e.getAnnotation(PropertyName.class); + if (annotation == null) { + name = e.getName(); + } else { + name = annotation.value(); + } + return name; + } + + private JBeanMethod action; + private JClassType elementType; + private Map<JEnumConstant, String> enumMap; + private JClassType keyType; + private JMethod method; + private boolean isNoWrap; + private boolean isValueType; + private String propertyName; + private JMethod staticImpl; + private JClassType valueType; + + private AutoBeanMethod() { + } + + public JBeanMethod getAction() { + return action; + } + + public JClassType getElementType() { + return elementType; + } + + public Map<JEnumConstant, String> getEnumMap() { + return enumMap; + } + + public JClassType getKeyType() { + return keyType; + } + + public JMethod getMethod() { + return method; + } + + public String getPropertyName() { + return propertyName; + } + + /** + * If the AutoBean method was declared in a type containing a + * {@link com.google.gwt.editor.client.AutoBean.Category Category} annotation, + * this method will return the static implementation. + */ + public JMethod getStaticImpl() { + return staticImpl; + } + + public JClassType getValueType() { + return valueType; + } + + public boolean hasEnumMap() { + return enumMap != null; + } + + public boolean isCollection() { + return elementType != null; + } + + public boolean isMap() { + return keyType != null; + } + + public boolean isNoWrap() { + return isNoWrap; + } + + public boolean isValueType() { + return isValueType; + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + return method.toString(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java new file mode 100644 index 0000000..da40afe --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java
@@ -0,0 +1,168 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind.model; + +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Describes an AutoBean. + */ +public class AutoBeanType { + + /** + * Builder. + */ + public static class Builder { + private boolean affectedByCategories; + private String beanSimpleSourceName; + private String categorySuffix; + private AutoBeanType toReturn = new AutoBeanType(); + + public AutoBeanType build() { + // Different implementations necessary for category-affected impls + toReturn.simpleSourceName = beanSimpleSourceName + + (affectedByCategories ? categorySuffix : ""); + try { + return toReturn; + } finally { + toReturn = null; + } + } + + public void setInterceptor(JMethod interceptor) { + affectedByCategories = interceptor != null; + toReturn.interceptor = interceptor; + } + + public void setMethods(List<AutoBeanMethod> methods) { + toReturn.methods = new ArrayList<AutoBeanMethod>(methods); + Collections.sort(toReturn.methods, new Comparator<AutoBeanMethod>() { + public int compare(AutoBeanMethod o1, AutoBeanMethod o2) { + int c = o1.getAction().compareTo(o2.getAction()); + if (c != 0) { + return c; + } + // Name alone would cause overload conflicts + return o1.getMethod().getReadableDeclaration().compareTo( + o2.getMethod().getReadableDeclaration()); + } + }); + toReturn.methods = Collections.unmodifiableList(toReturn.methods); + + toReturn.simpleBean = true; + for (AutoBeanMethod method : methods) { + if (method.getAction().equals(JBeanMethod.CALL)) { + if (method.getStaticImpl() == null) { + toReturn.simpleBean = false; + } else { + affectedByCategories = true; + } + } + } + } + + public void setNoWrap(boolean noWrap) { + toReturn.noWrap = noWrap; + } + + public void setOwnerFactory(AutoBeanFactoryModel autoBeanFactoryModel) { + if (autoBeanFactoryModel.getCategoryTypes() == null) { + return; + } + StringBuilder sb = new StringBuilder(); + for (JClassType category : autoBeanFactoryModel.getCategoryTypes()) { + sb.append("_").append( + category.getQualifiedSourceName().replace('.', '_')); + } + categorySuffix = sb.toString(); + } + + public void setPeerType(JClassType type) { + assert type.isParameterized() == null && type.isRawType() == null; + toReturn.peerType = type; + String packageName = type.getPackage().getName(); + if (packageName.startsWith("java")) { + packageName = "emul." + packageName; + } + toReturn.packageName = packageName; + beanSimpleSourceName = type.getName().replace('.', '_') + "AutoBean"; + } + } + + private JMethod interceptor; + private List<AutoBeanMethod> methods; + private boolean noWrap; + private String packageName; + private JClassType peerType; + private boolean simpleBean; + private String simpleSourceName; + + private AutoBeanType() { + } + + /** + * A method that is allowed to intercept and modify return values from + * getters. + */ + public JMethod getInterceptor() { + return interceptor; + } + + public List<AutoBeanMethod> getMethods() { + return methods; + } + + public String getPackageNome() { + return packageName; + } + + public JClassType getPeerType() { + return peerType; + } + + public String getQualifiedSourceName() { + return getPackageNome() + "." + getSimpleSourceName(); + } + + public String getSimpleSourceName() { + return simpleSourceName; + } + + public boolean isNoWrap() { + return noWrap; + } + + /** + * A simple bean has only getters and setters. + */ + public boolean isSimpleBean() { + return simpleBean; + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + return peerType.toString(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java new file mode 100644 index 0000000..d600c19 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java
@@ -0,0 +1,154 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.gwt.rebind.model; + +import static com.google.web.bindery.autobean.vm.impl.BeanMethod.GET_PREFIX; +import static com.google.web.bindery.autobean.vm.impl.BeanMethod.HAS_PREFIX; +import static com.google.web.bindery.autobean.vm.impl.BeanMethod.IS_PREFIX; +import static com.google.web.bindery.autobean.vm.impl.BeanMethod.SET_PREFIX; + +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JMethod; +import com.google.gwt.core.ext.typeinfo.JPrimitiveType; +import com.google.gwt.core.ext.typeinfo.JType; + +import java.beans.Introspector; + +/** + * Common utility code for matching {@link JMethod} and against bean-style + * accessor semantics. + * + * @see com.google.web.bindery.autobean.vm.impl.BeanMethod + */ +public enum JBeanMethod { + GET { + @Override + public String inferName(JMethod method) { + if (isBooleanProperty(method) && method.getName().startsWith(IS_PREFIX)) { + return Introspector.decapitalize(method.getName().substring(2)); + } + return super.inferName(method); + } + + @Override + public boolean matches(JMethod method) { + if (method.getParameters().length > 0) { + return false; + } + + if (isBooleanProperty(method)) { + return true; + } + + String name = method.getName(); + if (name.startsWith(GET_PREFIX) && name.length() > 3) { + return true; + } + return false; + } + + /** + * Returns {@code true} if the method matches {@code boolean isFoo()} or + * {@code boolean hasFoo()} property accessors. + */ + private boolean isBooleanProperty(JMethod method) { + JType returnType = method.getReturnType(); + if (JPrimitiveType.BOOLEAN.equals(returnType) + || method.getEnclosingType().getOracle().findType( + Boolean.class.getCanonicalName()).equals(returnType)) { + String name = method.getName(); + if (name.startsWith(IS_PREFIX) && name.length() > 2) { + return true; + } + if (name.startsWith(HAS_PREFIX) && name.length() > 3) { + return true; + } + } + return false; + } + }, + SET { + @Override + public boolean matches(JMethod method) { + if (!JPrimitiveType.VOID.equals(method.getReturnType())) { + return false; + } + if (method.getParameters().length != 1) { + return false; + } + String name = method.getName(); + if (name.startsWith(SET_PREFIX) && name.length() > 3) { + return true; + } + return false; + } + }, + SET_BUILDER { + @Override + public boolean matches(JMethod method) { + JClassType returnClass = method.getReturnType().isClassOrInterface(); + if (returnClass == null + || !returnClass.isAssignableFrom(method.getEnclosingType())) { + return false; + } + if (method.getParameters().length != 1) { + return false; + } + String name = method.getName(); + if (name.startsWith(SET_PREFIX) && name.length() > 3) { + return true; + } + return false; + } + }, + CALL { + /** + * Matches all leftover methods. + */ + @Override + public boolean matches(JMethod method) { + return true; + } + }; + + /** + * Determine which Action a method maps to. + */ + public static JBeanMethod which(JMethod method) { + for (JBeanMethod action : JBeanMethod.values()) { + if (action.matches(method)) { + return action; + } + } + throw new RuntimeException("CALL should have matched"); + } + + /** + * Infer the name of a property from the method. + */ + public String inferName(JMethod method) { + if (this == CALL) { + throw new UnsupportedOperationException( + "Cannot infer a property name for a CALL-type method"); + } + return Introspector.decapitalize(method.getName().substring(3)); + } + + /** + * Returns {@code true} if the BeanLikeMethod matches the method. + */ + public abstract boolean matches(JMethod method); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBean.java b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java new file mode 100644 index 0000000..9aa94fc --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java
@@ -0,0 +1,141 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A controller for an implementation of a bean interface. Instances of + * AutoBeans are obtained from an {@link AutoBeanFactory}. + * + * @param <T> the type of interface that will be wrapped. + */ +public interface AutoBean<T> { + /** + * An annotation that allows inferred property names to be overridden. + * <p> + * This annotation is asymmetric, applying it to a getter will not affect the + * setter. The asymmetry allows existing users of an interface to read old + * {@link AutoBeanCodex} messages, but write new ones. + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) + public @interface PropertyName { + String value(); + } + + /** + * Accept an AutoBeanVisitor. + * + * @param visitor an {@link AutoBeanVisitor} + */ + void accept(AutoBeanVisitor visitor); + + /** + * Returns a proxy implementation of the <code>T</code> interface which will + * delegate to the underlying wrapped object, if any. + * + * @return a proxy that delegates to the wrapped object + */ + T as(); + + /** + * This method always throws an {@link UnsupportedOperationException}. The + * implementation of this method in previous releases was not sufficiently + * robust and there are no further uses of this method within the GWT code + * base. Furthermore, there are many different semantics that can be applied + * to a cloning process that cannot be adequately addressed with a single + * implementation. + * <p> + * A simple clone of an acyclic datastructure can be created by using + * {@link AutoBeanCodex} to encode and decode the root object. Other cloning + * algorithms are best implemented by using an {@link AutoBeanVisitor}. + * + * @throws UnsupportedOperationException + * @deprecated with no replacement + */ + @Deprecated + AutoBean<T> clone(boolean deep); + + /** + * Returns the AutoBeanFactory that created the AutoBean. + * + * @return an AutoBeanFactory + */ + AutoBeanFactory getFactory(); + + /** + * Retrieve a tag value that was previously provided to + * {@link #setTag(String, Object)}. + * + * @param tagName the tag name + * @return the tag value + * @see #setTag(String, Object) + */ + <Q> Q getTag(String tagName); + + /** + * Returns the wrapped interface type. + */ + Class<T> getType(); + + /** + * Returns the value most recently passed to {@link #setFrozen}, or + * {@code false} if it has never been called. + * + * @return {@code true} if this instance is frozen + */ + boolean isFrozen(); + + /** + * Returns {@code true} if the AutoBean was provided with an external object. + * + * @return {@code true} if this instance is a wrapper + */ + boolean isWrapper(); + + /** + * Disallows any method calls other than getters. All setter and call + * operations will throw an {@link IllegalStateException}. + * + * @param frozen if {@code true}, freeze this instance + */ + void setFrozen(boolean frozen); + + /** + * A tag is an arbitrary piece of external metadata to be associated with the + * wrapped value. + * + * @param tagName the tag name + * @param value the wrapped value + * @see #getTag(String) + */ + void setTag(String tagName, Object value); + + /** + * If the AutoBean wraps an object, return the underlying object. The AutoBean + * will no longer function once unwrapped. + * + * @return the previously-wrapped object + * @throws IllegalStateException if the AutoBean is not a wrapper + */ + T unwrap(); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java new file mode 100644 index 0000000..2f07b1a --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java
@@ -0,0 +1,86 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; + +/** + * Utility methods for encoding an AutoBean graph into a JSON-compatible string. + * This codex intentionally does not preserve object identity, nor does it + * encode cycles, but it will detect them. + */ +public class AutoBeanCodex { + + /** + * Decode an AutoBeanCodex payload. + * + * @param <T> the expected return type + * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>} + * @param clazz the expected return type + * @param data a payload previously generated by {@link #encode(AutoBean)} + * @return an AutoBean containing the payload contents + */ + public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, Splittable data) { + return AutoBeanCodexImpl.doDecode(EncodeState.forDecode(factory), clazz, data); + } + + /** + * Decode an AutoBeanCodex payload. + * + * @param <T> the expected return type + * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>} + * @param clazz the expected return type + * @param payload a payload string previously generated by + * {@link #encode(AutoBean)}{@link Splittable#getPayload() + * .getPayload()}. + * @return an AutoBean containing the payload contents + */ + public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, String payload) { + Splittable data = StringQuoter.split(payload); + return decode(factory, clazz, data); + } + + /** + * Copy data from a {@link Splittable} into an AutoBean. Unset values in the + * Splittable will not nullify data that already exists in the AutoBean. + * + * @param data the source data to copy + * @param bean the target AutoBean + */ + public static void decodeInto(Splittable data, AutoBean<?> bean) { + AutoBeanCodexImpl.doDecodeInto(EncodeState.forDecode(bean.getFactory()), data, bean); + } + + /** + * Encodes an AutoBean. The actual payload contents can be retrieved through + * {@link Splittable#getPayload()}. + * + * @param bean the bean to encode + * @return a Splittable that encodes the state of the AutoBean + */ + public static Splittable encode(AutoBean<?> bean) { + if (bean == null) { + return Splittable.NULL; + } + + StringBuilder sb = new StringBuilder(); + EncodeState state = EncodeState.forEncode(bean.getFactory(), sb); + AutoBeanCodexImpl.doEncode(state, bean); + return StringQuoter.split(sb.toString()); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java new file mode 100644 index 0000000..44496a5 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java
@@ -0,0 +1,120 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A tag interface for the AutoBean generator. Instances of AutoBeans are + * created by declaring factory methods on a subtype of this interface. + * <p> + * Simple interfaces, consisting of only getters and setters, can be constructed + * with a no-arg method. Non-simple interfaces must provide a delegate object to + * implement a non-simple interface or use a {@link Category}. + * + * <pre> + * interface MyFactory extends AutoBeanFactory { + * // A factory method for a simple bean + * AutoBean<BeanInterface> beanInterface(); + * // A factory method for a wrapper bean + * AutoBean<ArbitraryInterface> wrapper(ArbitraryInterface delegate); + * } + * </pre> + * + * @see <a + * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean + * wiki page</a> + */ +public interface AutoBeanFactory { + /** + * Allows non-property methods on simple bean implementations when applied. + * For any given method, the specified classes will be searched for a public, + * static method whose method signature is exactly equal to the declared + * method's signature, save for the addition of a new initial paramater that + * must accept <code>AutoBean<T></code>. + * + * <pre> + * interface HasMethod { + * void doSomething(int a, double b); + * } + * </pre> + * + * would be paired with a category implemenation such as + * + * <pre> + * class HasMethodCategory { + * public static void doSomething(AutoBean<HasMethod> bean, int a, double b) { + * } + * } + * </pre> + * + * and registered with + * + * <pre> + * {@literal @}Category(HasMethodCategory.class) + * interface MyBeanFactory extends AutoBeanFactory { + * AutoBean<HasMethod> hasMethod(); + * } + * </pre> + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Category { + Class<?>[] value(); + } + + /** + * The types specified by this annotation will not be wrapped by an AutoBean + * when returned from an AutoBean-controlled method. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface NoWrap { + /** + * The interface types that should not be wrapped. + */ + Class<?>[] value(); + } + + /** + * Allows dynamic creation of AutoBean instances based on declared + * parameterizations. + * + * @param <T> the parameterization of the created {@link AutoBean} + * @param clazz the Class of type T of the new instance + * @return an {@link AutoBean} of type T or {@code null} if the interface type + * is unknown to the factory + */ + <T> AutoBean<T> create(Class<T> clazz); + + /** + * Allows dynamic creation of wrapped AutoBean instances based on declared + * parameterizations. + * + * @param <T> the parameterization of the created {@link AutoBean} + * @param <U> the delegate's type, a subtype of T + * @param clazz the Class of type T of the new instance + * @param delegate a delegate that extends type T + * @return an {@link AutoBean} of type T or {@code null} if the interface type + * is unknown to the factory + */ + <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java new file mode 100644 index 0000000..e97c438 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java
@@ -0,0 +1,460 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import com.google.gwt.core.client.impl.WeakMapping; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility methods for working with AutoBeans. + */ +public final class AutoBeanUtils { + /* + * TODO(bobv): Make Comparison a real type that holds a map contain the diff + * between the two objects. Then export a Map of PendingComparison to + * Comparisons as a public API to make it easy for developers to perform deep + * diffs across a graph structure. + * + * Three-way merge... + */ + + private enum Comparison { + TRUE, FALSE, PENDING; + } + + /** + * A Pair where order does not matter and the objects are compared by + * identity. + */ + private static class PendingComparison { + private final AutoBean<?> a; + private final AutoBean<?> b; + private final int hashCode; + + public PendingComparison(AutoBean<?> a, AutoBean<?> b) { + this.a = a; + this.b = b; + // Don't make relatively prime since order does not matter + hashCode = System.identityHashCode(a) + System.identityHashCode(b); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof PendingComparison)) { + return false; + } + PendingComparison other = (PendingComparison) o; + return a == other.a && b == other.b || // Direct match + a == other.b && b == other.a; // Swapped + } + + @Override + public int hashCode() { + return hashCode; + } + } + + /** + * Compare two graphs of AutoBeans based on values. + * <p> + * <ul> + * <li>AutoBeans are compared based on type and property values</li> + * <li>Lists are compared with element-order equality</li> + * <li>Sets and all other Collection types are compare with bag equality</li> + * <li>Maps are compared as a lists of keys-value pairs</li> + * <li>{@link Splittable Splittables} are compared by value</li> + * </ul> + * <p> + * This will work for both simple and wrapper AutoBeans. + * <p> + * This method may crawl the entire object graph reachable from the input + * parameters and may be arbitrarily expensive to compute. + * + * @param a an {@link AutoBean} + * @param b an {@link AutoBean} + * @return {@code false} if any values in the graph reachable through + * <code>a</code> are different from those reachable from + * <code>b</code> + */ + public static boolean deepEquals(AutoBean<?> a, AutoBean<?> b) { + return sameOrEquals(a, b, new HashMap<PendingComparison, Comparison>()); + } + + /** + * Returns a map of properties that differ (via {@link Object#equals(Object)}) + * between two AutoBeans. The keys are property names and the values are the + * value of the property in <code>b</code>. Properties present in + * <code>a</code> but missing in <code>b</code> will be represented by + * <code>null</code> values. This implementation will compare AutoBeans of + * different parameterizations, although the diff produced is likely + * meaningless. + * <p> + * This will work for both simple and wrapper AutoBeans. + * + * @param a an {@link AutoBean} + * @param b an {@link AutoBean} + * @return a {@link Map} of differing properties + */ + public static Map<String, Object> diff(AutoBean<?> a, AutoBean<?> b) { + // Fast check for comparing an object to itself + if (a.equals(b)) { + return Collections.emptyMap(); + } + final Map<String, Object> toReturn = getAllProperties(b); + + // Remove the entries that are equal, adding nulls for missing properties + a.accept(new AutoBeanVisitor() { + @Override + public boolean visitReferenceProperty(String propertyName, AutoBean<?> previousValue, + PropertyContext ctx) { + if (toReturn.containsKey(propertyName)) { + if (equal(propertyName, previousValue)) { + // No change + toReturn.remove(propertyName); + } + } else { + // The predecessor has a value that this object doesn't. + toReturn.put(propertyName, null); + } + return false; + } + + @Override + public boolean visitValueProperty(String propertyName, Object previousValue, + PropertyContext ctx) { + if (toReturn.containsKey(propertyName)) { + if (equal(propertyName, previousValue)) { + // No change + toReturn.remove(propertyName); + } + } else { + // The predecessor has a value that this object doesn't. + toReturn.put(propertyName, null); + } + return false; + } + + private boolean equal(String propertyName, AutoBean<?> previousValue) { + return previousValue == null && toReturn.get(propertyName) == null || previousValue != null + && equal(propertyName, previousValue.as()); + } + + private boolean equal(String propertyName, Object previousValue) { + Object currentValue = toReturn.get(propertyName); + return previousValue == null && currentValue == null || previousValue != null + && previousValue.equals(currentValue); + } + }); + return toReturn; + } + + /** + * Returns a map that is a copy of the properties contained in an AutoBean. + * The returned map is mutable, but editing it will not have any effect on the + * bean that produced it. + * + * @param bean an {@link AutoBean} + * @return a {@link Map} of the bean's properties + */ + public static Map<String, Object> getAllProperties(AutoBean<?> bean) { + final Map<String, Object> toReturn = new LinkedHashMap<String, Object>(); + + // Look at the previous value of all properties + bean.accept(new AutoBeanVisitor() { + @Override + public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, + PropertyContext ctx) { + toReturn.put(propertyName, value == null ? null : value.as()); + return false; + } + + @Override + public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) { + toReturn.put(propertyName, value); + return false; + } + }); + return toReturn; + } + + /** + * Return the single AutoBean wrapper that is observing the delegate object or + * {@code null} if the parameter is {@code null}or not wrapped by an AutoBean. + * + * @param delegate a delegate object, or {@code null} + * @return the {@link AutoBean} wrapper for the delegate, or {@code null} + */ + @SuppressWarnings("unchecked") + public static <T, U extends T> AutoBean<T> getAutoBean(U delegate) { + return delegate == null ? null : (AutoBean<T>) WeakMapping.get(delegate, AutoBean.class + .getName()); + } + + /** + * Compare two AutoBeans, this method has the type fan-out. + */ + static boolean sameOrEquals(Object value, Object otherValue, + Map<PendingComparison, Comparison> pending) { + if (value == otherValue) { + // Fast exit + return true; + } + + if (value instanceof Collection<?> && otherValue instanceof Collection<?>) { + // Check collections + return sameOrEquals((Collection<?>) value, (Collection<?>) otherValue, pending, null); + } + + if (value instanceof Map<?, ?> && otherValue instanceof Map<?, ?>) { + // Check maps + return sameOrEquals((Map<?, ?>) value, (Map<?, ?>) otherValue, pending); + } + + if (value instanceof Splittable && otherValue instanceof Splittable) { + return sameOrEquals((Splittable) value, (Splittable) otherValue, pending); + } + + // Possibly substitute the AutoBean for its shim + { + AutoBean<?> maybeValue = AutoBeanUtils.getAutoBean(value); + AutoBean<?> maybeOther = AutoBeanUtils.getAutoBean(otherValue); + if (maybeValue != null && maybeOther != null) { + value = maybeValue; + otherValue = maybeOther; + } + } + + if (value instanceof AutoBean<?> && otherValue instanceof AutoBean<?>) { + // Check ValueProxies + return sameOrEquals((AutoBean<?>) value, (AutoBean<?>) otherValue, pending); + } + + if (value == null ^ otherValue == null) { + // One is null, the other isn't + return false; + } + + if (value != null && !value.equals(otherValue)) { + // Regular object equality + return false; + } + return true; + } + + /** + * If a comparison between two AutoBeans is currently pending, this method + * will skip their comparison. + */ + private static boolean sameOrEquals(AutoBean<?> value, AutoBean<?> otherValue, + Map<PendingComparison, Comparison> pending) { + if (value == otherValue) { + // Simple case + return true; + } else if (!value.getType().equals(otherValue.getType())) { + // Beans of different types + return false; + } + + /* + * The PendingComparison key allows us to break reference cycles when + * crawling the graph. Since the entire operation is essentially a + * concatenated && operation, it's ok to speculatively return true for + * repeated a.equals(b) tests. + */ + PendingComparison key = new PendingComparison(value, otherValue); + Comparison previous = pending.get(key); + if (previous == null) { + // Prevent the same comparison from being made + pending.put(key, Comparison.PENDING); + + // Compare each property + Map<String, Object> beanProperties = AutoBeanUtils.getAllProperties(value); + Map<String, Object> otherProperties = AutoBeanUtils.getAllProperties(otherValue); + for (Map.Entry<String, Object> entry : beanProperties.entrySet()) { + Object property = entry.getValue(); + Object otherProperty = otherProperties.get(entry.getKey()); + if (!sameOrEquals(property, otherProperty, pending)) { + pending.put(key, Comparison.FALSE); + return false; + } + } + pending.put(key, Comparison.TRUE); + return true; + } else { + // Return true for TRUE or PENDING + return !Comparison.FALSE.equals(previous); + } + } + + /** + * Compare two collections by size, then by contents. List comparisons will + * preserve order. All other collections will be treated with bag semantics. + */ + private static boolean sameOrEquals(Collection<?> collection, Collection<?> otherCollection, + Map<PendingComparison, Comparison> pending, Map<Object, Object> pairs) { + if (collection.size() != otherCollection.size()) { + return false; + } + + if (collection instanceof List<?>) { + // Lists we can simply iterate over + Iterator<?> it = collection.iterator(); + Iterator<?> otherIt = otherCollection.iterator(); + while (it.hasNext()) { + assert otherIt.hasNext(); + Object element = it.next(); + Object otherElement = otherIt.next(); + if (!sameOrEquals(element, otherElement, pending)) { + return false; + } + if (pairs != null) { + pairs.put(element, otherElement); + } + } + } else { + // Do an n*m comparison on any other collection type + List<Object> values = new ArrayList<Object>(collection); + List<Object> otherValues = new ArrayList<Object>(otherCollection); + it : for (Iterator<Object> it = values.iterator(); it.hasNext();) { + Object value = it.next(); + for (Iterator<Object> otherIt = otherValues.iterator(); otherIt.hasNext();) { + Object otherValue = otherIt.next(); + if (sameOrEquals(value, otherValue, pending)) { + if (pairs != null) { + pairs.put(value, otherValue); + } + // If a match is found, remove both values from their lists + it.remove(); + otherIt.remove(); + continue it; + } + } + // A match for the value wasn't found + return false; + } + assert values.isEmpty() && otherValues.isEmpty(); + } + return true; + } + + /** + * Compare two Maps by size, and key-value pairs. + */ + private static boolean sameOrEquals(Map<?, ?> map, Map<?, ?> otherMap, + Map<PendingComparison, Comparison> pending) { + if (map.size() != otherMap.size()) { + return false; + } + Map<Object, Object> pairs = new IdentityHashMap<Object, Object>(); + if (!sameOrEquals(map.keySet(), otherMap.keySet(), pending, pairs)) { + return false; + } + for (Map.Entry<?, ?> entry : map.entrySet()) { + Object otherValue = otherMap.get(pairs.get(entry.getKey())); + if (!sameOrEquals(entry.getValue(), otherValue, pending)) { + return false; + } + } + return true; + } + + /** + * Compare Splittables by kind and values. + */ + private static boolean sameOrEquals(Splittable value, Splittable otherValue, + Map<PendingComparison, Comparison> pending) { + if (value == otherValue) { + return true; + } + + // Strings + if (value.isString()) { + if (!otherValue.isString()) { + return false; + } + return value.asString().equals(otherValue.asString()); + } + + // Arrays + if (value.isIndexed()) { + if (!otherValue.isIndexed()) { + return false; + } + + if (value.size() != otherValue.size()) { + return false; + } + + for (int i = 0, j = value.size(); i < j; i++) { + if (!sameOrEquals(value.get(i), otherValue.get(i), pending)) { + return false; + } + } + return true; + } + + // Objects + if (value.isKeyed()) { + if (!otherValue.isKeyed()) { + return false; + } + /* + * We want to treat a missing property key as a null value, so we can't + * just compare the key lists. + */ + List<String> keys = value.getPropertyKeys(); + for (String key : keys) { + if (value.isNull(key)) { + // If value['foo'] is null, other['foo'] must also be null + if (!otherValue.isNull(key)) { + return false; + } + } else if (otherValue.isNull(key) + || !sameOrEquals(value.get(key), otherValue.get(key), pending)) { + return false; + } + } + + // Look at keys only in otherValue, and ensure nullness + List<String> otherKeys = new ArrayList<String>(otherValue.getPropertyKeys()); + otherKeys.removeAll(keys); + for (String key : otherKeys) { + if (!value.isNull(key)) { + return false; + } + } + return true; + } + + // Unexpected + throw new UnsupportedOperationException("Splittable of unknown type"); + } + + /** + * Utility class. + */ + private AutoBeanUtils() { + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java new file mode 100644 index 0000000..bff9d4a --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java
@@ -0,0 +1,267 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import java.util.Collection; +import java.util.Map; + +/** + * Allows traversal of an AutoBean object graph. + */ +public class AutoBeanVisitor { + /** + * A PropertyContext that describes the parameterization of the Collection + * being visited. + */ + public interface CollectionPropertyContext extends PropertyContext { + /** + * Returns the collection's element type. + * + * @return a Class object representing the element type + */ + Class<?> getElementType(); + } + + /** + * Reserved for future expansion to avoid API breaks. + */ + public interface Context { + } + + /** + * A PropertyContext that describes the parameterization of the Map being + * visited. + */ + public interface MapPropertyContext extends PropertyContext { + /** + * Returns the map's key type. + * + * @return a Class object representing the key type + */ + Class<?> getKeyType(); + + /** + * Returns the map's value type. + * + * @return a Class object representing the value type + */ + Class<?> getValueType(); + } + + /** + * The ParameterizationVisitor provides access to more complete type + * information than a simple class literal can provide. + * <p> + * The order of traversal reflects the declared parameterization of the + * property. For example, a {@code Map<String, List<Foo>>} would be traversed + * via the following sequence: + * + * <pre> + * visitType(Map.class); + * visitParameter(); + * visitType(String.class); + * endVisitType(String.class); + * endVisitParameter(); + * visitParameter(); + * visitType(List.class); + * visitParameter(); + * visitType(Foo.class); + * endVisitType(Foo.class); + * endParameter(); + * endVisitType(List.class); + * endVisitParameter(); + * endVisitType(Map.class); + * </pre> + */ + public static class ParameterizationVisitor { + /** + * Called when finished with a type parameter. + */ + public void endVisitParameter() { + } + + /** + * Called when finished with a type. + * + * @param type a Class object + */ + public void endVisitType(Class<?> type) { + } + + /** + * Called when visiting a type parameter. + * + * @return {@code true} if the type parameter should be visited + */ + public boolean visitParameter() { + return true; + } + + /** + * Called when visiting a possibly parameterized type. + * + * @param type a Class object + * @return {@code true} if the type should be visited + */ + public boolean visitType(Class<?> type) { + return true; + } + } + + /** + * Allows properties to be reset. + */ + public interface PropertyContext { + /** + * Allows deeper inspection of the declared parameterization of the + * property. + */ + void accept(ParameterizationVisitor visitor); + + /** + * Indicates if the {@link #set} method will succeed. + * + * @return {@code true} if the property can be set + */ + boolean canSet(); + + /** + * Returns the expected type of the property. + * + * @return a Class object representing the property type + */ + Class<?> getType(); + + /** + * Sets a property value. + * + * @param value the new value + */ + void set(Object value); + } + + /** + * Called after visiting an {@link AutoBean}. + * + * @param bean an {@link AutoBean} + * @param ctx a Context + */ + public void endVisit(AutoBean<?> bean, Context ctx) { + } + + /** + * Called after visiting a reference property. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value, + CollectionPropertyContext ctx) { + endVisitReferenceProperty(propertyName, value, ctx); + } + + /** + * Called after visiting a reference property. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value, + MapPropertyContext ctx) { + endVisitReferenceProperty(propertyName, value, ctx); + } + + /** + * Called after visiting a reference property. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public void endVisitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) { + } + + /** + * Called after visiting a value property. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) { + } + + /** + * Called when visiting an {@link AutoBean}. + * + * @param bean an {@link AutoBean} + * @param ctx a Context + */ + public boolean visit(AutoBean<?> bean, Context ctx) { + return true; + } + + /** + * Called every time, but {@link #visit(AutoBean, Context)} will be called for + * the value only the first time it is encountered. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value, + CollectionPropertyContext ctx) { + return visitReferenceProperty(propertyName, value, ctx); + } + + /** + * Called every time, but {@link #visit(AutoBean, Context)} will be called for + * the value only the first time it is encountered. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public boolean visitMapProperty(String propertyName, AutoBean<Map<?, ?>> value, + MapPropertyContext ctx) { + return visitReferenceProperty(propertyName, value, ctx); + } + + /** + * Called every time, but {@link #visit(AutoBean, Context)} will be called for + * the value only the first time it is encountered. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) { + return true; + } + + /** + * TODO: document. + * + * @param propertyName the property name, as a String + * @param value the property value + * @param ctx a PropertyContext + */ + public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) { + return true; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/Splittable.java b/user/src/com/google/web/bindery/autobean/shared/Splittable.java new file mode 100644 index 0000000..2698345 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/Splittable.java
@@ -0,0 +1,152 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import com.google.web.bindery.autobean.shared.impl.StringQuoter; + +import java.util.List; + +/** + * This interface provides an abstraction around the underlying data model + * (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex + * payload. + */ +public interface Splittable { + /** + * A value that represents {@code null}. + */ + Splittable NULL = StringQuoter.nullValue(); + + /** + * Returns a boolean representation of the data; + */ + boolean asBoolean(); + + /** + * Returns a numeric representation of the data. + */ + double asNumber(); + + /** + * Assign the splittable to the specified index of the {@code parent} object. + */ + void assign(Splittable parent, int index); + + /** + * Assign the splittable to the named property of the {@code parent} object. + */ + void assign(Splittable parent, String propertyName); + + /** + * Returns a string representation of the data. + */ + String asString(); + + /** + * Clones the Splittable, ignoring cycles and tags. + */ + Splittable deepCopy(); + + /** + * Returns the nth element of a list. + */ + Splittable get(int index); + + /** + * Returns the named property. + */ + Splittable get(String key); + + /** + * Returns a wire-format representation of the data. + */ + String getPayload(); + + /** + * Returns all keys available in the Splittable. This method may be expensive + * to compute. + */ + List<String> getPropertyKeys(); + + /** + * Returns a value previously set with {@link #setReified(String, Object)}. + */ + Object getReified(String key); + + /** + * Returns {@code true} if the value of the Splittable is a boolean. + */ + boolean isBoolean(); + + /** + * Returns {@code} true if {@link #size()} and {@link #get(int)} can be + * expected to return meaningful values. + */ + boolean isIndexed(); + + /** + * Returns {@code} true if {@link #getPropertyKeys()} and {@link #get(String)} + * can be expected to return meaningful values. + */ + boolean isKeyed(); + + /** + * Indicates if the nth element of a list is null or undefined. + */ + boolean isNull(int index); + + /** + * Indicates if the named property is null or undefined. + */ + boolean isNull(String key); + + /** + * Returns {@code true} if the value of the Splittable is numeric. + */ + boolean isNumber(); + + /** + * Returns {@code true} if {@link #setReified(String, Object)} has been called + * with the given key. + */ + boolean isReified(String key); + + /** + * Returns {@code} true if {@link #asString()} can be expected to return a + * meaningful value. + */ + boolean isString(); + + /** + * Returns {@code true} if the value of the key is undefined. + */ + boolean isUndefined(String key); + + /** + * Associates a tag value with the Splittable. + */ + void setReified(String key, Object object); + + /** + * Resets the length of an indexed Splittable. + */ + void setSize(int i); + + /** + * Returns the size of an indexed Splittable. + */ + int size(); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java new file mode 100644 index 0000000..4bcb304 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java
@@ -0,0 +1,368 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import com.google.web.bindery.autobean.shared.impl.StringQuoter; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Provides unified encoding and decoding of value objects. + */ +public class ValueCodex { + enum Type { + BIG_DECIMAL(BigDecimal.class) { + @Override + public boolean canUpcast(Object value) { + return value instanceof BigDecimal; + } + + @Override + public BigDecimal decode(Class<?> clazz, Splittable value) { + return new BigDecimal(value.asString()); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(((BigDecimal) value).toString()); + } + }, + BIG_INTEGER(BigInteger.class) { + @Override + public boolean canUpcast(Object value) { + return value instanceof BigInteger; + } + + @Override + public BigInteger decode(Class<?> clazz, Splittable value) { + return new BigInteger(value.asString()); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(((BigInteger) value).toString()); + } + }, + BOOLEAN(Boolean.class, boolean.class, false) { + @Override + public Boolean decode(Class<?> clazz, Splittable value) { + return value.asBoolean(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Boolean) value); + } + }, + BYTE(Byte.class, byte.class, (byte) 0) { + @Override + public Byte decode(Class<?> clazz, Splittable value) { + return (byte) value.asNumber(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Byte) value); + } + }, + CHARACTER(Character.class, char.class, (char) 0) { + @Override + public Character decode(Class<?> clazz, Splittable value) { + return value.asString().charAt(0); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(String.valueOf((Character) value)); + } + }, + DATE(Date.class) { + @Override + public boolean canUpcast(Object value) { + return value instanceof Date; + } + + @Override + public Date decode(Class<?> clazz, Splittable value) { + return StringQuoter.tryParseDate(value.asString()); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(String.valueOf(((Date) value).getTime())); + } + }, + DOUBLE(Double.class, double.class, 0d) { + @Override + public Double decode(Class<?> clazz, Splittable value) { + return value.asNumber(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Double) value); + } + }, + ENUM(Enum.class) { + @Override + public Enum<?> decode(Class<?> clazz, Splittable value) { + return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()]; + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(((Enum<?>) value).ordinal()); + } + }, + FLOAT(Float.class, float.class, 0f) { + @Override + public Float decode(Class<?> clazz, Splittable value) { + return (float) value.asNumber(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Float) value); + } + }, + INTEGER(Integer.class, int.class, 0) { + @Override + public Integer decode(Class<?> clazz, Splittable value) { + return Integer.valueOf((int) value.asNumber()); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Integer) value); + } + }, + LONG(Long.class, long.class, 0L) { + @Override + public Long decode(Class<?> clazz, Splittable value) { + return Long.parseLong(value.asString()); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create(String.valueOf((Long) value)); + } + }, + SHORT(Short.class, short.class, (short) 0) { + @Override + public Short decode(Class<?> clazz, Splittable value) { + return (short) value.asNumber(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((Short) value); + } + }, + STRING(String.class) { + @Override + public String decode(Class<?> clazz, Splittable value) { + return value.asString(); + } + + @Override + public Splittable encode(Object value) { + return StringQuoter.create((String) value); + } + }, + SPLITTABLE(Splittable.class) { + @Override + public Splittable decode(Class<?> clazz, Splittable value) { + return value; + } + + @Override + public Splittable encode(Object value) { + return (Splittable) value; + } + }, + VOID(Void.class, void.class, null) { + @Override + public Void decode(Class<?> clazz, Splittable value) { + return null; + } + + @Override + public Splittable encode(Object value) { + return null; + } + }; + private final Object defaultValue; + private final Class<?> type; + private final Class<?> primitiveType; + + Type(Class<?> objectType) { + this(objectType, null, null); + } + + Type(Class<?> objectType, Class<?> primitiveType, Object defaultValue) { + this.type = objectType; + this.primitiveType = primitiveType; + this.defaultValue = defaultValue; + } + + /** + * Determines whether or not the Type can handle the given value via + * upcasting semantics. + * + * @param value a value Object + */ + public boolean canUpcast(Object value) { + // Most value types are final, so this method is meaningless + return false; + } + + public abstract Object decode(Class<?> clazz, Splittable value); + + public abstract Splittable encode(Object value); + + public Object getDefaultValue() { + return defaultValue; + } + + public Class<?> getPrimitiveType() { + return primitiveType; + } + + public Class<?> getType() { + return type; + } + } + + private static final Set<Class<?>> ALL_VALUE_TYPES; + private static final Map<Class<?>, Type> TYPES_BY_CLASS; + static { + Map<Class<?>, Type> temp = new HashMap<Class<?>, Type>(); + for (Type t : Type.values()) { + temp.put(t.getType(), t); + if (t.getPrimitiveType() != null) { + temp.put(t.getPrimitiveType(), t); + } + } + ALL_VALUE_TYPES = Collections.unmodifiableSet(temp.keySet()); + TYPES_BY_CLASS = Collections.unmodifiableMap(temp); + } + + /** + * Returns true if ValueCodex can operate on values of the given type. + * + * @param clazz a Class object + * @return {@code true} if the given object type can be decoded + */ + public static boolean canDecode(Class<?> clazz) { + if (findType(clazz) != null) { + return true; + } + // Use other platform-specific tests + return ValueCodexHelper.canDecode(clazz); + } + + @SuppressWarnings("unchecked") + public static <T> T decode(Class<T> clazz, Splittable split) { + if (split == null || split == Splittable.NULL) { + return null; + } + return (T) getTypeOrDie(clazz).decode(clazz, split); + } + + /** + * No callers in GWT codebase. + * + * @deprecated use {@link #decode(Class, Splittable)} instead. + * @throws UnsupportedOperationException + */ + @Deprecated + public static <T> T decode(Class<T> clazz, String string) { + throw new UnsupportedOperationException(); + } + + /** + * Encode a value object when the wire format type is known. This method + * should be preferred over {@link #encode(Object)} when possible. + */ + public static Splittable encode(Class<?> clazz, Object obj) { + if (obj == null) { + return Splittable.NULL; + } + return getTypeOrDie(clazz).encode(obj); + } + + public static Splittable encode(Object obj) { + if (obj == null) { + return Splittable.NULL; + } + Type t = findType(obj.getClass()); + // Try upcasting + if (t == null) { + for (Type maybe : Type.values()) { + if (maybe.canUpcast(obj)) { + t = maybe; + break; + } + } + } + if (t == null) { + throw new UnsupportedOperationException(obj.getClass().getName()); + } + return t.encode(obj); + } + + /** + * Return all Value types that can be processed by the ValueCodex. + */ + public static Set<Class<?>> getAllValueTypes() { + return ALL_VALUE_TYPES; + } + + /** + * Returns the uninitialized field value for the given primitive type. + */ + public static Object getUninitializedFieldValue(Class<?> clazz) { + Type type = getTypeOrDie(clazz); + if (clazz.equals(type.getPrimitiveType())) { + return type.getDefaultValue(); + } + return null; + } + + /** + * May return <code>null</code>. + */ + private static <T> Type findType(Class<T> clazz) { + if (clazz.isEnum()) { + return Type.ENUM; + } + return TYPES_BY_CLASS.get(clazz); + } + + private static <T> Type getTypeOrDie(Class<T> clazz) { + Type toReturn = findType(clazz); + if (toReturn == null) { + throw new UnsupportedOperationException(clazz.getName()); + } + return toReturn; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java new file mode 100644 index 0000000..fb9fcc9 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,38 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared; + +import com.google.gwt.core.client.GWT; + +/** + * Provides reflection-based operation for server (JVM) implementation. There is + * a no-op super-source version for client (dev- and web-mode) code. + */ +class ValueCodexHelper { + /** + * Returns {@code true} if {@code clazz} is assignable to any of the value + * types. + */ + static boolean canDecode(Class<?> clazz) { + assert !GWT.isClient(); + for (Class<?> valueType : ValueCodex.getAllValueTypes()) { + if (valueType.isAssignableFrom(clazz)) { + return true; + } + } + return false; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java new file mode 100644 index 0000000..4e801d9 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
@@ -0,0 +1,291 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.Context; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; +import com.google.gwt.core.client.impl.WeakMapping; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Basic implementation. + * + * @param <T> the wrapper type + */ +public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable { + /** + * Used to avoid cycles when visiting. + */ + public static class OneShotContext implements Context { + private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>(); + + public boolean hasSeen(AbstractAutoBean<?> bean) { + return !seen.add(bean); + } + } + + public static final String UNSPLITTABLE_VALUES_KEY = "__unsplittableValues"; + protected static final Object[] EMPTY_OBJECT = new Object[0]; + + /** + * Used by {@link #createSimplePeer()}. + */ + protected Splittable data; + protected T wrapped; + private final AutoBeanFactory factory; + private boolean frozen; + /** + * Lazily initialized by {@link #setTag(String, Object)} because not all + * instances will make use of tags. + */ + private Map<String, Object> tags; + private final boolean usingSimplePeer; + + /** + * Constructor that will use a generated simple peer. + */ + protected AbstractAutoBean(AutoBeanFactory factory) { + this(factory, StringQuoter.createSplittable()); + } + + /** + * Constructor that will use a generated simple peer, backed with existing + * data. + */ + protected AbstractAutoBean(AutoBeanFactory factory, Splittable data) { + this.data = data; + this.factory = factory; + usingSimplePeer = true; + wrapped = createSimplePeer(); + } + + /** + * Constructor that wraps an existing object. The parameters on this method + * are reversed to avoid conflicting with the other two-arg constructor for + * {@code AutoBean<Splittable>} instances. + */ + protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) { + this.factory = factory; + usingSimplePeer = false; + data = null; + this.wrapped = wrapped; + + // Used by AutoBeanUtils + WeakMapping.set(wrapped, AutoBean.class.getName(), this); + } + + public void accept(AutoBeanVisitor visitor) { + traverse(visitor, new OneShotContext()); + } + + public abstract T as(); + + public AutoBean<T> clone(boolean deep) { + throw new UnsupportedOperationException(); + } + + public AutoBeanFactory getFactory() { + return factory; + } + + public Splittable getSplittable() { + return data; + } + + @SuppressWarnings("unchecked") + public <Q> Q getTag(String tagName) { + return tags == null ? null : (Q) tags.get(tagName); + } + + /** + * Indicates that the value returned from {@link #getSplittable()} may not + * contain all of the data encapsulated by the AutoBean. + */ + public boolean hasUnsplittableValues() { + return data.isReified(UNSPLITTABLE_VALUES_KEY); + } + + public boolean isFrozen() { + return frozen; + } + + public boolean isWrapper() { + return !usingSimplePeer; + } + + public void setData(Splittable data) { + assert data != null : "null data"; + this.data = data; + /* + * The simple peer aliases the data object from the enclosing bean to avoid + * needing to call up the this.this$0 chain. + */ + wrapped = createSimplePeer(); + } + + public void setFrozen(boolean frozen) { + this.frozen = frozen; + } + + public void setTag(String tagName, Object value) { + if (tags == null) { + tags = new HashMap<String, Object>(); + } + tags.put(tagName, value); + } + + public void traverse(AutoBeanVisitor visitor, OneShotContext ctx) { + // Avoid cycles + if (ctx.hasSeen(this)) { + return; + } + if (visitor.visit(this, ctx)) { + traverseProperties(visitor, ctx); + } + visitor.endVisit(this, ctx); + } + + public T unwrap() { + if (usingSimplePeer) { + throw new IllegalStateException(); + } + try { + WeakMapping.set(wrapped, AutoBean.class.getName(), null); + return wrapped; + } finally { + wrapped = null; + } + } + + /** + * No-op. Used as a debugger hook point for generated code. + * + * @param method the method name + * @param returned the returned object + * @param parameters the parameter list + */ + protected void call(String method, Object returned, Object... parameters) { + } + + protected void checkFrozen() { + if (frozen) { + throw new IllegalStateException("The AutoBean has been frozen"); + } + } + + protected void checkWrapped() { + if (wrapped == null && !usingSimplePeer) { + throw new IllegalStateException("The AutoBean has been unwrapped"); + } + } + + protected T createSimplePeer() { + throw new UnsupportedOperationException(); + } + + /** + * No-op. Used as a debugger hook point for generated code. + * + * @param method the method name + * @param toReturn the value to return + */ + protected <V> V get(String method, V toReturn) { + return toReturn; + } + + protected <W> W getFromWrapper(W obj) { + // Some versions of javac have problem inferring the generics here + return AutoBeanUtils.<W, W> getAutoBean(obj).as(); + } + + /** + * Native getters and setters for primitive properties are generated for each + * type to ensure inlining. + */ + protected <Q> Q getOrReify(String propertyName) { + checkWrapped(); + if (data.isReified(propertyName)) { + @SuppressWarnings("unchecked") + Q temp = (Q) data.getReified(propertyName); + return temp; + } + if (data.isNull(propertyName)) { + return null; + } + data.setReified(propertyName, null); + Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName); + @SuppressWarnings("unchecked") + Q toReturn = (Q) coder.decode(EncodeState.forDecode(factory), data.get(propertyName)); + data.setReified(propertyName, toReturn); + return toReturn; + } + + protected T getWrapped() { + checkWrapped(); + return wrapped; + } + + protected boolean isUsingSimplePeer() { + return usingSimplePeer; + } + + protected boolean isWrapped(Object obj) { + return AutoBeanUtils.getAutoBean(obj) != null; + } + + /** + * No-op. Used as a debugger hook point for generated code. + * + * @param method the method name + * @param value the Object value to be set + */ + protected void set(String method, Object value) { + } + + protected void setProperty(String propertyName, Object value) { + checkWrapped(); + checkFrozen(); + data.setReified(propertyName, value); + if (value == null) { + Splittable.NULL.assign(data, propertyName); + return; + } + Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName); + Splittable backing = coder.extractSplittable(EncodeState.forDecode(factory), value); + if (backing == null) { + /* + * External data type, such as an ArrayList or a concrete implementation + * of a setter's interface type. This means that a slow serialization pass + * is necessary. + */ + data.setReified(UNSPLITTABLE_VALUES_KEY, true); + } else { + backing.assign(data, propertyName); + } + } + + protected abstract void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java new file mode 100644 index 0000000..78b4f2e --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java
@@ -0,0 +1,613 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * Contains the implementation details of AutoBeanCodex. This type was factored + * out of AutoBeanCodex so that various implementation details can be accessed + * without polluting a public API. + */ +public class AutoBeanCodexImpl { + + /** + * Describes a means of encoding or decoding a particular type of data to or + * from a wire format representation. Any given instance of a Coder should be + * stateless; any state required for operation must be maintained in an + * {@link EncodeState}. + */ + public interface Coder { + Object decode(EncodeState state, Splittable data); + + void encode(EncodeState state, Object value); + + Splittable extractSplittable(EncodeState state, Object value); + } + + /** + * Contains transient state for Coder operation. + */ + public static class EncodeState { + /** + * Constructs a state object used for decoding payloads. + */ + public static EncodeState forDecode(AutoBeanFactory factory) { + return new EncodeState(factory, null); + } + + /** + * Constructs a state object used for encoding payloads. + */ + public static EncodeState forEncode(AutoBeanFactory factory, StringBuilder sb) { + return new EncodeState(factory, sb); + } + + /** + * Constructs a "stateless" state for testing Coders that do not require + * AutoBean implementation details. + */ + public static EncodeState forTesting() { + return new EncodeState(null, null); + } + + final EnumMap enumMap; + final AutoBeanFactory factory; + final StringBuilder sb; + final Stack<AutoBean<?>> seen; + + private EncodeState(AutoBeanFactory factory, StringBuilder sb) { + this.factory = factory; + enumMap = factory instanceof EnumMap ? (EnumMap) factory : null; + this.sb = sb; + this.seen = sb == null ? null : new Stack<AutoBean<?>>(); + } + } + + /** + * Dynamically creates a Coder that is capable of operating on a particular + * parameterization of a datastructure (e.g. {@code Map<String, List<String>>} + * ). + */ + static class CoderCreator extends ParameterizationVisitor { + private Stack<Coder> stack = new Stack<Coder>(); + + @Override + public void endVisitType(Class<?> type) { + if (List.class.equals(type) || Set.class.equals(type)) { + stack.push(collectionCoder(type, stack.pop())); + } else if (Map.class.equals(type)) { + // Note that the parameters are passed in reverse order + stack.push(mapCoder(stack.pop(), stack.pop())); + } else if (Splittable.class.equals(type)) { + stack.push(splittableCoder()); + } else if (type.getEnumConstants() != null) { + @SuppressWarnings(value = {"unchecked"}) + Class<Enum<?>> enumType = (Class<Enum<?>>) type; + stack.push(enumCoder(enumType)); + } else if (ValueCodex.canDecode(type)) { + stack.push(valueCoder(type)); + } else { + stack.push(objectCoder(type)); + } + } + + public Coder getCoder() { + assert stack.size() == 1 : "Incorrect size: " + stack.size(); + return stack.pop(); + } + } + + /** + * Constructs one of the lightweight collection types. + */ + static class CollectionCoder implements Coder { + private final Coder elementDecoder; + private final Class<?> type; + + public CollectionCoder(Class<?> type, Coder elementDecoder) { + this.elementDecoder = elementDecoder; + this.type = type; + } + + public Object decode(EncodeState state, Splittable data) { + Collection<Object> collection; + if (List.class.equals(type)) { + collection = new SplittableList<Object>(data, elementDecoder, state); + } else if (Set.class.equals(type)) { + collection = new SplittableSet<Object>(data, elementDecoder, state); + } else { + // Should not reach here + throw new RuntimeException(type.getName()); + } + return collection; + } + + public void encode(EncodeState state, Object value) { + if (value == null) { + state.sb.append("null"); + return; + } + + Iterator<?> it = ((Collection<?>) value).iterator(); + state.sb.append("["); + if (it.hasNext()) { + elementDecoder.encode(state, it.next()); + while (it.hasNext()) { + state.sb.append(","); + elementDecoder.encode(state, it.next()); + } + } + state.sb.append("]"); + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return tryExtractSplittable(value); + } + } + + /** + * Produces enums. + * + * @param <E> + */ + static class EnumCoder<E extends Enum<?>> implements Coder { + private final Class<E> type; + + public EnumCoder(Class<E> type) { + this.type = type; + } + + public Object decode(EncodeState state, Splittable data) { + return state.enumMap.getEnum(type, data.asString()); + } + + public void encode(EncodeState state, Object value) { + if (value == null) { + state.sb.append("null"); + return; + } + state.sb.append(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value))); + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return StringQuoter.split(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value))); + } + } + + /** + * Used to stop processing. + */ + static class HaltException extends RuntimeException { + public HaltException(RuntimeException cause) { + super(cause); + } + + @Override + public RuntimeException getCause() { + return (RuntimeException) super.getCause(); + } + } + + /** + * Constructs one of the lightweight Map types, depending on the key type. + */ + static class MapCoder implements Coder { + private final Coder keyDecoder; + private final Coder valueDecoder; + + /** + * Parameters in reversed order to accommodate stack-based setup. + */ + public MapCoder(Coder valueDecoder, Coder keyDecoder) { + this.keyDecoder = keyDecoder; + this.valueDecoder = valueDecoder; + } + + public Object decode(EncodeState state, Splittable data) { + Map<Object, Object> toReturn; + if (data.isIndexed()) { + assert data.size() == 2 : "Wrong data size: " + data.size(); + toReturn = new SplittableComplexMap<Object, Object>(data, keyDecoder, valueDecoder, state); + } else { + toReturn = new SplittableSimpleMap<Object, Object>(data, keyDecoder, valueDecoder, state); + } + return toReturn; + } + + public void encode(EncodeState state, Object value) { + if (value == null) { + state.sb.append("null"); + return; + } + + Map<?, ?> map = (Map<?, ?>) value; + boolean isSimpleMap = keyDecoder instanceof ValueCoder; + if (isSimpleMap) { + boolean first = true; + state.sb.append("{"); + for (Map.Entry<?, ?> entry : map.entrySet()) { + Object mapKey = entry.getKey(); + if (mapKey == null) { + // A null key in a simple map is meaningless + continue; + } + Object mapValue = entry.getValue(); + + if (first) { + first = false; + } else { + state.sb.append(","); + } + + keyDecoder.encode(state, mapKey); + state.sb.append(":"); + if (mapValue == null) { + // Null values must be preserved + state.sb.append("null"); + } else { + valueDecoder.encode(state, mapValue); + } + } + state.sb.append("}"); + } else { + List<Object> keys = new ArrayList<Object>(map.size()); + List<Object> values = new ArrayList<Object>(map.size()); + for (Map.Entry<?, ?> entry : map.entrySet()) { + keys.add(entry.getKey()); + values.add(entry.getValue()); + } + state.sb.append("["); + collectionCoder(List.class, keyDecoder).encode(state, keys); + state.sb.append(","); + collectionCoder(List.class, valueDecoder).encode(state, values); + state.sb.append("]"); + } + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return tryExtractSplittable(value); + } + } + + /** + * Recurses into {@link AutoBeanCodexImpl}. + */ + static class ObjectCoder implements Coder { + private final Class<?> type; + + public ObjectCoder(Class<?> type) { + this.type = type; + } + + public Object decode(EncodeState state, Splittable data) { + AutoBean<?> bean = doDecode(state, type, data); + return bean == null ? null : bean.as(); + } + + public void encode(EncodeState state, Object value) { + if (value == null) { + state.sb.append("null"); + return; + } + doEncode(state, AutoBeanUtils.getAutoBean(value)); + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return tryExtractSplittable(value); + } + } + + static class PropertyCoderCreator extends AutoBeanVisitor { + private AutoBean<?> bean; + + @Override + public boolean visit(AutoBean<?> bean, Context ctx) { + this.bean = bean; + return true; + } + + @Override + public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, + PropertyContext ctx) { + maybeCreateCoder(propertyName, ctx); + return false; + } + + @Override + public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) { + maybeCreateCoder(propertyName, ctx); + return false; + } + + private void maybeCreateCoder(String propertyName, PropertyContext ctx) { + CoderCreator creator = new CoderCreator(); + ctx.accept(creator); + coderFor.put(key(bean, propertyName), creator.getCoder()); + } + } + + /** + * Extracts properties from a bean and turns them into JSON text. + */ + static class PropertyGetter extends AutoBeanVisitor { + private boolean first = true; + private final EncodeState state; + + public PropertyGetter(EncodeState state) { + this.state = state; + } + + @Override + public void endVisit(AutoBean<?> bean, Context ctx) { + state.sb.append("}"); + state.seen.pop(); + } + + @Override + public boolean visit(AutoBean<?> bean, Context ctx) { + if (state.seen.contains(bean)) { + throw new HaltException(new UnsupportedOperationException("Cycles not supported")); + } + state.seen.push(bean); + state.sb.append("{"); + return true; + } + + @Override + public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, + PropertyContext ctx) { + if (value != null) { + encodeProperty(propertyName, value.as(), ctx); + } + return false; + } + + @Override + public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) { + if (value != null && !value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) { + encodeProperty(propertyName, value, ctx); + } + return false; + } + + private void encodeProperty(String propertyName, Object value, PropertyContext ctx) { + CoderCreator pd = new CoderCreator(); + ctx.accept(pd); + Coder decoder = pd.getCoder(); + if (first) { + first = false; + } else { + state.sb.append(","); + } + state.sb.append(StringQuoter.quote(propertyName)); + state.sb.append(":"); + decoder.encode(state, value); + } + } + + /** + * Populates beans with data extracted from an evaluated JSON payload. + */ + static class PropertySetter extends AutoBeanVisitor { + private Splittable data; + private EncodeState state; + + public void decodeInto(EncodeState state, Splittable data, AutoBean<?> bean) { + this.data = data; + this.state = state; + bean.accept(this); + } + + @Override + public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, + PropertyContext ctx) { + decodeProperty(propertyName, ctx); + return false; + } + + @Override + public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) { + decodeProperty(propertyName, ctx); + return false; + } + + protected void decodeProperty(String propertyName, PropertyContext ctx) { + if (!data.isNull(propertyName)) { + CoderCreator pd = new CoderCreator(); + ctx.accept(pd); + Coder decoder = pd.getCoder(); + Object propertyValue = decoder.decode(state, data.get(propertyName)); + ctx.set(propertyValue); + } + } + } + + /** + * A passthrough Coder. + */ + static class SplittableCoder implements Coder { + static final Coder INSTANCE = new SplittableCoder(); + + public Object decode(EncodeState state, Splittable data) { + return data; + } + + public void encode(EncodeState state, Object value) { + if (value == null) { + state.sb.append("null"); + return; + } + state.sb.append(((Splittable) value).getPayload()); + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return (Splittable) value; + } + } + + /** + * Delegates to ValueCodex. + */ + static class ValueCoder implements Coder { + private final Class<?> type; + + public ValueCoder(Class<?> type) { + assert type.getEnumConstants() == null : "Should use EnumTypeCodex"; + this.type = type; + } + + public Object decode(EncodeState state, Splittable propertyValue) { + if (propertyValue == null || propertyValue == Splittable.NULL) { + return ValueCodex.getUninitializedFieldValue(type); + } + return ValueCodex.decode(type, propertyValue); + } + + public void encode(EncodeState state, Object value) { + state.sb.append(ValueCodex.encode(type, value).getPayload()); + } + + public Splittable extractSplittable(EncodeState state, Object value) { + return ValueCodex.encode(type, value); + } + } + + /** + * A map of AutoBean interface+property names to the Coder for that property. + */ + private static final Map<String, Coder> coderFor = new HashMap<String, Coder>(); + /** + * A map of types to a Coder that handles the type. + */ + private static final Map<Class<?>, Coder> coders = new HashMap<Class<?>, Coder>(); + + public static Coder collectionCoder(Class<?> type, Coder elementCoder) { + return new CollectionCoder(type, elementCoder); + } + + public static Coder doCoderFor(AutoBean<?> bean, String propertyName) { + String key = key(bean, propertyName); + Coder toReturn = coderFor.get(key); + if (toReturn == null) { + bean.accept(new PropertyCoderCreator()); + toReturn = coderFor.get(key); + if (toReturn == null) { + throw new IllegalArgumentException(propertyName); + } + } + return toReturn; + } + + public static <T> AutoBean<T> doDecode(EncodeState state, Class<T> clazz, Splittable data) { + /* + * If we decode the same Splittable twice, re-use the ProxyAutoBean to + * maintain referential integrity. If we didn't do this, either facade would + * update the same backing data, yet not be the same object via == + * comparison. + */ + @SuppressWarnings("unchecked") + AutoBean<T> toReturn = (AutoBean<T>) data.getReified(AutoBeanCodexImpl.class.getName()); + if (toReturn != null) { + return toReturn; + } + toReturn = state.factory.create(clazz); + data.setReified(AutoBeanCodexImpl.class.getName(), toReturn); + if (toReturn == null) { + throw new IllegalArgumentException(clazz.getName()); + } + ((AbstractAutoBean<T>) toReturn).setData(data); + return toReturn; + } + + public static void doDecodeInto(EncodeState state, Splittable data, AutoBean<?> bean) { + new PropertySetter().decodeInto(state, data, bean); + } + + public static void doEncode(EncodeState state, AutoBean<?> bean) { + PropertyGetter e = new PropertyGetter(state); + try { + bean.accept(e); + } catch (HaltException ex) { + throw ex.getCause(); + } + } + + public static <E extends Enum<?>> Coder enumCoder(Class<E> type) { + Coder toReturn = coders.get(type); + if (toReturn == null) { + toReturn = new EnumCoder<E>(type); + coders.put(type, toReturn); + } + return toReturn; + } + + public static Coder mapCoder(Coder valueCoder, Coder keyCoder) { + return new MapCoder(valueCoder, keyCoder); + } + + public static Coder objectCoder(Class<?> type) { + Coder toReturn = coders.get(type); + if (toReturn == null) { + toReturn = new ObjectCoder(type); + coders.put(type, toReturn); + } + return toReturn; + } + + public static Coder splittableCoder() { + return SplittableCoder.INSTANCE; + } + + public static Coder valueCoder(Class<?> type) { + Coder toReturn = coders.get(type); + if (toReturn == null) { + toReturn = new ValueCoder(type); + coders.put(type, toReturn); + } + return toReturn; + } + + static Splittable tryExtractSplittable(Object value) { + AutoBean<?> bean = AutoBeanUtils.getAutoBean(value); + if (bean != null) { + value = bean; + } + if (bean instanceof HasSplittable) { + return ((HasSplittable) bean).getSplittable(); + } + return null; + } + + private static String key(AutoBean<?> bean, String propertyName) { + return bean.getType().getName() + ":" + propertyName; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java new file mode 100644 index 0000000..8356831 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java
@@ -0,0 +1,33 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +/** + * This interface is implemented by our generated AutoBeanFactory types to + * convert enum values to strings. + */ +public interface EnumMap { + /** + * Extra enums that should be included in the AutoBeanFactory. + */ + public @interface ExtraEnums { + Class<? extends Enum<?>>[] value(); + } + + <E extends Enum<?>> E getEnum(Class<E> clazz, String token); + + String getToken(Enum<?> e); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java new file mode 100644 index 0000000..41b39d2 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java
@@ -0,0 +1,26 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; + +/** + * Allows reified type facades to return their underlying Splittable + * datastructure. + */ +public interface HasSplittable { + Splittable getSplittable(); +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java new file mode 100644 index 0000000..c118d29 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java
@@ -0,0 +1,200 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +/** + * A Map implementation for complex keys. + * + * @param <K> the key type + * @param <V> the value type + */ +public class SplittableComplexMap<K, V> implements Map<K, V>, HasSplittable { + private final Splittable data; + private final List<K> keys; + private final List<V> values; + + public SplittableComplexMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) { + this.data = data; + this.keys = new SplittableList<K>(data.get(0), keyCoder, state); + this.values = new SplittableList<V>(data.get(1), valueCoder, state); + assert this.keys.size() == this.values.size(); + } + + public void clear() { + // Trigger ConcurrentModificationExceptions for any outstanding Iterators + keys.clear(); + values.clear(); + } + + public boolean containsKey(Object key) { + return keys.contains(key); + } + + public boolean containsValue(Object value) { + return values.contains(value); + } + + public Set<java.util.Map.Entry<K, V>> entrySet() { + return new AbstractSet<Map.Entry<K, V>>() { + + @Override + public Iterator<java.util.Map.Entry<K, V>> iterator() { + return new Iterator<Map.Entry<K, V>>() { + Iterator<K> keyIt = keys.iterator(); + ListIterator<V> valueIt = values.listIterator(); + + public boolean hasNext() { + assert keyIt.hasNext() == valueIt.hasNext(); + return keyIt.hasNext(); + } + + public java.util.Map.Entry<K, V> next() { + return new Map.Entry<K, V>() { + final K key = keyIt.next(); + final V value = valueIt.next(); + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + valueIt.set(value); + return value; + } + }; + } + + public void remove() { + keyIt.remove(); + valueIt.remove(); + } + }; + } + + @Override + public int size() { + return keys.size(); + } + }; + } + + public V get(Object key) { + int idx = keys.indexOf(key); + if (idx == -1) { + return null; + } + return values.get(idx); + } + + public Splittable getSplittable() { + return data; + } + + public boolean isEmpty() { + return keys.isEmpty(); + } + + public Set<K> keySet() { + return new AbstractSet<K>() { + @Override + public Iterator<K> iterator() { + return keys.iterator(); + } + + @Override + public int size() { + return keys.size(); + } + }; + } + + public V put(K key, V value) { + int idx = keys.indexOf(key); + if (idx == -1) { + keys.add(key); + values.add(value); + return null; + } + return values.set(idx, value); + } + + public void putAll(Map<? extends K, ? extends V> m) { + for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public V remove(Object key) { + int idx = keys.indexOf(key); + if (idx == -1) { + return null; + } + keys.remove(idx); + return values.remove(idx); + } + + public int size() { + return keys.size(); + } + + public Collection<V> values() { + return new AbstractCollection<V>() { + @Override + public Iterator<V> iterator() { + return new Iterator<V>() { + final Iterator<K> keyIt = keys.iterator(); + final Iterator<V> valueIt = values.iterator(); + + public boolean hasNext() { + return keyIt.hasNext(); + } + + public V next() { + keyIt.next(); + return valueIt.next(); + } + + public void remove() { + keyIt.remove(); + valueIt.remove(); + } + }; + } + + @Override + public int size() { + return keys.size(); + } + }; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java new file mode 100644 index 0000000..48fe0f6 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java
@@ -0,0 +1,114 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; + +import java.util.AbstractList; + +/** + * A list implementation that lazily reifies its constituent elements. + * + * @param <E> the element type + */ +public class SplittableList<E> extends AbstractList<E> implements HasSplittable { + static <Q> Q reify(EncodeState state, Splittable data, int index, Coder coder) { + if (data.isNull(index)) { + return null; + } + @SuppressWarnings("unchecked") + Q toReturn = (Q) coder.decode(state, data.get(index)); + data.setReified(String.valueOf(index), toReturn); + return toReturn; + } + + static void set(EncodeState state, Splittable data, int index, Coder coder, Object value) { + data.setReified(String.valueOf(index), value); + if (value == null) { + Splittable.NULL.assign(data, index); + return; + } + Splittable backing = coder.extractSplittable(state, value); + if (backing == null) { + /* + * External data type, such as an ArrayList or a concrete implementation + * of a setter's interface type. This means that a slow serialization pass + * is necessary. + */ + data.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true); + } else { + backing.assign(data, index); + } + } + + private Splittable data; + private final Coder elementCoder; + private final EncodeState state; + + public SplittableList(Splittable data, Coder elementCoder, EncodeState state) { + assert data.isIndexed() : "Expecting indexed data"; + this.data = data; + this.elementCoder = elementCoder; + this.state = state; + } + + @Override + public void add(int index, E element) { + set(state, data, index, elementCoder, element); + } + + @Override + public E get(int index) { + if (data.isReified(String.valueOf(index))) { + @SuppressWarnings("unchecked") + E toReturn = (E) data.getReified(String.valueOf(index)); + return toReturn; + } + // javac generics bug + return SplittableList.<E> reify(state, data, index, elementCoder); + } + + public Splittable getSplittable() { + return data; + } + + @Override + public E remove(int index) { + E toReturn = get(index); + // XXX This is terrible, use Array.splice + int newSize = data.size() - 1; + for (int i = index; i < newSize; i++) { + data.get(i + 1).assign(data, i); + data.setReified(String.valueOf(i), data.getReified(String.valueOf(i + 1))); + } + data.setSize(newSize); + return toReturn; + } + + @Override + public E set(int index, E element) { + E previous = get(index); + set(state, data, index, elementCoder, element); + return previous; + } + + @Override + public int size() { + return data.size(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java new file mode 100644 index 0000000..9b6d8ab --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java
@@ -0,0 +1,71 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; + +import java.util.AbstractSet; +import java.util.Iterator; + +/** + * This type is optimized for the read-only case and has {@code O(n)} insertion + * / lookup performance since computing hashcodes for the elements would require + * up-front reification. + * + * @param <E> the element type + */ +public class SplittableSet<E> extends AbstractSet<E> implements HasSplittable { + private SplittableList<E> data; + + public SplittableSet(Splittable data, Coder elementCoder, EncodeState state) { + this.data = new SplittableList<E>(data, elementCoder, state); + } + + @Override + public boolean add(E e) { + if (!data.contains(e)) { + data.add(e); + return true; + } + return false; + } + + @Override + public void clear() { + data.clear(); + } + + public Splittable getSplittable() { + return data.getSplittable(); + } + + @Override + public Iterator<E> iterator() { + return data.iterator(); + } + + @Override + public boolean remove(Object o) { + return data.remove(o); + } + + @Override + public int size() { + return data.size(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java new file mode 100644 index 0000000..fc4dc0f --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java
@@ -0,0 +1,255 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; +import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A Map implementation for regular JSON maps with value-type keys. + * + * @param <K> the key type + * @param <V> the value type + */ +public class SplittableSimpleMap<K, V> implements Map<K, V>, HasSplittable { + private final Splittable data; + private final Coder keyCoder; + private final EncodeState state; + private final Coder valueCoder; + /** + * Don't hang the reified data from {@link #data} since we can't tell the + * __reified field from the actual data. + */ + private Splittable reified = StringQuoter.createSplittable(); + + public SplittableSimpleMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) { + this.data = data; + this.keyCoder = keyCoder; + this.state = state; + this.valueCoder = valueCoder; + } + + public void clear() { + for (String key : data.getPropertyKeys()) { + Splittable.NULL.assign(data, key); + reified.setReified(key, null); + } + } + + public boolean containsKey(Object key) { + String encodedKey = encodedKey(key); + return !data.isUndefined(encodedKey) || reified.isReified(encodedKey); + } + + public boolean containsValue(Object value) { + return values().contains(value); + } + + public Set<java.util.Map.Entry<K, V>> entrySet() { + return new AbstractSet<Map.Entry<K, V>>() { + final List<String> keys = data.getPropertyKeys(); + + @Override + public Iterator<java.util.Map.Entry<K, V>> iterator() { + return new Iterator<Map.Entry<K, V>>() { + Iterator<String> keyIterator = keys.iterator(); + String encodedKey; + + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public java.util.Map.Entry<K, V> next() { + encodedKey = keyIterator.next(); + return new Map.Entry<K, V>() { + @SuppressWarnings("unchecked") + final K key = (K) keyCoder.decode(state, StringQuoter.split(StringQuoter + .quote(encodedKey))); + @SuppressWarnings("unchecked") + final V value = (V) valueCoder.decode(state, data.get(encodedKey)); + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V newValue) { + return put(key, newValue); + } + }; + } + + public void remove() { + Splittable.NULL.assign(data, encodedKey); + reified.setReified(encodedKey, null); + } + }; + } + + @Override + public int size() { + return keys.size(); + } + }; + } + + public V get(Object key) { + String encodedKey = encodedKey(key); + return getRaw(encodedKey); + } + + public Splittable getSplittable() { + return data; + } + + public boolean isEmpty() { + return data.getPropertyKeys().isEmpty(); + } + + public Set<K> keySet() { + return new AbstractSet<K>() { + final List<String> keys = data.getPropertyKeys(); + + @Override + public Iterator<K> iterator() { + return new Iterator<K>() { + final Iterator<String> it = keys.iterator(); + String lastEncodedKey; + + public boolean hasNext() { + return it.hasNext(); + } + + public K next() { + lastEncodedKey = it.next(); + @SuppressWarnings("unchecked") + K toReturn = + (K) keyCoder.decode(state, StringQuoter.split(StringQuoter.quote(lastEncodedKey))); + return toReturn; + } + + public void remove() { + Splittable.NULL.assign(data, lastEncodedKey); + reified.setReified(lastEncodedKey, null); + } + }; + } + + @Override + public int size() { + return keys.size(); + } + }; + } + + public V put(K key, V value) { + V toReturn = get(key); + String encodedKey = encodedKey(key); + reified.setReified(encodedKey, value); + Splittable encodedValue = valueCoder.extractSplittable(state, value); + if (encodedValue == null) { + // External datastructure + reified.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true); + } else { + encodedValue.assign(data, encodedKey); + } + return toReturn; + } + + public void putAll(Map<? extends K, ? extends V> m) { + for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + public V remove(Object key) { + V toReturn = get(key); + String encodedKey = encodedKey(key); + reified.setReified(encodedKey, null); + Splittable.NULL.assign(data, encodedKey); + return toReturn; + } + + public int size() { + return data.getPropertyKeys().size(); + } + + public Collection<V> values() { + return new AbstractCollection<V>() { + final List<String> keys = data.getPropertyKeys(); + + @Override + public Iterator<V> iterator() { + return new Iterator<V>() { + final Iterator<String> it = keys.iterator(); + String lastEncodedKey; + + public boolean hasNext() { + return it.hasNext(); + } + + public V next() { + lastEncodedKey = it.next(); + return getRaw(lastEncodedKey); + } + + public void remove() { + Splittable.NULL.assign(data, lastEncodedKey); + reified.setReified(lastEncodedKey, null); + } + }; + } + + @Override + public int size() { + return keys.size(); + } + }; + } + + private String encodedKey(Object key) { + return keyCoder.extractSplittable(state, key).asString(); + } + + private V getRaw(String encodedKey) { + if (reified.isReified(encodedKey)) { + @SuppressWarnings("unchecked") + V toReturn = (V) reified.getReified(encodedKey); + return toReturn; + } + // Both undefined or an explicit null should return null here + if (data.isNull(encodedKey)) { + return null; + } + Splittable value = data.get(encodedKey); + @SuppressWarnings("unchecked") + V toReturn = (V) valueCoder.decode(state, value); + reified.setReified(encodedKey, toReturn); + return toReturn; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java new file mode 100644 index 0000000..d30fb3e --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,98 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.shared.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.vm.impl.JsonSplittable; + +import org.json.JSONObject; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * This class has a super-source version with a client-only implementation. + */ +public class StringQuoter { + private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSz"; + private static final DateFormat ISO8601 = new SimpleDateFormat(ISO8601_PATTERN, Locale + .getDefault()); + + private static final String RFC2822_PATTERN = "EEE, d MMM yyyy HH:mm:ss Z"; + private static final DateFormat RFC2822 = new SimpleDateFormat(RFC2822_PATTERN, Locale + .getDefault()); + + public static Splittable create(boolean value) { + return JsonSplittable.create(String.valueOf(value)); + } + + public static Splittable create(double value) { + return JsonSplittable.create(String.valueOf(value)); + } + + public static Splittable create(String value) { + return JsonSplittable.create(quote(value)); + } + + public static Splittable createIndexed() { + return JsonSplittable.createIndexed(); + } + + public static Splittable createSplittable() { + return JsonSplittable.create(); + } + + public static Splittable nullValue() { + return JsonSplittable.createNull(); + } + + /** + * Create a quoted JSON string. + */ + public static String quote(String raw) { + return JSONObject.quote(raw); + } + + public static Splittable split(String payload) { + return JsonSplittable.create(payload); + } + + /** + * Attempt to parse an ISO-8601 date format. May return {@code null} if the + * input cannot be parsed. + */ + public static Date tryParseDate(String date) { + try { + return new Date(Long.parseLong(date)); + } catch (NumberFormatException ignored) { + } + if (date.endsWith("Z")) { + date = date.substring(0, date.length() - 1) + "+0000"; + } + try { + return ISO8601.parse(date); + } catch (ParseException ignored) { + } + try { + return RFC2822.parse(date); + } catch (ParseException ignored) { + } + return null; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/shared/package-info.java b/user/src/com/google/web/bindery/autobean/shared/package-info.java new file mode 100644 index 0000000..4dec6fa --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/shared/package-info.java
@@ -0,0 +1,31 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * The AutoBean framework provides automatically-generated implementations of + * bean-like interfaces and a low-level serialization mechanism for those + * interfaces. AutoBeans can be used in both client and server code to improve + * code re-use. + * + * @see <a + * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean + * wiki page</a> + * @see com.google.web.bindery.autobean.shared.AutoBeanFactory + * @see com.google.web.bindery.autobean.vm.AutoBeanFactorySource + */ +@com.google.gwt.util.PreventSpuriousRebuilds +package com.google.web.bindery.autobean.shared; +
diff --git a/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java new file mode 100644 index 0000000..eaba1d0 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java
@@ -0,0 +1,76 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap; +import com.google.web.bindery.autobean.shared.impl.EnumMap; +import com.google.web.bindery.autobean.vm.impl.FactoryHandler; +import com.google.web.bindery.autobean.vm.impl.ProxyAutoBean; + +/** + * Generates JVM-compatible implementations of AutoBeanFactory and AutoBean + * types. + * <p> + * This implementation is written assuming that the AutoBeanFactory and + * associated declarations will validate if compiled and used with the + * AutoBeanFactoyModel. + * <p> + * <span style='color: red'>This is experimental, unsupported code.</span> + */ +public class AutoBeanFactorySource { + /* + * NB: This implementation is excessively dynamic, however the inability to + * create a TypeOracle fram a ClassLoader prevents re-using the existing model + * code. If the model code could be reused, it would be straightforward to + * simply generate implementations of the various interfaces. + */ + private static final AutoBeanFactory EMPTY = create(AutoBeanFactory.class); + + /** + * Create an instance of an AutoBeanFactory. + * + * @param <F> the factory type + * @param clazz the Class representing the factory interface + * @return an instance of the AutoBeanFactory + */ + public static <F extends AutoBeanFactory> F create(Class<F> clazz) { + Configuration.Builder builder = new Configuration.Builder(); + Category cat = clazz.getAnnotation(Category.class); + if (cat != null) { + builder.setCategories(cat.value()); + } + NoWrap noWrap = clazz.getAnnotation(NoWrap.class); + if (noWrap != null) { + builder.setNoWrap(noWrap.value()); + } + + return ProxyAutoBean.makeProxy(clazz, new FactoryHandler(builder.build()), EnumMap.class); + } + + /** + * Create an instance of an AutoBean directly. + * + * @param <T> the interface type implemented by the AutoBean + * @param clazz the interface type implemented by the AutoBean + * @return an instance of an AutoBean + */ + public static <T> AutoBean<T> createBean(Class<T> clazz, Configuration configuration) { + return new ProxyAutoBean<T>(EMPTY, clazz, configuration); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/Configuration.java b/user/src/com/google/web/bindery/autobean/vm/Configuration.java new file mode 100644 index 0000000..593ad52 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/Configuration.java
@@ -0,0 +1,94 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm; + +import com.google.web.bindery.autobean.shared.AutoBean; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Used by {@link AutoBeanFactorySource#createBean(Class, Configuration)}. This + * type replicates the annotations that may be applied to an AutoBeanFactory + * declaration. + * <p> + * <span style='color: red'>This is experimental, unsupported code.</span> + */ +public class Configuration { + /** + * Builds {@link Configuration} objects. + */ + public static class Builder { + private Configuration toReturn = new Configuration(); + + public Configuration build() { + toReturn.noWrap.add(AutoBean.class); + toReturn.noWrap = Collections.unmodifiableSet(toReturn.noWrap); + try { + return toReturn; + } finally { + toReturn = null; + } + } + + /** + * Equivalent to applying a + * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.Category + * Category} annotation to an AutoBeanFactory declaration. + * + * @param categories the category types that should be searched for static + * implementations of non-property methods + * @return the Builder + */ + public Builder setCategories(Class<?>... categories) { + toReturn.categories = + Collections.unmodifiableList(new ArrayList<Class<?>>(Arrays.asList(categories))); + return this; + } + + /** + * Equivalent to applying a + * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap + * NoWrap} annotation to an AutoBeanFactory declaration. + * + * @param noWrap the types that should be excluded from wrapping + * @return the Builder + */ + public Builder setNoWrap(Class<?>... noWrap) { + toReturn.noWrap.addAll(Arrays.asList(noWrap)); + return this; + } + } + + private List<Class<?>> categories = Collections.emptyList(); + + private Set<Class<?>> noWrap = new HashSet<Class<?>>(); + + private Configuration() { + } + + public List<Class<?>> getCategories() { + return categories; + } + + public Set<Class<?>> getNoWrap() { + return noWrap; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java new file mode 100644 index 0000000..f165239 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java
@@ -0,0 +1,248 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Breakout of method types that an AutoBean shim interface can implement. The + * order of the values of the enum is important. + * + * @see com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod + */ +public enum BeanMethod { + /** + * Methods defined in Object. + */ + OBJECT { + + @Override + public String inferName(Method method) { + throw new UnsupportedOperationException(); + } + + @Override + Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable { + if (CALL.matches(handler, method)) { + return CALL.invoke(handler, method, args); + } + return method.invoke(handler, args); + } + + @Override + boolean matches(SimpleBeanHandler<?> handler, Method method) { + return method.getDeclaringClass().equals(Object.class); + } + }, + /** + * Getters. + */ + GET { + @Override + public String inferName(Method method) { + String name = method.getName(); + if (name.startsWith(IS_PREFIX) && !method.isAnnotationPresent(PropertyName.class)) { + Class<?> returnType = method.getReturnType(); + if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) { + return decapitalize(name.substring(2)); + } + } + return super.inferName(method); + } + + @Override + Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { + String propertyName = inferName(method); + Object toReturn = handler.getBean().getOrReify(propertyName); + if (toReturn == null && method.getReturnType().isPrimitive()) { + toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType()); + } + return toReturn; + } + + @Override + boolean matches(SimpleBeanHandler<?> handler, Method method) { + Class<?> returnType = method.getReturnType(); + if (method.getParameterTypes().length != 0 || Void.TYPE.equals(returnType)) { + return false; + } + + String name = method.getName(); + if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) { + if (name.startsWith(IS_PREFIX) && name.length() > 2 || name.startsWith(HAS_PREFIX) + && name.length() > 3) { + return true; + } + } + return name.startsWith(GET_PREFIX) && name.length() > 3; + } + }, + /** + * Setters. + */ + SET { + @Override + Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { + handler.getBean().setProperty(inferName(method), args[0]); + return null; + } + + @Override + boolean matches(SimpleBeanHandler<?> handler, Method method) { + String name = method.getName(); + return name.startsWith(SET_PREFIX) && name.length() > 3 + && method.getParameterTypes().length == 1 && method.getReturnType().equals(Void.TYPE); + } + }, + /** + * A setter that returns a type assignable from the interface in which the + * method is declared to support chained, builder-pattern setters. For + * example, {@code foo.setBar(1).setBaz(42)}. + */ + SET_BUILDER { + @Override + Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { + ProxyAutoBean<?> bean = handler.getBean(); + bean.setProperty(inferName(method), args[0]); + return bean.as(); + } + + @Override + boolean matches(SimpleBeanHandler<?> handler, Method method) { + String name = method.getName(); + return name.startsWith(SET_PREFIX) && name.length() > 3 + && method.getParameterTypes().length == 1 + && method.getReturnType().isAssignableFrom(method.getDeclaringClass()); + } + }, + /** + * Domain methods. + */ + CALL { + @Override + public String inferName(Method method) { + throw new UnsupportedOperationException(); + } + + @Override + Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable { + if (args == null) { + args = EMPTY_OBJECT; + } + + Method found = findMethod(handler, method); + if (found != null) { + Object[] realArgs = new Object[args.length + 1]; + realArgs[0] = handler.getBean(); + System.arraycopy(args, 0, realArgs, 1, args.length); + return found.invoke(null, realArgs); + } + throw new RuntimeException("Could not find category implementation of " + + method.toGenericString()); + } + + @Override + boolean matches(SimpleBeanHandler<?> handler, Method method) { + return handler.getBean().isWrapper() + || !handler.getBean().getConfiguration().getCategories().isEmpty() + && findMethod(handler, method) != null; + } + }; + + public static final String GET_PREFIX = "get"; + public static final String HAS_PREFIX = "has"; + public static final String IS_PREFIX = "is"; + public static final String SET_PREFIX = "set"; + + private static final Object[] EMPTY_OBJECT = new Object[0]; + + static Method findMethod(SimpleBeanHandler<?> handler, Method method) { + Class<?>[] declaredParams = method.getParameterTypes(); + Class<?>[] searchParams = new Class<?>[declaredParams.length + 1]; + searchParams[0] = AutoBean.class; + System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length); + Class<?> autoBeanType = handler.getBean().getType(); + + for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) { + try { + Method found = clazz.getMethod(method.getName(), searchParams); + if (!Modifier.isStatic(found.getModifiers())) { + continue; + } + // Check the AutoBean parameterization of the 0th argument + Class<?> foundAutoBean = + TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(AutoBean.class, found + .getGenericParameterTypes()[0])); + if (!foundAutoBean.isAssignableFrom(autoBeanType)) { + continue; + } + return found; + } catch (NoSuchMethodException expected) { + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } + } + return null; + } + + /** + * Private equivalent of Introspector.decapitalize(String) since + * java.beans.Introspector is not available in Android 2.2. + */ + private static String decapitalize(String name) { + if (name == null) { + return null; + } + int length = name.length(); + if (length == 0 || (length > 1 && Character.isUpperCase(name.charAt(1)))) { + return name; + } + StringBuilder sb = new StringBuilder(length); + sb.append(Character.toLowerCase(name.charAt(0))); + sb.append(name.substring(1)); + return sb.toString(); + } + + public String inferName(Method method) { + PropertyName prop = method.getAnnotation(PropertyName.class); + if (prop != null) { + return prop.value(); + } + return decapitalize(method.getName().substring(3)); + } + + /** + * Convenience method, not valid for {@link BeanMethod#CALL}. + */ + public boolean matches(Method method) { + return matches(null, method); + } + + /** + * Invoke the method. + */ + abstract Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) + throws Throwable; + + /** + * Determine if the method maches the given type. + */ + abstract boolean matches(SimpleBeanHandler<?> handler, Method method); +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java new file mode 100644 index 0000000..a4fe31f --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java
@@ -0,0 +1,46 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import java.lang.reflect.Method; + +/** + * A property context that allows setters to be called on a simple peer, + * regardless of whether or not the interface actually has a setter. + */ +class BeanPropertyContext extends MethodPropertyContext { + private final ProxyAutoBean<?> bean; + private final String propertyName; + + public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) { + super(getter); + this.bean = bean; + propertyName = BeanMethod.GET.inferName(getter); + } + + @Override + public boolean canSet() { + return true; + } + + @Override + public void set(Object value) { + Class<?> maybeAutobox = TypeUtils.maybeAutobox(getType()); + assert value == null || maybeAutobox.isInstance(value) : value.getClass().getCanonicalName() + + " is not assignable to " + maybeAutobox.getCanonicalName(); + bean.setProperty(propertyName, maybeAutobox.cast(value)); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java new file mode 100644 index 0000000..8532f41 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java
@@ -0,0 +1,129 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.vm.Configuration; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; + +/** + * Handles dispatches on AutoBeanFactory interfaces. + */ +public class FactoryHandler implements InvocationHandler { + private final Configuration configuration; + + /** + * Constructor. + * + * @param categories the classes specified by a Category annotation + */ + public FactoryHandler(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Handles both declared factory methods as well as the dynamic create + * methods. + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + + Class<?> beanType; + Object toWrap = null; + String name = method.getName(); + if (name.equals("create")) { + // Dynamic create. Guaranteed to have at least one argument + // create(clazz); or create(clazz, toWrap); + beanType = (Class<?>) args[0]; + if (args.length == 2) { + toWrap = args[1]; + } + } else if (name.equals("getEnum")) { + Class<?> clazz = (Class<?>) args[0]; + String token = (String) args[1]; + return getEnum(clazz, token); + } else if (name.equals("getToken")) { + Enum<?> e = (Enum<?>) args[0]; + return getToken(e); + } else { + // Declared factory method, use the parameterization + // AutoBean<Foo> foo(); or Autobean<foo> foo(Foo toWrap); + ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType(); + beanType = (Class<?>) returnType.getActualTypeArguments()[0]; + + if (args != null && args.length == 1) { + toWrap = args[0]; + } + } + + // Return any existing wrapper + ProxyAutoBean<Object> toReturn = (ProxyAutoBean<Object>) AutoBeanUtils.getAutoBean(toWrap); + if (toReturn == null) { + // Create the implementation bean + if (toWrap == null) { + toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType, + configuration); + } else { + toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType, + configuration, toWrap); + } + } + + return toReturn; + } + + /** + * EnumMap support. + */ + private Object getEnum(Class<?> clazz, String token) + throws IllegalAccessException { + for (Field f : clazz.getFields()) { + String fieldName; + PropertyName annotation = f.getAnnotation(PropertyName.class); + if (annotation != null) { + fieldName = annotation.value(); + } else { + fieldName = f.getName(); + } + if (token.equals(fieldName)) { + f.setAccessible(true); + return f.get(null); + } + } + throw new IllegalArgumentException("Cannot find enum " + token + + " in type " + clazz.getCanonicalName()); + } + + /** + * EnumMap support. + */ + private Object getToken(Enum<?> e) throws NoSuchFieldException { + // Remember enum constants are fields + PropertyName annotation = e.getDeclaringClass().getField(e.name()).getAnnotation( + PropertyName.class); + if (annotation != null) { + return annotation.value(); + } else { + return e.name(); + } + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java new file mode 100644 index 0000000..d00b8ed --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java
@@ -0,0 +1,68 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Used by {@link ProxyAutoBean#traverseProperties()}. + */ +class GetterPropertyContext extends MethodPropertyContext { + private final Method setter; + private final Object shim; + + GetterPropertyContext(ProxyAutoBean<?> bean, Method getter) { + super(getter); + this.shim = bean.as(); + + // Look for the setter method. + Method found = null; + String name = BeanMethod.GET.inferName(getter); + for (Method m : getter.getDeclaringClass().getMethods()) { + if (BeanMethod.SET.matches(m) || BeanMethod.SET_BUILDER.matches(m)) { + if (BeanMethod.SET.inferName(m).equals(name) + && getter.getReturnType().isAssignableFrom(m.getParameterTypes()[0])) { + found = m; + break; + } + } + } + setter = found; + } + + @Override + public boolean canSet() { + return setter != null; + } + + @Override + public void set(Object value) { + if (!canSet()) { + throw new UnsupportedOperationException("No setter"); + } + try { + setter.setAccessible(true); + setter.invoke(shim, value); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java new file mode 100644 index 0000000..8fdad79 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
@@ -0,0 +1,351 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.impl.HasSplittable; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Uses the org.json packages to slice and dice request payloads. + */ +public class JsonSplittable implements Splittable, HasSplittable { + + /** + * Ensures that the same JsonSplittable will be returned for a given backing + * JSONObject. + */ + private static final Map<Object, Reference<JsonSplittable>> canonical = + new WeakHashMap<Object, Reference<JsonSplittable>>(); + + public static JsonSplittable create() { + return new JsonSplittable(new JSONObject()); + } + + public static Splittable create(String payload) { + try { + switch (payload.charAt(0)) { + case '{': + return new JsonSplittable(new JSONObject(payload)); + case '[': + return new JsonSplittable(new JSONArray(payload)); + case '"': + return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0)); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return new JsonSplittable(Double.parseDouble(payload)); + case 't': + case 'f': + return new JsonSplittable(Boolean.parseBoolean(payload)); + case 'n': + return null; + default: + throw new RuntimeException("Could not parse payload: payload[0] = " + payload.charAt(0)); + } + } catch (JSONException e) { + throw new RuntimeException("Could not parse payload", e); + } + } + + public static Splittable createIndexed() { + return new JsonSplittable(new JSONArray()); + } + + public static Splittable createNull() { + return new JsonSplittable(); + } + + /** + * Private equivalent of org.json.JSONObject.getNames(JSONObject) since that + * method is not available in Android 2.2. Used to represent a null value. + */ + private static String[] getNames(JSONObject json) { + int length = json.length(); + if (length == 0) { + return null; + } + String[] names = new String[length]; + Iterator<?> i = json.keys(); + int j = 0; + while (i.hasNext()) { + names[j++] = (String) i.next(); + } + return names; + } + + private JSONArray array; + private Boolean bool; + /** + * Used to represent a null value. + */ + private boolean isNull; + private Double number; + private JSONObject obj; + private String string; + private final Map<String, Object> reified = new HashMap<String, Object>(); + + /** + * Constructor for a null value. + */ + private JsonSplittable() { + isNull = true; + } + + private JsonSplittable(boolean value) { + this.bool = value; + } + + private JsonSplittable(double value) { + this.number = value; + } + + private JsonSplittable(JSONArray array) { + this.array = array; + } + + private JsonSplittable(JSONObject obj) { + this.obj = obj; + } + + private JsonSplittable(String string) { + this.array = null; + this.obj = null; + this.string = string; + } + + public boolean asBoolean() { + return bool; + } + + public double asNumber() { + return number; + } + + public void assign(Splittable parent, int index) { + try { + ((JsonSplittable) parent).array.put(index, value()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public void assign(Splittable parent, String propertyName) { + try { + ((JsonSplittable) parent).obj.put(propertyName, value()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public String asString() { + return string; + } + + public Splittable deepCopy() { + return create(getPayload()); + } + + public Splittable get(int index) { + try { + return makeSplittable(array.get(index)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public Splittable get(String key) { + try { + return makeSplittable(obj.get(key)); + } catch (JSONException e) { + throw new RuntimeException(key, e); + } + } + + public String getPayload() { + if (isNull) { + return "null"; + } + if (obj != null) { + return obj.toString(); + } + if (array != null) { + return array.toString(); + } + if (string != null) { + return StringQuoter.quote(string); + } + if (number != null) { + return String.valueOf(number); + } + if (bool != null) { + return String.valueOf(bool); + } + throw new RuntimeException("No data in this JsonSplittable"); + } + + public List<String> getPropertyKeys() { + String[] names = getNames(obj); + if (names == null) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList(Arrays.asList(names)); + } + } + + public Object getReified(String key) { + return reified.get(key); + } + + public Splittable getSplittable() { + return this; + } + + public boolean isBoolean() { + return bool != null; + } + + public boolean isIndexed() { + return array != null; + } + + public boolean isKeyed() { + return obj != null; + } + + public boolean isNull(int index) { + return array.isNull(index); + } + + public boolean isNull(String key) { + // Treat undefined and null as the same + return !obj.has(key) || obj.isNull(key); + } + + public boolean isNumber() { + return number != null; + } + + public boolean isReified(String key) { + return reified.containsKey(key); + } + + public boolean isString() { + return string != null; + } + + public boolean isUndefined(String key) { + return !obj.has(key); + } + + public void setReified(String key, Object object) { + reified.put(key, object); + } + + public void setSize(int size) { + // This is terrible, but there's no API support for resizing or splicing + JSONArray newArray = new JSONArray(); + for (int i = 0; i < size; i++) { + try { + newArray.put(i, array.get(i)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + array = newArray; + } + + public int size() { + return array.length(); + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + return getPayload(); + } + + private synchronized JsonSplittable makeSplittable(Object object) { + if (JSONObject.NULL.equals(object)) { + return null; + } + Reference<JsonSplittable> ref = canonical.get(object); + JsonSplittable seen = ref == null ? null : ref.get(); + if (seen == null) { + if (object instanceof JSONObject) { + seen = new JsonSplittable((JSONObject) object); + } else if (object instanceof JSONArray) { + seen = new JsonSplittable((JSONArray) object); + } else if (object instanceof String) { + seen = new JsonSplittable(object.toString()); + } else if (object instanceof Number) { + seen = new JsonSplittable(((Number) object).doubleValue()); + } else if (object instanceof Boolean) { + seen = new JsonSplittable((Boolean) object); + } else { + throw new RuntimeException("Unhandled type " + object.getClass()); + } + canonical.put(object, new WeakReference<JsonSplittable>(seen)); + } + return seen; + } + + private Object value() { + if (isNull) { + return null; + } + if (obj != null) { + return obj; + } + if (array != null) { + return array; + } + if (string != null) { + return string; + } + if (number != null) { + return number; + } + if (bool != null) { + return bool; + } + throw new RuntimeException("No data"); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java new file mode 100644 index 0000000..10ce579 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java
@@ -0,0 +1,111 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * A base type to handle analyzing the return value of a getter method. The + * accessor methods are implemented in subtypes. + */ +abstract class MethodPropertyContext implements CollectionPropertyContext, + MapPropertyContext { + private static class Data { + Class<?> elementType; + Type genericType; + Class<?> keyType; + Class<?> valueType; + Class<?> type; + } + + /** + * Save prior instances in order to decrease the amount of data computed. + */ + private static final Map<Method, Data> cache = new WeakHashMap<Method, Data>(); + private final Data data; + + public MethodPropertyContext(Method getter) { + synchronized (cache) { + Data previous = cache.get(getter); + if (previous != null) { + this.data = previous; + return; + } + + this.data = new Data(); + data.genericType = getter.getGenericReturnType(); + data.type = getter.getReturnType(); + // Compute collection element type + if (Collection.class.isAssignableFrom(getType())) { + data.elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization( + Collection.class, getter.getGenericReturnType(), + getter.getReturnType())); + } else if (Map.class.isAssignableFrom(getType())) { + Type[] types = TypeUtils.getParameterization(Map.class, + getter.getGenericReturnType()); + data.keyType = TypeUtils.ensureBaseType(types[0]); + data.valueType = TypeUtils.ensureBaseType(types[1]); + } + cache.put(getter, data); + } + } + + public void accept(ParameterizationVisitor visitor) { + traverse(visitor, data.genericType); + } + + public abstract boolean canSet(); + + public Class<?> getElementType() { + return data.elementType; + } + + public Class<?> getKeyType() { + return data.keyType; + } + + public Class<?> getType() { + return data.type; + } + + public Class<?> getValueType() { + return data.valueType; + } + + public abstract void set(Object value); + + private void traverse(ParameterizationVisitor visitor, Type type) { + Class<?> base = TypeUtils.ensureBaseType(type); + if (visitor.visitType(base)) { + Type[] params = TypeUtils.getParameterization(base, type); + for (Type t : params) { + if (visitor.visitParameter()) { + traverse(visitor, t); + } + visitor.endVisitParameter(); + } + } + visitor.endVisitType(base); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java new file mode 100644 index 0000000..cdf61dc --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
@@ -0,0 +1,316 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean; +import com.google.web.bindery.autobean.vm.Configuration; +import com.google.gwt.core.client.impl.WeakMapping; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * An implementation of an AutoBean that uses reflection. + * + * @param <T> the type of interface being wrapped + */ +public class ProxyAutoBean<T> extends AbstractAutoBean<T> { + private static class Data { + final List<Method> getters = new ArrayList<Method>(); + final List<String> getterNames = new ArrayList<String>(); + final List<PropertyType> propertyType = new ArrayList<PropertyType>(); + } + + private enum PropertyType { + VALUE, REFERENCE, COLLECTION, MAP; + } + + private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, Data>(); + + /** + * Utility method to crete a new {@link Proxy} instance. + * + * @param <T> the interface type to be implemented by the Proxy + * @param intf the Class representing the interface type + * @param handler the implementation of the interface + * @param extraInterfaces additional interface types the Proxy should + * implement + * @return a Proxy instance + */ + public static <T> T makeProxy(Class<T> intf, InvocationHandler handler, + Class<?>... extraInterfaces) { + Class<?>[] intfs; + if (extraInterfaces == null) { + intfs = new Class<?>[]{intf}; + } else { + intfs = new Class<?>[extraInterfaces.length + 1]; + intfs[0] = intf; + System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length); + } + + return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs, handler)); + } + + private static Data calculateData(Class<?> beanType) { + Data toReturn; + synchronized (cache) { + toReturn = cache.get(beanType); + if (toReturn == null) { + toReturn = new Data(); + for (Method method : beanType.getMethods()) { + if (BeanMethod.GET.matches(method)) { + toReturn.getters.add(method); + + String name; + PropertyName annotation = method.getAnnotation(PropertyName.class); + if (annotation != null) { + name = annotation.value(); + } else { + name = BeanMethod.GET.inferName(method); + } + toReturn.getterNames.add(name); + + Class<?> returnType = method.getReturnType(); + if (TypeUtils.isValueType(returnType)) { + toReturn.propertyType.add(PropertyType.VALUE); + } else if (Collection.class.isAssignableFrom(returnType)) { + toReturn.propertyType.add(PropertyType.COLLECTION); + } else if (Map.class.isAssignableFrom(returnType)) { + toReturn.propertyType.add(PropertyType.MAP); + } else { + toReturn.propertyType.add(PropertyType.REFERENCE); + } + } + } + cache.put(beanType, toReturn); + } + } + return toReturn; + } + + private final Class<T> beanType; + private final Configuration configuration; + private final Data data; + private final T shim; + + // These constructors mirror the generated constructors. + @SuppressWarnings("unchecked") + public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration) { + super(factory); + this.beanType = (Class<T>) beanType; + this.configuration = configuration; + this.data = calculateData(beanType); + this.shim = createShim(); + } + + @SuppressWarnings("unchecked") + public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration, + T toWrap) { + super(toWrap, factory); + this.beanType = (Class<T>) beanType; + this.configuration = configuration; + this.data = calculateData(beanType); + this.shim = createShim(); + } + + @Override + public T as() { + return shim; + } + + public Configuration getConfiguration() { + return configuration; + } + + public Class<T> getType() { + return beanType; + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected void call(String method, Object returned, Object... parameters) { + super.call(method, returned, parameters); + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected void checkFrozen() { + super.checkFrozen(); + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected void checkWrapped() { + super.checkWrapped(); + } + + /** + * Not used in this implementation. Instead, the simple implementation is + * created lazily in {@link #getWrapped()}. + */ + @Override + protected T createSimplePeer() { + return null; + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected <V> V get(String method, V toReturn) { + return super.get(method, toReturn); + } + + /** + * Allow access by BeanMethod. + */ + @Override + protected <V> V getOrReify(String propertyName) { + return super.<V> getOrReify(propertyName); + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected T getWrapped() { + if (wrapped == null && isUsingSimplePeer()) { + wrapped = (T) ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this)); + } + return super.getWrapped(); + } + + /** + * Allow access by {@link ShimHandler}. + */ + @Override + protected void set(String method, Object value) { + super.set(method, value); + } + + @Override + protected void setProperty(String propertyName, Object value) { + super.setProperty(propertyName, value); + } + + // TODO: Port to model-based when class-based TypeOracle is available. + @Override + protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) { + assert data.getters.size() == data.getterNames.size() + && data.getters.size() == data.propertyType.size(); + Iterator<Method> getterIt = data.getters.iterator(); + Iterator<String> nameIt = data.getterNames.iterator(); + Iterator<PropertyType> typeIt = data.propertyType.iterator(); + while (getterIt.hasNext()) { + Method getter = getterIt.next(); + String name = nameIt.next(); + PropertyType propertyType = typeIt.next(); + + // Use the shim to handle automatic wrapping + Object value; + try { + getter.setAccessible(true); + value = getter.invoke(shim); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } + + // Create the context used for the property visitation + MethodPropertyContext x = + isUsingSimplePeer() ? new BeanPropertyContext(this, getter) : new GetterPropertyContext( + this, getter); + + switch (propertyType) { + case VALUE: { + if (visitor.visitValueProperty(name, value, x)) { + } + visitor.endVisitValueProperty(name, value, x); + break; + } + case COLLECTION: { + // Workaround for generics bug in mac javac 1.6.0_22 + @SuppressWarnings("rawtypes") + AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value); + @SuppressWarnings("unchecked") + AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp; + if (visitor.visitCollectionProperty(name, bean, x)) { + if (value != null) { + ((ProxyAutoBean<?>) bean).traverse(visitor, ctx); + } + } + visitor.endVisitCollectionProperty(name, bean, x); + break; + } + case MAP: { + // Workaround for generics bug in mac javac 1.6.0_22 + @SuppressWarnings("rawtypes") + AutoBean temp = AutoBeanUtils.getAutoBean((Map) value); + @SuppressWarnings("unchecked") + AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp; + if (visitor.visitMapProperty(name, bean, x)) { + if (value != null) { + ((ProxyAutoBean<?>) bean).traverse(visitor, ctx); + } + } + visitor.endVisitMapProperty(name, bean, x); + break; + } + case REFERENCE: { + ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value); + if (visitor.visitReferenceProperty(name, bean, x)) { + if (value != null) { + bean.traverse(visitor, ctx); + } + } + visitor.endVisitReferenceProperty(name, bean, x); + break; + } + } + } + } + + Class<?> getBeanType() { + return beanType; + } + + private T createShim() { + T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped())); + WeakMapping.set(toReturn, AutoBean.class.getName(), this); + return toReturn; + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java new file mode 100644 index 0000000..95dabe8 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java
@@ -0,0 +1,131 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Implements an AutoBean's shim interface that intercepts calls to the backing + * object. + * + * @param <T> the interface type of the AutoBean + */ +class ShimHandler<T> implements InvocationHandler { + private final ProxyAutoBean<T> bean; + private final Method interceptor; + + public ShimHandler(ProxyAutoBean<T> bean, T toWrap) { + this.bean = bean; + + Method maybe = null; + for (Class<?> clazz : bean.getConfiguration().getCategories()) { + try { + maybe = clazz.getMethod("__intercept", AutoBean.class, Object.class); + break; + } catch (SecurityException expected) { + } catch (NoSuchMethodException expected) { + } + } + interceptor = maybe; + } + + @Override + public boolean equals(Object couldBeShim) { + if (couldBeShim == null) { + return false; + } + // Handles the foo.equals(foo) case + if (Proxy.isProxyClass(couldBeShim.getClass()) + && this == Proxy.getInvocationHandler(couldBeShim)) { + return true; + } + return bean.getWrapped().equals(couldBeShim); + } + + @Override + public int hashCode() { + return bean.getWrapped().hashCode(); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + method.setAccessible(true); + Object toReturn; + String name = method.getName(); + method.setAccessible(true); + try { + if (BeanMethod.OBJECT.matches(method)) { + return method.invoke(this, args); + } else if (BeanMethod.GET.matches(method)) { + toReturn = method.invoke(bean.getWrapped(), args); + toReturn = bean.get(name, toReturn); + } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) { + toReturn = method.invoke(bean.getWrapped(), args); + bean.set(name, args[0]); + } else { + // XXX How should freezing and calls work together? + toReturn = method.invoke(bean.getWrapped(), args); + bean.call(name, toReturn, args); + } + Class<?> intf = method.getReturnType(); + if (!Object.class.equals(intf)) { + // XXX Need to deal with resolving generic T return types + toReturn = maybeWrap(intf, toReturn); + } + if (interceptor != null) { + toReturn = interceptor.invoke(null, bean, toReturn); + } + } catch (InvocationTargetException e) { + throw e.getCause(); + } + return toReturn; + } + + @Override + public String toString() { + return bean.getWrapped().toString(); + } + + private Object maybeWrap(Class<?> intf, Object toReturn) { + if (toReturn == null) { + return null; + } + AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn); + if (returnBean != null) { + return returnBean.as(); + } + if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass()) + || bean.getConfiguration().getNoWrap().contains(intf)) { + return toReturn; + } + if (toReturn.getClass().isArray()) { + /* + * We can't reliably wrap arrays, but the only time we typically see an + * array is with toArray() call on a collection, since arrays aren't + * supported property types. + */ + return toReturn; + } + ProxyAutoBean<Object> newBean = + new ProxyAutoBean<Object>(bean.getFactory(), intf, bean.getConfiguration(), toReturn); + return newBean.as(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java new file mode 100644 index 0000000..a6624a6 --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java
@@ -0,0 +1,57 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * Dynamic implementation of an AutoBean's simple peer object. + * + * @param <T> the type of interface the shim allows access to + */ +class SimpleBeanHandler<T> implements InvocationHandler { + private final ProxyAutoBean<T> bean; + + public SimpleBeanHandler(ProxyAutoBean<T> bean) { + this.bean = bean; + } + + public ProxyAutoBean<T> getBean() { + return bean; + } + + /** + * Delegates most work to {@link BeanMethod}. + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + for (BeanMethod type : BeanMethod.values()) { + if (type.matches(this, method)) { + Object toReturn = type.invoke(this, method, args); + return toReturn; + } + } + throw new RuntimeException("Unhandled invocation " + method.getName()); + } + + /** + * For debugging use only. + */ + @Override + public String toString() { + return bean.getSplittable().getPayload(); + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java new file mode 100644 index 0000000..73b977e --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java
@@ -0,0 +1,175 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.web.bindery.autobean.vm.impl; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Shared code for answering question about Class objects. This is a + * server-compatible analog to ModelUtils. + */ +public class TypeUtils { + static final Map<Class<?>, Class<?>> AUTOBOX_MAP; + static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES; + @SuppressWarnings("unchecked") + static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>( + Arrays.asList(Boolean.class, Character.class, Class.class, Date.class, + Enum.class, Number.class, String.class, Void.class))); + + static { + Map<Class<?>, Object> temp = new HashMap<Class<?>, Object>(); + temp.put(boolean.class, false); + temp.put(byte.class, (byte) 0); + temp.put(char.class, (char) 0); + temp.put(double.class, (double) 0); + temp.put(float.class, (float) 0); + temp.put(int.class, 0); + temp.put(long.class, (long) 0); + temp.put(short.class, (short) 0); + temp.put(void.class, null); + + DEFAULT_PRIMITIVE_VALUES = Collections.unmodifiableMap(temp); + } + + static { + Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>(); + autoBoxMap.put(boolean.class, Boolean.class); + autoBoxMap.put(byte.class, Byte.class); + autoBoxMap.put(char.class, Character.class); + autoBoxMap.put(double.class, Double.class); + autoBoxMap.put(float.class, Float.class); + autoBoxMap.put(int.class, Integer.class); + autoBoxMap.put(long.class, Long.class); + autoBoxMap.put(short.class, Short.class); + autoBoxMap.put(void.class, Void.class); + AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap); + } + + /** + * Similar to ModelUtils#ensureBaseType(JType) but for the reflection API. + */ + public static Class<?> ensureBaseType(Type type) { + if (type instanceof Class<?>) { + return (Class<?>) type; + } + if (type instanceof GenericArrayType) { + return Array.newInstance( + ensureBaseType(((GenericArrayType) type).getGenericComponentType()), + 0).getClass(); + } + if (type instanceof ParameterizedType) { + return ensureBaseType(((ParameterizedType) type).getRawType()); + } + if (type instanceof TypeVariable<?>) { + return ensureBaseType(((TypeVariable<?>) type).getBounds()[0]); + } + if (type instanceof WildcardType) { + WildcardType wild = (WildcardType) type; + return ensureBaseType(wild.getUpperBounds()[0]); + } + throw new RuntimeException("Cannot handle " + type.getClass().getName()); + } + + /** + * Given a primitive Class type, return a default value. + */ + public static Object getDefaultPrimitiveValue(Class<?> clazz) { + assert clazz.isPrimitive() : "Expecting primitive type"; + return DEFAULT_PRIMITIVE_VALUES.get(clazz); + } + + public static Type[] getParameterization(Class<?> intf, Type... types) { + for (Type type : types) { + if (type == null) { + continue; + } else if (type instanceof ParameterizedType) { + ParameterizedType param = (ParameterizedType) type; + Type[] actualTypeArguments = param.getActualTypeArguments(); + Class<?> base = ensureBaseType(param.getRawType()); + Type[] typeParameters = base.getTypeParameters(); + + Map<Type, Type> map = new HashMap<Type, Type>(); + for (int i = 0, j = typeParameters.length; i < j; i++) { + map.put(typeParameters[i], actualTypeArguments[i]); + } + Type[] lookFor = intf.equals(base) ? intf.getTypeParameters() + : getParameterization(intf, base.getGenericInterfaces()); + List<Type> toReturn = new ArrayList<Type>(); + for (int i = 0, j = lookFor.length; i < j; i++) { + Type found = map.get(lookFor[i]); + if (found != null) { + toReturn.add(found); + } + } + return toReturn.toArray(new Type[toReturn.size()]); + } else if (type instanceof Class<?>) { + Class<?> clazz = (Class<?>) type; + if (intf.equals(clazz)) { + return intf.getTypeParameters(); + } + Type[] found = getParameterization(intf, clazz.getGenericSuperclass()); + if (found != null) { + return found; + } + found = getParameterization(intf, clazz.getGenericInterfaces()); + if (found != null) { + return found; + } + } + } + return null; + } + + public static Type getSingleParameterization(Class<?> intf, Type... types) { + Type[] found = getParameterization(intf, types); + return found == null ? null : found[0]; + } + + public static boolean isValueType(Class<?> clazz) { + if (clazz.isPrimitive() || VALUE_TYPES.contains(clazz)) { + return true; + } + for (Class<?> c : VALUE_TYPES) { + if (c.isAssignableFrom(clazz)) { + return true; + } + } + return false; + } + + public static <V> Class<V> maybeAutobox(Class<V> domainType) { + @SuppressWarnings("unchecked") + Class<V> autoBoxType = (Class<V>) AUTOBOX_MAP.get(domainType); + return autoBoxType == null ? domainType : autoBoxType; + } + + private TypeUtils() { + } +}
diff --git a/user/src/com/google/web/bindery/autobean/vm/package-info.java b/user/src/com/google/web/bindery/autobean/vm/package-info.java new file mode 100644 index 0000000..ef1a6ee --- /dev/null +++ b/user/src/com/google/web/bindery/autobean/vm/package-info.java
@@ -0,0 +1,28 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * Contains JVM-compatible implementations of the AutoBean framework. + * <p> + * <span style='color: red'>This is experimental, unsupported code.</span> + * + * @see <a + * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean + * wiki page</a> + */ +@com.google.gwt.util.PreventSpuriousRebuilds +package com.google.web.bindery.autobean.vm; +
diff --git a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml index 279acc4..f55cc8d 100644 --- a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml +++ b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
@@ -17,7 +17,7 @@ --> <module> <inherits name='com.google.gwt.core.Core'/> - <inherits name='com.google.gwt.autobean.AutoBean'/> + <inherits name='com.google.web.bindery.autobean.AutoBean'/> <inherits name='com.google.gwt.editor.Editor'/> <inherits name='com.google.gwt.http.HTTP'/> <inherits name='com.google.gwt.logging.LoggingDisabled'/>
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java index a4c10dd..df21135 100644 --- a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java +++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.gwt.client.impl; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.gwt.editor.client.Editor; import com.google.gwt.editor.client.EditorContext; import com.google.gwt.editor.client.EditorVisitor;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java index e14d3b2..ab4f8aa 100644 --- a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java +++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.gwt.client.impl; -import com.google.gwt.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.ValueCodex; import com.google.gwt.editor.client.EditorContext; import com.google.gwt.editor.client.EditorVisitor; @@ -62,4 +62,4 @@ } return true; } -} \ No newline at end of file +}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java index 834b5c0..e6d7f5c 100644 --- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java +++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
@@ -15,13 +15,13 @@ */ package com.google.web.bindery.requestfactory.gwt.rebind; -import com.google.gwt.autobean.rebind.model.JBeanMethod; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; -import com.google.gwt.autobean.shared.AutoBeanFactory; -import com.google.gwt.autobean.shared.AutoBeanFactory.Category; -import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap; -import com.google.gwt.autobean.shared.impl.EnumMap.ExtraEnums; +import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap; +import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums; import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.Generator; import com.google.gwt.core.ext.GeneratorContext;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java index ef1d7e8..6af1dbb 100644 --- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java +++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.gwt.rebind.model; -import com.google.gwt.autobean.rebind.model.JBeanMethod; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod; +import com.google.web.bindery.autobean.shared.Splittable; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java index f25e0ae..3433518 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java +++ b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
@@ -15,9 +15,9 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.server.impl.BeanMethod; -import com.google.gwt.autobean.server.impl.TypeUtils; -import com.google.gwt.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.vm.impl.BeanMethod; +import com.google.web.bindery.autobean.vm.impl.TypeUtils; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.InstanceRequest; import com.google.web.bindery.requestfactory.shared.Request;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java index dc69783..b6841ab 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java +++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.ValueCodex; import com.google.gwt.dev.asm.AnnotationVisitor; import com.google.gwt.dev.asm.ClassReader; import com.google.gwt.dev.asm.ClassVisitor;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java index 988250f..e167c41 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java +++ b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
@@ -15,12 +15,12 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.server.AutoBeanFactoryMagic; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanCodex; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.ValueCodex; -import com.google.gwt.autobean.shared.impl.StringQuoter; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanCodex; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; +import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.IdToEntityMap; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy; @@ -210,7 +210,7 @@ */ private <Q extends BaseProxy> AutoBean<Q> createProxyBean( SimpleProxyId<Q> id, Object domainObject) { - AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(), + AutoBean<Q> toReturn = AutoBeanFactorySource.createBean(id.getProxyClass(), SimpleRequestProcessor.CONFIGURATION); toReturn.setTag(Constants.STABLE_ID, id); toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java index 086adbf..9f641eb 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java +++ b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
@@ -15,12 +15,12 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.server.impl.TypeUtils; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; -import com.google.gwt.autobean.shared.AutoBeanVisitor; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.vm.impl.TypeUtils; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId; @@ -447,4 +447,4 @@ throw new ReportableException("Unsupported domain type " + returnClass.getCanonicalName()); } -} \ No newline at end of file +}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java index e9f6914..f95e896 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java +++ b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.server.impl.TypeUtils; +import com.google.web.bindery.autobean.vm.impl.TypeUtils; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java index c09a46a..a763dbb 100644 --- a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java +++ b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
@@ -15,16 +15,16 @@ */ package com.google.web.bindery.requestfactory.server; -import com.google.gwt.autobean.server.AutoBeanFactoryMagic; -import com.google.gwt.autobean.server.Configuration; -import com.google.gwt.autobean.server.impl.TypeUtils; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanCodex; -import com.google.gwt.autobean.shared.AutoBeanUtils; -import com.google.gwt.autobean.shared.AutoBeanVisitor; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.ValueCodex; import com.google.gwt.user.server.Base64Utils; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanCodex; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; +import com.google.web.bindery.autobean.vm.Configuration; +import com.google.web.bindery.autobean.vm.impl.TypeUtils; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId; import com.google.web.bindery.requestfactory.shared.InstanceRequest; @@ -37,6 +37,7 @@ import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory; import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId; import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory; +import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength; import com.google.web.bindery.requestfactory.shared.messages.InvocationMessage; import com.google.web.bindery.requestfactory.shared.messages.MessageFactory; import com.google.web.bindery.requestfactory.shared.messages.OperationMessage; @@ -44,7 +45,6 @@ import com.google.web.bindery.requestfactory.shared.messages.ResponseMessage; import com.google.web.bindery.requestfactory.shared.messages.ServerFailureMessage; import com.google.web.bindery.requestfactory.shared.messages.ViolationMessage; -import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; @@ -86,7 +86,7 @@ /** * Vends message objects. */ - static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class); + static final MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class); static String fromBase64(String encoded) { try {
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java index f594bdc..44dcbce 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
@@ -15,9 +15,9 @@ */ package com.google.web.bindery.requestfactory.shared; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanCodex; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanCodex; +import com.google.web.bindery.autobean.shared.Splittable; import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder; import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java index 4040217..ffb3f87 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.Splittable; /** * A ProxyStore provides a {@link ProxySerializer} with access to a low-level @@ -50,4 +50,4 @@ * @see Splittable#getPayload() */ void put(String key, Splittable value); -} \ No newline at end of file +}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java index fad1919..f7faed5 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.impl; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.Splittable; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.InstanceRequest; import com.google.web.bindery.requestfactory.shared.Receiver;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java index 429dd6f..dd61fa2 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
@@ -19,16 +19,16 @@ import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT; import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanCodex; -import com.google.gwt.autobean.shared.AutoBeanFactory; -import com.google.gwt.autobean.shared.AutoBeanUtils; -import com.google.gwt.autobean.shared.AutoBeanVisitor; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.ValueCodex; -import com.google.gwt.autobean.shared.impl.AbstractAutoBean; -import com.google.gwt.autobean.shared.impl.EnumMap; -import com.google.gwt.autobean.shared.impl.StringQuoter; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanCodex; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean; +import com.google.web.bindery.autobean.shared.impl.EnumMap; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; import com.google.gwt.event.shared.UmbrellaException; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java index 37a9c3d..a13e62d 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.impl; -import com.google.gwt.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; import com.google.gwt.event.shared.EventBus; import com.google.web.bindery.requestfactory.shared.EntityProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java index 7dea94b..481f4c8 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
@@ -18,8 +18,8 @@ import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT; import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.web.bindery.requestfactory.shared.BaseProxy; /**
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java index 8aca80f..f498ac3 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
@@ -15,11 +15,11 @@ */ package com.google.web.bindery.requestfactory.shared.impl; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.ValueCodex; -import com.google.gwt.autobean.shared.impl.StringQuoter; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.ValueCodex; +import com.google.web.bindery.autobean.shared.impl.StringQuoter; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java index bc5e04a..4349d23 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
@@ -18,8 +18,8 @@ import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.requestContext; import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.web.bindery.requestfactory.shared.EntityProxy; /**
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java index 160a2eb..9dc6b4a 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -15,12 +15,12 @@ */ package com.google.web.bindery.requestfactory.shared.impl; -import com.google.gwt.autobean.server.AutoBeanFactoryMagic; +import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; import com.google.web.bindery.requestfactory.shared.messages.MessageFactory; /** * This class has a super-source version with a client-only implementation. */ public interface MessageFactoryHolder { - MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class); + MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class); }
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java index 9b3797c..f13942b 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -15,12 +15,12 @@ */ package com.google.web.bindery.requestfactory.shared.impl; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanCodex; -import com.google.gwt.autobean.shared.AutoBeanFactory; -import com.google.gwt.autobean.shared.AutoBeanUtils; -import com.google.gwt.autobean.shared.AutoBeanVisitor; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanCodex; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBeanVisitor; +import com.google.web.bindery.autobean.shared.Splittable; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy; import com.google.web.bindery.requestfactory.shared.EntityProxyId;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java index 775673a..95c5ca2 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
@@ -17,8 +17,8 @@ import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanUtils; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.web.bindery.requestfactory.shared.ValueProxy; /**
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java index 48e42b2..6d1d6c1 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; /** * Used as a base type for messages that are about a particular id. @@ -81,4 +81,4 @@ @PropertyName(TYPE_TOKEN) void setTypeToken(String value); -} \ No newline at end of file +}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java index 38a124f..00fcac3 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.Splittable; import java.util.List; import java.util.Set;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java index 399a214..275c7d5 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.Splittable; import java.util.Map;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java index 09fe6d6..e22048b 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean; -import com.google.gwt.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBean; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; /** * The factory for creating RequestFactory wire messages.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java index 0fff04f..9ed7e67 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.Splittable; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; import com.google.web.bindery.requestfactory.shared.WriteOperation; import java.util.Map;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java index d8dc38d..8f35588 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; import java.util.List;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java index 08d9a91..47efd4d 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
@@ -15,8 +15,8 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; -import com.google.gwt.autobean.shared.Splittable; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.Splittable; import java.util.List;
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java index cda32c3..7e101e3 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; /** * Encapsulates a ServerFailure object.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java index ea3d06b..51fe40e 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; /** * Describes a message that contains version information.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java index e7b86d2..5b7725f 100644 --- a/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java +++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
@@ -15,7 +15,7 @@ */ package com.google.web.bindery.requestfactory.shared.messages; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; /** * Represents a ConstraintViolation.
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java index cd63ab9..bcdb434 100644 --- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java +++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -15,10 +15,10 @@ */ package com.google.web.bindery.requestfactory.vm; -import com.google.gwt.autobean.server.impl.BeanMethod; -import com.google.gwt.autobean.server.impl.TypeUtils; -import com.google.gwt.autobean.shared.AutoBean.PropertyName; -import com.google.gwt.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBean.PropertyName; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.vm.impl.BeanMethod; +import com.google.web.bindery.autobean.vm.impl.TypeUtils; import com.google.web.bindery.requestfactory.shared.InstanceRequest; import com.google.web.bindery.requestfactory.shared.JsonRpcContent; import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java index 718c324..3d931ca 100644 --- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java +++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -15,10 +15,10 @@ */ package com.google.web.bindery.requestfactory.vm; -import com.google.gwt.autobean.server.AutoBeanFactoryMagic; -import com.google.gwt.autobean.shared.AutoBeanFactory; -import com.google.gwt.autobean.shared.AutoBeanFactory.Category; -import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap; +import com.google.web.bindery.autobean.shared.AutoBeanFactory; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category; +import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap; +import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; import com.google.gwt.event.shared.EventBus; import com.google.web.bindery.requestfactory.shared.BaseProxy; import com.google.web.bindery.requestfactory.shared.EntityProxy; @@ -92,7 +92,7 @@ @Override protected AutoBeanFactory getAutoBeanFactory() { - return AutoBeanFactoryMagic.create(Factory.class); + return AutoBeanFactorySource.create(Factory.class); } @Override