All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.autobean.shared.AutoBeanCodex Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.
 *
 * 

AutoBeans has moved to * com.google.web.bindery.autobeans. This package will be * removed in a future version of GWT.

*/ @Deprecated public class AutoBeanCodex { /** * Describes a means of encoding or decoding a particular type of data to or * from a wire format representation. * *

AutoBeans has moved to * com.google.web.bindery.autobeans. This package will be * removed in a future version of GWT.

*/ @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>} * ). */ class CoderCreator extends ParameterizationVisitor { private Stack stack = new Stack(); @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 collection; if (List.class.equals(type)) { collection = new ArrayList(); } else if (Set.class.equals(type)) { collection = new HashSet(); } 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> implements Coder { private final Class type; public EnumCoder(Class 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. * *

AutoBeans has moved to * com.google.web.bindery.autobeans. This package will be * removed in a future version of GWT.

*/ @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 toReturn = new HashMap(); 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 keys = new ArrayList(map.size()); List values = new ArrayList(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 AutoBean decode(AutoBeanFactory factory, Class clazz, Splittable data) { return new AutoBeanCodex(factory).doDecode(clazz, data); } /** * Decode an AutoBeanCodex payload. * * @param the expected return type * @param factory an AutoBeanFactory capable of producing {@code AutoBean} * @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 AutoBean decode(AutoBeanFactory factory, Class 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> seen = new Stack>(); private AutoBeanCodex(AutoBeanFactory factory) { this.factory = factory; this.enumMap = factory instanceof EnumMap ? (EnumMap) factory : null; } AutoBean doDecode(Class clazz, Splittable data) { AutoBean 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(); } } }