]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement @OnStateChange (#12958)
authorLeif Åstrand <leif@vaadin.com>
Thu, 27 Mar 2014 09:14:55 +0000 (11:14 +0200)
committerVaadin Code Review <review@vaadin.com>
Tue, 1 Apr 2014 08:15:56 +0000 (08:15 +0000)
Change-Id: I8ea2b781fab42458bf55a751c1229e391365e965

client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java
client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java
client-compiler/src/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java [new file with mode: 0644]
client-compiler/src/com/vaadin/server/widgetsetutils/metadata/StateInitVisitor.java
client-compiler/src/com/vaadin/server/widgetsetutils/metadata/TypeVisitor.java
client-compiler/src/com/vaadin/server/widgetsetutils/metadata/WidgetInitVisitor.java
client/src/com/vaadin/client/annotations/OnStateChange.java [new file with mode: 0644]
client/src/com/vaadin/client/metadata/OnStateChangeMethod.java [new file with mode: 0644]
client/src/com/vaadin/client/metadata/TypeDataStore.java
client/src/com/vaadin/client/ui/AbstractConnector.java
client/src/com/vaadin/client/ui/button/ButtonConnector.java

index edbf5e260c09b16d904513cb3e3c78f6ad17d915..9b00142534449c07191c0c8ab3c57327a11d706a 100644 (file)
@@ -46,8 +46,10 @@ import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 import com.vaadin.client.JsArrayObject;
 import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
 import com.vaadin.client.metadata.ConnectorBundleLoader;
 import com.vaadin.client.metadata.InvokationHandler;
+import com.vaadin.client.metadata.OnStateChangeMethod;
 import com.vaadin.client.metadata.ProxyHandler;
 import com.vaadin.client.metadata.TypeData;
 import com.vaadin.client.metadata.TypeDataStore;
@@ -56,6 +58,7 @@ import com.vaadin.server.widgetsetutils.metadata.ClientRpcVisitor;
 import com.vaadin.server.widgetsetutils.metadata.ConnectorBundle;
 import com.vaadin.server.widgetsetutils.metadata.ConnectorInitVisitor;
 import com.vaadin.server.widgetsetutils.metadata.GeneratedSerializer;
+import com.vaadin.server.widgetsetutils.metadata.OnStateChangeVisitor;
 import com.vaadin.server.widgetsetutils.metadata.Property;
 import com.vaadin.server.widgetsetutils.metadata.ServerRpcVisitor;
 import com.vaadin.server.widgetsetutils.metadata.StateInitVisitor;
@@ -463,6 +466,92 @@ public class ConnectorBundleLoaderFactory extends Generator {
         writePropertyTypes(logger, w, bundle);
         writeSerializers(logger, w, bundle);
         writeDelegateToWidget(logger, w, bundle);
+        writeOnStateChangeHandlers(logger, w, bundle);
+    }
+
+    private void writeOnStateChangeHandlers(TreeLogger logger,
+            SplittingSourceWriter w, ConnectorBundle bundle)
+            throws UnableToCompleteException {
+        Map<JClassType, Set<JMethod>> needsOnStateChangeHandler = bundle
+                .getNeedsOnStateChangeHandler();
+        for (Entry<JClassType, Set<JMethod>> entry : needsOnStateChangeHandler
+                .entrySet()) {
+            JClassType connector = entry.getKey();
+
+            TreeLogger typeLogger = logger.branch(
+                    Type.DEBUG,
+                    "Generating @OnStateChange support for "
+                            + connector.getName());
+
+            // Build map to speed up error checking
+            HashMap<String, Property> stateProperties = new HashMap<String, Property>();
+            JClassType stateType = ConnectorBundle
+                    .findInheritedMethod(connector, "getState").getReturnType()
+                    .isClassOrInterface();
+            for (Property property : bundle.getProperties(stateType)) {
+                stateProperties.put(property.getName(), property);
+            }
+
+            for (JMethod method : entry.getValue()) {
+                TreeLogger methodLogger = typeLogger.branch(Type.DEBUG,
+                        "Processing method " + method.getName());
+
+                if (method.isPublic() || method.isProtected()) {
+                    methodLogger
+                            .log(Type.ERROR,
+                                    "@OnStateChange is only supported for methods with private or default visibility.");
+                    throw new UnableToCompleteException();
+                }
+
+                OnStateChange onStateChange = method
+                        .getAnnotation(OnStateChange.class);
+
+                String[] properties = onStateChange.value();
+
+                if (properties.length == 0) {
+                    methodLogger.log(Type.ERROR,
+                            "There are no properties to listen to");
+                    throw new UnableToCompleteException();
+                }
+
+                // Verify that all properties do exist
+                for (String propertyName : properties) {
+                    if (!stateProperties.containsKey(propertyName)) {
+                        methodLogger.log(Type.ERROR,
+                                "State class has no property named "
+                                        + propertyName);
+                        throw new UnableToCompleteException();
+                    }
+                }
+
+                if (method.getParameters().length != 0) {
+                    methodLogger.log(Type.ERROR,
+                            "Method should accept zero parameters");
+                    throw new UnableToCompleteException();
+                }
+
+                // new OnStateChangeMethod(Class declaringClass, String
+                // methodName, String[], properties)
+                w.print("store.addOnStateChangeMethod(%s, new %s(",
+                        getClassLiteralString(connector),
+                        OnStateChangeMethod.class.getName());
+                if (!connector.equals(method.getEnclosingType())) {
+                    w.print("%s, ",
+                            getClassLiteralString(method.getEnclosingType()));
+                }
+                w.print("\"%s\", ", method.getName());
+
+                w.print("new String[] {");
+                for (String propertyName : properties) {
+                    w.print("\"%s\", ", propertyName);
+                }
+                w.print("}");
+
+                w.println("));");
+
+                w.splitIfNeeded();
+            }
+        }
     }
 
     private void writeSuperClasses(SplittingSourceWriter w,
@@ -1109,7 +1198,7 @@ public class ConnectorBundleLoaderFactory extends Generator {
         List<TypeVisitor> visitors = Arrays.<TypeVisitor> asList(
                 new ConnectorInitVisitor(), new StateInitVisitor(),
                 new WidgetInitVisitor(), new ClientRpcVisitor(),
-                new ServerRpcVisitor());
+                new ServerRpcVisitor(), new OnStateChangeVisitor());
         for (TypeVisitor typeVisitor : visitors) {
             typeVisitor.init(oracle);
         }
index 0064a24aef2eeadbb97dc6272b01eddbe19ba0c9..f762a484b70b739a5f8d690b9a59c25d3ce02d0e 100644 (file)
@@ -69,6 +69,7 @@ public class ConnectorBundle {
     private final Map<JClassType, Set<JMethod>> needsInvoker = new HashMap<JClassType, Set<JMethod>>();
     private final Map<JClassType, Set<JMethod>> needsParamTypes = new HashMap<JClassType, Set<JMethod>>();
     private final Map<JClassType, Set<JMethod>> needsDelayedInfo = new HashMap<JClassType, Set<JMethod>>();
+    private final Map<JClassType, Set<JMethod>> needsOnStateChange = new HashMap<JClassType, Set<JMethod>>();
 
     private final Set<Property> needsProperty = new HashSet<Property>();
     private final Set<Property> needsDelegateToWidget = new HashSet<Property>();
@@ -199,7 +200,7 @@ public class ConnectorBundle {
             JType type = iterator.next();
             iterator.remove();
 
-            if (hasSserializeSupport(type)) {
+            if (hasSerializeSupport(type)) {
                 continue;
             }
 
@@ -516,7 +517,7 @@ public class ConnectorBundle {
     }
 
     public void setNeedsSerialize(JType type) {
-        if (!hasSserializeSupport(type)) {
+        if (!hasSerializeSupport(type)) {
             needsSerializeSupport.add(type);
         }
     }
@@ -557,12 +558,12 @@ public class ConnectorBundle {
         return false;
     }
 
-    private boolean hasSserializeSupport(JType type) {
+    private boolean hasSerializeSupport(JType type) {
         if (hasSerializeSupport.contains(type)) {
             return true;
         } else {
             return previousBundle != null
-                    && previousBundle.hasSserializeSupport(type);
+                    && previousBundle.hasSerializeSupport(type);
         }
     }
 
@@ -585,4 +586,45 @@ public class ConnectorBundle {
         return Collections.unmodifiableSet(needsDelegateToWidget);
     }
 
+    public void setNeedsOnStateChangeHandler(JClassType type, JMethod method) {
+        if (!isNeedsOnStateChangeHandler(type, method)) {
+            addMapping(needsOnStateChange, type, method);
+        }
+    }
+
+    private boolean isNeedsOnStateChangeHandler(JClassType type, JMethod method) {
+        if (hasMapping(needsOnStateChange, type, method)) {
+            return true;
+        } else {
+            return previousBundle != null
+                    && previousBundle.isNeedsOnStateChangeHandler(type, method);
+        }
+    }
+
+    public Map<JClassType, Set<JMethod>> getNeedsOnStateChangeHandler() {
+        return Collections.unmodifiableMap(needsOnStateChange);
+    }
+
+    public static JMethod findInheritedMethod(JClassType type,
+            String methodName, JType... params) {
+
+        JClassType currentType = type;
+        while (currentType != null) {
+            JMethod method = currentType.findMethod(methodName, params);
+            if (method != null) {
+                return method;
+            }
+            currentType = currentType.getSuperclass();
+        }
+
+        JClassType[] interfaces = type.getImplementedInterfaces();
+        for (JClassType iface : interfaces) {
+            JMethod method = iface.findMethod(methodName, params);
+            if (method != null) {
+                return method;
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/OnStateChangeVisitor.java
new file mode 100644 (file)
index 0000000..ea3e639
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ * 
+ * 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.vaadin.server.widgetsetutils.metadata;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.shared.ui.Connect;
+
+/**
+ * Visits Connector classes and check for methods with @OnStateChange
+ * annotations.
+ * 
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class OnStateChangeVisitor extends TypeVisitor {
+
+    @Override
+    public void visitConnector(TreeLogger logger, JClassType type,
+            ConnectorBundle bundle) throws UnableToCompleteException {
+        Connect connectAnnotation = type.getAnnotation(Connect.class);
+        if (connectAnnotation != null) {
+            // Find all the annotated methods in all the superclasses
+            JClassType connector = type;
+            while (connector != null) {
+                for (JMethod method : connector.getMethods()) {
+                    if (method.getAnnotation(OnStateChange.class) != null) {
+                        bundle.setNeedsInvoker(connector, method);
+                        bundle.setNeedsOnStateChangeHandler(type, method);
+                    }
+                }
+
+                connector = connector.getSuperclass();
+            }
+        }
+    }
+
+}
index 56a404fbb53891cc4ee2fd019d45a2e956876b15..f58d1c5d40f728234dbdbb879a27c41c0c26601c 100644 (file)
@@ -24,7 +24,8 @@ public class StateInitVisitor extends TypeVisitor {
     @Override
     public void visitConnector(TreeLogger logger, JClassType type,
             ConnectorBundle bundle) {
-        JMethod getState = findInheritedMethod(type, "getState");
+        JMethod getState = ConnectorBundle
+                .findInheritedMethod(type, "getState");
         bundle.setNeedsReturnType(type, getState);
 
         bundle.setNeedsSerialize(getState.getReturnType());
index 0af3c6976d6ac09a48d0c958b04ed753c0422bb5..aae610cdcde1a62da689d9a134c27b3653839bdd 100644 (file)
@@ -18,8 +18,6 @@ package com.vaadin.server.widgetsetutils.metadata;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 
@@ -43,27 +41,4 @@ public abstract class TypeVisitor {
         // Default does nothing
     }
 
-    protected JMethod findInheritedMethod(JClassType type, String methodName,
-            JType... params) {
-
-        JClassType currentType = type;
-        while (currentType != null) {
-            JMethod method = currentType.findMethod(methodName, params);
-            if (method != null) {
-                return method;
-            }
-            currentType = currentType.getSuperclass();
-        }
-
-        JClassType[] interfaces = type.getImplementedInterfaces();
-        for (JClassType iface : interfaces) {
-            JMethod method = iface.findMethod(methodName, params);
-            if (method != null) {
-                return method;
-            }
-        }
-
-        return null;
-    }
-
 }
index 4de9d2ae9976c2d2fff4b512ce0bb80c0546ea34..bac7f5a0f73062b941481adce5df496f4dec3af1 100644 (file)
@@ -32,10 +32,11 @@ public class WidgetInitVisitor extends TypeVisitor {
             ConnectorBundle bundle) throws UnableToCompleteException {
         if (ConnectorBundle.isConnectedComponentConnector(type)) {
             // The class in which createWidget is implemented
-            JClassType createWidgetClass = findInheritedMethod(type,
-                    "createWidget").getEnclosingType();
+            JClassType createWidgetClass = ConnectorBundle.findInheritedMethod(
+                    type, "createWidget").getEnclosingType();
 
-            JMethod getWidget = findInheritedMethod(type, "getWidget");
+            JMethod getWidget = ConnectorBundle.findInheritedMethod(type,
+                    "getWidget");
             JClassType widgetType = getWidget.getReturnType().isClass();
 
             // Needs GWT constructor if createWidget is not overridden
@@ -48,7 +49,8 @@ public class WidgetInitVisitor extends TypeVisitor {
             }
 
             // Check state properties for @DelegateToWidget
-            JMethod getState = findInheritedMethod(type, "getState");
+            JMethod getState = ConnectorBundle.findInheritedMethod(type,
+                    "getState");
             JClassType stateType = getState.getReturnType().isClass();
 
             Collection<Property> properties = bundle.getProperties(stateType);
@@ -63,8 +65,9 @@ public class WidgetInitVisitor extends TypeVisitor {
                     String methodName = DelegateToWidget.Helper
                             .getDelegateTarget(property.getName(),
                                     delegateToWidget.value());
-                    JMethod delegatedSetter = findInheritedMethod(widgetType,
-                            methodName, property.getPropertyType());
+                    JMethod delegatedSetter = ConnectorBundle
+                            .findInheritedMethod(widgetType, methodName,
+                                    property.getPropertyType());
                     if (delegatedSetter == null) {
                         logger.log(
                                 Type.ERROR,
diff --git a/client/src/com/vaadin/client/annotations/OnStateChange.java b/client/src/com/vaadin/client/annotations/OnStateChange.java
new file mode 100644 (file)
index 0000000..80c1095
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ * 
+ * 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.vaadin.client.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.client.communication.StateChangeEvent;
+
+/**
+ * Marks a method in Connector classes that should be used to handle changes to
+ * specific properties in the connector's shared state.
+ * <p>
+ * The annotated method will by called whenever at least one of the named state
+ * properties have changed. If multiple listened properties are changed by the
+ * same {@link StateChangeEvent}, the method will only be called once.
+ * <p>
+ * If there is no state variable with the provided name, the widgetset
+ * compilation will fail.
+ * 
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface OnStateChange {
+    /**
+     * Defines a list of property names to listen for.
+     * 
+     * @return an array of property names, should contain at least one item
+     */
+    public String[] value();
+}
diff --git a/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java b/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java
new file mode 100644 (file)
index 0000000..2ba06fd
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ * 
+ * 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.vaadin.client.metadata;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.communication.StateChangeEvent;
+
+/**
+ * Encapsulates the data that the widgetset compiler generates for supporting a
+ * connector method annotated with {@link OnStateChange}
+ * 
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class OnStateChangeMethod {
+
+    private final String methodName;
+    private final List<String> properties;
+    private final Class<?> declaringClass;
+
+    /**
+     * Creates a new instance based on a method name, a list of parameters names
+     * and a list of properties to listen for.
+     * 
+     * @param methodName
+     *            the name of the method to call
+     * @param properties
+     *            an array of state property names to listen to
+     */
+    public OnStateChangeMethod(String methodName, String[] properties) {
+        this(null, methodName, properties);
+    }
+
+    /**
+     * Creates a new instance based on declaring class, a method name, a list of
+     * parameters names and a list of properties to listen for.
+     * <p>
+     * If the declaring class is <code>null</code>, the method is found based on
+     * the type of the connector that fired the state change event.
+     * 
+     * @param declaringClass
+     *            the class in which the target method is declared, or
+     *            <code>null</code> to use the class of the connector firing the
+     *            event
+     * @param methodName
+     *            the name of the method to call
+     * @param properties
+     *            an array of state property names to listen to
+     */
+    public OnStateChangeMethod(Class<?> declaringClass, String methodName,
+            String[] properties) {
+
+        this.methodName = methodName;
+
+        this.properties = Collections.unmodifiableList(Arrays
+                .asList(properties));
+
+        this.declaringClass = declaringClass;
+    }
+
+    /**
+     * Invokes the listener method for a state change.
+     * 
+     * @param stateChangeEvent
+     *            the state change event
+     */
+    public void invoke(StateChangeEvent stateChangeEvent) {
+        ServerConnector connector = (ServerConnector) stateChangeEvent
+                .getSource();
+
+        Class<?> declaringClass = this.declaringClass;
+        if (declaringClass == null) {
+            declaringClass = connector.getClass();
+        }
+        Type declaringType = TypeDataStore.getType(declaringClass);
+
+        try {
+            declaringType.getMethod(methodName).invoke(connector);
+        } catch (NoDataException e) {
+            throw new RuntimeException("Couldn't invoke @OnStateChange method "
+                    + declaringType.getSignature() + "." + methodName, e);
+        }
+    }
+
+    /**
+     * Gets the list of state property names to listen for.
+     * 
+     * @return the list of state property names to listen for
+     */
+    public List<String> getProperties() {
+        return properties;
+    }
+}
index d5fbc94823aa9ce78797bc7cb5c92a7fd722ab2f..bc6610a6ff2d31a2332059621b00c2c357bff96b 100644 (file)
@@ -23,6 +23,7 @@ import com.google.gwt.core.client.JsArrayString;
 import com.vaadin.client.FastStringMap;
 import com.vaadin.client.FastStringSet;
 import com.vaadin.client.JsArrayObject;
+import com.vaadin.client.annotations.OnStateChange;
 import com.vaadin.client.communication.JSONSerializer;
 
 public class TypeDataStore {
@@ -37,6 +38,12 @@ public class TypeDataStore {
     private final FastStringMap<JsArrayString> delegateToWidgetProperties = FastStringMap
             .create();
 
+    /**
+     * Maps connector class -> state property name -> hander method data
+     */
+    private final FastStringMap<FastStringMap<JsArrayObject<OnStateChangeMethod>>> onStateChangeMethods = FastStringMap
+            .create();
+
     private final FastStringSet delayedMethods = FastStringSet.create();
     private final FastStringSet lastOnlyMethods = FastStringSet.create();
 
@@ -368,4 +375,47 @@ public class TypeDataStore {
         return typeData[beanName] !== undefined ;
     }-*/;
 
+    /**
+     * Gets data for all methods annotated with {@link OnStateChange} in the
+     * given connector type.
+     * 
+     * @since 7.2
+     * @param type
+     *            the connector type
+     * @return a map of state property names to handler method data
+     */
+    public static FastStringMap<JsArrayObject<OnStateChangeMethod>> getOnStateChangeMethods(
+            Class<?> type) {
+        return get().onStateChangeMethods.get(getType(type).getSignature());
+    }
+
+    /**
+     * Adds data about a method annotated with {@link OnStateChange} for the
+     * given connector type.
+     * 
+     * @since 7.2
+     * @param clazz
+     *            the connector type
+     * @param method
+     *            the state change method data
+     */
+    public void addOnStateChangeMethod(Class<?> clazz,
+            OnStateChangeMethod method) {
+        FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = getOnStateChangeMethods(clazz);
+        if (handlers == null) {
+            handlers = FastStringMap.create();
+            onStateChangeMethods.put(getType(clazz).getSignature(), handlers);
+        }
+
+        for (String property : method.getProperties()) {
+            JsArrayObject<OnStateChangeMethod> propertyHandlers = handlers
+                    .get(property);
+            if (propertyHandlers == null) {
+                propertyHandlers = JsArrayObject.createArray().cast();
+                handlers.put(property, propertyHandlers);
+            }
+
+            propertyHandlers.add(method);
+        }
+    }
 }
index 6855c5cd2de6eae5129d0f33cf455cd78cff3a7a..bd499ac4bc583d3709fac422d095b707761fe986 100644 (file)
@@ -18,6 +18,7 @@ package com.vaadin.client.ui;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -27,6 +28,7 @@ import com.google.gwt.event.shared.HandlerManager;
 import com.google.web.bindery.event.shared.HandlerRegistration;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.FastStringMap;
+import com.vaadin.client.JsArrayObject;
 import com.vaadin.client.Profiler;
 import com.vaadin.client.ServerConnector;
 import com.vaadin.client.Util;
@@ -35,8 +37,10 @@ import com.vaadin.client.communication.RpcProxy;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.OnStateChangeMethod;
 import com.vaadin.client.metadata.Type;
 import com.vaadin.client.metadata.TypeData;
+import com.vaadin.client.metadata.TypeDataStore;
 import com.vaadin.shared.communication.ClientRpc;
 import com.vaadin.shared.communication.ServerRpc;
 import com.vaadin.shared.communication.SharedState;
@@ -290,6 +294,37 @@ public abstract class AbstractConnector implements ServerConnector,
         }
 
         updateEnabledState(isEnabled());
+
+        FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = TypeDataStore
+                .getOnStateChangeMethods(getClass());
+        if (handlers != null) {
+            Profiler.enter("AbstractConnector.onStateChanged @OnStateChange");
+
+            HashSet<OnStateChangeMethod> invokedMethods = new HashSet<OnStateChangeMethod>();
+
+            JsArrayString propertyNames = handlers.getKeys();
+            for (int i = 0; i < propertyNames.length(); i++) {
+                String propertyName = propertyNames.get(i);
+
+                if (stateChangeEvent.hasPropertyChanged(propertyName)) {
+                    JsArrayObject<OnStateChangeMethod> propertyMethods = handlers
+                            .get(propertyName);
+
+                    for (int j = 0; j < propertyMethods.size(); j++) {
+                        OnStateChangeMethod method = propertyMethods.get(j);
+
+                        if (invokedMethods.add(method)) {
+
+                            method.invoke(stateChangeEvent);
+
+                        }
+                    }
+                }
+            }
+
+            Profiler.leave("AbstractConnector.onStateChanged @OnStateChange");
+        }
+
         Profiler.leave("AbstractConnector.onStateChanged");
     }
 
index 94c2841c3c396fa42831668dc8f2f8d0e08e84cc..32a457c1f1f223b99e5f8f95a5c4331877900129 100644 (file)
@@ -26,8 +26,8 @@ import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
 import com.vaadin.client.EventHelper;
 import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.annotations.OnStateChange;
 import com.vaadin.client.communication.StateChangeEvent;
-import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.Icon;
 import com.vaadin.client.ui.VButton;
@@ -56,44 +56,38 @@ public class ButtonConnector extends AbstractComponentConnector implements
         super.init();
         getWidget().addClickHandler(this);
         getWidget().client = getConnection();
-        addStateChangeHandler("errorMessage", new StateChangeHandler() {
-            @Override
-            public void onStateChanged(StateChangeEvent stateChangeEvent) {
-                if (null != getState().errorMessage) {
-                    if (getWidget().errorIndicatorElement == null) {
-                        getWidget().errorIndicatorElement = DOM.createSpan();
-                        getWidget().errorIndicatorElement
-                                .setClassName("v-errorindicator");
-                    }
-                    getWidget().wrapper.insertBefore(
-                            getWidget().errorIndicatorElement,
-                            getWidget().captionElement);
-
-                } else if (getWidget().errorIndicatorElement != null) {
-                    getWidget().wrapper
-                            .removeChild(getWidget().errorIndicatorElement);
-                    getWidget().errorIndicatorElement = null;
-                }
-            }
-        });
-
-        addStateChangeHandler("resources", new StateChangeHandler() {
-            @Override
-            public void onStateChanged(StateChangeEvent stateChangeEvent) {
-                if (getWidget().icon != null) {
-                    getWidget().wrapper.removeChild(getWidget().icon
-                            .getElement());
-                    getWidget().icon = null;
-                }
-                Icon icon = getIcon();
-                if (icon != null) {
-                    getWidget().icon = icon;
-                    icon.setAlternateText(getState().iconAltText);
-                    getWidget().wrapper.insertBefore(icon.getElement(),
-                            getWidget().captionElement);
-                }
+    }
+
+    @OnStateChange("errorMessage")
+    void setErrorMessage() {
+        if (null != getState().errorMessage) {
+            if (getWidget().errorIndicatorElement == null) {
+                getWidget().errorIndicatorElement = DOM.createSpan();
+                getWidget().errorIndicatorElement
+                        .setClassName("v-errorindicator");
             }
-        });
+            getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement,
+                    getWidget().captionElement);
+
+        } else if (getWidget().errorIndicatorElement != null) {
+            getWidget().wrapper.removeChild(getWidget().errorIndicatorElement);
+            getWidget().errorIndicatorElement = null;
+        }
+    }
+
+    @OnStateChange("resources")
+    void onResourceChange() {
+        if (getWidget().icon != null) {
+            getWidget().wrapper.removeChild(getWidget().icon.getElement());
+            getWidget().icon = null;
+        }
+        Icon icon = getIcon();
+        if (icon != null) {
+            getWidget().icon = icon;
+            icon.setAlternateText(getState().iconAltText);
+            getWidget().wrapper.insertBefore(icon.getElement(),
+                    getWidget().captionElement);
+        }
     }
 
     @Override
@@ -103,22 +97,27 @@ public class ButtonConnector extends AbstractComponentConnector implements
                 focusHandlerRegistration);
         blurHandlerRegistration = EventHelper.updateBlurHandler(this,
                 blurHandlerRegistration);
+    }
 
-        if (stateChangeEvent.hasPropertyChanged("caption")
-                || stateChangeEvent.hasPropertyChanged("htmlContentAllowed")) {
-            // Set text
-            if (getState().htmlContentAllowed) {
-                getWidget().setHtml(getState().caption);
-            } else {
-                getWidget().setText(getState().caption);
-            }
+    @OnStateChange({ "caption", "htmlContentAllowed" })
+    void setCaption() {
+        String caption = getState().caption;
+        if (getState().htmlContentAllowed) {
+            getWidget().setHtml(caption);
+        } else {
+            getWidget().setText(caption);
         }
+    }
 
-        if (getWidget().icon != null
-                && stateChangeEvent.hasPropertyChanged("iconAltText")) {
+    @OnStateChange("iconAltText")
+    void setIconAltText() {
+        if (getWidget().icon != null) {
             getWidget().icon.setAlternateText(getState().iconAltText);
         }
+    }
 
+    @OnStateChange("clickShortcutKeyCode")
+    void setClickShortcut() {
         getWidget().clickShortcut = getState().clickShortcutKeyCode;
     }