| /* |
| * 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.requestfactory.shared.impl; |
| |
| import com.google.web.bindery.autobean.shared.AutoBean; |
| import com.google.web.bindery.autobean.shared.AutoBeanUtils; |
| import com.google.web.bindery.autobean.shared.Splittable; |
| import com.google.web.bindery.autobean.shared.ValueCodex; |
| import com.google.web.bindery.autobean.shared.impl.StringQuoter; |
| import com.google.web.bindery.requestfactory.shared.BaseProxy; |
| import com.google.web.bindery.requestfactory.shared.EntityProxyId; |
| |
| 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; |
| |
| /** |
| * Analogous to {@link ValueCodex}, but for object types. |
| */ |
| public class EntityCodex { |
| /** |
| * Abstracts the process by which EntityProxies are created. |
| */ |
| public interface EntitySource { |
| /** |
| * Expects an encoded |
| * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}. |
| */ |
| <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedIdMessage); |
| |
| /** |
| * Should return an encoded |
| * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}. |
| */ |
| Splittable getSerializedProxyId(SimpleProxyId<?> stableId); |
| |
| boolean isEntityType(Class<?> clazz); |
| |
| boolean isValueType(Class<?> clazz); |
| } |
| |
| /** |
| * Collection support is limited to value types and resolving ids. |
| */ |
| public static Object decode(EntitySource source, Class<?> type, Class<?> elementType, |
| Splittable split) { |
| if (split == null || split == Splittable.NULL) { |
| return null; |
| } |
| |
| // Collection support |
| if (elementType != null) { |
| Collection<Object> collection = null; |
| if (List.class.equals(type)) { |
| collection = new ArrayList<Object>(); |
| } else if (Set.class.equals(type)) { |
| collection = new HashSet<Object>(); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| |
| // Decode values |
| if (ValueCodex.canDecode(elementType)) { |
| for (int i = 0, j = split.size(); i < j; i++) { |
| if (split.isNull(i)) { |
| collection.add(null); |
| } else { |
| Object element = ValueCodex.decode(elementType, split.get(i)); |
| collection.add(element); |
| } |
| } |
| } else { |
| for (int i = 0, j = split.size(); i < j; i++) { |
| if (split.isNull(i)) { |
| collection.add(null); |
| } else { |
| Object element = decode(source, elementType, null, split.get(i)); |
| collection.add(element); |
| } |
| } |
| } |
| return collection; |
| } |
| |
| if (source.isEntityType(type) || source.isValueType(type) || EntityProxyId.class.equals(type)) { |
| return source.getBeanForPayload(split).as(); |
| } |
| |
| // Fall back to values |
| return ValueCodex.decode(type, split); |
| } |
| |
| /** |
| * Collection support is limited to value types and resolving ids. |
| */ |
| public static Object decode(EntitySource source, Class<?> type, Class<?> elementType, |
| String jsonPayload) { |
| Splittable split = StringQuoter.split(jsonPayload); |
| return decode(source, type, elementType, split); |
| } |
| |
| /** |
| * Map decoding follows behaviour of AutoBeanCodexImpl.MapCoder |
| */ |
| public static Object decode(EntitySource source, |
| Class<?> type, Class<?> keyType, Class<?> valueType, Splittable split) { |
| if (split == null || split == Splittable.NULL) { |
| return null; |
| } |
| |
| if (!Map.class.equals(type)) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| Map<Object, Object> map = new HashMap<Object, Object>(); |
| if (ValueCodex.canDecode(keyType) || !split.isIndexed()) { |
| List<String> keys = split.getPropertyKeys(); |
| for (String propertyKey : keys) { |
| Object key = (keyType == String.class) ? |
| propertyKey : ValueCodex.decode(keyType, StringQuoter.split(propertyKey)); |
| if (split.isNull(propertyKey)) { |
| map.put(key, null); |
| } else { |
| Splittable valueSplit = split.get(propertyKey); |
| Object value = null; |
| if (ValueCodex.canDecode(valueType)) { |
| value = ValueCodex.decode(valueType, valueSplit); |
| } else { |
| value = decode(source, valueType, null, valueSplit); |
| } |
| map.put(key, value); |
| } |
| } |
| } else { |
| if (split.size() != 2) { |
| throw new UnsupportedOperationException(); |
| } |
| List<?> keys = (List<?>) decode(source, List.class, keyType, split.get(0)); |
| List<?> values = (List<?>) decode(source, List.class, valueType, split.get(1)); |
| if (keys.size() != values.size()) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| for (int i = 0, size = keys.size(); i < size; i++) { |
| map.put(keys.get(i), values.get(i)); |
| } |
| } |
| |
| return map; |
| } |
| |
| /** |
| * Create a wire-format representation of an object. |
| */ |
| public static Splittable encode(EntitySource source, Object value) { |
| if (value == null) { |
| return Splittable.NULL; |
| } |
| |
| if (value instanceof Poser<?>) { |
| value = ((Poser<?>) value).getPosedValue(); |
| } |
| |
| if (value instanceof Iterable<?>) { |
| StringBuilder toReturn = new StringBuilder("["); |
| boolean first = true; |
| for (Object val : ((Iterable<?>) value)) { |
| if (!first) { |
| toReturn.append(','); |
| } else { |
| first = false; |
| } |
| if (val == null) { |
| toReturn.append("null"); |
| } else { |
| toReturn.append(encode(source, val).getPayload()); |
| } |
| } |
| toReturn.append(']'); |
| return StringQuoter.split(toReturn.toString()); |
| } |
| |
| // Map encoding follows behaviour of AutoBeanCodexImpl.MapCoder |
| if (value instanceof Map<?, ?>) { |
| Map<?, ?> map = (Map<?, ?>) value; |
| StringBuilder sb = new StringBuilder(); |
| |
| if (map.containsKey(null)) { |
| throw new IllegalArgumentException("null Map keys are not supported"); |
| } |
| |
| boolean isSimpleMap = (map.isEmpty() || ValueCodex.canDecode(map.keySet().iterator().next().getClass())); |
| 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 (first) { |
| first = false; |
| } else { |
| sb.append(","); |
| } |
| |
| final String encodedKey = (mapKey.getClass() == String.class) ? |
| (String) mapKey : encode(source, mapKey).getPayload(); |
| sb.append(StringQuoter.quote(encodedKey)); |
| sb.append(":"); |
| if (mapValue == null) { |
| // Null values must be preserved |
| sb.append("null"); |
| } else { |
| sb.append(encode(source, mapValue).getPayload()); |
| } |
| } |
| 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("["); |
| sb.append(encode(source, keys).getPayload()); |
| sb.append(","); |
| sb.append(encode(source, values).getPayload()); |
| sb.append("]"); |
| } |
| |
| return StringQuoter.split(sb.toString()); |
| } |
| |
| if (value instanceof BaseProxy) { |
| AutoBean<BaseProxy> autoBean = AutoBeanUtils.getAutoBean((BaseProxy) value); |
| value = BaseProxyCategory.stableId(autoBean); |
| } |
| |
| if (value instanceof SimpleProxyId<?>) { |
| return source.getSerializedProxyId((SimpleProxyId<?>) value); |
| } |
| |
| return ValueCodex.encode(value); |
| } |
| } |