blob: 9801f82c8eb6fffd42aa41a326552a227e199f01 [file] [log] [blame]
/*
* 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);
}
}