Package com.google.gwt.autobean.shared

Source Code of com.google.gwt.autobean.shared.AutoBeanCodex$HaltException

/*
* 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.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.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.
*/
public class AutoBeanCodex {
  static class Decoder extends AutoBeanVisitor {
    private final Stack<AutoBean<?>> beanStack = new Stack<AutoBean<?>>();
    private final Stack<Splittable> dataStack = new Stack<Splittable>();
    private AutoBean<?> bean;
    private Splittable data;
    private final AutoBeanFactory factory;

    public Decoder(AutoBeanFactory factory) {
      this.factory = factory;
    }

    @SuppressWarnings("unchecked")
    public <T> AutoBean<T> decode(Splittable data, Class<T> type) {
      push(data, type);
      bean.accept(this);
      return (AutoBean<T>) pop();
    }

    @Override
    public boolean visitCollectionProperty(String propertyName,
        AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
      if (data.isNull(propertyName)) {
        return false;
      }

      Collection<Object> collection;
      if (List.class.equals(ctx.getType())) {
        collection = new ArrayList<Object>();
      } else if (Set.class.equals(ctx.getType())) {
        collection = new HashSet<Object>();
      } else {
        throw new UnsupportedOperationException("Only List and Set supported");
      }

      boolean isValue = ValueCodex.canDecode(ctx.getElementType());
      boolean isEncoded = Splittable.class.equals(ctx.getElementType());
      Splittable listData = data.get(propertyName);
      for (int i = 0, j = listData.size(); i < j; i++) {
        if (listData.isNull(i)) {
          collection.add(null);
        } else {
          if (isValue) {
            collection.add(decodeValue(ctx.getElementType(), listData.get(i)));
          } else if (isEncoded) {
            collection.add(listData.get(i));
          } else {
            collection.add(decode(listData.get(i), ctx.getElementType()).as());
          }
        }
      }
      ctx.set(collection);
      return false;
    }

    @Override
    public boolean visitMapProperty(String propertyName,
        AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
      if (data.isNull(propertyName)) {
        return false;
      }

      Map<?, ?> map;
      if (ValueCodex.canDecode(ctx.getKeyType())) {
        map = decodeValueKeyMap(data.get(propertyName), ctx.getKeyType(),
            ctx.getValueType());
      } else {
        map = decodeObjectKeyMap(data.get(propertyName), ctx.getKeyType(),
            ctx.getValueType());
      }
      ctx.set(map);
      return false;
    }

    @Override
    public boolean visitReferenceProperty(String propertyName,
        AutoBean<?> value, PropertyContext ctx) {
      if (data.isNull(propertyName)) {
        return false;
      }

      if (Splittable.class.equals(ctx.getType())) {
        ctx.set(data.get(propertyName));
        return false;
      }

      push(data.get(propertyName), ctx.getType());
      bean.accept(this);
      ctx.set(pop().as());
      return false;
    }

    @Override
    public boolean visitValueProperty(String propertyName, Object value,
        PropertyContext ctx) {
      if (!data.isNull(propertyName)) {
        Object object;
        Splittable propertyValue = data.get(propertyName);
        Class<?> type = ctx.getType();
        object = decodeValue(type, propertyValue);
        ctx.set(object);
      }
      return false;
    }

    private Map<?, ?> decodeObjectKeyMap(Splittable map, Class<?> keyType,
        Class<?> valueType) {
      boolean isEncodedKey = Splittable.class.equals(keyType);
      boolean isEncodedValue = Splittable.class.equals(valueType);
      boolean isValueValue = Splittable.class.equals(valueType);

      Splittable keyList = map.get(0);
      Splittable valueList = map.get(1);
      assert keyList.size() == valueList.size();

      Map<Object, Object> toReturn = new HashMap<Object, Object>(keyList.size());
      for (int i = 0, j = keyList.size(); i < j; i++) {
        Object key;
        if (isEncodedKey) {
          key = keyList.get(i);
        } else {
          key = decode(keyList.get(i), keyType).as();
        }

        Object value;
        if (valueList.isNull(i)) {
          value = null;
        } else if (isEncodedValue) {
          value = keyList.get(i);
        } else if (isValueValue) {
          value = decodeValue(valueType, keyList.get(i));
        } else {
          value = decode(valueList.get(i), valueType).as();
        }

        toReturn.put(key, value);
      }
      return toReturn;
    }

    private Object decodeValue(Class<?> type, Splittable propertyValue) {
      return decodeValue(type, propertyValue.asString());
    }

    private Object decodeValue(Class<?> type, String propertyValue) {
      Object object;
      if (type.isEnum() && bean.getFactory() instanceof EnumMap) {
        // The generics kind of get in the way here
        @SuppressWarnings({"unchecked", "rawtypes"})
        Class<Enum> enumType = (Class<Enum>) type;
        @SuppressWarnings("unchecked")
        Enum<?> e = ((EnumMap) bean.getFactory()).getEnum(enumType,
            propertyValue);
        object = e;
      } else {
        object = ValueCodex.decode(type, propertyValue);
      }
      return object;
    }

    private Map<?, ?> decodeValueKeyMap(Splittable map, Class<?> keyType,
        Class<?> valueType) {
      Map<Object, Object> toReturn = new HashMap<Object, Object>();

      boolean isEncodedValue = Splittable.class.equals(valueType);
      boolean isValueValue = ValueCodex.canDecode(valueType);
      for (String encodedKey : map.getPropertyKeys()) {
        Object key = decodeValue(keyType, encodedKey);
        Object value;
        if (map.isNull(encodedKey)) {
          value = null;
        } else if (isEncodedValue) {
          value = map.get(encodedKey);
        } else if (isValueValue) {
          value = decodeValue(valueType, map.get(encodedKey));
        } else {
          value = decode(map.get(encodedKey), valueType).as();
        }
        toReturn.put(key, value);
      }

      return toReturn;
    }

    private AutoBean<?> pop() {
      dataStack.pop();
      if (dataStack.isEmpty()) {
        data = null;
      } else {
        data = dataStack.peek();
      }
      AutoBean<?> toReturn = beanStack.pop();
      if (beanStack.isEmpty()) {
        bean = null;
      } else {
        bean = beanStack.peek();
      }
      return toReturn;
    }

    private void push(Splittable data, Class<?> type) {
      this.data = data;
      bean = factory.create(type);
      if (bean == null) {
        throw new IllegalArgumentException(
            "The AutoBeanFactory cannot create a " + type.getName());
      }
      dataStack.push(data);
      beanStack.push(bean);
    }
  }

  static class Encoder extends AutoBeanVisitor {
    private EnumMap enumMap;
    private Set<AutoBean<?>> seen = new HashSet<AutoBean<?>>();
    private Stack<StringBuilder> stack = new Stack<StringBuilder>();
    private StringBuilder sb;

    public Encoder(AutoBeanFactory factory) {
      if (factory instanceof EnumMap) {
        enumMap = (EnumMap) factory;
      }
    }

    @Override
    public void endVisit(AutoBean<?> bean, Context ctx) {
      if (sb.length() == 0) {
        // No properties
        sb.append("{");
      } else {
        sb.setCharAt(0, '{');
      }
      sb.append("}");
    }

    @Override
    public void endVisitReferenceProperty(String propertyName,
        AutoBean<?> value, PropertyContext ctx) {
      StringBuilder popped = pop();
      if (popped.length() > 0) {
        sb.append(",\"").append(propertyName).append("\":").append(
            popped.toString());
      }
    }

    @Override
    public boolean visitCollectionProperty(String propertyName,
        AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
      push(new StringBuilder());

      if (value == null) {
        return false;
      }

      Collection<?> collection = value.as();
      if (collection.isEmpty()) {
        sb.append("[]");
        return false;
      }

      if (ValueCodex.canDecode(ctx.getElementType())) {
        for (Object element : collection) {
          sb.append(",").append(
              encodeValue(ctx.getElementType(), element).getPayload());
        }
      } else {
        boolean isEncoded = Splittable.class.equals(ctx.getElementType());
        for (Object element : collection) {
          sb.append(",");
          if (element == null) {
            sb.append("null");
          } else if (isEncoded) {
            sb.append(((Splittable) element).getPayload());
          } else {
            encodeToStringBuilder(sb, element);
          }
        }
      }
      sb.setCharAt(0, '[');
      sb.append("]");
      return false;
    }

    @Override
    public boolean visitMapProperty(String propertyName,
        AutoBean<Map<?, ?>> value, MapPropertyContext ctx) {
      push(new StringBuilder());

      if (value == null) {
        return false;
      }

      Map<?, ?> map = value.as();
      if (map.isEmpty()) {
        sb.append("{}");
        return false;
      }

      Class<?> keyType = ctx.getKeyType();
      Class<?> valueType = ctx.getValueType();
      boolean isEncodedKey = Splittable.class.equals(keyType);
      boolean isEncodedValue = Splittable.class.equals(valueType);
      boolean isValueKey = ValueCodex.canDecode(keyType);
      boolean isValueValue = ValueCodex.canDecode(valueType);

      if (isValueKey) {
        writeValueKeyMap(map, keyType, valueType, isEncodedValue, isValueValue);
      } else {
        writeObjectKeyMap(map, valueType, isEncodedKey, isEncodedValue,
            isValueValue);
      }

      return false;
    }

    @Override
    public boolean visitReferenceProperty(String propertyName,
        AutoBean<?> value, PropertyContext ctx) {
      push(new StringBuilder());

      if (value == null) {
        return false;
      }

      if (Splittable.class.equals(ctx.getType())) {
        sb.append(((Splittable) value.as()).getPayload());
        return false;
      }

      if (seen.contains(value)) {
        haltOnCycle();
      }

      return true;
    }

    @Override
    public boolean visitValueProperty(String propertyName, Object value,
        PropertyContext ctx) {
      // Skip primitive types whose values are uninteresting.
      Class<?> type = ctx.getType();
      Object blankValue = ValueCodex.getUninitializedFieldValue(type);
      if (value == blankValue || value != null && value.equals(blankValue)) {
        return false;
      }

      // Special handling for enums if we have an obfuscation map
      Splittable split;
      split = encodeValue(type, value);
      sb.append(",\"").append(propertyName).append("\":").append(
          split.getPayload());
      return false;
    }

    StringBuilder pop() {
      StringBuilder toReturn = stack.pop();
      sb = stack.peek();
      return toReturn;
    }

    void push(StringBuilder sb) {
      stack.push(sb);
      this.sb = sb;
    }

    private void encodeToStringBuilder(StringBuilder accumulator, Object value) {
      push(new StringBuilder());
      AutoBean<?> bean = AutoBeanUtils.getAutoBean(value);
      if (!seen.add(bean)) {
        haltOnCycle();
      }
      bean.accept(this);
      accumulator.append(pop().toString());
      seen.remove(bean);
    }

    /**
     * Encodes a value, with special handling for enums to allow the field name
     * to be overridden.
     */
    private Splittable encodeValue(Class<?> expectedType, Object value) {
      Splittable split;
      if (value instanceof Enum<?> && enumMap != null) {
        split = ValueCodex.encode(String.class,
            enumMap.getToken((Enum<?>) value));
      } else {
        split = ValueCodex.encode(expectedType, value);
      }
      return split;
    }

    private void haltOnCycle() {
      throw new HaltException(new UnsupportedOperationException(
          "Cycle detected"));
    }

    /**
     * Writes a map JSON literal where the keys are object types. This is
     * encoded as a list of two lists, since it's possible that two distinct
     * objects have the same encoded form.
     */
    private void writeObjectKeyMap(Map<?, ?> map, Class<?> valueType,
        boolean isEncodedKey, boolean isEncodedValue, boolean isValueValue) {
      StringBuilder keys = new StringBuilder();
      StringBuilder values = new StringBuilder();

      for (Map.Entry<?, ?> entry : map.entrySet()) {
        if (isEncodedKey) {
          keys.append(",").append(((Splittable) entry.getKey()).getPayload());
        } else {
          encodeToStringBuilder(keys.append(","), entry.getKey());
        }

        if (isEncodedValue) {
          values.append(",").append(
              ((Splittable) entry.getValue()).getPayload());
        } else if (isValueValue) {
          values.append(",").append(
              encodeValue(valueType, entry.getValue()).getPayload());
        } else {
          encodeToStringBuilder(values.append(","), entry.getValue());
        }
      }
      keys.setCharAt(0, '[');
      keys.append("]");
      values.setCharAt(0, '[');
      values.append("]");

      sb.append("[").append(keys.toString()).append(",").append(
          values.toString()).append("]");
    }

    /**
     * Writes a map JSON literal where the keys are value types.
     */
    private void writeValueKeyMap(Map<?, ?> map, Class<?> keyType,
        Class<?> valueType, boolean isEncodedValue, boolean isValueValue) {
      for (Map.Entry<?, ?> entry : map.entrySet()) {
        sb.append(",").append(encodeValue(keyType, entry.getKey()).getPayload()).append(
            ":");
        if (isEncodedValue) {
          sb.append(((Splittable) entry.getValue()).getPayload());
        } else if (isValueValue) {
          sb.append(encodeValue(valueType, entry.getValue()).getPayload());
        } else {
          encodeToStringBuilder(sb, entry.getValue());
        }
      }
      sb.setCharAt(0, '{');
      sb.append("}");
    }
  }

  /**
   * Used to stop processing.
   */
  static class HaltException extends RuntimeException {
    public HaltException(RuntimeException cause) {
      super(cause);
    }

    @Override
    public RuntimeException getCause() {
      return (RuntimeException) super.getCause();
    }
  }

  public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz,
      Splittable data) {
    return new Decoder(factory).decode(data, clazz);
  }

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

  /**
   * 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();
    encodeForJsoPayload(sb, bean);
    return new LazySplittable(sb.toString());
  }

  // ["prop",value,"prop",value, ...]
  private static void encodeForJsoPayload(StringBuilder sb, AutoBean<?> bean) {
    Encoder e = new Encoder(bean.getFactory());
    e.push(sb);
    try {
      bean.accept(e);
    } catch (HaltException ex) {
      throw ex.getCause();
    }
  }

  private AutoBeanCodex() {
  }
}
TOP

Related Classes of com.google.gwt.autobean.shared.AutoBeanCodex$HaltException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.