You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JsonCodec.java 39KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.beans.IntrospectionException;
  18. import java.beans.Introspector;
  19. import java.beans.PropertyDescriptor;
  20. import java.io.Serializable;
  21. import java.lang.reflect.Array;
  22. import java.lang.reflect.Field;
  23. import java.lang.reflect.GenericArrayType;
  24. import java.lang.reflect.Method;
  25. import java.lang.reflect.Modifier;
  26. import java.lang.reflect.ParameterizedType;
  27. import java.lang.reflect.Type;
  28. import java.util.ArrayList;
  29. import java.util.Collection;
  30. import java.util.Date;
  31. import java.util.HashMap;
  32. import java.util.HashSet;
  33. import java.util.List;
  34. import java.util.Locale;
  35. import java.util.Map;
  36. import java.util.Map.Entry;
  37. import java.util.Set;
  38. import java.util.concurrent.ConcurrentHashMap;
  39. import java.util.concurrent.ConcurrentMap;
  40. import com.vaadin.server.communication.DateSerializer;
  41. import com.vaadin.server.communication.JSONSerializer;
  42. import com.vaadin.shared.Connector;
  43. import com.vaadin.shared.JsonConstants;
  44. import com.vaadin.shared.communication.UidlValue;
  45. import com.vaadin.ui.Component;
  46. import com.vaadin.ui.ConnectorTracker;
  47. import com.vaadin.util.ReflectTools;
  48. import elemental.json.Json;
  49. import elemental.json.JsonArray;
  50. import elemental.json.JsonException;
  51. import elemental.json.JsonNull;
  52. import elemental.json.JsonObject;
  53. import elemental.json.JsonString;
  54. import elemental.json.JsonType;
  55. import elemental.json.JsonValue;
  56. import elemental.json.impl.JreJsonArray;
  57. /**
  58. * Decoder for converting RPC parameters and other values from JSON in transfer
  59. * between the client and the server and vice versa.
  60. *
  61. * @since 7.0
  62. */
  63. public class JsonCodec implements Serializable {
  64. /* Immutable Encode Result representing null */
  65. private static final EncodeResult ENCODE_RESULT_NULL = new EncodeResult(
  66. Json.createNull());
  67. /* Immutable empty JSONArray */
  68. private static final JsonArray EMPTY_JSON_ARRAY = new JreJsonArray(
  69. Json.instance()) {
  70. @Override
  71. public void set(int index, JsonValue value) {
  72. throw new UnsupportedOperationException(
  73. "Immutable empty JsonArray.");
  74. }
  75. @Override
  76. public void set(int index, String string) {
  77. throw new UnsupportedOperationException(
  78. "Immutable empty JsonArray.");
  79. }
  80. @Override
  81. public void set(int index, double number) {
  82. throw new UnsupportedOperationException(
  83. "Immutable empty JsonArray.");
  84. }
  85. @Override
  86. public void set(int index, boolean bool) {
  87. throw new UnsupportedOperationException(
  88. "Immutable empty JsonArray.");
  89. }
  90. };
  91. public static interface BeanProperty extends Serializable {
  92. public Object getValue(Object bean) throws Exception;
  93. public void setValue(Object bean, Object value) throws Exception;
  94. public String getName();
  95. public Type getType();
  96. }
  97. private static class FieldProperty implements BeanProperty {
  98. private final Field field;
  99. public FieldProperty(Field field) {
  100. this.field = field;
  101. }
  102. @Override
  103. public Object getValue(Object bean) throws Exception {
  104. return field.get(bean);
  105. }
  106. @Override
  107. public void setValue(Object bean, Object value) throws Exception {
  108. field.set(bean, value);
  109. }
  110. @Override
  111. public String getName() {
  112. return field.getName();
  113. }
  114. @Override
  115. public Type getType() {
  116. return field.getGenericType();
  117. }
  118. public static Collection<FieldProperty> find(Class<?> type)
  119. throws IntrospectionException {
  120. Field[] fields = type.getFields();
  121. Collection<FieldProperty> properties = new ArrayList<>(
  122. fields.length);
  123. for (Field field : fields) {
  124. if (!Modifier.isStatic(field.getModifiers())) {
  125. properties.add(new FieldProperty(field));
  126. }
  127. }
  128. return properties;
  129. }
  130. }
  131. private static class MethodProperty implements BeanProperty {
  132. private final PropertyDescriptor pd;
  133. public MethodProperty(PropertyDescriptor pd) {
  134. this.pd = pd;
  135. }
  136. @Override
  137. public Object getValue(Object bean) throws Exception {
  138. Method readMethod = pd.getReadMethod();
  139. return readMethod.invoke(bean);
  140. }
  141. @Override
  142. public void setValue(Object bean, Object value) throws Exception {
  143. pd.getWriteMethod().invoke(bean, value);
  144. }
  145. @Override
  146. public String getName() {
  147. String fieldName = pd.getWriteMethod().getName().substring(3);
  148. fieldName = Character.toLowerCase(fieldName.charAt(0))
  149. + fieldName.substring(1);
  150. return fieldName;
  151. }
  152. public static Collection<MethodProperty> find(Class<?> type)
  153. throws IntrospectionException {
  154. Collection<MethodProperty> properties = new ArrayList<>();
  155. for (PropertyDescriptor pd : Introspector.getBeanInfo(type)
  156. .getPropertyDescriptors()) {
  157. if (pd.getReadMethod() == null || pd.getWriteMethod() == null) {
  158. continue;
  159. }
  160. properties.add(new MethodProperty(pd));
  161. }
  162. return properties;
  163. }
  164. @Override
  165. public Type getType() {
  166. return pd.getReadMethod().getGenericReturnType();
  167. }
  168. }
  169. /**
  170. * Cache the collection of bean properties for a given type to avoid doing a
  171. * quite expensive lookup multiple times. Will be used from any thread that
  172. * happens to process Vaadin requests, so it must be protected from
  173. * corruption caused by concurrent access.
  174. */
  175. private static final ConcurrentMap<Class<?>, Collection<BeanProperty>> TYPE_PROPERTY_CACHE = new ConcurrentHashMap<>();
  176. private static final Map<Class<?>, String> TYPE_TO_TRANSPORT_TYPE = new HashMap<>();
  177. /**
  178. * Note! This does not contain primitives.
  179. * <p>
  180. */
  181. private static final Map<String, Class<?>> TRANSPORT_TYPE_TO_TYPE = new HashMap<>();
  182. private static final Map<Class<?>, JSONSerializer<?>> CUSTOM_SERIALIZERS = new HashMap<>();
  183. static {
  184. CUSTOM_SERIALIZERS.put(Date.class, new DateSerializer());
  185. }
  186. static {
  187. registerType(String.class, JsonConstants.VTYPE_STRING);
  188. registerType(Connector.class, JsonConstants.VTYPE_CONNECTOR);
  189. registerType(Boolean.class, JsonConstants.VTYPE_BOOLEAN);
  190. registerType(boolean.class, JsonConstants.VTYPE_BOOLEAN);
  191. registerType(Integer.class, JsonConstants.VTYPE_INTEGER);
  192. registerType(int.class, JsonConstants.VTYPE_INTEGER);
  193. registerType(Float.class, JsonConstants.VTYPE_FLOAT);
  194. registerType(float.class, JsonConstants.VTYPE_FLOAT);
  195. registerType(Double.class, JsonConstants.VTYPE_DOUBLE);
  196. registerType(double.class, JsonConstants.VTYPE_DOUBLE);
  197. registerType(Long.class, JsonConstants.VTYPE_LONG);
  198. registerType(long.class, JsonConstants.VTYPE_LONG);
  199. registerType(String[].class, JsonConstants.VTYPE_STRINGARRAY);
  200. registerType(Object[].class, JsonConstants.VTYPE_ARRAY);
  201. registerType(Map.class, JsonConstants.VTYPE_MAP);
  202. registerType(HashMap.class, JsonConstants.VTYPE_MAP);
  203. registerType(List.class, JsonConstants.VTYPE_LIST);
  204. registerType(Set.class, JsonConstants.VTYPE_SET);
  205. registerType(Void.class, JsonConstants.VTYPE_NULL);
  206. }
  207. private static void registerType(Class<?> type, String transportType) {
  208. TYPE_TO_TRANSPORT_TYPE.put(type, transportType);
  209. if (!type.isPrimitive()) {
  210. TRANSPORT_TYPE_TO_TYPE.put(transportType, type);
  211. }
  212. }
  213. public static boolean isInternalTransportType(String transportType) {
  214. return TRANSPORT_TYPE_TO_TYPE.containsKey(transportType);
  215. }
  216. public static boolean isInternalType(Type type) {
  217. if (type instanceof Class && ((Class<?>) type).isPrimitive()) {
  218. if (type == byte.class || type == char.class) {
  219. // Almost all primitive types are handled internally
  220. return false;
  221. }
  222. // All primitive types are handled internally
  223. return true;
  224. } else if (type == UidlValue.class) {
  225. // UidlValue is a special internal type wrapping type info and a
  226. // value
  227. return true;
  228. }
  229. return TYPE_TO_TRANSPORT_TYPE.containsKey(getClassForType(type));
  230. }
  231. private static Class<?> getClassForType(Type type) {
  232. if (type instanceof ParameterizedType) {
  233. return (Class<?>) (((ParameterizedType) type).getRawType());
  234. } else if (type instanceof Class<?>) {
  235. return (Class<?>) type;
  236. } else {
  237. return null;
  238. }
  239. }
  240. private static Class<?> getType(String transportType) {
  241. return TRANSPORT_TYPE_TO_TYPE.get(transportType);
  242. }
  243. public static Object decodeInternalOrCustomType(Type targetType,
  244. JsonValue value, ConnectorTracker connectorTracker) {
  245. if (isInternalType(targetType)) {
  246. return decodeInternalType(targetType, false, value,
  247. connectorTracker);
  248. } else {
  249. return decodeCustomType(targetType, value, connectorTracker);
  250. }
  251. }
  252. public static Object decodeCustomType(Type targetType, JsonValue value,
  253. ConnectorTracker connectorTracker) {
  254. if (isInternalType(targetType)) {
  255. throw new JsonException("decodeCustomType cannot be used for "
  256. + targetType + ", which is an internal type");
  257. }
  258. // Try to decode object using fields
  259. if (isJsonType(targetType)) {
  260. return value;
  261. } else if (value.getType() == JsonType.NULL) {
  262. return null;
  263. } else if (targetType == byte.class || targetType == Byte.class) {
  264. return Byte.valueOf((byte) value.asNumber());
  265. } else if (targetType == char.class || targetType == Character.class) {
  266. return Character.valueOf(value.asString().charAt(0));
  267. } else if (targetType instanceof Class<?>
  268. && ((Class<?>) targetType).isArray()) {
  269. // Legacy Object[] and String[] handled elsewhere, this takes care
  270. // of generic arrays
  271. Class<?> componentType = ((Class<?>) targetType).getComponentType();
  272. return decodeArray(componentType, (JsonArray) value,
  273. connectorTracker);
  274. } else if (targetType instanceof GenericArrayType) {
  275. Type componentType = ((GenericArrayType) targetType)
  276. .getGenericComponentType();
  277. return decodeArray(componentType, (JsonArray) value,
  278. connectorTracker);
  279. } else if (JsonValue.class
  280. .isAssignableFrom(getClassForType(targetType))) {
  281. return value;
  282. } else if (CUSTOM_SERIALIZERS
  283. .containsKey(getClassForType(targetType))) {
  284. return CUSTOM_SERIALIZERS.get(getClassForType(targetType))
  285. .deserialize(targetType, value, connectorTracker);
  286. } else if (Enum.class.isAssignableFrom(getClassForType(targetType))) {
  287. Class<?> classForType = getClassForType(targetType);
  288. return decodeEnum(classForType.asSubclass(Enum.class),
  289. (JsonString) value);
  290. } else {
  291. return decodeObject(targetType, (JsonObject) value,
  292. connectorTracker);
  293. }
  294. }
  295. private static boolean isJsonType(Type type) {
  296. return type instanceof Class<?>
  297. && JsonValue.class.isAssignableFrom((Class<?>) type);
  298. }
  299. private static Object decodeArray(Type componentType, JsonArray value,
  300. ConnectorTracker connectorTracker) {
  301. Class<?> componentClass = getClassForType(componentType);
  302. Object array = Array.newInstance(componentClass, value.length());
  303. for (int i = 0; i < value.length(); i++) {
  304. Object decodedValue = decodeInternalOrCustomType(componentType,
  305. value.get(i), connectorTracker);
  306. Array.set(array, i, decodedValue);
  307. }
  308. return array;
  309. }
  310. /**
  311. * Decodes a value that is of an internal type.
  312. * <p>
  313. * Ensures the encoded value is of the same type as target type.
  314. * </p>
  315. * <p>
  316. * Allows restricting collections so that they must be declared using
  317. * generics. If this is used then all objects in the collection are encoded
  318. * using the declared type. Otherwise only internal types are allowed in
  319. * collections.
  320. * </p>
  321. *
  322. * @param targetType
  323. * The type that should be returned by this method
  324. * @param restrictToInternalTypes
  325. * @param encodedJsonValue
  326. * @param connectorTracker
  327. * @return
  328. */
  329. public static Object decodeInternalType(Type targetType,
  330. boolean restrictToInternalTypes, JsonValue encodedJsonValue,
  331. ConnectorTracker connectorTracker) {
  332. if (!isInternalType(targetType)) {
  333. throw new JsonException("Type " + targetType
  334. + " is not a supported internal type.");
  335. }
  336. String transportType = getInternalTransportType(targetType);
  337. if (encodedJsonValue.getType() == JsonType.NULL) {
  338. return null;
  339. } else if (targetType == Void.class) {
  340. throw new JsonException(
  341. "Something other than null was encoded for a null type");
  342. }
  343. // UidlValue
  344. if (targetType == UidlValue.class) {
  345. return decodeUidlValue((JsonArray) encodedJsonValue,
  346. connectorTracker);
  347. }
  348. // Collections
  349. if (JsonConstants.VTYPE_LIST.equals(transportType)) {
  350. return decodeList(targetType, restrictToInternalTypes,
  351. (JsonArray) encodedJsonValue, connectorTracker);
  352. } else if (JsonConstants.VTYPE_SET.equals(transportType)) {
  353. return decodeSet(targetType, restrictToInternalTypes,
  354. (JsonArray) encodedJsonValue, connectorTracker);
  355. } else if (JsonConstants.VTYPE_MAP.equals(transportType)) {
  356. return decodeMap(targetType, restrictToInternalTypes,
  357. encodedJsonValue, connectorTracker);
  358. }
  359. // Arrays
  360. if (JsonConstants.VTYPE_ARRAY.equals(transportType)) {
  361. return decodeObjectArray(targetType, (JsonArray) encodedJsonValue,
  362. connectorTracker);
  363. } else if (JsonConstants.VTYPE_STRINGARRAY.equals(transportType)) {
  364. return decodeArray(String.class, (JsonArray) encodedJsonValue,
  365. null);
  366. }
  367. // Special Vaadin types
  368. if (JsonConstants.VTYPE_CONNECTOR.equals(transportType)) {
  369. return connectorTracker.getConnector(encodedJsonValue.asString());
  370. }
  371. // Legacy types
  372. if (JsonConstants.VTYPE_STRING.equals(transportType)) {
  373. return encodedJsonValue.asString();
  374. } else if (JsonConstants.VTYPE_INTEGER.equals(transportType)) {
  375. return (int) encodedJsonValue.asNumber();
  376. } else if (JsonConstants.VTYPE_LONG.equals(transportType)) {
  377. return (long) encodedJsonValue.asNumber();
  378. } else if (JsonConstants.VTYPE_FLOAT.equals(transportType)) {
  379. return (float) encodedJsonValue.asNumber();
  380. } else if (JsonConstants.VTYPE_DOUBLE.equals(transportType)) {
  381. return encodedJsonValue.asNumber();
  382. } else if (JsonConstants.VTYPE_BOOLEAN.equals(transportType)) {
  383. return encodedJsonValue.asBoolean();
  384. }
  385. throw new JsonException("Unknown type " + transportType);
  386. }
  387. /**
  388. * Set a custom JSONSerializer for a specific Class. Existence of custom
  389. * serializers is checked after basic types (Strings, Booleans, Numbers,
  390. * Characters), Collections and Maps, so setting custom serializers for
  391. * these won't have any effect.
  392. * <p>
  393. * To remove a previously set serializer, call this method with the second
  394. * parameter set to {@code null}.
  395. * <p>
  396. * Custom serializers should only be added from static initializers or other
  397. * places that are guaranteed to run only once. Trying to add a serializer
  398. * to a class that already has one will cause an exception.
  399. * <p>
  400. * Warning: removing existing custom serializers may lead into unexpected
  401. * behavior in components that expect the customized data. The framework's
  402. * custom serializers are loaded in the static initializer block of this
  403. * class.
  404. *
  405. * @see DateSerializer
  406. * @throws IllegalArgumentException
  407. * Thrown if parameter clazz is null.
  408. * @throws IllegalStateException
  409. * Thrown if serializer for parameter clazz is already
  410. * registered and parameter jsonSerializer is not null.
  411. * @param clazz
  412. * The target class.
  413. * @param jsonSerializer
  414. * Custom JSONSerializer to add. If {@code null}, remove custom
  415. * serializer from class clazz.
  416. */
  417. public static <TYPE> void setCustomSerializer(Class<TYPE> clazz,
  418. JSONSerializer<TYPE> jsonSerializer) {
  419. if (clazz == null) {
  420. throw new IllegalArgumentException(
  421. "Cannot add serializer for null");
  422. }
  423. if (jsonSerializer == null) {
  424. CUSTOM_SERIALIZERS.remove(clazz);
  425. } else {
  426. if (CUSTOM_SERIALIZERS.containsKey(clazz)) {
  427. String err = String.format(
  428. "Class %s already has a custom serializer. "
  429. + "This exception can be thrown if you try to "
  430. + "add a serializer from a non-static context. "
  431. + "Try using a static block instead.",
  432. clazz.getName());
  433. throw new IllegalStateException(err);
  434. }
  435. CUSTOM_SERIALIZERS.put(clazz, jsonSerializer);
  436. }
  437. }
  438. private static UidlValue decodeUidlValue(JsonArray encodedJsonValue,
  439. ConnectorTracker connectorTracker) {
  440. String type = encodedJsonValue.getString(0);
  441. Object decodedValue = decodeInternalType(getType(type), true,
  442. encodedJsonValue.get(1), connectorTracker);
  443. return new UidlValue(decodedValue);
  444. }
  445. private static Map<Object, Object> decodeMap(Type targetType,
  446. boolean restrictToInternalTypes, JsonValue jsonMap,
  447. ConnectorTracker connectorTracker) {
  448. if (jsonMap.getType() == JsonType.ARRAY) {
  449. // Client-side has no declared type information to determine
  450. // encoding method for empty maps, so these are handled separately.
  451. // See #8906.
  452. JsonArray jsonArray = (JsonArray) jsonMap;
  453. if (jsonArray.length() == 0) {
  454. return new HashMap<>();
  455. }
  456. }
  457. if (!restrictToInternalTypes
  458. && targetType instanceof ParameterizedType) {
  459. Type keyType = ((ParameterizedType) targetType)
  460. .getActualTypeArguments()[0];
  461. Type valueType = ((ParameterizedType) targetType)
  462. .getActualTypeArguments()[1];
  463. if (keyType == String.class) {
  464. return decodeStringMap(valueType, (JsonObject) jsonMap,
  465. connectorTracker);
  466. } else if (keyType == Connector.class) {
  467. return decodeConnectorMap(valueType, (JsonObject) jsonMap,
  468. connectorTracker);
  469. } else {
  470. return decodeObjectMap(keyType, valueType, (JsonArray) jsonMap,
  471. connectorTracker);
  472. }
  473. } else {
  474. return decodeStringMap(UidlValue.class, (JsonObject) jsonMap,
  475. connectorTracker);
  476. }
  477. }
  478. private static Map<Object, Object> decodeObjectMap(Type keyType,
  479. Type valueType, JsonArray jsonMap,
  480. ConnectorTracker connectorTracker) {
  481. JsonArray keys = jsonMap.getArray(0);
  482. JsonArray values = jsonMap.getArray(1);
  483. assert (keys.length() == values.length());
  484. Map<Object, Object> map = new HashMap<>(keys.length() * 2);
  485. for (int i = 0; i < keys.length(); i++) {
  486. Object key = decodeInternalOrCustomType(keyType, keys.get(i),
  487. connectorTracker);
  488. Object value = decodeInternalOrCustomType(valueType, values.get(i),
  489. connectorTracker);
  490. map.put(key, value);
  491. }
  492. return map;
  493. }
  494. private static Map<Object, Object> decodeConnectorMap(Type valueType,
  495. JsonObject jsonMap, ConnectorTracker connectorTracker) {
  496. Map<Object, Object> map = new HashMap<>();
  497. for (String key : jsonMap.keys()) {
  498. Object value = decodeInternalOrCustomType(valueType,
  499. jsonMap.get(key), connectorTracker);
  500. if (valueType == UidlValue.class) {
  501. value = ((UidlValue) value).getValue();
  502. }
  503. map.put(connectorTracker.getConnector(key), value);
  504. }
  505. return map;
  506. }
  507. private static Map<Object, Object> decodeStringMap(Type valueType,
  508. JsonObject jsonMap, ConnectorTracker connectorTracker) {
  509. Map<Object, Object> map = new HashMap<>();
  510. for (String key : jsonMap.keys()) {
  511. Object value = decodeInternalOrCustomType(valueType,
  512. jsonMap.get(key), connectorTracker);
  513. if (valueType == UidlValue.class) {
  514. value = ((UidlValue) value).getValue();
  515. }
  516. map.put(key, value);
  517. }
  518. return map;
  519. }
  520. /**
  521. * @param targetType
  522. * @param restrictToInternalTypes
  523. * @param typeIndex
  524. * The index of a generic type to use to define the child type
  525. * that should be decoded
  526. * @param connectorTracker
  527. * @param value
  528. * @return
  529. */
  530. private static Object decodeParametrizedType(Type targetType,
  531. boolean restrictToInternalTypes, int typeIndex, JsonValue value,
  532. ConnectorTracker connectorTracker) {
  533. if (!restrictToInternalTypes
  534. && targetType instanceof ParameterizedType) {
  535. Type childType = ((ParameterizedType) targetType)
  536. .getActualTypeArguments()[typeIndex];
  537. // Only decode the given type
  538. return decodeInternalOrCustomType(childType, value,
  539. connectorTracker);
  540. } else {
  541. // Only UidlValue when not enforcing a given type to avoid security
  542. // issues
  543. UidlValue decodeInternalType = (UidlValue) decodeInternalType(
  544. UidlValue.class, true, value, connectorTracker);
  545. return decodeInternalType.getValue();
  546. }
  547. }
  548. private static Object decodeEnum(Class<? extends Enum> cls,
  549. JsonString value) {
  550. return Enum.valueOf(cls, value.getString());
  551. }
  552. private static Object[] decodeObjectArray(Type targetType,
  553. JsonArray jsonArray, ConnectorTracker connectorTracker) {
  554. List<Object> list = decodeList(List.class, true, jsonArray,
  555. connectorTracker);
  556. return list.toArray(new Object[list.size()]);
  557. }
  558. private static List<Object> decodeList(Type targetType,
  559. boolean restrictToInternalTypes, JsonArray jsonArray,
  560. ConnectorTracker connectorTracker) {
  561. int arrayLength = jsonArray.length();
  562. List<Object> list = new ArrayList<>(arrayLength);
  563. for (int i = 0; i < arrayLength; ++i) {
  564. // each entry always has two elements: type and value
  565. JsonValue encodedValue = jsonArray.get(i);
  566. Object decodedChild = decodeParametrizedType(targetType,
  567. restrictToInternalTypes, 0, encodedValue, connectorTracker);
  568. list.add(decodedChild);
  569. }
  570. return list;
  571. }
  572. private static Set<Object> decodeSet(Type targetType,
  573. boolean restrictToInternalTypes, JsonArray jsonArray,
  574. ConnectorTracker connectorTracker) {
  575. HashSet<Object> set = new HashSet<>();
  576. set.addAll(decodeList(targetType, restrictToInternalTypes, jsonArray,
  577. connectorTracker));
  578. return set;
  579. }
  580. private static Object decodeObject(Type targetType,
  581. JsonObject serializedObject, ConnectorTracker connectorTracker) {
  582. Class<?> targetClass = getClassForType(targetType);
  583. try {
  584. Object decodedObject = ReflectTools.createInstance(targetClass);
  585. for (BeanProperty property : getProperties(targetClass)) {
  586. String fieldName = property.getName();
  587. JsonValue encodedFieldValue = serializedObject.get(fieldName);
  588. Type fieldType = property.getType();
  589. Object decodedFieldValue = decodeInternalOrCustomType(fieldType,
  590. encodedFieldValue, connectorTracker);
  591. property.setValue(decodedObject, decodedFieldValue);
  592. }
  593. return decodedObject;
  594. } catch (Exception e) {
  595. throw new RuntimeException(e);
  596. }
  597. }
  598. public static EncodeResult encode(Object value, JsonValue diffState,
  599. Type valueType, ConnectorTracker connectorTracker) {
  600. if (null == value) {
  601. return ENCODE_RESULT_NULL;
  602. }
  603. // Storing a single reference and only returning the EncodeResult at the
  604. // end the method is much shorter in bytecode which allows inlining
  605. JsonValue toReturn;
  606. if (value instanceof JsonValue) {
  607. // all JSON compatible types are returned as is.
  608. toReturn = (JsonValue) value;
  609. } else if (value instanceof String) {
  610. toReturn = Json.create((String) value);
  611. } else if (value instanceof Boolean) {
  612. toReturn = Json.create((Boolean) value);
  613. } else if (value instanceof Number) {
  614. toReturn = Json.create(((Number) value).doubleValue());
  615. } else if (value instanceof Character) {
  616. toReturn = Json.create(Character.toString((Character) value));
  617. } else if (value instanceof Collection) {
  618. toReturn = encodeCollection(valueType, (Collection<?>) value,
  619. connectorTracker);
  620. } else if (value instanceof Map) {
  621. toReturn = encodeMap(valueType, (Map<?, ?>) value,
  622. connectorTracker);
  623. } else if (value instanceof Connector) {
  624. if (value instanceof Component && !(LegacyCommunicationManager
  625. .isComponentVisibleToClient((Component) value))) {
  626. // an encoded null is cached, return it directly.
  627. return ENCODE_RESULT_NULL;
  628. }
  629. // Connectors are simply serialized as ID.
  630. toReturn = Json.create(((Connector) value).getConnectorId());
  631. } else if (CUSTOM_SERIALIZERS.containsKey(value.getClass())) {
  632. toReturn = serializeJson(value, connectorTracker);
  633. } else if (value instanceof Enum) {
  634. toReturn = Json.create(((Enum<?>) value).name());
  635. } else if (valueType instanceof GenericArrayType) {
  636. toReturn = encodeArrayContents(
  637. ((GenericArrayType) valueType).getGenericComponentType(),
  638. value, connectorTracker);
  639. } else if (valueType instanceof Class<?>) {
  640. if (((Class<?>) valueType).isArray()) {
  641. toReturn = encodeArrayContents(
  642. ((Class<?>) valueType).getComponentType(), value,
  643. connectorTracker);
  644. } else {
  645. // encodeObject returns an EncodeResult with a diff, thus it
  646. // needs to return it directly rather than assigning it to
  647. // toReturn.
  648. return encodeObject(value, (Class<?>) valueType,
  649. (JsonObject) diffState, connectorTracker);
  650. }
  651. } else {
  652. throw new JsonException("Can not encode type " + valueType);
  653. }
  654. return new EncodeResult(toReturn);
  655. }
  656. public static Collection<BeanProperty> getProperties(Class<?> type)
  657. throws IntrospectionException {
  658. Collection<BeanProperty> cachedProperties = TYPE_PROPERTY_CACHE
  659. .get(type);
  660. if (cachedProperties != null) {
  661. return cachedProperties;
  662. }
  663. Collection<BeanProperty> properties = new ArrayList<>();
  664. properties.addAll(MethodProperty.find(type));
  665. properties.addAll(FieldProperty.find(type));
  666. // Doesn't matter if the same calculation is done multiple times from
  667. // different threads, so there's no need to do e.g. putIfAbsent
  668. TYPE_PROPERTY_CACHE.put(type, properties);
  669. return properties;
  670. }
  671. /*
  672. * Loops through the fields of value and encodes them.
  673. */
  674. private static EncodeResult encodeObject(Object value, Class<?> valueType,
  675. JsonObject referenceValue, ConnectorTracker connectorTracker) {
  676. JsonObject encoded = Json.createObject();
  677. JsonObject diff = Json.createObject();
  678. try {
  679. for (BeanProperty property : getProperties(valueType)) {
  680. String fieldName = property.getName();
  681. // We can't use PropertyDescriptor.getPropertyType() as it does
  682. // not support generics
  683. Type fieldType = property.getType();
  684. Object fieldValue = property.getValue(value);
  685. if (encoded.hasKey(fieldName)) {
  686. throw new RuntimeException("Can't encode "
  687. + valueType.getName()
  688. + " as it has multiple properties with the name "
  689. + fieldName.toLowerCase(Locale.ROOT)
  690. + ". This can happen if there are getters and setters for a public field (the framework can't know which to ignore) or if there are properties with only casing distinguishing between the names (e.g. getFoo() and getFOO())");
  691. }
  692. JsonValue fieldReference;
  693. if (referenceValue != null) {
  694. fieldReference = referenceValue.get(fieldName);
  695. if (fieldReference instanceof JsonNull) {
  696. fieldReference = null;
  697. }
  698. } else {
  699. fieldReference = null;
  700. }
  701. EncodeResult encodeResult = encode(fieldValue, fieldReference,
  702. fieldType, connectorTracker);
  703. encoded.put(fieldName, encodeResult.getEncodedValue());
  704. if (valueChanged(encodeResult.getEncodedValue(),
  705. fieldReference)) {
  706. diff.put(fieldName, encodeResult.getDiffOrValue());
  707. }
  708. }
  709. } catch (Exception e) {
  710. // TODO: Should exceptions be handled in a different way?
  711. throw new RuntimeException(e);
  712. }
  713. return new EncodeResult(encoded, diff);
  714. }
  715. /**
  716. * Compares the value with the reference. If they match, returns false.
  717. *
  718. * @param fieldValue
  719. * @param referenceValue
  720. * @return
  721. */
  722. private static boolean valueChanged(JsonValue fieldValue,
  723. JsonValue referenceValue) {
  724. if (fieldValue instanceof JsonNull) {
  725. fieldValue = null;
  726. }
  727. if (fieldValue == referenceValue) {
  728. return false;
  729. } else if (fieldValue == null || referenceValue == null) {
  730. return true;
  731. } else {
  732. return !jsonEquals(fieldValue, referenceValue);
  733. }
  734. }
  735. /**
  736. * Compares two json values for deep equality.
  737. *
  738. * This is a helper for overcoming the fact that
  739. * {@link JsonValue#equals(Object)} only does an identity check and
  740. * {@link JsonValue#jsEquals(JsonValue)} is defined to use JavaScript
  741. * semantics where arrays and objects are equals only based on identity.
  742. *
  743. * @since 7.4
  744. * @param a
  745. * the first json value to check, may not be null
  746. * @param b
  747. * the second json value to check, may not be null
  748. * @return <code>true</code> if both json values are the same;
  749. * <code>false</code> otherwise
  750. */
  751. public static boolean jsonEquals(JsonValue a, JsonValue b) {
  752. assert a != null;
  753. assert b != null;
  754. if (a == b) {
  755. return true;
  756. }
  757. JsonType type = a.getType();
  758. if (type != b.getType()) {
  759. return false;
  760. }
  761. switch (type) {
  762. case NULL:
  763. return true;
  764. case BOOLEAN:
  765. return a.asBoolean() == b.asBoolean();
  766. case NUMBER:
  767. return a.asNumber() == b.asNumber();
  768. case STRING:
  769. return a.asString().equals(b.asString());
  770. case OBJECT:
  771. return jsonObjectEquals((JsonObject) a, (JsonObject) b);
  772. case ARRAY:
  773. return jsonArrayEquals((JsonArray) a, (JsonArray) b);
  774. default:
  775. throw new RuntimeException("Unsupported JsonType: " + type);
  776. }
  777. }
  778. private static boolean jsonObjectEquals(JsonObject a, JsonObject b) {
  779. String[] keys = a.keys();
  780. if (keys.length != b.keys().length) {
  781. return false;
  782. }
  783. for (String key : keys) {
  784. JsonValue value = b.get(key);
  785. if (value == null || !jsonEquals(a.get(key), value)) {
  786. return false;
  787. }
  788. }
  789. return true;
  790. }
  791. private static boolean jsonArrayEquals(JsonArray a, JsonArray b) {
  792. if (a.length() != b.length()) {
  793. return false;
  794. }
  795. for (int i = 0; i < a.length(); i++) {
  796. if (!jsonEquals(a.get(i), b.get(i))) {
  797. return false;
  798. }
  799. }
  800. return true;
  801. }
  802. private static JsonArray encodeArrayContents(Type componentType,
  803. Object array, ConnectorTracker connectorTracker) {
  804. JsonArray jsonArray = Json.createArray();
  805. for (int i = 0; i < Array.getLength(array); i++) {
  806. EncodeResult encodeResult = encode(Array.get(array, i), null,
  807. componentType, connectorTracker);
  808. jsonArray.set(i, encodeResult.getEncodedValue());
  809. }
  810. return jsonArray;
  811. }
  812. private static JsonArray encodeCollection(Type targetType,
  813. Collection<?> collection, ConnectorTracker connectorTracker) {
  814. JsonArray jsonArray = Json.createArray();
  815. for (Object o : collection) {
  816. jsonArray.set(jsonArray.length(),
  817. encodeChild(targetType, 0, o, connectorTracker));
  818. }
  819. return jsonArray;
  820. }
  821. private static JsonValue encodeChild(Type targetType, int typeIndex,
  822. Object o, ConnectorTracker connectorTracker) {
  823. if (targetType instanceof ParameterizedType) {
  824. Type childType = ((ParameterizedType) targetType)
  825. .getActualTypeArguments()[typeIndex];
  826. // Encode using the given type
  827. EncodeResult encodeResult = encode(o, null, childType,
  828. connectorTracker);
  829. return encodeResult.getEncodedValue();
  830. } else {
  831. throw new JsonException("Collection is missing generics");
  832. }
  833. }
  834. private static JsonValue encodeMap(Type mapType, Map<?, ?> map,
  835. ConnectorTracker connectorTracker) {
  836. Type keyType, valueType;
  837. if (mapType instanceof ParameterizedType) {
  838. keyType = ((ParameterizedType) mapType).getActualTypeArguments()[0];
  839. valueType = ((ParameterizedType) mapType)
  840. .getActualTypeArguments()[1];
  841. } else {
  842. throw new JsonException("Map is missing generics");
  843. }
  844. if (map.isEmpty()) {
  845. // Client -> server encodes empty map as an empty array because of
  846. // #8906. Do the same for server -> client to maintain symmetry.
  847. return EMPTY_JSON_ARRAY;
  848. }
  849. if (keyType == String.class) {
  850. return encodeStringMap(valueType, map, connectorTracker);
  851. } else if (keyType == Connector.class) {
  852. return encodeConnectorMap(valueType, map, connectorTracker);
  853. } else {
  854. return encodeObjectMap(keyType, valueType, map, connectorTracker);
  855. }
  856. }
  857. private static JsonArray encodeObjectMap(Type keyType, Type valueType,
  858. Map<?, ?> map, ConnectorTracker connectorTracker) {
  859. JsonArray keys = Json.createArray();
  860. JsonArray values = Json.createArray();
  861. for (Entry<?, ?> entry : map.entrySet()) {
  862. EncodeResult encodedKey = encode(entry.getKey(), null, keyType,
  863. connectorTracker);
  864. EncodeResult encodedValue = encode(entry.getValue(), null,
  865. valueType, connectorTracker);
  866. keys.set(keys.length(), encodedKey.getEncodedValue());
  867. values.set(values.length(), encodedValue.getEncodedValue());
  868. }
  869. JsonArray jsonMap = Json.createArray();
  870. jsonMap.set(0, keys);
  871. jsonMap.set(1, values);
  872. return jsonMap;
  873. }
  874. /*
  875. * Encodes a connector map. Invisible connectors are skipped.
  876. */
  877. private static JsonObject encodeConnectorMap(Type valueType, Map<?, ?> map,
  878. ConnectorTracker connectorTracker) {
  879. JsonObject jsonMap = Json.createObject();
  880. for (Entry<?, ?> entry : map.entrySet()) {
  881. ClientConnector key = (ClientConnector) entry.getKey();
  882. if (LegacyCommunicationManager.isConnectorVisibleToClient(key)) {
  883. EncodeResult encodedValue = encode(entry.getValue(), null,
  884. valueType, connectorTracker);
  885. jsonMap.put(key.getConnectorId(),
  886. encodedValue.getEncodedValue());
  887. }
  888. }
  889. return jsonMap;
  890. }
  891. private static JsonObject encodeStringMap(Type valueType, Map<?, ?> map,
  892. ConnectorTracker connectorTracker) {
  893. JsonObject jsonMap = Json.createObject();
  894. for (Entry<?, ?> entry : map.entrySet()) {
  895. String key = (String) entry.getKey();
  896. EncodeResult encodedValue = encode(entry.getValue(), null,
  897. valueType, connectorTracker);
  898. jsonMap.put(key, encodedValue.getEncodedValue());
  899. }
  900. return jsonMap;
  901. }
  902. /*
  903. * These methods looks good to inline, but are on a cold path of the
  904. * otherwise hot encode method, which needed to be shorted to allow inlining
  905. * of the hot part.
  906. */
  907. private static String getInternalTransportType(Type valueType) {
  908. return TYPE_TO_TRANSPORT_TYPE.get(getClassForType(valueType));
  909. }
  910. private static JsonValue serializeJson(Object value,
  911. ConnectorTracker connectorTracker) {
  912. JSONSerializer serializer = CUSTOM_SERIALIZERS.get(value.getClass());
  913. return serializer.serialize(value, connectorTracker);
  914. }
  915. private JsonCodec() {
  916. }
  917. }