@@ -66,6 +66,49 @@ public class MouseEventDetails implements Serializable { | |||
return relativeY; | |||
} | |||
public void setButton(int button) { | |||
this.button = button; | |||
} | |||
public void setClientX(int clientX) { | |||
this.clientX = clientX; | |||
} | |||
public void setClientY(int clientY) { | |||
this.clientY = clientY; | |||
} | |||
public void setAltKey(boolean altKey) { | |||
this.altKey = altKey; | |||
} | |||
public void setCtrlKey(boolean ctrlKey) { | |||
this.ctrlKey = ctrlKey; | |||
} | |||
public void setMetaKey(boolean metaKey) { | |||
this.metaKey = metaKey; | |||
} | |||
public void setShiftKey(boolean shiftKey) { | |||
this.shiftKey = shiftKey; | |||
} | |||
public void setType(int type) { | |||
this.type = type; | |||
} | |||
public void setRelativeX(int relativeX) { | |||
this.relativeX = relativeX; | |||
} | |||
public void setRelativeY(int relativeY) { | |||
this.relativeY = relativeY; | |||
} | |||
public MouseEventDetails() { | |||
} | |||
public MouseEventDetails(NativeEvent evt) { | |||
this(evt, null); | |||
} | |||
@@ -85,9 +128,6 @@ public class MouseEventDetails implements Serializable { | |||
} | |||
} | |||
private MouseEventDetails() { | |||
} | |||
@Override | |||
public String toString() { | |||
return serialize(); | |||
@@ -128,8 +168,8 @@ public class MouseEventDetails implements Serializable { | |||
return ""; | |||
} | |||
public Class<MouseEventDetails> getType() { | |||
return MouseEventDetails.class; | |||
public int getType() { | |||
return type; | |||
} | |||
public boolean isDoubleClick() { |
@@ -29,8 +29,7 @@ import com.vaadin.terminal.gwt.client.ConnectorMap; | |||
* @since 7.0 | |||
*/ | |||
public class JsonDecoder { | |||
private static SerializerMap serializerMap = GWT | |||
.create(SerializerMap.class); | |||
static SerializerMap serializerMap = GWT.create(SerializerMap.class); | |||
/** | |||
* Convert a JSON array with two elements (type and value) into a |
@@ -93,8 +93,20 @@ public class JsonEncoder { | |||
return combineTypeAndValue(VTYPE_PAINTABLE, new JSONString( | |||
connectorMap.getConnectorId(paintable))); | |||
} else { | |||
return combineTypeAndValue(getTransportType(value), new JSONString( | |||
String.valueOf(value))); | |||
if (getTransportType(value) != VTYPE_UNDEFINED) { | |||
return combineTypeAndValue(getTransportType(value), | |||
new JSONString(String.valueOf(value))); | |||
} else { | |||
// Try to find a generated serializer object, class name is the | |||
// type | |||
String type = value.getClass().getName(); | |||
VaadinSerializer serializer = JsonDecoder.serializerMap | |||
.getSerializer(type); | |||
// TODO handle case with no serializer found | |||
return combineTypeAndValue(type, | |||
serializer.serialize(value, connectorMap)); | |||
} | |||
} | |||
} | |||
@@ -5,7 +5,9 @@ | |||
package com.vaadin.terminal.gwt.client.communication; | |||
import com.google.gwt.json.client.JSONObject; | |||
import com.google.gwt.json.client.JSONValue; | |||
import com.vaadin.terminal.gwt.client.ConnectorMap; | |||
import com.vaadin.terminal.gwt.server.JsonCodec; | |||
/** | |||
* Serializer that can deserialize custom objects received from the server. | |||
@@ -18,16 +20,34 @@ import com.vaadin.terminal.gwt.client.ConnectorMap; | |||
public interface VaadinSerializer { | |||
/** | |||
* Creates and deserializes an object received from the server. | |||
* Creates and deserializes an object received from the server. Must be | |||
* compatible with {@link #serialize(Object, ConnectorMap)} and also with | |||
* the server side | |||
* {@link JsonCodec#encode(Object, com.vaadin.terminal.gwt.server.PaintableIdMapper)} | |||
* . | |||
* | |||
* @param jsonValue | |||
* JSON map from property name to property value | |||
* @param idMapper | |||
* mapper from paintable id to paintable, used to decode | |||
* references to paintables | |||
* @return deserialized object | |||
* @return A deserialized object | |||
*/ | |||
// TODO Object -> something | |||
Object deserialize(JSONObject jsonValue, ConnectorMap idMapper); | |||
/** | |||
* Serialize the given object into JSON. Must be compatible with | |||
* {@link #deserialize(JSONObject, ConnectorMap)} and also with the server | |||
* side | |||
* {@link JsonCodec#decode(com.vaadin.external.json.JSONArray, com.vaadin.terminal.gwt.server.PaintableIdMapper)} | |||
* | |||
* @param value | |||
* The object to serialize | |||
* @param idMapper | |||
* mapper from paintable id to paintable, used to decode | |||
* references to paintables | |||
* @return A JSON serialized version of the object | |||
*/ | |||
JSONObject serialize(Object value, ConnectorMap idMapper); | |||
} |
@@ -28,7 +28,7 @@ public class ButtonConnector extends AbstractComponentConnector { | |||
* @param mouseEventDetails | |||
* serialized mouse event details | |||
*/ | |||
public void click(String mouseEventDetails); | |||
public void click(MouseEventDetails mouseEventDetails); | |||
/** | |||
* Indicate to the server that the client has disabled the button as a |
@@ -307,7 +307,7 @@ public class VButton extends FocusWidget implements ClickHandler, FocusHandler, | |||
// Add mouse details | |||
MouseEventDetails details = new MouseEventDetails( | |||
event.getNativeEvent(), getElement()); | |||
buttonRpcProxy.click(details.serialize()); | |||
buttonRpcProxy.click(details); | |||
clickPending = false; | |||
} |
@@ -125,7 +125,7 @@ public class VNativeButton extends Button implements ClickHandler, | |||
// Add mouse details | |||
MouseEventDetails details = new MouseEventDetails( | |||
event.getNativeEvent(), getElement()); | |||
buttonRpcProxy.click(details.serialize()); | |||
buttonRpcProxy.click(details); | |||
clickPending = false; | |||
} |
@@ -1485,7 +1485,7 @@ public abstract class AbstractCommunicationManager implements | |||
JSONArray parametersJson = invocationJson.getJSONArray(3); | |||
Object[] parameters = new Object[parametersJson.length()]; | |||
for (int j = 0; j < parametersJson.length(); ++j) { | |||
parameters[j] = JsonCodec.convertVariableValue( | |||
parameters[j] = JsonCodec.decode( | |||
parametersJson.getJSONArray(j), this); | |||
} | |||
MethodInvocation invocation = new MethodInvocation(connectorId, |
@@ -4,9 +4,11 @@ | |||
package com.vaadin.terminal.gwt.server; | |||
import java.beans.IntrospectionException; | |||
import java.beans.Introspector; | |||
import java.beans.PropertyDescriptor; | |||
import java.io.Serializable; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.lang.reflect.Method; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
@@ -61,8 +63,8 @@ public class JsonCodec implements Serializable { | |||
* @throws JSONException | |||
* if the conversion fails | |||
*/ | |||
public static Object convertVariableValue(JSONArray value, | |||
PaintableIdMapper idMapper) throws JSONException { | |||
public static Object decode(JSONArray value, PaintableIdMapper idMapper) | |||
throws JSONException { | |||
return convertVariableValue(value.getString(0), value.get(1), idMapper); | |||
} | |||
@@ -96,6 +98,10 @@ public class JsonCodec implements Serializable { | |||
} else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) { | |||
// TODO handle properly | |||
val = idMapper.getPaintable(String.valueOf(value)); | |||
} else { | |||
// Try to decode object using fields | |||
return decodeObject(variableType, (JSONObject) value, idMapper); | |||
} | |||
return val; | |||
@@ -107,8 +113,7 @@ public class JsonCodec implements Serializable { | |||
Iterator<String> it = jsonMap.keys(); | |||
while (it.hasNext()) { | |||
String key = it.next(); | |||
map.put(key, | |||
convertVariableValue(jsonMap.getJSONArray(key), idMapper)); | |||
map.put(key, decode(jsonMap.getJSONArray(key), idMapper)); | |||
} | |||
return map; | |||
} | |||
@@ -129,7 +134,7 @@ public class JsonCodec implements Serializable { | |||
for (int i = 0; i < jsonArray.length(); ++i) { | |||
// each entry always has two elements: type and value | |||
JSONArray entryArray = jsonArray.getJSONArray(i); | |||
tokens.add(convertVariableValue(entryArray, idMapper)); | |||
tokens.add(decode(entryArray, idMapper)); | |||
} | |||
return tokens.toArray(new Object[tokens.size()]); | |||
} | |||
@@ -210,6 +215,44 @@ public class JsonCodec implements Serializable { | |||
return jsonMap; | |||
} | |||
private static Object decodeObject(String type, | |||
JSONObject serializedObject, PaintableIdMapper idMapper) | |||
throws JSONException { | |||
Class<?> cls; | |||
try { | |||
cls = Class.forName(type); | |||
Object decodedObject = cls.newInstance(); | |||
for (PropertyDescriptor pd : Introspector.getBeanInfo(cls) | |||
.getPropertyDescriptors()) { | |||
if (pd.getReadMethod() == null || pd.getWriteMethod() == null) { | |||
continue; | |||
} | |||
String fieldName = pd.getName(); | |||
JSONArray encodedObject = serializedObject | |||
.getJSONArray(fieldName); | |||
pd.getWriteMethod().invoke(decodedObject, | |||
decode(encodedObject, idMapper)); | |||
} | |||
return decodedObject; | |||
} catch (ClassNotFoundException e) { | |||
throw new JSONException(e); | |||
} catch (IllegalArgumentException e) { | |||
throw new JSONException(e); | |||
} catch (IllegalAccessException e) { | |||
throw new JSONException(e); | |||
} catch (InvocationTargetException e) { | |||
throw new JSONException(e); | |||
} catch (InstantiationException e) { | |||
throw new JSONException(e); | |||
} catch (IntrospectionException e) { | |||
throw new JSONException(e); | |||
} | |||
} | |||
private static JSONArray encodeArrayContents(Object[] array, | |||
PaintableIdMapper idMapper) throws JSONException { | |||
JSONArray jsonArray = new JSONArray(); |
@@ -22,10 +22,12 @@ import com.google.gwt.core.ext.typeinfo.JType; | |||
import com.google.gwt.core.ext.typeinfo.TypeOracle; | |||
import com.google.gwt.json.client.JSONArray; | |||
import com.google.gwt.json.client.JSONObject; | |||
import com.google.gwt.json.client.JSONValue; | |||
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; | |||
import com.google.gwt.user.rebind.SourceWriter; | |||
import com.vaadin.terminal.gwt.client.ConnectorMap; | |||
import com.vaadin.terminal.gwt.client.communication.JsonDecoder; | |||
import com.vaadin.terminal.gwt.client.communication.JsonEncoder; | |||
import com.vaadin.terminal.gwt.client.communication.SerializerMap; | |||
import com.vaadin.terminal.gwt.client.communication.VaadinSerializer; | |||
@@ -110,6 +112,42 @@ public class SerializerGenerator extends Generator { | |||
printWriter); | |||
sourceWriter.indent(); | |||
// Serializer | |||
// public JSONValue serialize(Object value, ConnectorMap idMapper) { | |||
sourceWriter.println("public " + JSONValue.class.getName() | |||
+ " serialize(" + Object.class.getName() + " value, " | |||
+ ConnectorMap.class.getName() + " idMapper) {"); | |||
sourceWriter.indent(); | |||
// MouseEventDetails castedValue = (MouseEventDetails) value; | |||
sourceWriter.println(beanQualifiedSourceName + " castedValue = (" | |||
+ beanQualifiedSourceName + ") value;"); | |||
// JSONObject json = new JSONObject(); | |||
sourceWriter.println(JSONObject.class.getName() + " json = new " | |||
+ JSONObject.class.getName() + "();"); | |||
for (JMethod setterMethod : getSetters(beanType)) { | |||
String setterName = setterMethod.getName(); | |||
String capitalizedFieldName = setterName.substring(3); | |||
String fieldName = decapitalize(capitalizedFieldName); | |||
String getterName = findGetter(beanType, setterMethod); | |||
if (getterName == null) { | |||
logger.log(TreeLogger.WARN, "No getter found for " + fieldName | |||
+ ". Serialization will likely fail"); | |||
} | |||
// json.put("button", | |||
// JsonEncoder.encode(castedValue.getButton(), idMapper)); | |||
sourceWriter.println("json.put(\"" + fieldName + "\", " | |||
+ JsonEncoder.class.getName() + ".encode(castedValue." | |||
+ getterName + "(), idMapper));"); | |||
} | |||
// return json; | |||
sourceWriter.println("return json;"); | |||
// } | |||
sourceWriter.println("}"); | |||
// Deserializer | |||
sourceWriter.println("public " + beanQualifiedSourceName | |||
+ " deserialize(" + JSONObject.class.getName() + " jsonValue, " | |||
+ ConnectorMap.class.getName() + " idMapper) {"); | |||
@@ -118,44 +156,35 @@ public class SerializerGenerator extends Generator { | |||
// VButtonState state = GWT.create(VButtonState.class); | |||
sourceWriter.println(beanQualifiedSourceName + " state = GWT.create(" | |||
+ beanQualifiedSourceName + ".class);"); | |||
JClassType objectType = typeOracle.findType("java.lang.Object"); | |||
while (!objectType.equals(beanType)) { | |||
for (JMethod method : getSetters(beanType)) { | |||
String setterName = method.getName(); | |||
String capitalizedFieldName = setterName.substring(3); | |||
String fieldName = decapitalize(capitalizedFieldName); | |||
JType setterParameterType = method.getParameterTypes()[0]; | |||
logger.log( | |||
Type.INFO, | |||
"* Processing field " + fieldName + " in " | |||
+ beanQualifiedSourceName + " (" | |||
+ beanType.getName() + ")"); | |||
String jsonFieldName = "json" + capitalizedFieldName; | |||
// JSONArray jsonHeight = (JSONArray) jsonValue.get("height"); | |||
sourceWriter.println("JSONArray " + jsonFieldName | |||
+ " = (JSONArray) jsonValue.get(\"" + fieldName | |||
+ "\");"); | |||
// state.setHeight((String) | |||
// JsonDecoder.convertValue(jsonFieldValue,idMapper)); | |||
String fieldType; | |||
JPrimitiveType primitiveType = setterParameterType | |||
.isPrimitive(); | |||
if (primitiveType != null) { | |||
// This is a primitive type -> must used the boxed type | |||
fieldType = primitiveType.getQualifiedBoxedSourceName(); | |||
} else { | |||
fieldType = setterParameterType.getQualifiedSourceName(); | |||
} | |||
for (JMethod method : getSetters(beanType)) { | |||
String setterName = method.getName(); | |||
String capitalizedFieldName = setterName.substring(3); | |||
String fieldName = decapitalize(capitalizedFieldName); | |||
JType setterParameterType = method.getParameterTypes()[0]; | |||
logger.log(Type.INFO, "* Processing field " + fieldName + " in " | |||
+ beanQualifiedSourceName + " (" + beanType.getName() + ")"); | |||
String jsonFieldName = "json" + capitalizedFieldName; | |||
// JSONArray jsonHeight = (JSONArray) jsonValue.get("height"); | |||
sourceWriter.println("JSONArray " + jsonFieldName | |||
+ " = (JSONArray) jsonValue.get(\"" + fieldName + "\");"); | |||
sourceWriter.println("state." + setterName + "((" + fieldType | |||
+ ") JsonDecoder.convertValue(" + jsonFieldName | |||
+ ", idMapper));"); | |||
// state.setHeight((String) | |||
// JsonDecoder.convertValue(jsonFieldValue,idMapper)); | |||
String fieldType; | |||
JPrimitiveType primitiveType = setterParameterType.isPrimitive(); | |||
if (primitiveType != null) { | |||
// This is a primitive type -> must used the boxed type | |||
fieldType = primitiveType.getQualifiedBoxedSourceName(); | |||
} else { | |||
fieldType = setterParameterType.getQualifiedSourceName(); | |||
} | |||
beanType = beanType.getSuperclass(); | |||
sourceWriter.println("state." + setterName + "((" + fieldType | |||
+ ") JsonDecoder.convertValue(" + jsonFieldName | |||
+ ", idMapper));"); | |||
} | |||
// return state; | |||
@@ -175,19 +204,41 @@ public class SerializerGenerator extends Generator { | |||
} | |||
private String findGetter(JClassType beanType, JMethod setterMethod) { | |||
JType setterParameterType = setterMethod.getParameterTypes()[0]; | |||
String capitalizedFieldName = setterMethod.getName().substring(3); | |||
if (setterParameterType.getQualifiedSourceName().equals( | |||
boolean.class.getName())) { | |||
return "is" + capitalizedFieldName; | |||
} else { | |||
return "get" + capitalizedFieldName; | |||
} | |||
} | |||
/** | |||
* Returns a list of all setters found in the beanType or its parent class | |||
* | |||
* @param beanType | |||
* The type to check | |||
* @return A list of setter methods from the class and its parents | |||
*/ | |||
protected static List<JMethod> getSetters(JClassType beanType) { | |||
List<JMethod> setterMethods = new ArrayList<JMethod>(); | |||
for (JMethod method : beanType.getMethods()) { | |||
// Process all setters that have corresponding fields | |||
if (!method.isPublic() || method.isStatic() | |||
|| !method.getName().startsWith("set") | |||
|| method.getParameterTypes().length != 1) { | |||
// Not setter, skip to next method | |||
continue; | |||
while (!beanType.getQualifiedSourceName() | |||
.equals(Object.class.getName())) { | |||
for (JMethod method : beanType.getMethods()) { | |||
// Process all setters that have corresponding fields | |||
if (!method.isPublic() || method.isStatic() | |||
|| !method.getName().startsWith("set") | |||
|| method.getParameterTypes().length != 1) { | |||
// Not setter, skip to next method | |||
continue; | |||
} | |||
setterMethods.add(method); | |||
} | |||
setterMethods.add(method); | |||
beanType = beanType.getSuperclass(); | |||
} | |||
return setterMethods; |
@@ -22,6 +22,7 @@ import com.google.gwt.core.ext.typeinfo.TypeOracle; | |||
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; | |||
import com.google.gwt.user.rebind.SourceWriter; | |||
import com.vaadin.terminal.gwt.client.communication.SerializerMap; | |||
import com.vaadin.terminal.gwt.client.communication.ServerRpc; | |||
import com.vaadin.terminal.gwt.client.communication.SharedState; | |||
import com.vaadin.terminal.gwt.client.communication.VaadinSerializer; | |||
@@ -144,7 +145,15 @@ public class SerializerMapGenerator extends Generator { | |||
types.add(type); | |||
} | |||
// Add all types used from/in the determined types | |||
// Serializer classes might also be needed for RPC methods | |||
JClassType serverRpcType = typeOracle.findType(ServerRpc.class | |||
.getName()); | |||
JClassType[] serverRpcSubtypes = serverRpcType.getSubtypes(); | |||
for (JClassType type : serverRpcSubtypes) { | |||
addMethodParameterTypes(type, types); | |||
} | |||
// Add all types used from/in the types | |||
for (Object t : types.toArray()) { | |||
findSubTypesNeedingSerializers((JClassType) t, types); | |||
} | |||
@@ -153,6 +162,18 @@ public class SerializerMapGenerator extends Generator { | |||
return types; | |||
} | |||
private void addMethodParameterTypes(JClassType classContainingMethods, | |||
HashSet<JClassType> types) { | |||
for (JMethod method : classContainingMethods.getMethods()) { | |||
if (method.getName().equals("initRpc")) { | |||
continue; | |||
} | |||
for (JType type : method.getParameterTypes()) { | |||
types.add(type.isClass()); | |||
} | |||
} | |||
} | |||
public void findSubTypesNeedingSerializers(JClassType type, | |||
Set<JClassType> serializableTypes) { | |||
// Find all setters and look at their parameter type to determine if a | |||
@@ -169,6 +190,7 @@ public class SerializerMapGenerator extends Generator { | |||
} | |||
serializableTypes.add(setterType.isClass()); | |||
findSubTypesNeedingSerializers(type, serializableTypes); | |||
} | |||
} | |||
@@ -48,8 +48,8 @@ public class Button extends AbstractComponent implements | |||
public Button() { | |||
// TODO take the implementation out of an anonymous class? | |||
registerRpcImplementation(new ButtonServerRpc() { | |||
public void click(String mouseEventDetails) { | |||
fireClick(MouseEventDetails.deSerialize(mouseEventDetails)); | |||
public void click(MouseEventDetails mouseEventDetails) { | |||
fireClick(mouseEventDetails); | |||
} | |||
public void disableOnClick() { |