]> source.dussan.org Git - vaadin-framework.git/commitdiff
#8500 Initial implementation for sending component hierarchy
authorArtur Signell <artur@vaadin.com>
Thu, 8 Mar 2012 15:56:20 +0000 (17:56 +0200)
committerArtur Signell <artur@vaadin.com>
Tue, 13 Mar 2012 16:10:36 +0000 (18:10 +0200)
automatically to the client and calling a listener method (for the
parent) when its child hierarchy has been updated.

Minor cleanup of JSON handling at the same time.

src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/client/ComponentConnector.java
src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java
src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/Util.java
src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java
src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/ui/Component.java

index 35fc0a3058505b744aba44aa39c63431492d36c2..8ac2f1f7b11192d80310a6d2d817ef95f83cb088 100644 (file)
@@ -5,10 +5,13 @@
 package com.vaadin.terminal.gwt.client;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -996,121 +999,30 @@ public class ApplicationConnection {
                     redirectTimer.schedule(1000 * sessionExpirationInterval);
                 }
 
-                // three phases/loops:
-                // - changes: create paintables (if necessary)
-                // - state: set shared states
-                // - changes: call updateFromUIDL() for each paintable
-
-                // Process changes
-                JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
-
-                ArrayList<ComponentConnector> updatedComponentConnectors = new ArrayList<ComponentConnector>();
                 componentCaptionSizeChanges.clear();
 
                 Duration updateDuration = new Duration();
 
-                int length = changes.length();
-
-                VConsole.log(" * Creating connectors (if needed)");
-                // create paintables if necessary
-                for (int i = 0; i < length; i++) {
-                    try {
-                        final UIDL change = changes.get(i).cast();
-                        final UIDL uidl = change.getChildUIDL(0);
-                        Connector paintable = connectorMap.getConnector(uidl
-                                .getId());
-                        if (null == paintable
-                                && !uidl.getTag().equals(
-                                        configuration.getEncodedWindowTag())) {
-                            // create, initialize and register the paintable
-                            getConnector(uidl.getId(), uidl.getTag());
-                        }
-                    } catch (final Throwable e) {
-                        VConsole.error(e);
-                    }
-                }
-
-                VConsole.log(" * Updating connector states");
-                // set states for all paintables mentioned in "state"
-                ValueMap states = json.getValueMap("state");
-                JsArrayString keyArray = states.getKeyArray();
-                for (int i = 0; i < keyArray.length(); i++) {
-                    try {
-                        String connectorId = keyArray.get(i);
-                        Connector paintable = connectorMap
-                                .getConnector(connectorId);
-                        if (null != paintable) {
-
-                            JSONArray stateDataAndType = new JSONArray(
-                                    states.getJavaScriptObject(connectorId));
-
-                            Object state = JsonDecoder.convertValue(
-                                    stateDataAndType, connectorMap);
-
-                            paintable.setState((SharedState) state);
-                        }
-                    } catch (final Throwable e) {
-                        VConsole.error(e);
-                    }
-                }
-
-                VConsole.log(" * Passing UIDL to Vaadin 6 style connectors");
-                // update paintables
-                for (int i = 0; i < length; i++) {
-                    try {
-                        final UIDL change = changes.get(i).cast();
-                        final UIDL uidl = change.getChildUIDL(0);
-                        String connectorId = uidl.getId();
-
-                        if (!connectorMap.hasConnector(connectorId)
-                                && uidl.getTag().equals(
-                                        configuration.getEncodedWindowTag())) {
-                            // First RootConnector update. Up until this
-                            // point the connectorId for RootConnector has
-                            // not been known
-                            connectorMap.registerConnector(connectorId, view);
-                            view.doInit(connectorId, ApplicationConnection.this);
-                        }
+                // Ensure that all connectors that we are about to update exist
+                createConnectorsIfNeeded(json);
 
-                        final ComponentConnector paintable = (ComponentConnector) connectorMap
-                                .getConnector(connectorId);
-                        if (paintable != null) {
-                            paintable.updateFromUIDL(uidl,
-                                    ApplicationConnection.this);
-                            updatedComponentConnectors.add(paintable);
-                        } else {
-                            VConsole.error("Received update for "
-                                    + uidl.getTag()
-                                    + ", but there is no such paintable ("
-                                    + connectorId + ") rendered.");
+                // Update states, do not fire events
+                updateConnectorState(json);
 
-                        }
+                // Update hierarchy, do not fire events
+                Collection<ConnectorHierarchyChangedEvent> pendingHierarchyChangeEvents = updateConnectorHierarchy(json);
 
-                    } catch (final Throwable e) {
-                        VConsole.error(e);
-                    }
-                }
+                // Fire state change events (TODO)
+                VConsole.log(" * Sending state change events");
 
-                if (json.containsKey("rpc")) {
-                    VConsole.log(" * Performing server to client RPC calls");
+                // Fire hierarchy change events
+                sendHierarchyChangeEvents(pendingHierarchyChangeEvents);
 
-                    JSONArray rpcCalls = new JSONArray(
-                            json.getJavaScriptObject("rpc"));
+                // Update of legacy (UIDL) style connectors
+                updateVaadin6StyleConnectors(json);
 
-                    int rpcLength = rpcCalls.size();
-                    for (int i = 0; i < rpcLength; i++) {
-                        try {
-                            JSONArray rpcCall = (JSONArray) rpcCalls.get(i);
-                            MethodInvocation invocation = parseMethodInvocation(rpcCall);
-                            VConsole.log("Server to client RPC call: "
-                                    + invocation);
-                            rpcManager.applyInvocation(invocation,
-                                    getConnectorMap());
-                        } catch (final Throwable e) {
-                            VConsole.error(e);
-                        }
-                    }
-                }
+                // Handle any RPC invocations done on the server side
+                handleRpcInvocations(json);
 
                 if (json.containsKey("dd")) {
                     // response contains data for drag and drop service
@@ -1188,6 +1100,211 @@ public class ApplicationConnection {
 
             }
 
+            private void createConnectorsIfNeeded(ValueMap json) {
+                VConsole.log(" * Creating connectors (if needed)");
+
+                JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
+                // FIXME: This should be based on shared state, not the old
+                // "changes"
+                int length = changes.length();
+                for (int i = 0; i < length; i++) {
+                    try {
+                        final UIDL change = changes.get(i).cast();
+                        final UIDL uidl = change.getChildUIDL(0);
+                        String connectorId = uidl.getId();
+                        Connector connector = connectorMap
+                                .getConnector(connectorId);
+                        if (connector != null) {
+                            continue;
+                        }
+
+                        // Connector does not exist so we must create it
+                        if (!uidl.getTag().equals(
+                                configuration.getEncodedWindowTag())) {
+                            // create, initialize and register the paintable
+                            getConnector(uidl.getId(), uidl.getTag());
+                        } else {
+                            // First RootConnector update. Before this the
+                            // RootConnector has been created but not
+                            // initialized as the connector id has not been
+                            // known
+                            connectorMap.registerConnector(connectorId, view);
+                            view.doInit(connectorId, ApplicationConnection.this);
+                        }
+                    } catch (final Throwable e) {
+                        VConsole.error(e);
+                    }
+                }
+            }
+
+            private void updateVaadin6StyleConnectors(ValueMap json) {
+                JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
+                int length = changes.length();
+
+                VConsole.log(" * Passing UIDL to Vaadin 6 style connectors");
+                // update paintables
+                for (int i = 0; i < length; i++) {
+                    try {
+                        final UIDL change = changes.get(i).cast();
+                        final UIDL uidl = change.getChildUIDL(0);
+                        String connectorId = uidl.getId();
+
+                        final ComponentConnector paintable = (ComponentConnector) connectorMap
+                                .getConnector(connectorId);
+                        if (paintable != null) {
+                            paintable.updateFromUIDL(uidl,
+                                    ApplicationConnection.this);
+                        } else {
+                            VConsole.error("Received update for "
+                                    + uidl.getTag()
+                                    + ", but there is no such paintable ("
+                                    + connectorId + ") rendered.");
+
+                        }
+
+                    } catch (final Throwable e) {
+                        VConsole.error(e);
+                    }
+                }
+            }
+
+            private void sendHierarchyChangeEvents(
+                    Collection<ConnectorHierarchyChangedEvent> pendingHierarchyChangeEvents) {
+                if (pendingHierarchyChangeEvents.isEmpty()) {
+                    return;
+                }
+
+                VConsole.log(" * Sending hierarchy change events");
+                for (ConnectorHierarchyChangedEvent event : pendingHierarchyChangeEvents) {
+                    event.getParent().connectorHierarchyChanged(event);
+                }
+
+            }
+
+            private void updateConnectorState(ValueMap json) {
+                VConsole.log(" * Updating connector states");
+                // set states for all paintables mentioned in "state"
+                ValueMap states = json.getValueMap("state");
+                JsArrayString keyArray = states.getKeyArray();
+                for (int i = 0; i < keyArray.length(); i++) {
+                    try {
+                        String connectorId = keyArray.get(i);
+                        Connector paintable = connectorMap
+                                .getConnector(connectorId);
+                        if (null != paintable) {
+
+                            JSONArray stateDataAndType = new JSONArray(
+                                    states.getJavaScriptObject(connectorId));
+
+                            Object state = JsonDecoder.convertValue(
+                                    stateDataAndType, connectorMap);
+
+                            paintable.setState((SharedState) state);
+                        }
+                    } catch (final Throwable e) {
+                        VConsole.error(e);
+                    }
+                }
+            }
+
+            /**
+             * Updates the connector hierarchy and returns a list of events that
+             * should be fired after update of the hierarchy and the state is
+             * done.
+             * 
+             * @param json
+             *            The JSON containing the hierarchy information
+             * @return A collection of events that should be fired when update
+             *         of hierarchy and state is complete
+             */
+            private Collection<ConnectorHierarchyChangedEvent> updateConnectorHierarchy(
+                    ValueMap json) {
+                List<ConnectorHierarchyChangedEvent> events = new LinkedList<ConnectorHierarchyChangedEvent>();
+
+                VConsole.log(" * Updating connector hierarchy");
+                ValueMap hierarchies = json.getValueMap("hierarchy");
+                JsArrayString hierarchyKeys = hierarchies.getKeyArray();
+                for (int i = 0; i < hierarchyKeys.length(); i++) {
+                    try {
+                        String connectorId = hierarchyKeys.get(i);
+                        Connector connector = connectorMap
+                                .getConnector(connectorId);
+                        if (!(connector instanceof ComponentContainerConnector)) {
+                            VConsole.error("Retrieved a hierarchy update for a connector ("
+                                    + connectorId
+                                    + ") that is not a ComponentContainerConnector");
+                            continue;
+                        }
+                        ComponentContainerConnector ccc = (ComponentContainerConnector) connector;
+
+                        JsArrayString childConnectorIds = hierarchies
+                                .getJSStringArray(connectorId);
+                        int childConnectorSize = childConnectorIds.length();
+
+                        List<Connector> newChildren = new ArrayList<Connector>();
+                        for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) {
+                            String childConnectorId = childConnectorIds
+                                    .get(connectorIndex);
+                            ComponentConnector childConnector = (ComponentConnector) connectorMap
+                                    .getConnector(childConnectorId);
+                            newChildren.add(childConnector);
+                            if (childConnector.getParent() != ccc) {
+                                // Avoid extra calls to setParent
+                                childConnector.setParent(ccc);
+                            }
+                        }
+
+                        // TODO This check should be done on the server side in
+                        // the future so the hierarchy update is only sent when
+                        // something actually has changed
+                        Collection<ComponentConnector> oldChildren = ccc
+                                .getChildren();
+                        boolean actuallyChanged = !Util.collectionsEquals(
+                                oldChildren, newChildren);
+
+                        if (!actuallyChanged) {
+                            continue;
+                        }
+
+                        // Fire change event if the hierarchy has changed
+                        ConnectorHierarchyChangedEvent event = GWT
+                                .create(ConnectorHierarchyChangedEvent.class);
+                        event.setOldChildren(oldChildren);
+                        event.setParent(ccc);
+                        ccc.setChildren((Collection) newChildren);
+                        events.add(event);
+                    } catch (final Throwable e) {
+                        VConsole.error(e);
+                    }
+                }
+                return events;
+
+            }
+
+            private void handleRpcInvocations(ValueMap json) {
+                if (json.containsKey("rpc")) {
+                    VConsole.log(" * Performing server to client RPC calls");
+
+                    JSONArray rpcCalls = new JSONArray(
+                            json.getJavaScriptObject("rpc"));
+
+                    int rpcLength = rpcCalls.size();
+                    for (int i = 0; i < rpcLength; i++) {
+                        try {
+                            JSONArray rpcCall = (JSONArray) rpcCalls.get(i);
+                            MethodInvocation invocation = parseMethodInvocation(rpcCall);
+                            VConsole.log("Server to client RPC call: "
+                                    + invocation);
+                            rpcManager.applyInvocation(invocation,
+                                    getConnectorMap());
+                        } catch (final Throwable e) {
+                            VConsole.error(e);
+                        }
+                    }
+                }
+
+            }
+
         };
         ApplicationConfiguration.runWhenWidgetsLoaded(c);
     }
index cb7028742757ccaa86ae64a9a6e6d4af8cabe9cd..a083da0dff4c2da4eaccca11893f7c14f7fb2a0c 100644 (file)
@@ -28,13 +28,6 @@ public interface ComponentConnector extends Connector {
      */
     public Widget getWidget();
 
-    /**
-     * Returns the parent {@link ComponentContainerConnector}
-     * 
-     * @return
-     */
-    public ComponentContainerConnector getParent();
-
     public LayoutManager getLayoutManager();
 
     /**
@@ -90,4 +83,27 @@ public interface ComponentConnector extends Connector {
      * @return the server side height definition
      */
     public String getDeclaredHeight();
+
+    /**
+     * Returns the parent of this connector. Can be null for only the root
+     * connector.
+     * 
+     * @return The parent of this connector, as set by
+     *         {@link #setParent(ComponentContainerConnector)}.
+     */
+    public ComponentContainerConnector getParent();
+
+    /**
+     * Sets the parent for this connector. This method should only be called by
+     * the framework to ensure that the connector hierarchy on the client side
+     * and the server side are in sync.
+     * <p>
+     * Note that calling this method does not fire a
+     * {@link ConnectorHierarchyChangedEvent}. The event is fired only when the
+     * whole hierarchy has been updated.
+     * 
+     * @param parent
+     *            The new parent of the connector
+     */
+    public void setParent(ComponentContainerConnector parent);
 }
index c6b1e724f27844c6d5a4281497dad839d7b9e4b0..49e31e86907779aaac0d9c9ae6e894473edf1d61 100644 (file)
@@ -9,7 +9,7 @@ import java.util.Collection;
 import com.google.gwt.user.client.ui.HasWidgets;
 
 /**
- * An interface used by client-side paintables whose widget is a component
+ * An interface used by client-side connectors whose widget is a component
  * container (implements {@link HasWidgets}).
  */
 public interface ComponentContainerConnector extends ComponentConnector {
@@ -24,24 +24,51 @@ public interface ComponentContainerConnector extends ComponentConnector {
      * must provide service for it's childen to show those elements for them.
      * </p>
      * 
-     * @param paintable
+     * @param connector
      *            Child component for which service is requested.
      * @param uidl
      *            UIDL of the child component.
      */
-    void updateCaption(ComponentConnector paintable, UIDL uidl);
+    void updateCaption(ComponentConnector connector, UIDL uidl);
 
     /**
-     * Returns the children for this paintable.
+     * Returns the children for this connector.
      * <p>
-     * The children for this paintable are defined as all
+     * The children for this connector are defined as all
      * {@link ComponentConnector}s whose parent is this
      * {@link ComponentContainerConnector}.
      * </p>
      * 
-     * @return A collection of children for this paintable. An empty collection
-     *         if there are no children.
+     * @return A collection of children for this connector. An empty collection
+     *         if there are no children. Never returns null.
      */
     public Collection<ComponentConnector> getChildren();
 
+    /**
+     * Sets the children for this connector. This method should only be called
+     * by the framework to ensure that the connector hierarchy on the client
+     * side and the server side are in sync.
+     * <p>
+     * Note that calling this method does not call
+     * {@link #connectorHierarchyChanged(ConnectorHierarchyChangedEvent)}. The
+     * event method is called only when the hierarchy has been updated for all
+     * connectors.
+     * 
+     * @param children
+     *            The new child connectors
+     */
+    public void setChildren(Collection<ComponentConnector> children);
+
+    /**
+     * Called when the child connector hierarchy of this connector has changed.
+     * When this method is called the full hierarchy has been updated so
+     * {@link #getChildren()} returns the new child connectors of this
+     * connector.
+     * 
+     * @param event
+     *            An event containing additional information about how the
+     *            hierarchy has changed.
+     */
+    public void connectorHierarchyChanged(ConnectorHierarchyChangedEvent event);
+
 }
diff --git a/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java b/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java
new file mode 100644 (file)
index 0000000..43b6133
--- /dev/null
@@ -0,0 +1,66 @@
+package com.vaadin.terminal.gwt.client;
+
+import java.util.Collection;
+
+/**
+ * Event for containing data related to a change in the {@link Connector}
+ * hierarchy. A {@link ConnectorHierarchyChangedEvent} is fired when an update
+ * from the server has been fully processed and all hierarchy updates have been
+ * completed.
+ * 
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.0.0
+ * 
+ */
+public class ConnectorHierarchyChangedEvent {
+    Collection<ComponentConnector> oldChildren;
+    private ComponentContainerConnector parent;
+
+    public ConnectorHierarchyChangedEvent() {
+    }
+
+    /**
+     * Returns a collection of the old children for the connector. This was the
+     * state before the update was received from the server.
+     * 
+     * @return A collection of old child connectors. Never returns null.
+     */
+    public Collection<ComponentConnector> getOldChildren() {
+        return oldChildren;
+    }
+
+    /**
+     * Sets the collection of the old children for the connector.
+     * 
+     * @param oldChildren
+     *            The old child connectors. Must not be null.
+     */
+    public void setOldChildren(Collection<ComponentConnector> oldChildren) {
+        this.oldChildren = oldChildren;
+    }
+
+    /**
+     * Returns the {@link ComponentContainerConnector} for which this event
+     * occurred.
+     * 
+     * @return The {@link ComponentContainerConnector} whose child collection
+     *         has changed. Never returns null.
+     */
+    public ComponentContainerConnector getParent() {
+        return parent;
+    }
+
+    /**
+     * Sets the {@link ComponentContainerConnector} for which this event
+     * occurred.
+     * 
+     * @param The
+     *            {@link ComponentContainerConnector} whose child collection has
+     *            changed.
+     */
+    public void setParent(ComponentContainerConnector parent) {
+        this.parent = parent;
+    }
+
+}
index 572a9bebb27b70abbf7e851b12e1d68f6a28ace6..39962de907946ef42ac6a4fb675e336e73fac162 100644 (file)
@@ -6,6 +6,8 @@ package com.vaadin.terminal.gwt.client;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 
 import com.google.gwt.core.client.Scheduler;
@@ -1094,4 +1096,39 @@ public class Util {
         boolean touchEvent = Util.isTouchEvent(event);
         return touchEvent || event.getButton() == Event.BUTTON_LEFT;
     }
+
+    /**
+     * Performs a shallow comparison of the collections.
+     * 
+     * @param collection1 The first collection
+     * @param collection2 The second collection
+     * @return true if the collections contain the same elements in the same
+     *         order, false otherwise
+     */
+    public static boolean collectionsEquals(Collection collection1, Collection collection2) {
+        if (collection1 == null) {
+            return collection2 == null;
+        }
+        if (collection2 == null) {
+            return false;
+        }
+        Iterator<Object> collection1Iterator = collection1.iterator();
+        Iterator<Object> collection2Iterator = collection2.iterator();
+
+        while (collection1Iterator.hasNext()) {
+            if (!collection2Iterator.hasNext()) {
+                return false;
+            }
+            Object collection1Object = collection1Iterator.next();
+            Object collection2Object = collection2Iterator.next();
+            if (collection1Object != collection2Object) {
+                return false;
+            }
+        }
+        if (collection2Iterator.hasNext()) {
+            return false;
+        }
+
+        return true;
+    }
 }
index 446d941399bbf81f867a9f2bf2fd7d5a8e7f0f58..f2d4f082d1a0332c71b3d8826ecfdec5149d383c 100644 (file)
@@ -24,6 +24,8 @@ import com.vaadin.terminal.gwt.client.communication.SharedState;
 public abstract class AbstractComponentConnector extends AbstractConnector
         implements ComponentConnector {
 
+    private ComponentContainerConnector parent;
+
     // Generic UIDL parameter names, to be moved to shared state.
     // Attributes are here mainly if they apply to all paintable widgets or
     // affect captions - otherwise, they are in the relevant subclasses.
@@ -110,23 +112,6 @@ public abstract class AbstractComponentConnector extends AbstractConnector
         return GWT.create(ComponentState.class);
     }
 
-    public ComponentContainerConnector getParent() {
-        // FIXME: Hierarchy should be set by framework instead of looked up here
-        ConnectorMap paintableMap = ConnectorMap.get(getConnection());
-
-        Widget w = getWidget();
-        while (true) {
-            w = w.getParent();
-            if (w == null) {
-                return null;
-            }
-            if (paintableMap.isConnector(w)) {
-                return (ComponentContainerConnector) paintableMap
-                        .getConnector(w);
-            }
-        }
-    }
-
     protected static boolean isRealUpdate(UIDL uidl) {
         return !isCachedUpdate(uidl) && !uidl.getBooleanAttribute("invisible")
                 && !uidl.hasAttribute("deferred");
@@ -438,4 +423,25 @@ public abstract class AbstractComponentConnector extends AbstractConnector
     public LayoutManager getLayoutManager() {
         return LayoutManager.get(getConnection());
     }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.terminal.gwt.client.Connector#getParent()
+     */
+    public ComponentContainerConnector getParent() {
+        return parent;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.vaadin.terminal.gwt.client.Connector#setParent(com.vaadin.terminal
+     * .gwt.client.ComponentContainerConnector)
+     */
+    public void setParent(ComponentContainerConnector parent) {
+        this.parent = parent;
+    }
+
 }
index 12adb60fb19e51a1d0dcccd29f4f66e444aad4bf..db0cbc29a29746f913dc9308ab6b0b59f5137022 100644 (file)
@@ -3,58 +3,61 @@
  */
 package com.vaadin.terminal.gwt.client.ui;
 
-import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedList;
 
-import com.google.gwt.user.client.ui.HasOneWidget;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.terminal.gwt.client.ComponentConnector;
 import com.vaadin.terminal.gwt.client.ComponentContainerConnector;
-import com.vaadin.terminal.gwt.client.ConnectorMap;
+import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangedEvent;
+import com.vaadin.terminal.gwt.client.Util;
 
 public abstract class AbstractComponentContainerConnector extends
         AbstractComponentConnector implements ComponentContainerConnector {
 
+    Collection<ComponentConnector> children;
+
     /**
      * Default constructor
      */
     public AbstractComponentContainerConnector() {
     }
 
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.vaadin.terminal.gwt.client.ComponentContainerConnector#getChildren()
+     */
     public Collection<ComponentConnector> getChildren() {
-        Collection<ComponentConnector> children = new ArrayList<ComponentConnector>();
-
-        addDescendantPaintables(getWidget(), children,
-                ConnectorMap.get(getConnection()));
+        if (children == null) {
+            return new LinkedList<ComponentConnector>();
+        }
 
         return children;
     }
 
-    private static void addDescendantPaintables(Widget widget,
-            Collection<ComponentConnector> paintables, ConnectorMap paintableMap) {
-        // FIXME: Store hierarchy instead of doing lookup every time
-
-        if (widget instanceof HasWidgets) {
-            for (Widget child : (HasWidgets) widget) {
-                addIfPaintable(child, paintables, paintableMap);
-            }
-        } else if (widget instanceof HasOneWidget) {
-            Widget child = ((HasOneWidget) widget).getWidget();
-            addIfPaintable(child, paintables, paintableMap);
-        }
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.vaadin.terminal.gwt.client.ComponentContainerConnector#setChildren
+     * (java.util.Collection)
+     */
+    public void setChildren(Collection<ComponentConnector> children) {
+        this.children = children;
     }
 
-    private static void addIfPaintable(Widget widget,
-            Collection<ComponentConnector> paintables, ConnectorMap paintableMap) {
-        ComponentConnector paintable = paintableMap.getConnector(widget);
-        if (paintable != null) {
-            // If widget is a paintable, add it to the collection
-            paintables.add(paintable);
-        } else {
-            // Else keep looking for paintables inside the widget
-            addDescendantPaintables(widget, paintables, paintableMap);
-        }
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.terminal.gwt.client.ComponentContainerConnector#
+     * connectorHierarchyChanged
+     * (com.vaadin.terminal.gwt.client.ConnectorHierarchyChangedEvent)
+     */
+    public void connectorHierarchyChanged(ConnectorHierarchyChangedEvent event) {
+        //TODO Remove debug info
+        System.out.println("Hierarchy changed for " + Util.getSimpleName(this));
+        System.out.println("* Old children: " + event.getOldChildren());
+        System.out.println("* New children: " + getChildren());
     }
-
 }
index 749c9ece6fd16a7fb81d0dcc05ccd635caf45268..553c6fe5727106d1f1215b1d0bc4aa70a1cdc405 100644 (file)
@@ -26,6 +26,7 @@ import java.text.StringCharacterIterator;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -67,6 +68,8 @@ import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
 import com.vaadin.ui.AbstractComponent;
 import com.vaadin.ui.AbstractField;
 import com.vaadin.ui.Component;
+import com.vaadin.ui.ComponentContainer;
+import com.vaadin.ui.Panel;
 import com.vaadin.ui.Root;
 
 /**
@@ -806,8 +809,11 @@ public abstract class AbstractCommunicationManager implements
                     windowCache);
         }
 
+        // TODO These seem unnecessary and could be removed/replaced by looping
+        // through paintQueue without removing paintables from it
         LinkedList<Paintable> stateQueue = new LinkedList<Paintable>();
         LinkedList<Paintable> rpcPendingQueue = new LinkedList<Paintable>();
+        LinkedList<Paintable> hierarchyPendingQueue = new LinkedList<Paintable>();
 
         if (paintables != null) {
 
@@ -819,6 +825,8 @@ public abstract class AbstractCommunicationManager implements
                 final Paintable p = paintQueue.removeFirst();
                 // for now, all painted components may need a state refresh
                 stateQueue.push(p);
+                // also a hierarchy update
+                hierarchyPendingQueue.push(p);
                 // ... and RPC calls to be sent
                 rpcPendingQueue.push(p);
 
@@ -870,7 +878,7 @@ public abstract class AbstractCommunicationManager implements
         outWriter.print("], "); // close changes
 
         if (!stateQueue.isEmpty()) {
-            // paint shared state
+            // send shared state to client
 
             // for now, send the complete state of all modified and new
             // components
@@ -903,6 +911,45 @@ public abstract class AbstractCommunicationManager implements
             outWriter.append(sharedStates.toString());
             outWriter.print(", "); // close states
         }
+        if (!hierarchyPendingQueue.isEmpty()) {
+            // Send update hierarchy information to the client.
+
+            // This could be optimized aswell to send only info if hierarchy has
+            // actually changed. Much like with the shared state. Note though
+            // that an empty hierarchy is information aswell (e.g. change from 1
+            // child to 0 children)
+
+            outWriter.print("\"hierarchy\":");
+
+            JSONObject hierarchyInfo = new JSONObject();
+            for (Paintable p : hierarchyPendingQueue) {
+                if (p instanceof ComponentContainer) {
+                    ComponentContainer cc = (ComponentContainer) p;
+                    String connectorId = paintableIdMap.get(cc);
+                    JSONArray children = new JSONArray();
+
+                    Iterator<Component> iterator = getChildComponentIterator(cc);
+                    while (iterator.hasNext()) {
+                        Component child = iterator.next();
+                        if (child.getState().isVisible()) {
+                            String childConnectorId = paintableIdMap.get(child);
+                            children.put(childConnectorId);
+                        }
+                    }
+                    try {
+                        hierarchyInfo.put(connectorId, children);
+                    } catch (JSONException e) {
+                        throw new PaintException(
+                                "Failed to send hierarchy information about "
+                                        + connectorId + " to the client: "
+                                        + e.getMessage());
+                    }
+                }
+            }
+            outWriter.append(hierarchyInfo.toString());
+            outWriter.print(", "); // close states
+
+        }
 
         // send server to client RPC calls for components in the root, in call
         // order
@@ -1093,6 +1140,34 @@ public abstract class AbstractCommunicationManager implements
         }
     }
 
+    private static class NullIterator<E> implements Iterator<E> {
+
+        public boolean hasNext() {
+            return false;
+        }
+
+        public E next() {
+            return null;
+        }
+
+        public void remove() {
+        }
+
+    }
+
+    private Iterator<Component> getChildComponentIterator(ComponentContainer cc) {
+        if (cc instanceof Panel) {
+            // This is so wrong.. (#2924)
+            if (((Panel) cc).getContent() == null) {
+                return new NullIterator<Component>();
+            } else {
+                return Collections.singleton(
+                        (Component) ((Panel) cc).getContent()).iterator();
+            }
+        }
+        return cc.getComponentIterator();
+    }
+
     /**
      * Collects all pending RPC calls from listed {@link Paintable}s and clears
      * their RPC queues.
@@ -2197,6 +2272,8 @@ public abstract class AbstractCommunicationManager implements
         writeUidlResponse(true, pWriter, root, false);
         pWriter.print("}");
         String initialUIDL = sWriter.toString();
+        System.out.println("Initial UIDL:");
+        System.out.println(initialUIDL);
         return initialUIDL;
     }
 
index 7de94f6696caf79cd9445028f20a5062bfec8966..0354bccd1f5f94d20aa0dc65128821db03a9e4e0 100644 (file)
@@ -17,6 +17,7 @@ import com.vaadin.terminal.Paintable;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.Sizeable;
 import com.vaadin.terminal.VariableOwner;
+import com.vaadin.terminal.gwt.client.ComponentState;
 
 /**
  * {@code Component} is the top-level interface that is and must be implemented
@@ -684,6 +685,18 @@ public interface Component extends Paintable, VariableOwner, Sizeable,
      */
     public Locale getLocale();
 
+    /**
+     * Returns the current shared state bean for the component. The state (or
+     * changes to it) is communicated from the server to the client.
+     * 
+     * Subclasses can use a more specific return type for this method.
+     * 
+     * @return The state object for the component
+     * 
+     * @since 7.0
+     */
+    public ComponentState getState();
+
     /**
      * The child components of the component must call this method when they
      * need repainting. The call must be made even in the case in which the