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/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;
+ }
+}