com.google.gwt.autobean.shared.AutoBeanCodex.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.autobean.shared.AutoBeanCodex.java

Source

/*
 * 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();
        }
    }
}