]> source.dussan.org Git - vaadin-framework.git/commitdiff
Initial support for sending difference in state (#8419)
authorArtur Signell <artur@vaadin.com>
Mon, 23 Apr 2012 18:48:02 +0000 (21:48 +0300)
committerArtur Signell <artur@vaadin.com>
Fri, 11 May 2012 19:18:19 +0000 (22:18 +0300)
Forces the shared state to be the type declared by getStateType (#8677)

Creates an empty object for reference when doing a full repaint, causing
only the differences between a freshly created object and the current
state to be sent.

12 files changed:
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/client/communication/JSONSerializer.java
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
src/com/vaadin/terminal/gwt/client/communication/URLReference_Serializer.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/ClientConnector.java
src/com/vaadin/terminal/gwt/server/DragAndDropService.java
src/com/vaadin/terminal/gwt/server/JsonCodec.java
src/com/vaadin/terminal/gwt/widgetsetutils/SerializerGenerator.java
src/com/vaadin/ui/AbstractComponent.java
src/com/vaadin/ui/Root.java

index 739c232a721eb0c67aeb559219ce81c9226f7655..18b1afd813abb8a40002a4f03dce15edbf5f26fd 100644 (file)
@@ -43,6 +43,7 @@ import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
 import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
 import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
 import com.vaadin.terminal.gwt.client.communication.RpcManager;
+import com.vaadin.terminal.gwt.client.communication.SerializerMap;
 import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.client.communication.StateChangeEvent;
 import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector;
@@ -102,6 +103,9 @@ public class ApplicationConnection {
 
     public static final String PARAM_UNLOADBURST = "onunloadburst";
 
+    private static SerializerMap serializerMap = GWT
+            .create(SerializerMap.class);
+
     /**
      * A string that, if found in a non-JSON response to a UIDL request, will
      * cause the browser to refresh the page. If followed by a colon, optional
@@ -1414,8 +1418,8 @@ public class ApplicationConnection {
                                     states.getJavaScriptObject(connectorId));
 
                             Object state = JsonDecoder.decodeValue(
-                                    stateDataAndType, connectorMap,
-                                    ApplicationConnection.this);
+                                    stateDataAndType, connector.getState(),
+                                    connectorMap, ApplicationConnection.this);
 
                             connector.setState((SharedState) state);
                             StateChangeEvent event = GWT
@@ -1569,7 +1573,8 @@ public class ApplicationConnection {
         Object[] parameters = new Object[parametersJson.size()];
         for (int j = 0; j < parametersJson.size(); ++j) {
             parameters[j] = JsonDecoder.decodeValue(
-                    (JSONArray) parametersJson.get(j), getConnectorMap(), this);
+                    (JSONArray) parametersJson.get(j), null, getConnectorMap(),
+                    this);
         }
         return new MethodInvocation(connectorId, interfaceName, methodName,
                 parameters);
@@ -2439,4 +2444,8 @@ public class ApplicationConnection {
     LayoutManager getLayoutManager() {
         return layoutManager;
     }
+
+    public SerializerMap getSerializerMap() {
+        return serializerMap;
+    }
 }
index c626d31d0ac2701e31e130da532f80a71911baa4..5f82e339f463b8a5a5a009f43296f4ea05e3ee54 100644 (file)
@@ -33,12 +33,14 @@ public interface JSONSerializer<T> {
      * 
      * @param jsonValue
      *            JSON map from property name to property value
+     * @param target
+     *            The object to write the deserialized values to
      * @param idMapper
      *            mapper from paintable id to paintable, used to decode
      *            references to paintables
      * @return A deserialized object
      */
-    T deserialize(JSONObject jsonValue, ConnectorMap idMapper,
+    T deserialize(JSONObject jsonValue, T target, ConnectorMap idMapper,
             ApplicationConnection connection);
 
     /**
index 2b58c13f3ec4f510132e29eef3c3ee963bccf044..790b5cb3054fd5c296a6d87298bfbb0ff17f0f34 100644 (file)
@@ -12,7 +12,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import com.google.gwt.core.client.GWT;
 import com.google.gwt.json.client.JSONArray;
 import com.google.gwt.json.client.JSONObject;
 import com.google.gwt.json.client.JSONString;
@@ -33,7 +32,6 @@ import com.vaadin.terminal.gwt.client.ServerConnector;
  * @since 7.0
  */
 public class JsonDecoder {
-    static SerializerMap serializerMap = GWT.create(SerializerMap.class);
 
     /**
      * Decode a JSON array with two elements (type and value) into a client-side
@@ -48,14 +46,15 @@ public class JsonDecoder {
      *            reference to the current ApplicationConnection
      * @return decoded value (does not contain JSON types)
      */
-    public static Object decodeValue(JSONArray jsonArray,
+    public static Object decodeValue(JSONArray jsonArray, Object target,
             ConnectorMap idMapper, ApplicationConnection connection) {
         String type = ((JSONString) jsonArray.get(0)).stringValue();
-        return decodeValue(type, jsonArray.get(1), idMapper, connection);
+        return decodeValue(type, jsonArray.get(1), target, idMapper, connection);
     }
 
     private static Object decodeValue(String variableType, Object value,
-            ConnectorMap idMapper, ApplicationConnection connection) {
+            Object target, ConnectorMap idMapper,
+            ApplicationConnection connection) {
         Object val = null;
         // TODO type checks etc.
         if (JsonEncoder.VTYPE_NULL.equals(variableType)) {
@@ -92,18 +91,25 @@ public class JsonDecoder {
         } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) {
             val = idMapper.getConnector(((JSONString) value).stringValue());
         } else {
-            // object, class name as type
-            JSONSerializer serializer = serializerMap
-                    .getSerializer(variableType);
-            // TODO handle case with no serializer found
-            Object object = serializer.deserialize((JSONObject) value,
+            return decodeObject(variableType, (JSONObject) value, target,
                     idMapper, connection);
-            return object;
         }
 
         return val;
     }
 
+    private static Object decodeObject(String variableType,
+            JSONObject encodedValue, Object target, ConnectorMap idMapper,
+            ApplicationConnection connection) {
+        // object, class name as type
+        JSONSerializer<Object> serializer = connection.getSerializerMap()
+                .getSerializer(variableType);
+        // TODO handle case with no serializer found
+        Object object = serializer.deserialize(encodedValue, target, idMapper,
+                connection);
+        return object;
+    }
+
     private static Map<String, Object> decodeMap(JSONObject jsonMap,
             ConnectorMap idMapper, ApplicationConnection connection) {
         HashMap<String, Object> map = new HashMap<String, Object>();
@@ -111,7 +117,7 @@ public class JsonDecoder {
         while (it.hasNext()) {
             String key = it.next();
             map.put(key,
-                    decodeValue((JSONArray) jsonMap.get(key), idMapper,
+                    decodeValue((JSONArray) jsonMap.get(key), null, idMapper,
                             connection));
         }
         return map;
@@ -126,8 +132,8 @@ public class JsonDecoder {
             String connectorId = it.next();
             Connector connector = idMapper.getConnector(connectorId);
             map.put(connector,
-                    decodeValue((JSONArray) jsonMap.get(connectorId), idMapper,
-                            connection));
+                    decodeValue((JSONArray) jsonMap.get(connectorId), null,
+                            idMapper, connection));
         }
         return map;
     }
@@ -153,7 +159,7 @@ public class JsonDecoder {
         for (int i = 0; i < jsonArray.size(); ++i) {
             // each entry always has two elements: type and value
             JSONArray entryArray = (JSONArray) jsonArray.get(i);
-            tokens.add(decodeValue(entryArray, idMapper, connection));
+            tokens.add(decodeValue(entryArray, null, idMapper, connection));
         }
         return tokens;
     }
@@ -164,7 +170,7 @@ public class JsonDecoder {
         for (int i = 0; i < jsonArray.size(); ++i) {
             // each entry always has two elements: type and value
             JSONArray entryArray = (JSONArray) jsonArray.get(i);
-            tokens.add(decodeValue(entryArray, idMapper, connection));
+            tokens.add(decodeValue(entryArray, null, idMapper, connection));
         }
         return tokens;
     }
index fdc06b0e213a2626a6681dd604b767f7f7d84480..4a3515ff133551f470b33242aa4c8783d703ac26 100644 (file)
@@ -117,7 +117,7 @@ public class JsonEncoder {
                 // Try to find a generated serializer object, class name is the
                 // type
                 transportType = value.getClass().getName();
-                JSONSerializer serializer = JsonDecoder.serializerMap
+                JSONSerializer serializer = connection.getSerializerMap()
                         .getSerializer(transportType);
 
                 // TODO handle case with no serializer found
index 4fefc7f84560a9ac1f3c0af071d980ced2775582..27a0d0118edfa03e700bb5b31c0ec7e20e90309e 100644 (file)
@@ -11,13 +11,15 @@ import com.vaadin.terminal.gwt.client.ConnectorMap;
 
 public class URLReference_Serializer implements JSONSerializer<URLReference> {
 
-    public URLReference deserialize(JSONObject jsonValue,
+    public URLReference deserialize(JSONObject jsonValue, URLReference target,
             ConnectorMap idMapper, ApplicationConnection connection) {
         URLReference reference = GWT.create(URLReference.class);
-        JSONArray jsonURL = (JSONArray) jsonValue.get("URL");
-        String URL = (String) JsonDecoder.decodeValue(jsonURL, idMapper,
-                connection);
-        reference.setURL(connection.translateVaadinUri(URL));
+        if (jsonValue.containsKey("URL")) {
+            JSONArray jsonURL = (JSONArray) jsonValue.get("URL");
+            String URL = (String) JsonDecoder.decodeValue(jsonURL, null,
+                    idMapper, connection);
+            reference.setURL(connection.translateVaadinUri(URL));
+        }
         return reference;
     }
 
index c57e7d8bc105b512be99a70daf558a34e1cc8178..97cb3114b9eaeef2ef7ab42f0a3b7e0ce3f76642 100644 (file)
@@ -803,14 +803,28 @@ public abstract class AbstractCommunicationManager implements Serializable {
         // client after component creation but before legacy UIDL
         // processing.
         JSONObject sharedStates = new JSONObject();
-        for (Connector connector : dirtyVisibleConnectors) {
+        for (ClientConnector connector : dirtyVisibleConnectors) {
             SharedState state = connector.getState();
             if (null != state) {
                 // encode and send shared state
                 try {
-                    // FIXME Use declared type
+                    Class<? extends SharedState> stateType = connector
+                            .getStateType();
+                    SharedState referenceState = null;
+                    if (repaintAll) {
+                        // Use an empty state object as reference for full
+                        // repaints
+                        try {
+                            referenceState = stateType.newInstance();
+                        } catch (Exception e) {
+                            logger.log(Level.WARNING,
+                                    "Error creating reference object for state of type "
+                                            + stateType.getName());
+                        }
+                    }
                     JSONArray stateJsonArray = JsonCodec.encode(state,
-                            state.getClass(), application);
+                            referenceState, stateType, application);
+
                     sharedStates
                             .put(connector.getConnectorId(), stateJsonArray);
                 } catch (JSONException e) {
@@ -900,9 +914,21 @@ public abstract class AbstractCommunicationManager implements Serializable {
                 invocationJson.put(invocation.getMethodName());
                 JSONArray paramJson = new JSONArray();
                 for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
+                    Class<?> parameterType = invocation.getParameterTypes()[i];
+                    Object referenceParameter = null;
+                    // TODO Use default values for RPC parameter types
+                    // if (!JsonCodec.isInternalType(parameterType)) {
+                    // try {
+                    // referenceParameter = parameterType.newInstance();
+                    // } catch (Exception e) {
+                    // logger.log(Level.WARNING,
+                    // "Error creating reference object for parameter of type "
+                    // + parameterType.getName());
+                    // }
+                    // }
                     paramJson.put(JsonCodec.encode(
-                            invocation.getParameters()[i],
-                            invocation.getParameterTypes()[i], application));
+                            invocation.getParameters()[i], referenceParameter,
+                            parameterType, application));
                 }
                 invocationJson.put(paramJson);
                 rpcCalls.put(invocationJson);
index 7a1f0fad685fd7a20e64e83f446b085f3b3b7ab9..7e74c26fb1ccd17fa24b45bd1ad1c7a124fb9362 100644 (file)
@@ -6,6 +6,7 @@ package com.vaadin.terminal.gwt.server;
 import java.util.List;
 
 import com.vaadin.terminal.gwt.client.Connector;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 
 /**
  * Interface implemented by all connectors that are capable of communicating
@@ -35,4 +36,12 @@ public interface ClientConnector extends Connector, RpcTarget {
      * @return true if the connector can receive messages, false otherwise
      */
     public boolean isConnectorEnabled();
+
+    /**
+     * Returns the type of the shared state for this connector
+     * 
+     * @return The type of the state. Must never return null.
+     */
+    public Class<? extends SharedState> getStateType();
+
 }
index d3fe5a890ba5f5e91318e7bc9b4466b107d5e19f..f6c96557ea213ac884dd78974f368d94a60ed279 100644 (file)
@@ -238,4 +238,8 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
         // TODO Use rpc for drag'n'drop
         return null;
     }
+
+    public Class<? extends SharedState> getStateType() {
+        return SharedState.class;
+    }
 }
index 375cce4161b9eb07987e869b8f60ed2833e7f43f..c88a1c828c7eb323ed3dee3a10b1642baa0c0a44 100644 (file)
@@ -419,11 +419,11 @@ public class JsonCodec implements Serializable {
     @Deprecated
     private static JSONArray encode(Object value, Application application)
             throws JSONException {
-        return encode(value, null, application);
+        return encode(value, null, null, application);
     }
 
-    public static JSONArray encode(Object value, Class<?> valueType,
-            Application application) throws JSONException {
+    public static JSONArray encode(Object value, Object referenceValue,
+            Type valueType, Application application) throws JSONException {
 
         if (null == value) {
             return encodeNull();
@@ -453,7 +453,8 @@ public class JsonCodec implements Serializable {
                         "Unable to serialize unsupported type: " + valueType);
             }
             Collection<?> collection = (Collection<?>) value;
-            JSONArray jsonArray = encodeCollection(collection, application);
+            JSONArray jsonArray = encodeCollection(valueType, collection,
+                    application);
 
             return combineTypeAndValue(internalTransportType, jsonArray);
         } else if (value instanceof Object[]) {
@@ -486,8 +487,9 @@ public class JsonCodec implements Serializable {
         } else {
             // Any object that we do not know how to encode we encode by looping
             // through fields
-            return combineTypeAndValue(getCustomTransportType(valueType),
-                    encodeObject(value, application));
+            return combineTypeAndValue(
+                    getCustomTransportType((Class<?>) valueType),
+                    encodeObject(value, referenceValue, application));
         }
     }
 
@@ -495,22 +497,40 @@ public class JsonCodec implements Serializable {
         return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL);
     }
 
-    private static Object encodeObject(Object value, Application application)
-            throws JSONException {
+    private static Object encodeObject(Object value, Object referenceValue,
+            Application application) throws JSONException {
         JSONObject jsonMap = new JSONObject();
 
         try {
             for (PropertyDescriptor pd : Introspector.getBeanInfo(
                     value.getClass()).getPropertyDescriptors()) {
-                Class<?> fieldType = pd.getPropertyType();
                 String fieldName = getTransportFieldName(pd);
                 if (fieldName == null) {
                     continue;
                 }
                 Method getterMethod = pd.getReadMethod();
+                // We can't use PropertyDescriptor.getPropertyType() as it does
+                // not support generics
+                Type fieldType = getterMethod.getGenericReturnType();
                 Object fieldValue = getterMethod.invoke(value, (Object[]) null);
-                jsonMap.put(fieldName,
-                        encode(fieldValue, fieldType, application));
+                boolean equals = false;
+                Object referenceFieldValue = null;
+                if (referenceValue != null) {
+                    referenceFieldValue = getterMethod.invoke(referenceValue,
+                            (Object[]) null);
+                    equals = equals(fieldValue, referenceFieldValue);
+                }
+                if (!equals) {
+                    jsonMap.put(
+                            fieldName,
+                            encode(fieldValue, referenceFieldValue, fieldType,
+                                    application));
+                    // } else {
+                    // System.out.println("Skipping field " + fieldName
+                    // + " of type " + fieldType.getName()
+                    // + " for object " + value.getClass().getName()
+                    // + " as " + fieldValue + "==" + referenceFieldValue);
+                }
             }
         } catch (Exception e) {
             // TODO: Should exceptions be handled in a different way?
@@ -519,24 +539,56 @@ public class JsonCodec implements Serializable {
         return jsonMap;
     }
 
+    /**
+     * Compares the value with the reference. If they match, returns true.
+     * 
+     * @param fieldValue
+     * @param referenceValue
+     * @return
+     */
+    private static boolean equals(Object fieldValue, Object referenceValue) {
+        if (fieldValue == null) {
+            return referenceValue == null;
+        }
+
+        if (fieldValue.equals(referenceValue)) {
+            return true;
+        }
+
+        return false;
+    }
+
     private static JSONArray encodeArrayContents(Object[] array,
             Application application) throws JSONException {
         JSONArray jsonArray = new JSONArray();
         for (Object o : array) {
-            jsonArray.put(encode(o, null, application));
+            jsonArray.put(encode(o, null, null, application));
         }
         return jsonArray;
     }
 
-    private static JSONArray encodeCollection(Collection collection,
-            Application application) throws JSONException {
+    private static JSONArray encodeCollection(Type targetType,
+            Collection collection, Application application)
+            throws JSONException {
         JSONArray jsonArray = new JSONArray();
         for (Object o : collection) {
-            jsonArray.put(encode(o, application));
+            jsonArray.put(encodeChild(targetType, 0, o, application));
         }
         return jsonArray;
     }
 
+    private static JSONArray encodeChild(Type targetType, int typeIndex,
+            Object o, Application application) throws JSONException {
+        if (targetType instanceof ParameterizedType) {
+            Type childType = ((ParameterizedType) targetType)
+                    .getActualTypeArguments()[typeIndex];
+            // Encode using the given type
+            return encode(o, null, childType, application);
+        } else {
+            return encode(o, application);
+        }
+    }
+
     private static JSONObject encodeMapContents(Map<Object, Object> map,
             Application application) throws JSONException {
         JSONObject jsonMap = new JSONObject();
@@ -551,7 +603,8 @@ public class JsonCodec implements Serializable {
                         "Only maps with String/Connector keys are currently supported (#8602)");
             }
 
-            jsonMap.put((String) mapKey, encode(mapValue, null, application));
+            jsonMap.put((String) mapKey,
+                    encode(mapValue, null, null, application));
         }
         return jsonMap;
     }
index d3ed9fe4841b246c147cd1aaa4a6879cf75a622f..3b6ab8cdba277502e78d8fbd5ae9db582e595490 100644 (file)
@@ -106,7 +106,8 @@ public class SerializerGenerator extends Generator {
         composer.addImport(JsonDecoder.class.getName());
         // composer.addImport(VaadinSerializer.class.getName());
 
-        composer.addImplementedInterface(JSONSerializer.class.getName());
+        composer.addImplementedInterface(JSONSerializer.class.getName() + "<"
+                + beanQualifiedSourceName + ">");
 
         SourceWriter sourceWriter = composer.createSourceWriter(context,
                 printWriter);
@@ -117,7 +118,7 @@ public class SerializerGenerator extends Generator {
         // public JSONValue serialize(Object value, ConnectorMap idMapper,
         // ApplicationConnection connection) {
         sourceWriter.println("public " + JSONObject.class.getName()
-                + " serialize(" + Object.class.getName() + " value, "
+                + " serialize(" + beanQualifiedSourceName + " value, "
                 + ConnectorMap.class.getName() + " idMapper, "
                 + ApplicationConnection.class.getName() + " connection) {");
         sourceWriter.indent();
@@ -152,13 +153,20 @@ public class SerializerGenerator extends Generator {
         // Deserializer
         sourceWriter.println("public " + beanQualifiedSourceName
                 + " deserialize(" + JSONObject.class.getName() + " jsonValue, "
+                + beanQualifiedSourceName + " target, "
                 + ConnectorMap.class.getName() + " idMapper, "
                 + ApplicationConnection.class.getName() + " connection) {");
         sourceWriter.indent();
 
-        // VButtonState state = GWT.create(VButtonState.class);
-        sourceWriter.println(beanQualifiedSourceName + " state = GWT.create("
-                + beanQualifiedSourceName + ".class);");
+        // if (target == null) {
+        sourceWriter.println("if (target == null) {");
+        sourceWriter.indent();
+        // target = GWT.create(VButtonState.class);
+        sourceWriter.println("target = GWT.create(" + beanQualifiedSourceName
+                + ".class);");
+        sourceWriter.outdent();
+        sourceWriter.println("}");
+
         for (JMethod method : getSetters(beanType)) {
             String setterName = method.getName();
             String fieldName = setterName.substring(3); // setZIndex() -> ZIndex
@@ -167,30 +175,59 @@ public class SerializerGenerator extends Generator {
             logger.log(Type.DEBUG, "* Processing field " + fieldName + " in "
                     + beanQualifiedSourceName + " (" + beanType.getName() + ")");
 
+            // if (jsonValue.containsKey("height")) {
+            sourceWriter.println("if (jsonValue.containsKey(\"" + fieldName
+                    + "\")) {");
+            sourceWriter.indent();
             String jsonFieldName = "json_" + fieldName;
             // JSONArray json_Height = (JSONArray) jsonValue.get("height");
             sourceWriter.println("JSONArray " + jsonFieldName
                     + " = (JSONArray) jsonValue.get(\"" + fieldName + "\");");
 
-            // state.setHeight((String)
-            // JsonDecoder.decodeValue(jsonFieldValue,idMapper, connection));
-
             String fieldType;
+            String getterName = "get" + fieldName;
             JPrimitiveType primitiveType = setterParameterType.isPrimitive();
             if (primitiveType != null) {
                 // This is a primitive type -> must used the boxed type
                 fieldType = primitiveType.getQualifiedBoxedSourceName();
+                if (primitiveType == JPrimitiveType.BOOLEAN) {
+                    getterName = "is" + fieldName;
+                }
             } else {
                 fieldType = setterParameterType.getQualifiedSourceName();
             }
 
-            sourceWriter.println("state." + setterName + "((" + fieldType
+            // String referenceValue;
+            sourceWriter.println(fieldType + " referenceValue;");
+            // if (target == null) {
+            sourceWriter.println("if (target == null) {");
+            sourceWriter.indent();
+            // referenceValue = null;
+            sourceWriter.println("referenceValue = null;");
+            // } else {
+            sourceWriter.println("} else {");
+            // referenceValue = target.getHeight();
+            sourceWriter.println("referenceValue = target." + getterName
+                    + "();");
+            // }
+            sourceWriter.outdent();
+            sourceWriter.println("}");
+
+            // target.setHeight((String)
+            // JsonDecoder.decodeValue(jsonFieldValue,referenceValue, idMapper,
+            // connection));
+            sourceWriter.println("target." + setterName + "((" + fieldType
                     + ") " + JsonDecoder.class.getName() + ".decodeValue("
-                    + jsonFieldName + ", idMapper, connection));");
+                    + jsonFieldName
+                    + ", referenceValue, idMapper, connection));");
+
+            // } ... end of if contains
+            sourceWriter.println("}");
+            sourceWriter.outdent();
         }
 
-        // return state;
-        sourceWriter.println("return state;");
+        // return target;
+        sourceWriter.println("return target;");
         sourceWriter.println("}");
         sourceWriter.outdent();
 
index 79a07ae00ec8fe26a37697a41269d95f87e00e08..554d7806f931c8f4f8a9f2001deb65e4acaeae45 100644 (file)
@@ -18,7 +18,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -820,20 +819,28 @@ public abstract class AbstractComponent implements Component, MethodEventSource
      * @return new shared state object
      */
     protected ComponentState createState() {
+        try {
+            return getStateType().newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(
+                    "Error creating state of type " + getStateType().getName()
+                            + " for " + getClass().getName(), e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see com.vaadin.terminal.gwt.server.ClientConnector#getStateType()
+     */
+    public Class<? extends ComponentState> getStateType() {
         try {
             Method m = getClass().getMethod("getState", (Class[]) null);
             Class<? extends ComponentState> type = (Class<? extends ComponentState>) m
                     .getReturnType();
-            return type.newInstance();
+            return type;
         } catch (Exception e) {
-            getLogger().log(
-                    Level.INFO,
-                    "Error determining state object class for "
-                            + getClass().getName());
+            throw new RuntimeException("Error finding state type for "
+                    + getClass().getName(), e);
         }
-
-        // Fall back to ComponentState if detection fails for some reason.
-        return new ComponentState();
     }
 
     /* Documentation copied from interface */
index 405ae8da93ca4fb79ee7d0d9b13db834fb507bd2..07dd5a0f69a76345a6b1789847cbc95acfd1875a 100644 (file)
@@ -30,7 +30,6 @@ import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.Vaadin6Component;
 import com.vaadin.terminal.WrappedRequest;
 import com.vaadin.terminal.WrappedRequest.BrowserDetails;
-import com.vaadin.terminal.gwt.client.ComponentState;
 import com.vaadin.terminal.gwt.client.MouseEventDetails;
 import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
 import com.vaadin.terminal.gwt.client.ui.root.RootServerRpc;
@@ -475,10 +474,10 @@ public abstract class Root extends AbstractComponentContainer implements
     }
 
     @Override
-    protected ComponentState createState() {
+    public Class<? extends RootState> getStateType() {
         // This is a workaround for a problem with creating the correct state
         // object during build
-        return new RootState();
+        return RootState.class;
     }
 
     /**