/* * 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.web.bindery.autobean.shared; import com.google.web.bindery.autobean.shared.impl.StringQuoter; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Provides unified encoding and decoding of value objects. */ public class ValueCodex { enum Type { BIG_DECIMAL(BigDecimal.class) { @Override public boolean canUpcast(Object value) { return value instanceof BigDecimal; } @Override public BigDecimal decode(Class<?> clazz, Splittable value) { return new BigDecimal(value.asString()); } @Override public Splittable encode(Object value) { return StringQuoter.create(((BigDecimal) value).toString()); } }, BIG_INTEGER(BigInteger.class) { @Override public boolean canUpcast(Object value) { return value instanceof BigInteger; } @Override public BigInteger decode(Class<?> clazz, Splittable value) { return new BigInteger(value.asString()); } @Override public Splittable encode(Object value) { return StringQuoter.create(((BigInteger) value).toString()); } }, BOOLEAN(Boolean.class, boolean.class, false) { @Override public Boolean decode(Class<?> clazz, Splittable value) { return value.asBoolean(); } @Override public Splittable encode(Object value) { return StringQuoter.create((Boolean) value); } }, BYTE(Byte.class, byte.class, (byte) 0) { @Override public Byte decode(Class<?> clazz, Splittable value) { return (byte) value.asNumber(); } @Override public Splittable encode(Object value) { return StringQuoter.create((Byte) value); } }, CHARACTER(Character.class, char.class, (char) 0) { @Override public Character decode(Class<?> clazz, Splittable value) { return value.asString().charAt(0); } @Override public Splittable encode(Object value) { return StringQuoter.create(String.valueOf((Character) value)); } }, DATE(Date.class) { @Override public boolean canUpcast(Object value) { return value instanceof Date; } @Override public Date decode(Class<?> clazz, Splittable value) { return StringQuoter.tryParseDate(value.asString()); } @Override public Splittable encode(Object value) { return StringQuoter.create(String.valueOf(((Date) value).getTime())); } }, DOUBLE(Double.class, double.class, 0d) { @Override public Double decode(Class<?> clazz, Splittable value) { return value.asNumber(); } @Override public Splittable encode(Object value) { return StringQuoter.create((Double) value); } }, ENUM(Enum.class) { @Override public boolean canUpcast(Object value) { return value instanceof Enum; } @Override public Enum<?> decode(Class<?> clazz, Splittable value) { return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()]; } @Override public Splittable encode(Object value) { return StringQuoter.create(((Enum<?>) value).ordinal()); } }, FLOAT(Float.class, float.class, 0f) { @Override public Float decode(Class<?> clazz, Splittable value) { return (float) value.asNumber(); } @Override public Splittable encode(Object value) { return StringQuoter.create((Float) value); } }, INTEGER(Integer.class, int.class, 0) { @Override public Integer decode(Class<?> clazz, Splittable value) { return Integer.valueOf((int) value.asNumber()); } @Override public Splittable encode(Object value) { return StringQuoter.create((Integer) value); } }, LONG(Long.class, long.class, 0L) { @Override public Long decode(Class<?> clazz, Splittable value) { return Long.parseLong(value.asString()); } @Override public Splittable encode(Object value) { return StringQuoter.create(String.valueOf((Long) value)); } }, SHORT(Short.class, short.class, (short) 0) { @Override public Short decode(Class<?> clazz, Splittable value) { return (short) value.asNumber(); } @Override public Splittable encode(Object value) { return StringQuoter.create((Short) value); } }, STRING(String.class) { @Override public String decode(Class<?> clazz, Splittable value) { return value.asString(); } @Override public Splittable encode(Object value) { return StringQuoter.create((String) value); } }, SPLITTABLE(Splittable.class) { @Override public Splittable decode(Class<?> clazz, Splittable value) { return value; } @Override public Splittable encode(Object value) { return (Splittable) value; } }, VOID(Void.class, void.class, null) { @Override public Void decode(Class<?> clazz, Splittable value) { return null; } @Override public Splittable encode(Object value) { return null; } }; private final Object defaultValue; private final Class<?> type; private final Class<?> primitiveType; Type(Class<?> objectType) { this(objectType, null, null); } Type(Class<?> objectType, Class<?> primitiveType, Object defaultValue) { this.type = objectType; this.primitiveType = primitiveType; this.defaultValue = defaultValue; } /** * Determines whether or not the Type can handle the given value via * upcasting semantics. * * @param value a value Object */ public boolean canUpcast(Object value) { // Most value types are final, so this method is meaningless return false; } public abstract Object decode(Class<?> clazz, Splittable value); public abstract Splittable encode(Object value); public Object getDefaultValue() { return defaultValue; } public Class<?> getPrimitiveType() { return primitiveType; } public Class<?> getType() { return type; } } private static final Set<Class<?>> ALL_VALUE_TYPES; private static final Map<Class<?>, Type> TYPES_BY_CLASS; static { Map<Class<?>, Type> temp = new HashMap<Class<?>, Type>(); for (Type t : Type.values()) { temp.put(t.getType(), t); if (t.getPrimitiveType() != null) { temp.put(t.getPrimitiveType(), t); } } ALL_VALUE_TYPES = Collections.unmodifiableSet(temp.keySet()); TYPES_BY_CLASS = Collections.unmodifiableMap(temp); } /** * Returns true if ValueCodex can operate on values of the given type. * * @param clazz a Class object * @return {@code true} if the given object type can be decoded */ public static boolean canDecode(Class<?> clazz) { if (findType(clazz) != null) { return true; } // Use other platform-specific tests return ValueCodexHelper.canDecode(clazz); } @SuppressWarnings("unchecked") public static <T> T decode(Class<T> clazz, Splittable split) { if (split == null || split == Splittable.NULL) { return null; } return (T) getTypeOrDie(clazz).decode(clazz, split); } /** * No callers in GWT codebase. * * @deprecated use {@link #decode(Class, Splittable)} instead. * @throws UnsupportedOperationException */ @Deprecated public static <T> T decode(Class<T> clazz, String string) { throw new UnsupportedOperationException(); } /** * Encode a value object when the wire format type is known. This method * should be preferred over {@link #encode(Object)} when possible. */ public static Splittable encode(Class<?> clazz, Object obj) { if (obj == null) { return Splittable.NULL; } return getTypeOrDie(clazz).encode(obj); } public static Splittable encode(Object obj) { if (obj == null) { return Splittable.NULL; } Type t = findType(obj.getClass()); // Try upcasting if (t == null) { for (Type maybe : Type.values()) { if (maybe.canUpcast(obj)) { t = maybe; break; } } } if (t == null) { throw new UnsupportedOperationException(obj.getClass().getName()); } return t.encode(obj); } /** * Return all Value types that can be processed by the ValueCodex. */ public static Set<Class<?>> getAllValueTypes() { return ALL_VALUE_TYPES; } /** * Returns the uninitialized field value for the given primitive type. */ public static Object getUninitializedFieldValue(Class<?> clazz) { Type type = getTypeOrDie(clazz); if (clazz.equals(type.getPrimitiveType())) { return type.getDefaultValue(); } return null; } /** * May return <code>null</code>. */ private static <T> Type findType(Class<T> clazz) { if (clazz.isEnum() || (clazz.getSuperclass() != null && clazz.getSuperclass().isEnum())) { return Type.ENUM; } return TYPES_BY_CLASS.get(clazz); } private static <T> Type getTypeOrDie(Class<T> clazz) { Type toReturn = findType(clazz); if (toReturn == null) { throw new UnsupportedOperationException(clazz.getName()); } return toReturn; } }