| /* |
| * Copyright 2011 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.user.server.rpc; |
| |
| import static com.google.gwt.user.client.rpc.impl.AbstractSerializationStream.RPC_SEPARATOR_CHAR; |
| |
| import com.google.gwt.user.client.rpc.SerializationException; |
| import com.google.gwt.user.client.rpc.impl.AbstractSerializationStream; |
| import com.google.gwt.user.server.rpc.RPCTypeCheckCollectionsTest.TestHashSet; |
| import com.google.gwt.user.server.rpc.impl.SerializabilityUtil; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Type; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| /** |
| * Generates RPC messages for use in testing server message handling. |
| * |
| * This class is designed to make it easy to generate arbitrary messages, in the |
| * sense that the elements in the string do not need to match the method's |
| * expected elements in any way. |
| * |
| */ |
| public class RPCTypeCheckFactory { |
| /** |
| * A Serialization policy that allows anything to be serialized. |
| */ |
| public static class TestSerializationPolicy extends SerializationPolicy { |
| @Override |
| public boolean shouldDeserializeFields(Class<?> clazz) { |
| return true; |
| } |
| |
| @Override |
| public boolean shouldSerializeFields(Class<?> clazz) { |
| return true; |
| } |
| |
| @SuppressWarnings("unused") |
| @Override |
| public void validateDeserialize(Class<?> clazz) throws SerializationException { |
| } |
| |
| @SuppressWarnings("unused") |
| @Override |
| public void validateSerialize(Class<?> clazz) throws SerializationException { |
| } |
| } |
| |
| private static final TestSerializationPolicy testSerializationPolicy = |
| new TestSerializationPolicy(); |
| |
| /** |
| * Generates a HashMap containing HashSet for testing. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public static HashMap<HashSet, Integer> generateTestHashMap() { |
| HashMap<HashSet, Integer> result = new HashMap<HashSet, Integer>(); |
| HashSet entry1 = new HashSet(); |
| entry1.add(0); |
| result.put(entry1, 12345); |
| |
| return result; |
| } |
| |
| /** |
| * Generates a HashSet of HashSet for testing. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public static HashSet generateTestHashSet() { |
| HashSet result = new HashSet(); |
| HashSet entry1 = new HashSet(); |
| HashSet entry2 = new HashSet(); |
| entry1.add(12345); |
| entry1.add(67890); |
| result.add(entry1); |
| result.add(entry2); |
| |
| return result; |
| } |
| /** |
| * Generates a HashSet that contains a HashSet of HashSets for testing. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public static HashSet generateTestHashSetHashSet() { |
| HashSet result = new HashSet(); |
| HashSet entry0 = new HashSet(); |
| HashSet entry1 = new HashSet(); |
| HashSet entry2 = new HashSet(); |
| entry1.add(12345); |
| entry2.add(67890); |
| entry0.add(entry1); |
| entry0.add(entry2); |
| result.add(entry0); |
| |
| return result; |
| } |
| |
| /** |
| * Generates a IdentityHashMap that contains a HashSet for testing. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public static IdentityHashMap<HashSet, Integer> generateTestIdentityHashMap() { |
| IdentityHashMap<HashSet, Integer> result = new IdentityHashMap<HashSet, Integer>(); |
| HashSet entry1 = new HashSet(); |
| HashSet entry2 = new HashSet(); |
| entry1.add(12345); |
| entry2.add(67890); |
| result.put(entry1, 12345); |
| result.put(entry2, 67890); |
| |
| return result; |
| } |
| /** |
| * Generates a LinkedHashMap that contains a HashSet for testing. |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public static LinkedHashMap<HashSet, Integer> generateTestLinkedHashMap() { |
| LinkedHashMap<HashSet, Integer> result = new LinkedHashMap<HashSet, Integer>(); |
| HashSet entry1 = new HashSet(); |
| HashSet entry2 = new HashSet(); |
| entry1.add(12345); |
| entry2.add(67890); |
| result.put(entry1, 12345); |
| result.put(entry2, 67890); |
| |
| return result; |
| } |
| |
| /** |
| * Generates a LinkedHashSet that takes a very long time to deserialize. |
| * |
| * @return A LinkedHashSet that cannot be used but requires computation of an |
| * exponential number of hash codes. |
| */ |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public static TestHashSet generateTestLinkedHashSet() { |
| TestHashSet result = new TestHashSet(); |
| HashSet entry1 = new HashSet(); |
| HashSet entry2 = new HashSet(); |
| entry1.add(12345); |
| entry2.add(67890); |
| result.add(entry1); |
| result.add(entry2); |
| |
| return result; |
| } |
| |
| private static String generateSerializedClassString(Class<?> instanceType) { |
| if (instanceType == int.class) { |
| return "I"; |
| } |
| |
| return instanceType.getName() + "/" |
| + SerializabilityUtil.getSerializationSignature(instanceType, testSerializationPolicy); |
| } |
| |
| private String headerString = ""; |
| |
| private String bodyString = ""; |
| |
| private LinkedHashMap<String, Integer> stringTable; |
| |
| private IdentityHashMap<Object, Integer> encodedObjectTable; |
| |
| /** |
| * Create a new Factory that will generate an RPC string to invoke the given |
| * method found in the given class. |
| * |
| * @throws NoSuchMethodException when the method requested does not exist on |
| * the class provided. |
| */ |
| public RPCTypeCheckFactory(Class<?> testMethodClass, String testMethodName) |
| throws NoSuchMethodException { |
| headerString += |
| Integer.toString(AbstractSerializationStream.SERIALIZATION_STREAM_VERSION) |
| + RPC_SEPARATOR_CHAR + // version |
| "0" + RPC_SEPARATOR_CHAR; // flags |
| |
| stringTable = new LinkedHashMap<String, Integer>(20); |
| writeStringFromTable("moduleBaseURL"); |
| writeStringFromTable("whitelistHashcode"); |
| |
| encodedObjectTable = new IdentityHashMap<Object, Integer>(20); |
| |
| writeMethodSignature(testMethodClass, testMethodName); |
| } |
| |
| /** |
| * Convert the accumulated contents of this object to the final RPC message |
| * string. |
| */ |
| @Override |
| public String toString() { |
| String result = headerString; |
| |
| result += Integer.toString(stringTable.size()) + RPC_SEPARATOR_CHAR; |
| for (String strEntry : stringTable.keySet()) { |
| result += strEntry + RPC_SEPARATOR_CHAR; |
| } |
| |
| result += bodyString; |
| |
| return result; |
| } |
| |
| /** |
| * Add data for an int object. |
| */ |
| public void write(int integer) throws SerializationException { |
| try { |
| bodyString += Integer.toString(integer) + RPC_SEPARATOR_CHAR; |
| } catch (Exception e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Add data for an int array object. |
| */ |
| public void write(int[] intArray) { |
| writeStringFromTable(generateSerializedClassString(intArray.getClass())); |
| bodyString += Integer.toString(intArray.length) + RPC_SEPARATOR_CHAR; |
| for (int i : intArray) { |
| bodyString += Integer.toString(i) + RPC_SEPARATOR_CHAR; |
| } |
| } |
| |
| /** |
| * Add data for an {@link Integer} object. |
| */ |
| public void write(Integer integer) throws SerializationException { |
| if (getEncodedIndex(integer)) { |
| return; |
| } |
| try { |
| writeStringFromTable(generateSerializedClassString(Integer.class)); |
| bodyString += integer.toString() + RPC_SEPARATOR_CHAR; |
| } catch (Exception e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Add data for a {@link java.util.List} object. |
| */ |
| public void write(List<?> list) throws SerializationException { |
| if (getEncodedIndex(list)) { |
| return; |
| } |
| writeStringFromTable(generateSerializedClassString(list.getClass())); |
| writeListBody(list); |
| } |
| |
| /** |
| * Add data for a {@link java.util.Map} object. |
| */ |
| public void write(Map<?, ?> map) throws SerializationException { |
| if (getEncodedIndex(map)) { |
| return; |
| } |
| writeStringFromTable(generateSerializedClassString(map.getClass())); |
| if (map instanceof LinkedHashMap) { |
| // Set the iteration order for the linked hash map, always insertion |
| // order. |
| bodyString += "0" + RPC_SEPARATOR_CHAR; |
| } |
| if (map instanceof TreeMap) { |
| // Set the iteration order for the linked hash map, always insertion |
| // order. |
| Comparator<?> comp = ((TreeMap<?, ?>) map).comparator(); |
| if (comp == null) { |
| bodyString += "0" + RPC_SEPARATOR_CHAR; |
| } else { |
| writeStringFromTable(generateSerializedClassString(comp.getClass())); |
| } |
| } |
| bodyString += Integer.toString(map.size()) + RPC_SEPARATOR_CHAR; |
| for (Map.Entry<?, ?> entry : map.entrySet()) { |
| writeCollectionElement(entry.getKey()); |
| writeCollectionElement(entry.getValue()); |
| } |
| } |
| |
| /** |
| * Add data for an {@link Object} where no more specific write method exists. |
| */ |
| public void write(Object object) throws SerializationException { |
| if (getEncodedIndex(object)) { |
| return; |
| } |
| |
| try { |
| Class<?> clazz = object.getClass(); |
| writeStringFromTable(generateSerializedClassString(clazz)); |
| |
| writeClass(object, clazz); |
| } catch (Exception e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * Add data for a {@link Object} array. |
| */ |
| public void write(Object[] objectArray) throws SerializationException { |
| if (getEncodedIndex(objectArray)) { |
| return; |
| } |
| writeStringFromTable(generateSerializedClassString(objectArray.getClass())); |
| bodyString += Integer.toString(objectArray.length) + RPC_SEPARATOR_CHAR; |
| for (Object element : objectArray) { |
| writeCollectionElement(element); |
| } |
| } |
| |
| /** |
| * Add data for a {@link java.util.Set} object. |
| */ |
| public void write(Set<?> set) throws SerializationException { |
| if (getEncodedIndex(set)) { |
| return; |
| } |
| writeStringFromTable(generateSerializedClassString(set.getClass())); |
| if (set instanceof TreeSet) { |
| writeStringFromTable(generateSerializedClassString(((TreeSet<?>) set).comparator().getClass())); |
| } |
| bodyString += Integer.toString(set.size()) + RPC_SEPARATOR_CHAR; |
| for (Object element : set) { |
| writeCollectionElement(element); |
| } |
| } |
| |
| /** |
| * Add data for a {@link String} object. |
| */ |
| public void write(String string) { |
| writeStringFromTable(string); |
| } |
| |
| /** |
| * Add data for a {@link java.util.Vector} object. |
| */ |
| public void write(Vector<?> vector) throws SerializationException { |
| if (getEncodedIndex(vector)) { |
| return; |
| } |
| writeStringFromTable(generateSerializedClassString(vector.getClass())); |
| bodyString += Integer.toString(vector.size()) + RPC_SEPARATOR_CHAR; |
| for (Object element : vector) { |
| writeCollectionElement(element); |
| } |
| } |
| |
| /** |
| * Add data for an empty list object (as returned by |
| * {@link java.util.Collections}). |
| */ |
| public void writeEmptyList() { |
| List<?> emptyList = java.util.Collections.emptyList(); |
| writeStringFromTable(generateSerializedClassString(emptyList.getClass())); |
| } |
| |
| /** |
| * Add data for an empty map object (as returned by |
| * {@link java.util.Collections}). |
| */ |
| public void writeEmptyMap() { |
| Map<?, ?> emptyMap = java.util.Collections.emptyMap(); |
| writeStringFromTable(generateSerializedClassString(emptyMap.getClass())); |
| } |
| |
| /** |
| * Add data for an empty set object (as returned by |
| * {@link java.util.Collections}). |
| */ |
| public void writeEmptySet() { |
| Set<?> emptySet = java.util.Collections.emptySet(); |
| writeStringFromTable(generateSerializedClassString(emptySet.getClass())); |
| } |
| |
| private boolean getEncodedIndex(Object obj) { |
| Integer foundIndex = encodedObjectTable.get(obj); |
| if (foundIndex == null) { |
| foundIndex = encodedObjectTable.size(); |
| encodedObjectTable.put(obj, foundIndex); |
| return false; |
| } |
| |
| bodyString += Integer.toString(-(foundIndex.intValue() + 1)) + RPC_SEPARATOR_CHAR; |
| |
| return true; |
| } |
| |
| private void writeClass(Object object, Class<?> clazz) throws SerializationException { |
| Field[] fields = clazz.getDeclaredFields(); |
| try { |
| for (Field field : fields) { |
| Type type = field.getGenericType(); |
| if (type == int.class) { |
| write(field.getInt(object)); |
| } else { |
| Object value = field.get(object); |
| writeCollectionElement(value); |
| } |
| } |
| |
| Class<?> superClass = clazz.getSuperclass(); |
| if (superClass == Object.class) { |
| return; |
| } else if (superClass == LinkedList.class) { |
| writeListBody((List<?>) object); |
| return; |
| } |
| |
| writeClass(object, superClass); |
| |
| } catch (Exception e) { |
| throw new SerializationException(e); |
| } |
| } |
| |
| /** |
| * @param element |
| * @throws SerializationException |
| */ |
| @SuppressWarnings("cast") |
| private void writeCollectionElement(Object element) throws SerializationException { |
| if (element == null) { |
| bodyString += "0" + RPC_SEPARATOR_CHAR; |
| } else if (element instanceof Map) { |
| write((Map<?, ?>) element); |
| } else if (element instanceof HashSet) { |
| write((HashSet<?>) element); |
| } else if (element instanceof int[]) { |
| write((int[]) element); |
| } else if (element instanceof Integer) { |
| write((Integer) element); |
| } else if (element instanceof Long) { |
| writeLong((Long) element); |
| } else if (element instanceof Float) { |
| writeFloat((Float) element); |
| } else if (element instanceof Short) { |
| writeShort((Short) element); |
| } else if (element instanceof Double) { |
| writeDouble((Double) element); |
| } else if (element instanceof RPCTypeCheckTest.DClass) { |
| write((Object) element); |
| } else if (element instanceof String) { |
| writeStringInCollection((String) element); |
| } else if (element instanceof List) { |
| write((List<?>) element); |
| } else if (element instanceof Object[]) { |
| write((Object[]) element); |
| } else { |
| write(element); |
| } |
| } |
| |
| private void writeDouble(Double d) { |
| writeStringFromTable(generateSerializedClassString(Double.class)); |
| writeStringFromTable(d.toString()); |
| } |
| |
| private void writeFloat(Float f) { |
| writeStringFromTable(generateSerializedClassString(Float.class)); |
| writeStringFromTable(f.toString()); |
| } |
| |
| private void writeListBody(List<?> list) throws SerializationException { |
| if (list.getClass().toString().matches(".*Arrays\\$ArrayList.*")) { |
| write(list.toArray()); |
| return; |
| } |
| if (!list.getClass().toString().matches(".*Collections\\$SingletonList.*")) { |
| bodyString += Integer.toString(list.size()) + RPC_SEPARATOR_CHAR; |
| } |
| for (Object element : list) { |
| writeCollectionElement(element); |
| } |
| } |
| |
| private void writeLong(Long l) { |
| writeStringFromTable(generateSerializedClassString(Long.class)); |
| writeStringFromTable(l.toString()); |
| } |
| |
| private void writeMethodSignature(Class<?> testMethodClass, String testMethodName) |
| throws NoSuchMethodException { |
| writeStringFromTable(testMethodClass.getName()); |
| writeStringFromTable(testMethodName); |
| |
| Method[] testMethods = testMethodClass.getDeclaredMethods(); |
| Method targetMethod = null; |
| for (Method method : testMethods) { |
| if (method.getName().equals(testMethodName)) { |
| targetMethod = method; |
| break; |
| } |
| } |
| if (targetMethod == null) { |
| throw new NoSuchMethodException("Could not find " + testMethodName + " in " |
| + testMethodClass.getName()); |
| } |
| |
| Class<?>[] parameterClasses = targetMethod.getParameterTypes(); |
| bodyString += Integer.toString(parameterClasses.length) + RPC_SEPARATOR_CHAR; |
| for (Class<?> parameter : parameterClasses) { |
| writeStringFromTable(generateSerializedClassString(parameter)); |
| } |
| } |
| |
| private void writeShort(Short s) { |
| writeStringFromTable(generateSerializedClassString(Short.class)); |
| writeStringFromTable(s.toString()); |
| } |
| |
| private void writeStringFromTable(String string) { |
| Integer index = stringTable.get(string); |
| if (index == null) { |
| index = stringTable.size() + 1; |
| stringTable.put(string, index); |
| } |
| bodyString += index.toString() + RPC_SEPARATOR_CHAR; |
| } |
| |
| private void writeStringInCollection(String string) { |
| writeStringFromTable(generateSerializedClassString(string.getClass())); |
| writeStringFromTable(string); |
| } |
| |
| } |