Java tutorial
/* * 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.gwt.autobean.shared; import com.google.gwt.autobean.shared.AutoBeanVisitor.ParameterizationVisitor; import com.google.gwt.autobean.shared.impl.EnumMap; import com.google.gwt.autobean.shared.impl.LazySplittable; import com.google.gwt.autobean.shared.impl.StringQuoter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; /** * 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. * * <p><span style='color:red'>AutoBeans has moved to * <code>com.google.web.bindery.autobeans</code>. This package will be * removed in a future version of GWT.</span></p> */ @Deprecated public class AutoBeanCodex { /** * Describes a means of encoding or decoding a particular type of data to or * from a wire format representation. * * <p><span style='color:red'>AutoBeans has moved to * <code>com.google.web.bindery.autobeans</code>. This package will be * removed in a future version of GWT.</span></p> */ @Deprecated interface Coder { Object decode(Splittable data); void encode(StringBuilder sb, Object value); } /** * Creates a Coder that is capable of operating on a particular * parameterization of a datastructure (e.g. {@code Map<String, List<String>>} * ). */ 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(new CollectionCoder(type, stack.pop())); } else if (Map.class.equals(type)) { // Note that the parameters are passed in reverse order stack.push(new MapCoder(stack.pop(), stack.pop())); } else if (Splittable.class.equals(type)) { stack.push(new SplittableDecoder()); } else if (type.getEnumConstants() != null) { @SuppressWarnings(value = { "rawtypes", "unchecked" }) EnumCoder decoder = new EnumCoder(type); stack.push(decoder); } else if (ValueCodex.canDecode(type)) { stack.push(new ValueCoder(type)); } else { stack.push(new ObjectCoder(type)); } } public Coder getCoder() { assert stack.size() == 1 : "Incorrect size: " + stack.size(); return stack.pop(); } } 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(Splittable data) { Collection<Object> collection; if (List.class.equals(type)) { collection = new ArrayList<Object>(); } else if (Set.class.equals(type)) { collection = new HashSet<Object>(); } else { // Should not reach here throw new RuntimeException(type.getName()); } for (int i = 0, j = data.size(); i < j; i++) { Object element = data.isNull(i) ? null : elementDecoder.decode(data.get(i)); collection.add(element); } return collection; } public void encode(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); return; } Iterator<?> it = ((Collection<?>) value).iterator(); sb.append("["); if (it.hasNext()) { elementDecoder.encode(sb, it.next()); while (it.hasNext()) { sb.append(","); elementDecoder.encode(sb, it.next()); } } sb.append("]"); } } class EnumCoder<E extends Enum<E>> implements Coder { private final Class<E> type; public EnumCoder(Class<E> type) { this.type = type; } public Object decode(Splittable data) { return enumMap.getEnum(type, data.asString()); } public void encode(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); } sb.append(StringQuoter.quote(enumMap.getToken((Enum<?>) value))); } } /** * Used to stop processing. * * <p><span style='color:red'>AutoBeans has moved to * <code>com.google.web.bindery.autobeans</code>. This package will be * removed in a future version of GWT.</span></p> */ @Deprecated static class HaltException extends RuntimeException { public HaltException(RuntimeException cause) { super(cause); } @Override public RuntimeException getCause() { return (RuntimeException) super.getCause(); } } 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(Splittable data) { Map<Object, Object> toReturn = new HashMap<Object, Object>(); if (data.isIndexed()) { assert data.size() == 2 : "Wrong data size: " + data.size(); Splittable keys = data.get(0); Splittable values = data.get(1); for (int i = 0, j = keys.size(); i < j; i++) { Object key = keys.isNull(i) ? null : keyDecoder.decode(keys.get(i)); Object value = values.isNull(i) ? null : valueDecoder.decode(values.get(i)); toReturn.put(key, value); } } else { ValueCoder keyValueDecoder = (ValueCoder) keyDecoder; for (String rawKey : data.getPropertyKeys()) { Object key = keyValueDecoder.decode(rawKey); Object value = data.isNull(rawKey) ? null : valueDecoder.decode(data.get(rawKey)); toReturn.put(key, value); } } return toReturn; } public void encode(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); return; } Map<?, ?> map = (Map<?, ?>) value; boolean isSimpleMap = keyDecoder instanceof ValueCoder; if (isSimpleMap) { boolean first = true; 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 (mapValue == null) { // A null value can be ignored continue; } if (first) { first = false; } else { sb.append(","); } keyDecoder.encode(sb, mapKey); sb.append(":"); valueDecoder.encode(sb, mapValue); } 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()); } sb.append("["); new CollectionCoder(List.class, keyDecoder).encode(sb, keys); sb.append(","); new CollectionCoder(List.class, valueDecoder).encode(sb, values); sb.append("]"); } } } class ObjectCoder implements Coder { private final Class<?> type; public ObjectCoder(Class<?> type) { this.type = type; } public Object decode(Splittable data) { AutoBean<?> bean = doDecode(type, data); return bean == null ? null : bean.as(); } public void encode(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); return; } doEncode(sb, AutoBeanUtils.getAutoBean(value)); } } /** * Extracts properties from a bean and turns them into JSON text. */ class PropertyGetter extends AutoBeanVisitor { private boolean first = true; private final StringBuilder sb; public PropertyGetter(StringBuilder sb) { this.sb = sb; } @Override public void endVisit(AutoBean<?> bean, Context ctx) { sb.append("}"); seen.pop(); } @Override public boolean visit(AutoBean<?> bean, Context ctx) { if (seen.contains(bean)) { throw new HaltException(new UnsupportedOperationException("Cycles not supported")); } seen.push(bean); 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 { sb.append(","); } sb.append(StringQuoter.quote(propertyName)); sb.append(":"); decoder.encode(sb, value); } } /** * Populates beans with data extracted from an evaluated JSON payload. */ class PropertySetter extends AutoBeanVisitor { private Splittable data; public void decodeInto(Splittable data, AutoBean<?> bean) { this.data = data; 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(data.get(propertyName)); ctx.set(propertyValue); } } } class SplittableDecoder implements Coder { public Object decode(Splittable data) { return data; } public void encode(StringBuilder sb, Object value) { if (value == null) { sb.append("null"); return; } sb.append(((Splittable) value).getPayload()); } } 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(Splittable propertyValue) { return decode(propertyValue.asString()); } public Object decode(String propertyValue) { return ValueCodex.decode(type, propertyValue); } public void encode(StringBuilder sb, Object value) { sb.append(ValueCodex.encode(value).getPayload()); } } public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, Splittable data) { return new AutoBeanCodex(factory).doDecode(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)} * @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) { new AutoBeanCodex(bean.getFactory()).doDecodeInto(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 LazySplittable.NULL; } StringBuilder sb = new StringBuilder(); new AutoBeanCodex(bean.getFactory()).doEncode(sb, bean); return new LazySplittable(sb.toString()); } private final EnumMap enumMap; private final AutoBeanFactory factory; private final Stack<AutoBean<?>> seen = new Stack<AutoBean<?>>(); private AutoBeanCodex(AutoBeanFactory factory) { this.factory = factory; this.enumMap = factory instanceof EnumMap ? (EnumMap) factory : null; } <T> AutoBean<T> doDecode(Class<T> clazz, Splittable data) { AutoBean<T> toReturn = factory.create(clazz); if (toReturn == null) { throw new IllegalArgumentException(clazz.getName()); } doDecodeInto(data, toReturn); return toReturn; } void doDecodeInto(Splittable data, AutoBean<?> bean) { new PropertySetter().decodeInto(data, bean); } void doEncode(StringBuilder sb, AutoBean<?> bean) { PropertyGetter e = new PropertyGetter(sb); try { bean.accept(e); } catch (HaltException ex) { throw ex.getCause(); } } }