123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- /*
- * Copyright 2000-2014 Vaadin Ltd.
- *
- * 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.vaadin.client.communication;
-
- import java.util.Collection;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Set;
-
- import com.vaadin.client.ApplicationConnection;
- import com.vaadin.client.JsArrayObject;
- import com.vaadin.client.metadata.NoDataException;
- import com.vaadin.client.metadata.Property;
- import com.vaadin.client.metadata.Type;
- import com.vaadin.shared.Connector;
- import com.vaadin.shared.JsonConstants;
- import com.vaadin.shared.communication.UidlValue;
-
- import elemental.json.Json;
- import elemental.json.JsonArray;
- import elemental.json.JsonObject;
- import elemental.json.JsonValue;
-
- /**
- * Encoder for converting RPC parameters and other values to JSON for transfer
- * between the client and the server.
- *
- * Currently, basic data types as well as Map, String[] and Object[] are
- * supported, where maps and Object[] can contain other supported data types.
- *
- * TODO extensible type support
- *
- * @since 7.0
- */
- public class JsonEncoder {
-
- /**
- * Encode a value to a JSON representation for transport from the client to
- * the server.
- *
- * @param value
- * value to convert
- * @param connection
- * @return JSON representation of the value
- */
- public static JsonValue encode(Object value, Type type,
- ApplicationConnection connection) {
- if (null == value) {
- return Json.createNull();
- } else if (value instanceof JsonValue) {
- return (JsonValue) value;
- } else if (value instanceof String[]) {
- String[] array = (String[]) value;
- JsonArray jsonArray = Json.createArray();
- for (int i = 0; i < array.length; ++i) {
- jsonArray.set(i, array[i]);
- }
- return jsonArray;
- } else if (value instanceof String) {
- return Json.create((String) value);
- } else if (value instanceof Boolean) {
- return Json.create((Boolean) value);
- } else if (value instanceof Number) {
- return Json.create(((Number) value).doubleValue());
- } else if (value instanceof Character) {
- return Json.create(String.valueOf(value));
- } else if (value instanceof Object[] && type == null) {
- // Non-legacy arrays handed by generated serializer
- return encodeLegacyObjectArray((Object[]) value, connection);
- } else if (value instanceof Enum) {
- return encodeEnum((Enum<?>) value, connection);
- } else if (value instanceof Map) {
- return encodeMap((Map) value, type, connection);
- } else if (value instanceof Connector) {
- Connector connector = (Connector) value;
- return Json.create(connector.getConnectorId());
- } else if (value instanceof Collection) {
- return encodeCollection((Collection) value, type, connection);
- } else if (value instanceof UidlValue) {
- return encodeVariableChange((UidlValue) value, connection);
- } else {
- // First see if there's a custom serializer
- JSONSerializer<Object> serializer = null;
- if (type != null) {
- serializer = (JSONSerializer<Object>) type.findSerializer();
- if (serializer != null) {
- return serializer.serialize(value, connection);
- }
- }
-
- String transportType = getTransportType(value);
- if (transportType != null) {
- // Send the string value for remaining legacy types
- return Json.create(String.valueOf(value));
- } else if (type != null) {
- // And finally try using bean serialization logic
- try {
- JsArrayObject<Property> properties = type
- .getPropertiesAsArray();
-
- JsonObject jsonObject = Json.createObject();
-
- int size = properties.size();
- for (int i = 0; i < size; i++) {
- Property property = properties.get(i);
- Object propertyValue = property.getValue(value);
- Type propertyType = property.getType();
- JsonValue encodedPropertyValue = encode(propertyValue,
- propertyType, connection);
- jsonObject.put(property.getName(),
- encodedPropertyValue);
- }
- return jsonObject;
-
- } catch (NoDataException e) {
- throw new RuntimeException(
- "Can not encode " + type.getSignature(), e);
- }
-
- } else {
- throw new RuntimeException("Can't encode " + value.getClass()
- + " without type information");
- }
- }
- }
-
- private static JsonValue encodeVariableChange(UidlValue uidlValue,
- ApplicationConnection connection) {
- Object value = uidlValue.getValue();
-
- JsonArray jsonArray = Json.createArray();
- String transportType = getTransportType(value);
- if (transportType == null) {
- /*
- * This should not happen unless you try to send an unsupported type
- * in a legacy variable change from the client to the server.
- */
- String valueType = null;
- if (value != null) {
- valueType = value.getClass().getName();
- }
- throw new IllegalArgumentException(
- "Cannot encode object of type " + valueType);
- }
- jsonArray.set(0, Json.create(transportType));
- jsonArray.set(1, encode(value, null, connection));
-
- return jsonArray;
- }
-
- private static JsonValue encodeMap(Map<Object, Object> map, Type type,
- ApplicationConnection connection) {
- /*
- * As we have no info about declared types, we instead select encoding
- * scheme based on actual type of first key. We can't do this if there's
- * no first key, so instead we send some special value that the
- * server-side decoding must check for. (see #8906)
- */
- if (map.isEmpty()) {
- return Json.createArray();
- }
-
- Object firstKey = map.keySet().iterator().next();
- if (firstKey instanceof String) {
- return encodeStringMap(map, type, connection);
- } else if (type == null) {
- throw new IllegalStateException(
- "Only string keys supported for legacy maps");
- } else if (firstKey instanceof Connector) {
- return encodeConnectorMap(map, type, connection);
- } else {
- return encodeObjectMap(map, type, connection);
- }
- }
-
- private static JsonValue encodeChildValue(Object value, Type collectionType,
- int typeIndex, ApplicationConnection connection) {
- if (collectionType == null) {
- return encode(new UidlValue(value), null, connection);
- } else {
- assert collectionType.getParameterTypes() != null
- && collectionType.getParameterTypes().length > typeIndex
- && collectionType
- .getParameterTypes()[typeIndex] != null : "Proper generics required for encoding child value, assertion failed for "
- + collectionType;
- Type childType = collectionType.getParameterTypes()[typeIndex];
- return encode(value, childType, connection);
- }
- }
-
- private static JsonArray encodeObjectMap(Map<Object, Object> map, Type type,
- ApplicationConnection connection) {
- JsonArray keys = Json.createArray();
- JsonArray values = Json.createArray();
-
- assert type != null : "Should only be used for non-legacy types";
-
- for (Entry<?, ?> entry : map.entrySet()) {
- keys.set(keys.length(),
- encodeChildValue(entry.getKey(), type, 0, connection));
- values.set(values.length(),
- encodeChildValue(entry.getValue(), type, 1, connection));
- }
-
- JsonArray keysAndValues = Json.createArray();
- keysAndValues.set(0, keys);
- keysAndValues.set(1, values);
-
- return keysAndValues;
- }
-
- private static JsonValue encodeConnectorMap(Map<Object, Object> map,
- Type type, ApplicationConnection connection) {
- JsonObject jsonMap = Json.createObject();
-
- for (Entry<?, ?> entry : map.entrySet()) {
- Connector connector = (Connector) entry.getKey();
-
- JsonValue encodedValue = encodeChildValue(entry.getValue(), type, 1,
- connection);
-
- jsonMap.put(connector.getConnectorId(), encodedValue);
- }
-
- return jsonMap;
- }
-
- private static JsonValue encodeStringMap(Map<Object, Object> map, Type type,
- ApplicationConnection connection) {
- JsonObject jsonMap = Json.createObject();
-
- for (Entry<?, ?> entry : map.entrySet()) {
- String key = (String) entry.getKey();
- Object value = entry.getValue();
-
- jsonMap.put(key, encodeChildValue(value, type, 1, connection));
- }
-
- return jsonMap;
- }
-
- private static JsonValue encodeEnum(Enum<?> e,
- ApplicationConnection connection) {
- return Json.create(e.toString());
- }
-
- private static JsonValue encodeLegacyObjectArray(Object[] array,
- ApplicationConnection connection) {
- JsonArray jsonArray = Json.createArray();
- for (int i = 0; i < array.length; ++i) {
- // TODO handle object graph loops?
- Object value = array[i];
- jsonArray.set(i, encode(value, null, connection));
- }
- return jsonArray;
- }
-
- private static JsonArray encodeCollection(Collection collection, Type type,
- ApplicationConnection connection) {
- JsonArray jsonArray = Json.createArray();
- int idx = 0;
- for (Object o : collection) {
- JsonValue encodedObject = encodeChildValue(o, type, 0, connection);
- jsonArray.set(idx++, encodedObject);
- }
- if (collection instanceof Set) {
- return jsonArray;
- } else if (collection instanceof List) {
- return jsonArray;
- } else {
- throw new RuntimeException("Unsupport collection type: "
- + collection.getClass().getName());
- }
-
- }
-
- /**
- * Returns the transport type for the given value. Only returns a transport
- * type for internally handled values.
- *
- * @param value
- * The value that should be transported
- * @return One of the JsonEncode.VTYPE_ constants or null if the value
- * cannot be transported using an internally handled type.
- */
- private static String getTransportType(Object value) {
- if (value == null) {
- return JsonConstants.VTYPE_NULL;
- } else if (value instanceof String) {
- return JsonConstants.VTYPE_STRING;
- } else if (value instanceof Connector) {
- return JsonConstants.VTYPE_CONNECTOR;
- } else if (value instanceof Boolean) {
- return JsonConstants.VTYPE_BOOLEAN;
- } else if (value instanceof Integer) {
- return JsonConstants.VTYPE_INTEGER;
- } else if (value instanceof Float) {
- return JsonConstants.VTYPE_FLOAT;
- } else if (value instanceof Double) {
- return JsonConstants.VTYPE_DOUBLE;
- } else if (value instanceof Long) {
- return JsonConstants.VTYPE_LONG;
- } else if (value instanceof List) {
- return JsonConstants.VTYPE_LIST;
- } else if (value instanceof Set) {
- return JsonConstants.VTYPE_SET;
- } else if (value instanceof String[]) {
- return JsonConstants.VTYPE_STRINGARRAY;
- } else if (value instanceof Object[]) {
- return JsonConstants.VTYPE_ARRAY;
- } else if (value instanceof Map) {
- return JsonConstants.VTYPE_MAP;
- } else if (value instanceof Enum<?>) {
- // Enum value is processed as a string
- return JsonConstants.VTYPE_STRING;
- }
- return null;
- }
- }
|