From: Artur Signell Date: Fri, 16 Mar 2012 17:41:46 +0000 (+0200) Subject: Moved Connector -> Connector Id mapping to AbstractComponent X-Git-Tag: 7.0.0.alpha2~288 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=36dca644148d270250340afbc74b0981f2e94177;p=vaadin-framework.git Moved Connector -> Connector Id mapping to AbstractComponent Moved Connector Id -> Connector mapping to Application Moved dirty connector tracking to Root Removed adding of --- diff --git a/src/com/vaadin/Application.java b/src/com/vaadin/Application.java index e426ea3085..fdb7de0454 100644 --- a/src/com/vaadin/Application.java +++ b/src/com/vaadin/Application.java @@ -18,6 +18,7 @@ import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.Iterator; import java.util.LinkedList; import java.util.Locale; import java.util.Map; @@ -47,11 +48,13 @@ import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedRequest.BrowserDetails; import com.vaadin.terminal.WrappedResponse; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.server.AbstractApplicationServlet; import com.vaadin.terminal.gwt.server.ChangeVariablesErrorEvent; import com.vaadin.terminal.gwt.server.WebApplicationContext; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; +import com.vaadin.ui.Component; import com.vaadin.ui.Root; import com.vaadin.ui.Table; import com.vaadin.ui.Window; @@ -2266,4 +2269,67 @@ public class Application implements Terminal.ErrorListener, Serializable { public Collection getRoots() { return Collections.unmodifiableCollection(roots.values()); } + + private final HashMap connectorIdToConnector = new HashMap(); + + private int connectorIdSequence = 0; + + /** + * Generate an id for the given Connector. Connectors must not call this + * method more than once, the first time they need an id. + * + * @param connector + * A connector that has not yet been assigned an id. + * @return A new id for the connector + */ + public String createConnectorId(Connector connector) { + // String connectorId = "C_" + connectorIdSequence++; + String connectorId = String.valueOf(connectorIdSequence++); + Connector oldReference = connectorIdToConnector.put(connectorId, + connector); + if (oldReference != null) { + throw new RuntimeException( + "An error occured while generating connector ids. A connector with id " + + connectorId + " was already found!"); + } + return connectorId; + } + + /** + * Gets a connector by its id. + * + * @param connectorId + * The connector id to look for + * @return The connector with the given id or null if no connector has the + * given id + */ + public Connector getConnector(String connectorId) { + return connectorIdToConnector.get(connectorId); + } + + /** + * Cleans the connector map from all connectors that are no longer attached + * to the application. This should only be called by the framework. + */ + public void cleanConnectorMap() { + // remove detached components from paintableIdMap so they + // can be GC'ed + Iterator iterator = connectorIdToConnector.keySet().iterator(); + + while (iterator.hasNext()) { + String connectorId = iterator.next(); + Connector connector = connectorIdToConnector.get(connectorId); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getApplication() != this) { + // If component is no longer part of this application, + // remove it from the map. If it is re-attached to the + // application at some point it will be re-added to this + // collection when sent to the client. + iterator.remove(); + } + } + } + + } } diff --git a/src/com/vaadin/terminal/Paintable.java b/src/com/vaadin/terminal/Paintable.java index 435b00a0e4..d043cb2606 100644 --- a/src/com/vaadin/terminal/Paintable.java +++ b/src/com/vaadin/terminal/Paintable.java @@ -6,10 +6,6 @@ package com.vaadin.terminal; import java.io.Serializable; import java.util.EventObject; -import java.util.List; - -import com.vaadin.terminal.gwt.client.communication.SharedState; -import com.vaadin.terminal.gwt.server.ClientMethodInvocation; /** * Interface implemented by all classes that can be painted. Classes @@ -43,30 +39,6 @@ public interface Paintable extends java.util.EventListener, Serializable { */ public void paint(PaintTarget target) throws PaintException; - /** - * Returns the current shared state bean for the paintable. The state (or - * changes to it) is communicated from the server to the client when - * components are painted. - * - * Subclasses can use a more specific return type for this method. - * - * @return shared state instance or null - * - * @since 7.0 - */ - public SharedState getState(); - - /** - * Returns the list of pending server to client RPC calls and clears the - * list. - * - * @return unmodifiable ordered list of pending server to client method - * calls (not null) - * - * @since 7.0 - */ - public List retrievePendingRpcCalls(); - /** * Requests that the paintable should be repainted as soon as possible. */ diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java index e034ae563d..4bc9431d49 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java @@ -82,7 +82,7 @@ public class JsonDecoder { } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { // TODO handle properly val = Boolean.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) { + } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { val = idMapper.getConnector(((JSONString) value).stringValue()); } else { // object, class name as type diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index c704f36871..cc8b36dfa4 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -14,8 +14,8 @@ import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.json.client.JSONValue; import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.ServerConnector; /** * Encoder for converting RPC parameters and other values to JSON for transfer @@ -30,7 +30,7 @@ import com.vaadin.terminal.gwt.client.ServerConnector; */ public class JsonEncoder { - public static final String VTYPE_PAINTABLE = "p"; + public static final String VTYPE_CONNECTOR = "c"; public static final String VTYPE_BOOLEAN = "b"; public static final String VTYPE_DOUBLE = "d"; public static final String VTYPE_FLOAT = "f"; @@ -38,7 +38,7 @@ public class JsonEncoder { public static final String VTYPE_INTEGER = "i"; public static final String VTYPE_STRING = "s"; public static final String VTYPE_ARRAY = "a"; - public static final String VTYPE_STRINGARRAY = "c"; + public static final String VTYPE_STRINGARRAY = "S"; public static final String VTYPE_MAP = "m"; public static final String VTYPE_LIST = "L"; public static final String VTYPE_NULL = "n"; @@ -88,10 +88,10 @@ public class JsonEncoder { jsonMap.put(mapKey, encode(mapValue, connectorMap, connection)); } return combineTypeAndValue(VTYPE_MAP, jsonMap); - } else if (value instanceof ServerConnector) { - ServerConnector paintable = (ServerConnector) value; - return combineTypeAndValue(VTYPE_PAINTABLE, new JSONString( - connectorMap.getConnectorId(paintable))); + } else if (value instanceof Connector) { + Connector connector = (Connector) value; + return combineTypeAndValue(VTYPE_CONNECTOR, new JSONString( + connector.getConnectorId())); } else { String transportType = getTransportType(value); if (transportType != null) { @@ -123,8 +123,8 @@ public class JsonEncoder { return VTYPE_NULL; } else if (value instanceof String) { return VTYPE_STRING; - } else if (value instanceof ServerConnector) { - return VTYPE_PAINTABLE; + } else if (value instanceof Connector) { + return VTYPE_CONNECTOR; } else if (value instanceof Boolean) { return VTYPE_BOOLEAN; } else if (value instanceof Integer) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java index 4955eea7c6..c96f955870 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java +++ b/src/com/vaadin/terminal/gwt/client/ui/dd/VDragAndDropManager.java @@ -223,7 +223,7 @@ public class VDragAndDropManager { ENTER, LEAVE, OVER, DROP } - private static final String DD_SERVICE = "DD"; + public static final String DD_SERVICE = "DD"; private static VDragAndDropManager instance; private HandlerRegistration handlerRegistration; diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java index b28cb361b0..633c393913 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java @@ -472,7 +472,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements /* Handle the request */ if (requestType == RequestType.FILE_UPLOAD) { - applicationManager.handleFileUpload(request, response); + applicationManager.handleFileUpload(application, request, + response); return; } else if (requestType == RequestType.UIDL) { // Handles AJAX UIDL requests diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 6a5bcce7d7..37b0509218 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -69,9 +69,11 @@ 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.DirtyConnectorTracker; import com.vaadin.ui.HasComponents; import com.vaadin.ui.Panel; import com.vaadin.ui.Root; +import com.vaadin.ui.Window; /** * This is a common base class for the server-side implementations of the @@ -91,7 +93,7 @@ import com.vaadin.ui.Root; */ @SuppressWarnings("serial") public abstract class AbstractCommunicationManager implements - Paintable.RepaintRequestListener, PaintableIdMapper, Serializable { + Paintable.RepaintRequestListener, Serializable { private static final String DASHDASH = "--"; @@ -142,15 +144,16 @@ public abstract class AbstractCommunicationManager implements // cannot combine with paint queue: // this can contain dirty components from any Root - private final ArrayList dirtyPaintables = new ArrayList(); + // TODO Remove, it is now in Root + // private final ArrayList dirtyPaintables = new + // ArrayList(); // queue used during painting to keep track of what still needs to be // painted within the Root being painted - private LinkedList paintQueue = new LinkedList(); + // private LinkedList paintQueue = new LinkedList(); - private final HashMap paintableIdMap = new HashMap(); - - private final HashMap idPaintableMap = new HashMap(); + // private final HashMap idPaintableMap = new + // HashMap(); private int idSequence = 0; @@ -168,6 +171,8 @@ public abstract class AbstractCommunicationManager implements private int maxInactiveInterval; + private Connector highlightedConnector; + /** * TODO New constructor - document me! * @@ -193,8 +198,6 @@ public abstract class AbstractCommunicationManager implements private static final String GET_PARAM_HIGHLIGHT_COMPONENT = "highlightComponent"; - private Paintable highLightedPaintable; - private static String readLine(InputStream stream) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); int readByte = stream.read(); @@ -220,7 +223,7 @@ public abstract class AbstractCommunicationManager implements */ protected void doHandleSimpleMultipartFileUpload(WrappedRequest request, WrappedResponse response, StreamVariable streamVariable, - String variableName, VariableOwner owner, String boundary) + String variableName, Connector owner, String boundary) throws IOException { // multipart parsing, supports only one file for request, but that is // fine for our current terminal @@ -319,7 +322,7 @@ public abstract class AbstractCommunicationManager implements */ protected void doHandleXhrFilePost(WrappedRequest request, WrappedResponse response, StreamVariable streamVariable, - String variableName, VariableOwner owner, int contentLength) + String variableName, Connector owner, int contentLength) throws IOException { // These are unknown in filexhr ATM, maybe add to Accept header that @@ -536,8 +539,8 @@ public abstract class AbstractCommunicationManager implements if (request.getParameter(GET_PARAM_HIGHLIGHT_COMPONENT) != null) { String pid = request .getParameter(GET_PARAM_HIGHLIGHT_COMPONENT); - highLightedPaintable = idPaintableMap.get(pid); - highlightPaintable(highLightedPaintable); + highlightedConnector = root.getApplication().getConnector(pid); + highlightConnector(highlightedConnector); } } @@ -597,22 +600,35 @@ public abstract class AbstractCommunicationManager implements paintAfterVariableChanges(request, response, callback, repaintAll, outWriter, root, analyzeLayouts); - + postPaint(root); } outWriter.close(); requestThemeName = null; } - protected void highlightPaintable(Paintable highLightedPaintable2) { + /** + * Method called after the paint phase while still being synchronized on the + * application + * + * @param root + * + */ + protected void postPaint(Root root) { + // Remove connectors that have been detached from the application during + // handling of the request + root.getApplication().cleanConnectorMap(); + } + + protected void highlightConnector(Connector highlightedConnector) { StringBuilder sb = new StringBuilder(); sb.append("*** Debug details of a component: *** \n"); sb.append("Type: "); - sb.append(highLightedPaintable2.getClass().getName()); - if (highLightedPaintable2 instanceof AbstractComponent) { - AbstractComponent component = (AbstractComponent) highLightedPaintable2; + sb.append(highlightedConnector.getClass().getName()); + if (highlightedConnector instanceof AbstractComponent) { + AbstractComponent component = (AbstractComponent) highlightedConnector; sb.append("\nId:"); - sb.append(paintableIdMap.get(component)); + sb.append(highlightedConnector.getConnectorId()); if (component.getCaption() != null) { sb.append("\nCaption:"); sb.append(component.getCaption()); @@ -681,10 +697,6 @@ public abstract class AbstractCommunicationManager implements final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException, IOException { - if (repaintAll) { - makeAllPaintablesDirty(root); - } - // Removes application if it has stopped during variable changes if (!application.isRunning()) { endApplication(request, response, application); @@ -746,126 +758,75 @@ public abstract class AbstractCommunicationManager implements // for internal use by JsonPaintTarget public void queuePaintable(Paintable paintable) { - if (!paintQueue.contains(paintable)) { - paintQueue.add(paintable); - } + // if (!paintQueue.contains(paintable)) { + // paintQueue.add((Connector) paintable); + // } } public void writeUidlResponse(boolean repaintAll, final PrintWriter outWriter, Root root, boolean analyzeLayouts) throws PaintException { - ArrayList paintables = null; - + ArrayList dirtyVisibleConnectors = new ArrayList(); + Application application = root.getApplication(); // Paints components + DirtyConnectorTracker rootConnectorTracker = root + .getDirtyConnectorTracker(); + System.out.println("* Creating response to client"); if (repaintAll) { - paintables = new ArrayList(); - paintables.add(root); + System.out.println("Full repaint"); + + getClientCache(root).clear(); + + rootConnectorTracker.markAllComponentsDirty(); + + dirtyVisibleConnectors.addAll(rootConnectorTracker + .getDirtyComponents()); // Reset sent locales locales = null; requireLocale(application.getLocale().toString()); } else { - // remove detached components from paintableIdMap so they - // can be GC'ed - /* - * TODO figure out if we could move this beyond the painting phase, - * "respond as fast as possible, then do the cleanup". Beware of - * painting the dirty detached components. - */ - for (Iterator it = paintableIdMap.keySet().iterator(); it - .hasNext();) { - Component p = (Component) it.next(); - if (p.getApplication() == null) { - unregisterPaintable(p); - // Take into account that some other component may have - // reused p's ID by now (this can happen when manually - // assigning IDs with setDebugId().) See #8090. - String pid = paintableIdMap.get(p); - if (idPaintableMap.get(pid) == p) { - idPaintableMap.remove(pid); - } - it.remove(); - dirtyPaintables.remove(p); - } - } // TODO second list/stack for those whose state needs to be sent? - paintables = getDirtyVisibleComponents(root); + dirtyVisibleConnectors + .addAll(getDirtyVisibleComponents(rootConnectorTracker)); } + System.out.println("* Found " + dirtyVisibleConnectors.size() + + " dirty connectors to paint"); + rootConnectorTracker.markAllComponentsClean(); + outWriter.print("\"changes\":["); List invalidComponentRelativeSizes = null; JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter, !repaintAll); - ClientCache clientCache = getClientCache(root); - // TODO These seem unnecessary and could be removed/replaced by looping - // through paintQueue without removing paintables from it - LinkedList stateQueue = new LinkedList(); - LinkedList rpcPendingQueue = new LinkedList(); - LinkedList hierarchyPendingQueue = new LinkedList(); - LinkedList connectorTypeQueue = new LinkedList(); - - if (paintables != null) { - - // clear and rebuild paint queue - paintQueue.clear(); - paintQueue.addAll(paintables); - - while (!paintQueue.isEmpty()) { - final Paintable p = paintQueue.removeFirst(); - // for now, all painted components may need a state refresh - stateQueue.push(p); - // TODO This should be optimized. The type only needs to be sent - // once for each connector id + on refresh - connectorTypeQueue.push(p); - // also a hierarchy update - hierarchyPendingQueue.push(p); - // ... and RPC calls to be sent - rpcPendingQueue.push(p); - - // // TODO CLEAN - // if (p instanceof Root) { - // final Root r = (Root) p; - // if (r.getTerminal() == null) { - // r.setTerminal(application.getRoot().getTerminal()); - // } - // } - - // TODO we may still get changes that have been - // rendered already (changes with only cached flag) - if (paintTarget.needsToBePainted(p)) { - paintTarget.startTag("change"); - final String pid = getPaintableId(p); - paintTarget.addAttribute("pid", pid); - - // paints subcomponents as references (via - // JsonPaintTarget.startPaintable()) and defers painting - // their contents to another top-level change (via - // queuePaintable()) - p.paint(paintTarget); - - paintTarget.endTag("change"); - } - paintablePainted(p); + for (Connector connector : dirtyVisibleConnectors) { + if (connector instanceof Paintable) { + Paintable p = (Paintable) connector; + paintTarget.startTag("change"); + final String pid = connector.getConnectorId(); + paintTarget.addAttribute("pid", pid); + p.paint(paintTarget); + paintTarget.endTag("change"); + } + } - if (analyzeLayouts) { - Root w = (Root) p; + if (analyzeLayouts) { + // TODO Check if this works + invalidComponentRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes(root.getContent(), null, + null); + + // Also check any existing subwindows + if (root.getWindows() != null) { + for (Window subWindow : root.getWindows()) { invalidComponentRelativeSizes = ComponentSizeValidator - .validateComponentRelativeSizes(w.getContent(), - null, null); - - // // Also check any existing subwindows - // if (w.getChildWindows() != null) { - // for (Window subWindow : w.getChildWindows()) { - // invalidComponentRelativeSizes = ComponentSizeValidator - // .validateComponentRelativeSizes( - // subWindow.getContent(), - // invalidComponentRelativeSizes, null); - // } - // } + .validateComponentRelativeSizes( + subWindow.getContent(), + invalidComponentRelativeSizes, null); } } } @@ -873,136 +834,131 @@ public abstract class AbstractCommunicationManager implements paintTarget.close(); outWriter.print("], "); // close changes - if (!stateQueue.isEmpty()) { - // send shared state to client - - // for now, send the complete state of all modified and new - // components - - // Ideally, all this would be sent before "changes", but that causes - // complications with legacy components that create sub-components - // in their paint phase. Nevertheless, this will be processed on the - // client after component creation but before legacy UIDL - // processing. - - JSONObject sharedStates = new JSONObject(); - while (!stateQueue.isEmpty()) { - final Paintable p = stateQueue.pop(); - String paintableId = getPaintableId(p); - SharedState state = p.getState(); - if (null != state) { - // encode and send shared state - try { - JSONArray stateJsonArray = JsonCodec - .encode(state, this); - sharedStates.put(paintableId, stateJsonArray); - } catch (JSONException e) { - throw new PaintException( - "Failed to serialize shared state for paintable " - + paintableId + ": " + e.getMessage()); - } - } - } - outWriter.print("\"state\":"); - outWriter.append(sharedStates.toString()); - outWriter.print(", "); // close states - } - if (!connectorTypeQueue.isEmpty()) { - JSONObject connectorTypes = new JSONObject(); - while (!connectorTypeQueue.isEmpty()) { - final Paintable p = connectorTypeQueue.pop(); - String paintableId = getPaintableId(p); - String connectorType = paintTarget.getTag(p); + // send shared state to client + + // for now, send the complete state of all modified and new + // components + + // Ideally, all this would be sent before "changes", but that causes + // complications with legacy components that create sub-components + // in their paint phase. Nevertheless, this will be processed on the + // client after component creation but before legacy UIDL + // processing. + JSONObject sharedStates = new JSONObject(); + for (Connector connector : dirtyVisibleConnectors) { + SharedState state = connector.getState(); + if (null != state) { + // encode and send shared state try { - connectorTypes.put(paintableId, connectorType); + JSONArray stateJsonArray = JsonCodec.encode(state, + application); + sharedStates + .put(connector.getConnectorId(), stateJsonArray); } catch (JSONException e) { throw new PaintException( - "Failed to send connector type for paintable " - + paintableId + ": " + e.getMessage()); - } - } - outWriter.print("\"types\":"); - outWriter.append(connectorTypes.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 HasComponents) { - HasComponents parent = (HasComponents) p; - String parentConnectorId = paintableIdMap.get(parent); - JSONArray children = new JSONArray(); - - for (Component child : getChildComponents(parent)) { - if (isVisible(child)) { - String childConnectorId = getPaintableId(child); - children.put(childConnectorId); - } - } - try { - hierarchyInfo.put(parentConnectorId, children); - } catch (JSONException e) { - throw new PaintException( - "Failed to send hierarchy information about " - + parentConnectorId - + " to the client: " + e.getMessage()); - } + "Failed to serialize shared state for connector " + + connector.getConnectorId() + ": " + + e.getMessage()); } } - outWriter.append(hierarchyInfo.toString()); - outWriter.print(", "); // close hierarchy + } + outWriter.print("\"state\":"); + outWriter.append(sharedStates.toString()); + outWriter.print(", "); // close states + + // TODO This should be optimized. The type only needs to be + // sent once for each connector id + on refresh + JSONObject connectorTypes = new JSONObject(); + for (Connector connector : dirtyVisibleConnectors) { + String connectorType = paintTarget.getTag((Paintable) connector); + try { + connectorTypes.put(connector.getConnectorId(), connectorType); + } catch (JSONException e) { + throw new PaintException( + "Failed to send connector type for connector " + + connector.getConnectorId() + ": " + + e.getMessage()); + } } + outWriter.print("\"types\":"); + outWriter.append(connectorTypes.toString()); + outWriter.print(", "); // close states - // send server to client RPC calls for components in the root, in call - // order - if (!rpcPendingQueue.isEmpty()) { - // collect RPC calls from components in the root in the order in - // which they were performed, remove the calls from components - List pendingInvocations = collectPendingRpcCalls(rpcPendingQueue); - - JSONArray rpcCalls = new JSONArray(); - for (ClientMethodInvocation invocation : pendingInvocations) { - // add invocation to rpcCalls - try { - JSONArray invocationJson = new JSONArray(); - invocationJson - .put(getPaintableId(invocation.getPaintable())); - invocationJson.put(invocation.getInterfaceName()); - invocationJson.put(invocation.getMethodName()); - JSONArray paramJson = new JSONArray(); - for (int i = 0; i < invocation.getParameters().length; ++i) { - paramJson.put(JsonCodec.encode( - invocation.getParameters()[i], this)); + // 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 (Connector connector : dirtyVisibleConnectors) { + if (connector instanceof HasComponents) { + HasComponents parent = (HasComponents) connector; + String parentConnectorId = parent.getConnectorId(); + JSONArray children = new JSONArray(); + + for (Component child : getChildComponents(parent)) { + if (isVisible(child)) { + children.put(child.getConnectorId()); } - invocationJson.put(paramJson); - rpcCalls.put(invocationJson); + } + try { + hierarchyInfo.put(parentConnectorId, children); } catch (JSONException e) { throw new PaintException( - "Failed to serialize RPC method call parameters for paintable " - + getPaintableId(invocation.getPaintable()) - + " method " - + invocation.getInterfaceName() + "." - + invocation.getMethodName() + ": " + "Failed to send hierarchy information about " + + parentConnectorId + " to the client: " + e.getMessage()); } - } + } + outWriter.append(hierarchyInfo.toString()); + outWriter.print(", "); // close hierarchy + + // send server to client RPC calls for components in the root, in call + // order + + // collect RPC calls from components in the root in the order in + // which they were performed, remove the calls from components + + LinkedList rpcPendingQueue = new LinkedList( + (Collection) dirtyVisibleConnectors); + List pendingInvocations = collectPendingRpcCalls(rpcPendingQueue); - if (rpcCalls.length() > 0) { - outWriter.print("\"rpc\" : "); - outWriter.append(rpcCalls.toString()); - outWriter.print(", "); // close rpc + JSONArray rpcCalls = new JSONArray(); + for (ClientMethodInvocation invocation : pendingInvocations) { + // add invocation to rpcCalls + try { + JSONArray invocationJson = new JSONArray(); + invocationJson.put(invocation.getConnector().getConnectorId()); + invocationJson.put(invocation.getInterfaceName()); + invocationJson.put(invocation.getMethodName()); + JSONArray paramJson = new JSONArray(); + for (int i = 0; i < invocation.getParameters().length; ++i) { + paramJson.put(JsonCodec.encode( + invocation.getParameters()[i], application)); + } + invocationJson.put(paramJson); + rpcCalls.put(invocationJson); + } catch (JSONException e) { + throw new PaintException( + "Failed to serialize RPC method call parameters for connector " + + invocation.getConnector().getConnectorId() + + " method " + invocation.getInterfaceName() + + "." + invocation.getMethodName() + ": " + + e.getMessage()); } + + } + + if (rpcCalls.length() > 0) { + outWriter.print("\"rpc\" : "); + outWriter.append(rpcCalls.toString()); + outWriter.print(", "); // close rpc } outWriter.print("\"meta\" : {"); @@ -1027,11 +983,11 @@ public abstract class AbstractCommunicationManager implements } outWriter.write("]"); } - if (highLightedPaintable != null) { + if (highlightedConnector != null) { outWriter.write(", \"hl\":\""); - outWriter.write(paintableIdMap.get(highLightedPaintable)); + outWriter.write(highlightedConnector.getConnectorId()); outWriter.write("\""); - highLightedPaintable = null; + highlightedConnector = null; } } @@ -1123,6 +1079,8 @@ public abstract class AbstractCommunicationManager implements Collection> usedPaintableTypes = paintTarget .getUsedPaintableTypes(); boolean typeMappingsOpen = false; + ClientCache clientCache = getClientCache(root); + for (Class class1 : usedPaintableTypes) { if (clientCache.cache(class1)) { // client does not know the mapping key for this type, send @@ -1208,10 +1166,10 @@ public abstract class AbstractCommunicationManager implements * @return ordered list of pending RPC calls */ private List collectPendingRpcCalls( - LinkedList rpcPendingQueue) { + LinkedList rpcPendingQueue) { List pendingInvocations = new ArrayList(); - for (Paintable paintable : rpcPendingQueue) { - List paintablePendingRpc = paintable + for (ClientConnector connector : rpcPendingQueue) { + List paintablePendingRpc = connector .retrievePendingRpcCalls(); if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) { List oldPendingRpc = pendingInvocations; @@ -1262,30 +1220,6 @@ public abstract class AbstractCommunicationManager implements return requestThemeName; } - public void makeAllPaintablesDirty(Root root) { - // If repaint is requested, clean all ids in this root window - for (final Iterator it = idPaintableMap.keySet().iterator(); it - .hasNext();) { - final Component c = (Component) idPaintableMap.get(it.next()); - if (isChildOf(root, c)) { - it.remove(); - paintableIdMap.remove(c); - } - } - // clean WindowCache - getClientCache(root).clear(); - } - - /** - * Called when communication manager stops listening for repaints for given - * component. - * - * @param p - */ - protected void unregisterPaintable(Component p) { - p.removeListener(this); - } - /** * Returns false if the cross site request forgery protection is turned off. * @@ -1458,20 +1392,14 @@ public abstract class AbstractCommunicationManager implements if (!ApplicationConnection.UPDATE_VARIABLE_INTERFACE .equals(interfaceName)) { // handle other RPC calls than variable changes - applyInvocation(invocation); + applyInvocation(app, invocation); continue; } - final VariableOwner owner = getVariableOwner(invocation - .getConnectorId()); + final VariableOwner owner = (VariableOwner) getConnector(app, + invocation.getConnectorId()); - boolean connectorEnabled; - if (owner instanceof Connector) { - connectorEnabled = ((Connector) owner).isConnectorEnabled(); - } else { - // TODO Remove - connectorEnabled = owner.isEnabled(); - } + boolean connectorEnabled = owner.isEnabled(); if (owner != null && connectorEnabled) { VariableChange change = new VariableChange(invocation); @@ -1558,15 +1486,17 @@ public abstract class AbstractCommunicationManager implements * Execute an RPC call from the client by finding its target and letting the * RPC mechanism call the correct method for it. * + * @param app + * * @param invocation */ - protected void applyInvocation(MethodInvocation invocation) { - final RpcTarget target = getRpcTarget(invocation.getConnectorId()); - if (null != target) { - ServerRpcManager.applyInvocation(target, invocation); + protected void applyInvocation(Application app, MethodInvocation invocation) { + Connector c = app.getConnector(invocation.getConnectorId()); + if (c instanceof RpcTarget) { + ServerRpcManager.applyInvocation((RpcTarget) c, invocation); } else { // TODO better exception? - throw new RuntimeException("No RPC target for paintable " + throw new RuntimeException("No RPC target for connector " + invocation.getConnectorId()); } } @@ -1596,7 +1526,7 @@ public abstract class AbstractCommunicationManager implements Object[] parameters = new Object[parametersJson.length()]; for (int j = 0; j < parametersJson.length(); ++j) { parameters[j] = JsonCodec.decode( - parametersJson.getJSONArray(j), this); + parametersJson.getJSONArray(j), application); } MethodInvocation invocation = new MethodInvocation(connectorId, interfaceName, methodName, parameters); @@ -1610,34 +1540,17 @@ public abstract class AbstractCommunicationManager implements owner.changeVariables(source, m); } - protected VariableOwner getVariableOwner(String string) { - VariableOwner owner = (VariableOwner) idPaintableMap.get(string); - if (owner == null && string.startsWith("DD")) { + protected Connector getConnector(Application app, String connectorId) { + Connector c = app.getConnector(connectorId); + if (c == null + && connectorId.equals(getDragAndDropService().getConnectorId())) { return getDragAndDropService(); } - return owner; - } - /** - * Returns the RPC call target for a paintable ID. - * - * @since 7.0 - * - * @param string - * paintable ID - * @return RPC call target or null if none found - */ - protected RpcTarget getRpcTarget(String string) { - // TODO improve this - VariableOwner and RpcManager separate? - VariableOwner owner = getVariableOwner(string); - if (owner instanceof RpcTarget) { - return (RpcTarget) owner; - } else { - return null; - } + return c; } - private VariableOwner getDragAndDropService() { + private DragAndDropService getDragAndDropService() { if (dragAndDropService == null) { dragAndDropService = new DragAndDropService(this); } @@ -1973,56 +1886,6 @@ public abstract class AbstractCommunicationManager implements outWriter.print("for(;;);[{"); } - public Paintable getPaintable(String paintableId) { - return idPaintableMap.get(paintableId); - } - - /** - * Gets the Paintable Id. If Paintable has debug id set it will be used - * prefixed with "PID_S". Otherwise a sequenced ID is created. - * - * @param paintable - * @return the paintable Id. - */ - public String getPaintableId(Paintable paintable) { - - String id = paintableIdMap.get(paintable); - if (id == null) { - // use testing identifier as id if set - id = paintable.getDebugId(); - if (id == null) { - id = "PID" + Integer.toString(idSequence++); - } else { - id = "PID_S" + id; - } - Paintable old = idPaintableMap.put(id, paintable); - if (old != null && old != paintable) { - /* - * Two paintables have the same id. We still make sure the old - * one is a component which is still attached to the - * application. This is just a precaution and should not be - * absolutely necessary. - */ - - if (old instanceof Component - && ((Component) old).getApplication() != null) { - throw new IllegalStateException("Two paintables (" - + paintable.getClass().getSimpleName() + "," - + old.getClass().getSimpleName() - + ") have been assigned the same id: " - + paintable.getDebugId()); - } - } - paintableIdMap.put(paintable, id); - } - - return id; - } - - public boolean hasPaintableId(Paintable paintable) { - return paintableIdMap.containsKey(paintable); - } - /** * Returns dirty components which are in given window. Components in an * invisible subtrees are omitted. @@ -2031,65 +1894,26 @@ public abstract class AbstractCommunicationManager implements * root window for which dirty components is to be fetched * @return */ - private ArrayList getDirtyVisibleComponents(Root r) { - final ArrayList resultset = new ArrayList( - dirtyPaintables); - - // TODO mostly unnecessary? - // The following algorithm removes any components that would be painted - // as a direct descendant of other components from the dirty components - // list. The result is that each component should be painted exactly - // once and any unmodified components will be painted as "cached=true". - - for (final Iterator i = dirtyPaintables.iterator(); i - .hasNext();) { - final Paintable p = i.next(); - if (p instanceof Component) { - final Component component = (Component) p; - if (component.getApplication() == null) { - // component is detached after requestRepaint is called - resultset.remove(p); - i.remove(); - } else { - Root componentsRoot = component.getRoot(); - if (componentsRoot == null) { - // This should not happen unless somebody has overriden - // getApplication or getWindow in an illegal way. - throw new IllegalStateException( - "component.getWindow() returned null for a component attached to the application"); - } - // if (componentsRoot.getParent() != null) { - // // this is a subwindow - // componentsRoot = componentsRoot.getParent(); - // } - if (componentsRoot != r) { - resultset.remove(p); - } else if (component.getParent() != null - && !isVisible(component.getParent())) { - /* - * Do not return components in an invisible subtree. - * - * Components that are invisible in visible subree, must - * be rendered (to let client know that they need to be - * hidden). - */ - resultset.remove(p); - } - } + private ArrayList getDirtyVisibleComponents( + DirtyConnectorTracker dirtyConnectorTracker) { + ArrayList dirtyComponents = new ArrayList(); + for (Component c : dirtyConnectorTracker.getDirtyComponents()) { + if (isVisible(c)) { + dirtyComponents.add(c); } } - return resultset; + return dirtyComponents; } /** * @see com.vaadin.terminal.Paintable.RepaintRequestListener#repaintRequested(com.vaadin.terminal.Paintable.RepaintRequestEvent) */ public void repaintRequested(RepaintRequestEvent event) { - final Paintable p = event.getPaintable(); - if (!dirtyPaintables.contains(p)) { - dirtyPaintables.add(p); - } + // final Paintable p = event.getPaintable(); + // if (!dirtyPaintables.contains(p)) { + // dirtyPaintables.add(p); + // } } /** @@ -2099,8 +1923,8 @@ public abstract class AbstractCommunicationManager implements * @param paintable */ private void paintablePainted(Paintable paintable) { - dirtyPaintables.remove(paintable); - paintable.requestRepaintRequests(); + // dirtyPaintables.remove(paintable); + // paintable.requestRepaintRequests(); } /** @@ -2145,23 +1969,6 @@ public abstract class AbstractCommunicationManager implements } } - /** - * Helper method to test if a component contains another - * - * @param parent - * @param child - */ - private static boolean isChildOf(Component parent, Component child) { - Component p = child.getParent(); - while (p != null) { - if (parent == p) { - return true; - } - p = p.getParent(); - } - return false; - } - protected class InvalidUIDLSecurityKeyException extends GeneralSecurityException { @@ -2210,10 +2017,10 @@ public abstract class AbstractCommunicationManager implements } - abstract String getStreamVariableTargetUrl(VariableOwner owner, - String name, StreamVariable value); + abstract String getStreamVariableTargetUrl(Connector owner, String name, + StreamVariable value); - abstract protected void cleanStreamVariable(VariableOwner owner, String name); + abstract protected void cleanStreamVariable(Connector owner, String name); /** * Gets the bootstrap handler that should be used for generating the pages @@ -2297,7 +2104,6 @@ public abstract class AbstractCommunicationManager implements protected String getInitialUIDL(WrappedRequest request, Root root) throws PaintException { // TODO maybe unify writeUidlResponse()? - makeAllPaintablesDirty(root); StringWriter sWriter = new StringWriter(); PrintWriter pWriter = new PrintWriter(sWriter); pWriter.print("{"); @@ -2461,4 +2267,13 @@ public abstract class AbstractCommunicationManager implements return b; } } + + @Deprecated + public String getPaintableId(Paintable paintable) { + if (paintable instanceof Connector) { + return ((Connector) paintable).getConnectorId(); + } + throw new RuntimeException("Paintable " + paintable + + " must implement Connector"); + } } diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java new file mode 100644 index 0000000000..e2943b499f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -0,0 +1,19 @@ +package com.vaadin.terminal.gwt.server; + +import java.util.List; + +import com.vaadin.terminal.gwt.client.Connector; + +public interface ClientConnector extends Connector { + /** + * Returns the list of pending server to client RPC calls and clears the + * list. + * + * @return unmodifiable ordered list of pending server to client method + * calls (not null) + * + * @since 7.0 + */ + public List retrievePendingRpcCalls(); + +} diff --git a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java index 8133a617ca..2edcb8a9f2 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java +++ b/src/com/vaadin/terminal/gwt/server/ClientMethodInvocation.java @@ -6,17 +6,15 @@ package com.vaadin.terminal.gwt.server; import java.io.Serializable; -import com.vaadin.terminal.Paintable; - /** * Internal class for keeping track of pending server to client method - * invocations for a Paintable. + * invocations for a Connector. * * @since 7.0 */ public class ClientMethodInvocation implements Serializable, Comparable { - private final Paintable paintable; + private final ClientConnector connector; private final String interfaceName; private final String methodName; private final Object[] parameters; @@ -26,17 +24,17 @@ public class ClientMethodInvocation implements Serializable, // TODO may cause problems when clustering etc. private static long counter = 0; - public ClientMethodInvocation(Paintable paintable, String interfaceName, - String methodName, Object[] parameters) { - this.paintable = paintable; + public ClientMethodInvocation(ClientConnector connector, + String interfaceName, String methodName, Object[] parameters) { + this.connector = connector; this.interfaceName = interfaceName; this.methodName = methodName; this.parameters = (null != parameters) ? parameters : new Object[0]; sequenceNumber = ++counter; } - public Paintable getPaintable() { - return paintable; + public ClientConnector getConnector() { + return connector; } public String getInterfaceName() { diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java index 3a9b8ff2db..3dd2eb97fd 100644 --- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.UUID; @@ -15,12 +16,10 @@ import javax.servlet.ServletContext; import com.vaadin.Application; import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.Paintable; import com.vaadin.terminal.StreamVariable; -import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedResponse; -import com.vaadin.ui.Component; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.ui.Root; /** @@ -62,6 +61,8 @@ public class CommunicationManager extends AbstractCommunicationManager { /** * Handles file upload request submitted via Upload component. * + * @param application + * * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable) * * @param request @@ -69,9 +70,9 @@ public class CommunicationManager extends AbstractCommunicationManager { * @throws IOException * @throws InvalidUIDLSecurityKeyException */ - public void handleFileUpload(WrappedRequest request, - WrappedResponse response) throws IOException, - InvalidUIDLSecurityKeyException { + public void handleFileUpload(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException, InvalidUIDLSecurityKeyException { /* * URI pattern: APP/UPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl @@ -92,7 +93,7 @@ public class CommunicationManager extends AbstractCommunicationManager { String secKey = streamVariableToSeckey.get(streamVariable); if (secKey.equals(parts[2])) { - VariableOwner source = getVariableOwner(paintableId); + Connector source = getConnector(application, paintableId); String contentType = request.getContentType(); if (contentType.contains("boundary")) { // Multipart requests contain boundary string @@ -113,18 +114,26 @@ public class CommunicationManager extends AbstractCommunicationManager { } @Override - protected void unregisterPaintable(Component p) { - /* Cleanup possible receivers */ + protected void postPaint(Root root) { + super.postPaint(root); + + Application application = root.getApplication(); if (pidToNameToStreamVariable != null) { - Map removed = pidToNameToStreamVariable - .remove(getPaintableId(p)); - if (removed != null) { - for (String key : removed.keySet()) { - streamVariableToSeckey.remove(removed.get(key)); + Iterator iterator = pidToNameToStreamVariable.keySet() + .iterator(); + while (iterator.hasNext()) { + String connectorId = iterator.next(); + if (application.getConnector(connectorId) == null) { + // Owner is no longer attached to the application + Map removed = pidToNameToStreamVariable + .get(connectorId); + for (String key : removed.keySet()) { + streamVariableToSeckey.remove(removed.get(key)); + } + iterator.remove(); } } } - super.unregisterPaintable(p); } @@ -133,7 +142,7 @@ public class CommunicationManager extends AbstractCommunicationManager { private Map streamVariableToSeckey; @Override - String getStreamVariableTargetUrl(VariableOwner owner, String name, + String getStreamVariableTargetUrl(Connector owner, String name, StreamVariable value) { /* * We will use the same APP/* URI space as ApplicationResources but @@ -147,7 +156,7 @@ public class CommunicationManager extends AbstractCommunicationManager { * NAME and PID from URI forms a key to fetch StreamVariable when * handling post */ - String paintableId = getPaintableId((Paintable) owner); + String paintableId = owner.getConnectorId(); String key = paintableId + "/" + name; if (pidToNameToStreamVariable == null) { @@ -176,12 +185,12 @@ public class CommunicationManager extends AbstractCommunicationManager { } @Override - protected void cleanStreamVariable(VariableOwner owner, String name) { + protected void cleanStreamVariable(Connector owner, String name) { Map nameToStreamVar = pidToNameToStreamVariable - .get(getPaintableId((Paintable) owner)); + .get(owner.getConnectorId()); nameToStreamVar.remove("name"); if (nameToStreamVar.isEmpty()) { - pidToNameToStreamVariable.remove(getPaintableId((Paintable) owner)); + pidToNameToStreamVariable.remove(owner.getConnectorId()); } } diff --git a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java index 9fc8aa6bf0..335067ca7a 100644 --- a/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java +++ b/src/com/vaadin/terminal/gwt/server/ComponentSizeValidator.java @@ -185,7 +185,7 @@ public class ComponentSizeValidator implements Serializable { clientJSON.write("{"); Component parent = component.getParent(); - String paintableId = communicationManager.getPaintableId(component); + String paintableId = component.getConnectorId(); clientJSON.print("id:\"" + paintableId + "\""); diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index 3a923c1840..a637db2840 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -18,10 +18,13 @@ import com.vaadin.event.dd.TargetDetailsImpl; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager.DragEventType; import com.vaadin.ui.Component; -public class DragAndDropService implements VariableOwner { +public class DragAndDropService implements VariableOwner, Connector { private static final Logger logger = Logger .getLogger(DragAndDropService.class.getName()); @@ -177,7 +180,7 @@ public class DragAndDropService implements VariableOwner { } public boolean isEnabled() { - return true; + return isConnectorEnabled(); } public boolean isImmediate() { @@ -212,4 +215,18 @@ public class DragAndDropService implements VariableOwner { } return false; } + + public SharedState getState() { + // TODO Auto-generated method stub + return null; + } + + public String getConnectorId() { + return VDragAndDropManager.DD_SERVICE; + } + + public boolean isConnectorEnabled() { + // Drag'n'drop can't be disabled + return true; + } } diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index 5d2e39f10f..9fea64a1c7 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -16,6 +16,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import com.vaadin.Application; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; @@ -35,8 +36,7 @@ public class JsonCodec implements Serializable { static { registerType(String.class, JsonEncoder.VTYPE_STRING); - registerType(Paintable.class, JsonEncoder.VTYPE_PAINTABLE); - registerType(Connector.class, JsonEncoder.VTYPE_PAINTABLE); + registerType(Connector.class, JsonEncoder.VTYPE_CONNECTOR); registerType(Boolean.class, JsonEncoder.VTYPE_BOOLEAN); registerType(Integer.class, JsonEncoder.VTYPE_INTEGER); registerType(Float.class, JsonEncoder.VTYPE_FLOAT); @@ -60,27 +60,28 @@ public class JsonCodec implements Serializable { * * @param value * JSON array with two elements - * @param idMapper + * @param application * mapper between paintable ID and {@link Paintable} objects * @return converted value (does not contain JSON types) * @throws JSONException * if the conversion fails */ - public static Object decode(JSONArray value, PaintableIdMapper idMapper) + public static Object decode(JSONArray value, Application application) throws JSONException { - return decodeVariableValue(value.getString(0), value.get(1), idMapper); + return decodeVariableValue(value.getString(0), value.get(1), + application); } private static Object decodeVariableValue(String variableType, - Object value, PaintableIdMapper idMapper) throws JSONException { + Object value, Application application) throws JSONException { Object val = null; // TODO type checks etc. if (JsonEncoder.VTYPE_ARRAY.equals(variableType)) { - val = decodeArray((JSONArray) value, idMapper); + val = decodeArray((JSONArray) value, application); } else if (JsonEncoder.VTYPE_LIST.equals(variableType)) { - val = decodeList((JSONArray) value, idMapper); + val = decodeList((JSONArray) value, application); } else if (JsonEncoder.VTYPE_MAP.equals(variableType)) { - val = decodeMap((JSONObject) value, idMapper); + val = decodeMap((JSONObject) value, application); } else if (JsonEncoder.VTYPE_STRINGARRAY.equals(variableType)) { val = decodeStringArray((JSONArray) value); } else if (JsonEncoder.VTYPE_STRING.equals(variableType)) { @@ -100,27 +101,26 @@ public class JsonCodec implements Serializable { } else if (JsonEncoder.VTYPE_BOOLEAN.equals(variableType)) { // TODO handle properly val = Boolean.valueOf(String.valueOf(value)); - } else if (JsonEncoder.VTYPE_PAINTABLE.equals(variableType)) { - // TODO handle properly - val = idMapper.getPaintable(String.valueOf(value)); + } else if (JsonEncoder.VTYPE_CONNECTOR.equals(variableType)) { + val = application.getConnector(String.valueOf(value)); } else if (JsonEncoder.VTYPE_NULL.equals(variableType)) { val = null; } else { // Try to decode object using fields - return decodeObject(variableType, (JSONObject) value, idMapper); + return decodeObject(variableType, (JSONObject) value, application); } return val; } - private static Object decodeMap(JSONObject jsonMap, - PaintableIdMapper idMapper) throws JSONException { + private static Object decodeMap(JSONObject jsonMap, Application application) + throws JSONException { HashMap map = new HashMap(); Iterator it = jsonMap.keys(); while (it.hasNext()) { String key = it.next(); - map.put(key, decode(jsonMap.getJSONArray(key), idMapper)); + map.put(key, decode(jsonMap.getJSONArray(key), application)); } return map; } @@ -136,18 +136,18 @@ public class JsonCodec implements Serializable { } private static Object decodeArray(JSONArray jsonArray, - PaintableIdMapper idMapper) throws JSONException { - List list = decodeList(jsonArray, idMapper); + Application application) throws JSONException { + List list = decodeList(jsonArray, application); return list.toArray(new Object[list.size()]); } - private static List decodeList(JSONArray jsonArray, - PaintableIdMapper idMapper) throws JSONException { + private static List decodeList(JSONArray jsonArray, Application application) + throws JSONException { List list = new ArrayList(); for (int i = 0; i < jsonArray.length(); ++i) { // each entry always has two elements: type and value JSONArray entryArray = jsonArray.getJSONArray(i); - list.add(decode(entryArray, idMapper)); + list.add(decode(entryArray, application)); } return list; } @@ -158,19 +158,19 @@ public class JsonCodec implements Serializable { * * @param value * value to convert - * @param idMapper + * @param application * mapper between paintable ID and {@link Paintable} objects * @return JSON representation of the value * @throws JSONException * if encoding a value fails (e.g. NaN or infinite number) */ - public static JSONArray encode(Object value, PaintableIdMapper idMapper) + public static JSONArray encode(Object value, Application application) throws JSONException { - return encode(value, null, idMapper); + return encode(value, null, application); } public static JSONArray encode(Object value, Class valueType, - PaintableIdMapper idMapper) throws JSONException { + Application application) throws JSONException { if (null == value) { return combineTypeAndValue(JsonEncoder.VTYPE_NULL, JSONObject.NULL); @@ -189,20 +189,20 @@ public class JsonCodec implements Serializable { return combineTypeAndValue(getTransportType(value), value); } else if (value instanceof List) { List list = (List) value; - JSONArray jsonArray = encodeList(list, idMapper); + JSONArray jsonArray = encodeList(list, application); return combineTypeAndValue(JsonEncoder.VTYPE_LIST, jsonArray); } else if (value instanceof Object[]) { Object[] array = (Object[]) value; - JSONArray jsonArray = encodeArrayContents(array, idMapper); + JSONArray jsonArray = encodeArrayContents(array, application); return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray); } else if (value instanceof Map) { Map map = (Map) value; - JSONObject jsonMap = encodeMapContents(map, idMapper); + JSONObject jsonMap = encodeMapContents(map, application); return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap); - } else if (value instanceof Paintable) { - Paintable paintable = (Paintable) value; - return combineTypeAndValue(JsonEncoder.VTYPE_PAINTABLE, - idMapper.getPaintableId(paintable)); + } else if (value instanceof Connector) { + Connector connector = (Connector) value; + return combineTypeAndValue(JsonEncoder.VTYPE_CONNECTOR, + connector.getConnectorId()); } else if (getTransportType(value) != null) { return combineTypeAndValue(getTransportType(value), String.valueOf(value)); @@ -214,11 +214,11 @@ public class JsonCodec implements Serializable { } return combineTypeAndValue(valueType.getCanonicalName(), - encodeObject(value, idMapper)); + encodeObject(value, application)); } } - private static Object encodeObject(Object value, PaintableIdMapper idMapper) + private static Object encodeObject(Object value, Application application) throws JSONException { JSONObject jsonMap = new JSONObject(); @@ -232,7 +232,8 @@ public class JsonCodec implements Serializable { } Method getterMethod = pd.getReadMethod(); Object fieldValue = getterMethod.invoke(value, (Object[]) null); - jsonMap.put(fieldName, encode(fieldValue, fieldType, idMapper)); + jsonMap.put(fieldName, + encode(fieldValue, fieldType, application)); } } catch (Exception e) { // TODO: Should exceptions be handled in a different way? @@ -242,7 +243,7 @@ public class JsonCodec implements Serializable { } private static Object decodeObject(String type, - JSONObject serializedObject, PaintableIdMapper idMapper) + JSONObject serializedObject, Application application) throws JSONException { Class cls; @@ -260,7 +261,7 @@ public class JsonCodec implements Serializable { JSONArray encodedObject = serializedObject .getJSONArray(fieldName); pd.getWriteMethod().invoke(decodedObject, - decode(encodedObject, idMapper)); + decode(encodedObject, application)); } return decodedObject; @@ -280,32 +281,32 @@ public class JsonCodec implements Serializable { } private static JSONArray encodeArrayContents(Object[] array, - PaintableIdMapper idMapper) throws JSONException { + Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : array) { // TODO handle object graph loops? - jsonArray.put(encode(o, idMapper)); + jsonArray.put(encode(o, application)); } return jsonArray; } - private static JSONArray encodeList(List list, PaintableIdMapper idMapper) + private static JSONArray encodeList(List list, Application application) throws JSONException { JSONArray jsonArray = new JSONArray(); for (Object o : list) { // TODO handle object graph loops? - jsonArray.put(encode(o, idMapper)); + jsonArray.put(encode(o, application)); } return jsonArray; } private static JSONObject encodeMapContents(Map map, - PaintableIdMapper idMapper) throws JSONException { + Application application) throws JSONException { JSONObject jsonMap = new JSONObject(); for (String mapKey : map.keySet()) { // TODO handle object graph loops? Object mapValue = map.get(mapKey); - jsonMap.put(mapKey, encode(mapValue, idMapper)); + jsonMap.put(mapKey, encode(mapValue, application)); } return jsonMap; } diff --git a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java index 8a57d2110e..930a5bec34 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java +++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java @@ -34,9 +34,9 @@ import com.vaadin.terminal.Resource; import com.vaadin.terminal.StreamVariable; import com.vaadin.terminal.ThemeResource; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.ui.Alignment; import com.vaadin.ui.ClientWidget; -import com.vaadin.ui.Component; import com.vaadin.ui.CustomLayout; import com.vaadin.ui.Root; @@ -89,7 +89,7 @@ public class JsonPaintTarget implements PaintTarget { private final Collection paintedComponents = new HashSet(); - private Collection identifiersCreatedDueRefPaint; + // private Collection identifiersCreatedDueRefPaint; private Collection deferredPaintables; @@ -687,49 +687,46 @@ public class JsonPaintTarget implements PaintTarget { */ public PaintStatus startPaintable(Paintable paintable, String tagName) throws PaintException { + boolean topLevelPaintable = openPaintables.isEmpty(); + + System.out.println("startPaintable for " + + paintable.getClass().getName() + "@" + + Integer.toHexString(paintable.hashCode())); startTag(tagName, true); - final boolean isPreviouslyPainted = manager.hasPaintableId(paintable) - && (identifiersCreatedDueRefPaint == null || !identifiersCreatedDueRefPaint - .contains(paintable)) - && !deferredPaintables.contains(paintable); + + openPaintables.push(paintable); + openPaintableTags.push(tagName); + final String id = manager.getPaintableId(paintable); paintable.addListener(manager); addAttribute("id", id); // queue for painting later if already painting a paintable - boolean topLevelPaintableTag = openPaintables.isEmpty(); - - openPaintables.push(paintable); - openPaintableTags.push(tagName); - - if (!topLevelPaintableTag) { + if (!topLevelPaintable) { + // if (!deferredPaintables.contains(paintable)) { // notify manager: add to paint queue instead of painting now - manager.queuePaintable(paintable); - deferredPaintables.add(paintable); + // manager.queuePaintable(paintable); + // deferredPaintables.add(paintable); + // } return PaintStatus.DEFER; - } else if (cacheEnabled && isPreviouslyPainted) { - // cached (unmodified) paintable, paint the it now - paintedComponents.add(paintable); - deferredPaintables.remove(paintable); - return PaintStatus.CACHED; - } else { - // not a nested paintable, paint the it now - paintedComponents.add(paintable); - deferredPaintables.remove(paintable); + } - if (paintable instanceof CustomLayout) { - customLayoutArgumentsOpen = true; - } - return PaintStatus.PAINTING; + // not a nested paintable, paint the it now + paintedComponents.add(paintable); + // deferredPaintables.remove(paintable); + + if (paintable instanceof CustomLayout) { + customLayoutArgumentsOpen = true; } + return PaintStatus.PAINTING; } public void endPaintable(Paintable paintable) throws PaintException { Paintable openPaintable = openPaintables.peek(); if (paintable != openPaintable) { throw new PaintException("Invalid UIDL: closing wrong paintable: '" - + getPaintIdentifier(paintable) + "' expected: '" - + getPaintIdentifier(openPaintable) + "'."); + + manager.getPaintableId(paintable) + "' expected: '" + + manager.getPaintableId(openPaintable) + "'."); } // remove paintable from the stack openPaintables.pop(); @@ -738,12 +735,12 @@ public class JsonPaintTarget implements PaintTarget { } public String getPaintIdentifier(Paintable paintable) throws PaintException { - if (!manager.hasPaintableId(paintable)) { - if (identifiersCreatedDueRefPaint == null) { - identifiersCreatedDueRefPaint = new HashSet(); - } - identifiersCreatedDueRefPaint.add(paintable); - } + // if (!manager.hasPaintableId(paintable)) { + // if (identifiersCreatedDueRefPaint == null) { + // identifiersCreatedDueRefPaint = new HashSet(); + // } + // identifiersCreatedDueRefPaint.add(paintable); + // } return manager.getPaintableId(paintable); } @@ -1021,22 +1018,22 @@ public class JsonPaintTarget implements PaintTarget { return usedResources; } - /** - * Method to check if paintable is already painted into this target. - * - * @param p - * @return true if is not yet painted into this target and is connected to - * app - */ - public boolean needsToBePainted(Paintable p) { - if (paintedComponents.contains(p)) { - return false; - } else if (((Component) p).getApplication() == null) { - return false; - } else { - return true; - } - } + // /** + // * Method to check if paintable is already painted into this target. + // * + // * @param p + // * @return true if is not yet painted into this target and is connected to + // * app + // */ + // public boolean needsToBePainted(Paintable p) { + // if (paintedComponents.contains(p)) { + // return false; + // } else if (((Component) p).getApplication() == null) { + // return false; + // } else { + // return true; + // } + // } private static final Map, Class> widgetMappingCache = new HashMap, Class>(); @@ -1195,7 +1192,8 @@ public class JsonPaintTarget implements PaintTarget { public void addVariable(VariableOwner owner, String name, StreamVariable value) throws PaintException { - String url = manager.getStreamVariableTargetUrl(owner, name, value); + String url = manager.getStreamVariableTargetUrl((Connector) owner, + name, value); if (url != null) { addVariable(owner, name, url); } // else { //NOP this was just a cleanup by component } diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java index 49bcb7a79c..b3ec33a9e0 100644 --- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java @@ -6,6 +6,7 @@ package com.vaadin.terminal.gwt.server; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import javax.portlet.MimeResponse; @@ -22,12 +23,10 @@ import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.DeploymentConfiguration; import com.vaadin.terminal.PaintException; -import com.vaadin.terminal.Paintable; import com.vaadin.terminal.StreamVariable; -import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.WrappedRequest; import com.vaadin.terminal.WrappedResponse; -import com.vaadin.ui.Component; +import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.ui.Root; /** @@ -50,26 +49,36 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { String contentType = request.getContentType(); String name = request.getParameter("name"); String ownerId = request.getParameter("rec-owner"); - VariableOwner variableOwner = getVariableOwner(ownerId); - StreamVariable streamVariable = ownerToNameToStreamVariable.get( - variableOwner).get(name); + Connector owner = getConnector(getApplication(), ownerId); + StreamVariable streamVariable = ownerToNameToStreamVariable.get(owner) + .get(name); if (contentType.contains("boundary")) { doHandleSimpleMultipartFileUpload(request, response, - streamVariable, name, variableOwner, + streamVariable, name, owner, contentType.split("boundary=")[1]); } else { - doHandleXhrFilePost(request, response, streamVariable, name, - variableOwner, request.getContentLength()); + doHandleXhrFilePost(request, response, streamVariable, name, owner, + request.getContentLength()); } } @Override - protected void unregisterPaintable(Component p) { - super.unregisterPaintable(p); + protected void postPaint(Root root) { + super.postPaint(root); + + Application application = root.getApplication(); if (ownerToNameToStreamVariable != null) { - ownerToNameToStreamVariable.remove(p); + Iterator iterator = ownerToNameToStreamVariable.keySet() + .iterator(); + while (iterator.hasNext()) { + Connector owner = iterator.next(); + if (application.getConnector(owner.getConnectorId()) == null) { + // Owner is no longer attached to the application + iterator.remove(); + } + } } } @@ -83,13 +92,13 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { currentUidlResponse = null; } - private Map> ownerToNameToStreamVariable; + private Map> ownerToNameToStreamVariable; @Override - String getStreamVariableTargetUrl(VariableOwner owner, String name, + String getStreamVariableTargetUrl(Connector owner, String name, StreamVariable value) { if (ownerToNameToStreamVariable == null) { - ownerToNameToStreamVariable = new HashMap>(); + ownerToNameToStreamVariable = new HashMap>(); } Map nameToReceiver = ownerToNameToStreamVariable .get(owner); @@ -101,14 +110,14 @@ public class PortletCommunicationManager extends AbstractCommunicationManager { ResourceURL resurl = currentUidlResponse.createResourceURL(); resurl.setResourceID("UPLOAD"); resurl.setParameter("name", name); - resurl.setParameter("rec-owner", getPaintableId((Paintable) owner)); + resurl.setParameter("rec-owner", owner.getConnectorId()); resurl.setProperty("name", name); - resurl.setProperty("rec-owner", getPaintableId((Paintable) owner)); + resurl.setProperty("rec-owner", owner.getConnectorId()); return resurl.toString(); } @Override - protected void cleanStreamVariable(VariableOwner owner, String name) { + protected void cleanStreamVariable(Connector owner, String name) { Map map = ownerToNameToStreamVariable .get(owner); map.remove(name); diff --git a/src/com/vaadin/ui/AbstractComponent.java b/src/com/vaadin/ui/AbstractComponent.java index e2e8a9693e..c4bfa9298e 100644 --- a/src/com/vaadin/ui/AbstractComponent.java +++ b/src/com/vaadin/ui/AbstractComponent.java @@ -147,6 +147,8 @@ public abstract class AbstractComponent implements Component, MethodEventSource */ private ArrayList pendingInvocations = new ArrayList(); + private String connectorId; + /* Constructor */ /** @@ -651,6 +653,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource * interface. */ public void attach() { + getRoot().componentAttached(this); requestRepaint(); if (!getState().isVisible()) { /* @@ -676,6 +679,7 @@ public abstract class AbstractComponent implements Component, MethodEventSource // compiler happy actionManager.setViewer((Root) null); } + getRoot().componentDetached(this); } /** @@ -1752,7 +1756,13 @@ public abstract class AbstractComponent implements Component, MethodEventSource } public String getConnectorId() { - throw new RuntimeException( - "TODO: Move connector id handling to AbstractComponent"); + if (connectorId == null) { + if (getApplication() == null) { + throw new RuntimeException( + "Component must be attached to an application when getConnectorId() is called for the first time"); + } + connectorId = getApplication().createConnectorId(this); + } + return connectorId; } } diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index 6ca4d5e286..ae6fcd9378 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -18,7 +18,7 @@ import com.vaadin.terminal.Resource; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.VariableOwner; import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.server.ClientConnector; import com.vaadin.terminal.gwt.server.RpcTarget; /** @@ -54,7 +54,7 @@ import com.vaadin.terminal.gwt.server.RpcTarget; * @VERSION@ * @since 3.0 */ -public interface Component extends Connector, Paintable, VariableOwner, +public interface Component extends ClientConnector, Paintable, VariableOwner, Sizeable, Serializable, RpcTarget { /** diff --git a/src/com/vaadin/ui/DirtyConnectorTracker.java b/src/com/vaadin/ui/DirtyConnectorTracker.java new file mode 100644 index 0000000000..2e844e096d --- /dev/null +++ b/src/com/vaadin/ui/DirtyConnectorTracker.java @@ -0,0 +1,102 @@ +package com.vaadin.ui; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import com.vaadin.terminal.Paintable.RepaintRequestEvent; +import com.vaadin.terminal.Paintable.RepaintRequestListener; + +public class DirtyConnectorTracker implements RepaintRequestListener { + private Set dirtyComponents = new HashSet(); + private Root root; + + public static Logger getLogger() { + return Logger.getLogger(DirtyConnectorTracker.class.getName()); + } + + public DirtyConnectorTracker(Root root) { + this.root = root; + } + + public void repaintRequested(RepaintRequestEvent event) { + markDirty((Component) event.getPaintable()); + } + + public void componentAttached(Component component) { + component.addListener(this); + markDirty(component); + } + + private void markDirty(Component component) { + // TODO Remove debug info + if (!dirtyComponents.contains(component)) { + debug(component, "is now dirty"); + + } + dirtyComponents.add(component); + } + + private void debug(Component component, String string) { + getLogger().info(getDebugInfo(component) + " " + string); + } + + private void markClean(Component component) { + // TODO Remove debug info + if (dirtyComponents.contains(component)) { + debug(component, "is no longer dirty"); + } + dirtyComponents.remove(component); + + // TODO .... WTF .... + component.requestRepaintRequests(); + + } + + private String getDebugInfo(Component component) { + String message = getObjectString(component); + if (component.getParent() != null) { + message += " (parent: " + getObjectString(component.getParent()) + + ")"; + } + return message; + } + + private String getObjectString(Object component) { + return component.getClass().getName() + "@" + + Integer.toHexString(component.hashCode()); + } + + public void componentDetached(Component component) { + component.removeListener(this); + markClean(component); + } + + public void markAllComponentsDirty() { + markComponentsDirtyRecursively(root); + System.out.println("All components are now dirty"); + + } + + public void markAllComponentsClean() { + dirtyComponents.clear(); + System.out.println("All components are now clean"); + } + + private void markComponentsDirtyRecursively(Component c) { + markDirty(c); + if (c instanceof HasComponents) { + HasComponents container = (HasComponents) c; + for (Component child : container) { + markComponentsDirtyRecursively(child); + } + } + + } + + public Collection getDirtyComponents() { + return dirtyComponents; + } + +} diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 42d921d93f..802fc0a45f 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -409,6 +409,9 @@ public abstract class Root extends AbstractComponentContainer implements /** Identifies the click event */ private static final String CLICK_EVENT_ID = VView.CLICK_EVENT_ID; + private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( + this); + /** * Creates a new empty root without a caption. This root will have a * {@link VerticalLayout} with margins enabled as its content. @@ -1567,4 +1570,17 @@ public abstract class Root extends AbstractComponentContainer implements // TODO How can a Root be invisible? What does it mean? return isVisible() && isEnabled(); } + + public DirtyConnectorTracker getDirtyConnectorTracker() { + return dirtyConnectorTracker; + } + + public void componentAttached(Component component) { + getDirtyConnectorTracker().componentAttached(component); + } + + public void componentDetached(Component component) { + getDirtyConnectorTracker().componentDetached(component); + } + }