}
// Create fake server response that says that the uiConnector
// has no children
JSONObject fakeHierarchy = new JSONObject();
fakeHierarchy.put(uiConnectorId, new JSONArray());
JSONObject fakeJson = new JSONObject();
fakeJson.put("hierarchy", fakeHierarchy);
ValueMap fakeValueMap = fakeJson.getJavaScriptObject().cast();
// Update hierarchy based on the fake response
ConnectorHierarchyUpdateResult connectorHierarchyUpdateResult = updateConnectorHierarchy(fakeValueMap);
// Send hierarchy events based on the fake update
sendHierarchyChangeEvents(connectorHierarchyUpdateResult.events);
// Unregister all the old connectors that have now been removed
unregisterRemovedConnectors();
getLayoutManager().cleanMeasuredSizes();
}
private void updateCaptions(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents,
FastStringSet parentChangedIds) {
Profiler.enter("updateCaptions");
/*
* Find all components that might need a caption update based on
* pending state and hierarchy changes
*/
FastStringSet needsCaptionUpdate = FastStringSet.create();
needsCaptionUpdate.addAll(parentChangedIds);
// Find components with potentially changed caption state
int size = pendingStateChangeEvents.size();
for (int i = 0; i < size; i++) {
StateChangeEvent event = pendingStateChangeEvents.get(i);
if (VCaption.mightChange(event)) {
ServerConnector connector = event.getConnector();
needsCaptionUpdate.add(connector.getConnectorId());
}
}
ConnectorMap connectorMap = getConnectorMap();
// Update captions for all suitable candidates
JsArrayString dump = needsCaptionUpdate.dump();
int needsUpdateLength = dump.length();
for (int i = 0; i < needsUpdateLength; i++) {
String childId = dump.get(i);
ServerConnector child = connectorMap.getConnector(childId);
if (child instanceof ComponentConnector
&& ((ComponentConnector) child)
.delegateCaptionHandling()) {
ServerConnector parent = child.getParent();
if (parent instanceof HasComponentsConnector) {
Profiler.enter("HasComponentsConnector.updateCaption");
((HasComponentsConnector) parent)
.updateCaption((ComponentConnector) child);
Profiler.leave("HasComponentsConnector.updateCaption");
}
}
}
Profiler.leave("updateCaptions");
}
private void delegateToWidget(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents) {
Profiler.enter("@DelegateToWidget");
VConsole.log(" * Running @DelegateToWidget");
// Keep track of types that have no @DelegateToWidget in their
// state to optimize performance
FastStringSet noOpTypes = FastStringSet.create();
int size = pendingStateChangeEvents.size();
for (int eventIndex = 0; eventIndex < size; eventIndex++) {
StateChangeEvent sce = pendingStateChangeEvents
.get(eventIndex);
ServerConnector connector = sce.getConnector();
if (connector instanceof ComponentConnector) {
String className = connector.getClass().getName();
if (noOpTypes.contains(className)) {
continue;
}
ComponentConnector component = (ComponentConnector) connector;
Type stateType = AbstractConnector
.getStateType(component);
JsArrayString delegateToWidgetProperties = stateType
.getDelegateToWidgetProperties();
if (delegateToWidgetProperties == null) {
noOpTypes.add(className);
continue;
}
int length = delegateToWidgetProperties.length();
for (int i = 0; i < length; i++) {
String propertyName = delegateToWidgetProperties
.get(i);
if (sce.hasPropertyChanged(propertyName)) {
Property property = stateType
.getProperty(propertyName);
String method = property
.getDelegateToWidgetMethodName();
Profiler.enter("doDelegateToWidget");
doDelegateToWidget(component, property, method);
Profiler.leave("doDelegateToWidget");
}
}
}
}
Profiler.leave("@DelegateToWidget");
}
private void doDelegateToWidget(ComponentConnector component,
Property property, String methodName) {
Type type = TypeData.getType(component.getClass());
try {
Type widgetType = type.getMethod("getWidget")
.getReturnType();
Widget widget = component.getWidget();
Object propertyValue = property.getValue(component
.getState());
widgetType.getMethod(methodName).invoke(widget,
propertyValue);
} catch (NoDataException e) {
throw new RuntimeException(
"Missing data needed to invoke @DelegateToWidget for "
+ Util.getSimpleName(component), e);
}
}
/**
* Sends the state change events created while updating the state
* information.
*
* This must be called after hierarchy change listeners have been
* called. At least caption updates for the parent are strange if
* fired from state change listeners and thus calls the parent
* BEFORE the parent is aware of the child (through a
* ConnectorHierarchyChangedEvent)
*
* @param pendingStateChangeEvents
* The events to send
*/
private void sendStateChangeEvents(
JsArrayObject<StateChangeEvent> pendingStateChangeEvents) {
Profiler.enter("sendStateChangeEvents");
VConsole.log(" * Sending state change events");
int size = pendingStateChangeEvents.size();
for (int i = 0; i < size; i++) {
StateChangeEvent sce = pendingStateChangeEvents.get(i);
try {
sce.getConnector().fireEvent(sce);
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.leave("sendStateChangeEvents");
}
private void unregisterRemovedConnectors() {
Profiler.enter("unregisterRemovedConnectors");
int unregistered = 0;
JsArrayObject<ServerConnector> currentConnectors = connectorMap
.getConnectorsAsJsArray();
int size = currentConnectors.size();
for (int i = 0; i < size; i++) {
ServerConnector c = currentConnectors.get(i);
if (c.getParent() != null) {
// only do this check if debug mode is active
if (ApplicationConfiguration.isDebugMode()) {
Profiler.enter("unregisterRemovedConnectors check parent - this is only performed in debug mode");
// this is slow for large layouts, 25-30% of total
// time for some operations even on modern browsers
if (!c.getParent().getChildren().contains(c)) {
VConsole.error("ERROR: Connector is connected to a parent but the parent does not contain the connector");
}
Profiler.leave("unregisterRemovedConnectors check parent - this is only performed in debug mode");
}
} else if (c == getUIConnector()) {
// UIConnector for this connection, leave as-is
} else if (c instanceof WindowConnector
&& getUIConnector().hasSubWindow(
(WindowConnector) c)) {
// Sub window attached to this UIConnector, leave
// as-is
} else {
// The connector has been detached from the
// hierarchy, unregister it and any possible
// children. The UIConnector should never be
// unregistered even though it has no parent.
Profiler.enter("unregisterRemovedConnectors unregisterConnector");
connectorMap.unregisterConnector(c);
Profiler.leave("unregisterRemovedConnectors unregisterConnector");
unregistered++;
}
}
VConsole.log("* Unregistered " + unregistered + " connectors");
Profiler.leave("unregisterRemovedConnectors");
}
private JsArrayString createConnectorsIfNeeded(ValueMap json) {
VConsole.log(" * Creating connectors (if needed)");
JsArrayString createdConnectors = JavaScriptObject
.createArray().cast();
if (!json.containsKey("types")) {
return createdConnectors;
}
Profiler.enter("Creating connectors");
ValueMap types = json.getValueMap("types");
JsArrayString keyArray = types.getKeyArray();
for (int i = 0; i < keyArray.length(); i++) {
try {
String connectorId = keyArray.get(i);
ServerConnector connector = connectorMap
.getConnector(connectorId);
if (connector != null) {
continue;
}
int connectorType = Integer.parseInt(types
.getString(connectorId));
Class<? extends ServerConnector> connectorClass = configuration
.getConnectorClassByEncodedTag(connectorType);
// Connector does not exist so we must create it
if (connectorClass != uIConnector.getClass()) {
// create, initialize and register the paintable
Profiler.enter("ApplicationConnection.getConnector");
connector = getConnector(connectorId, connectorType);
Profiler.leave("ApplicationConnection.getConnector");
createdConnectors.push(connectorId);
} else {
// First UIConnector update. Before this the
// UIConnector has been created but not
// initialized as the connector id has not been
// known
connectorMap.registerConnector(connectorId,
uIConnector);
uIConnector.doInit(connectorId,
ApplicationConnection.this);
createdConnectors.push(connectorId);
}
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.leave("Creating connectors");
return createdConnectors;
}
private void updateVaadin6StyleConnectors(ValueMap json) {
Profiler.enter("updateVaadin6StyleConnectors");
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 legacyConnector = (ComponentConnector) connectorMap
.getConnector(connectorId);
if (legacyConnector instanceof Paintable) {
String key = null;
if (Profiler.isEnabled()) {
key = "updateFromUIDL for "
+ Util.getSimpleName(legacyConnector);
Profiler.enter(key);
}
((Paintable) legacyConnector).updateFromUIDL(uidl,
ApplicationConnection.this);
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} else if (legacyConnector == null) {
VConsole.error("Received update for "
+ uidl.getTag()
+ ", but there is no such paintable ("
+ connectorId + ") rendered.");
} else {
VConsole.error("Server sent Vaadin 6 style updates for "
+ Util.getConnectorString(legacyConnector)
+ " but this is not a Vaadin 6 Paintable");
}
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.leave("updateVaadin6StyleConnectors");
}
private void sendHierarchyChangeEvents(
JsArrayObject<ConnectorHierarchyChangeEvent> events) {
int eventCount = events.size();
if (eventCount == 0) {
return;
}
Profiler.enter("sendHierarchyChangeEvents");
VConsole.log(" * Sending hierarchy change events");
for (int i = 0; i < eventCount; i++) {
ConnectorHierarchyChangeEvent event = events.get(i);
try {
logHierarchyChange(event);
event.getConnector().fireEvent(event);
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.leave("sendHierarchyChangeEvents");
}
private void logHierarchyChange(ConnectorHierarchyChangeEvent event) {
if (true) {
// Always disabled for now. Can be enabled manually
return;
}
VConsole.log("Hierarchy changed for "
+ Util.getConnectorString(event.getConnector()));
String oldChildren = "* Old children: ";
for (ComponentConnector child : event.getOldChildren()) {
oldChildren += Util.getConnectorString(child) + " ";
}
VConsole.log(oldChildren);
String newChildren = "* New children: ";
HasComponentsConnector parent = (HasComponentsConnector) event
.getConnector();
for (ComponentConnector child : parent.getChildComponents()) {
newChildren += Util.getConnectorString(child) + " ";
}
VConsole.log(newChildren);
}
private JsArrayObject<StateChangeEvent> updateConnectorState(
ValueMap json, JsArrayString createdConnectorIds) {
JsArrayObject<StateChangeEvent> events = JavaScriptObject
.createArray().cast();
VConsole.log(" * Updating connector states");
if (!json.containsKey("state")) {
return events;
}
Profiler.enter("updateConnectorState");
FastStringSet remainingNewConnectors = FastStringSet.create();
remainingNewConnectors.addAll(createdConnectorIds);
// 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);
ServerConnector connector = connectorMap
.getConnector(connectorId);
if (null != connector) {
Profiler.enter("updateConnectorState inner loop");
if (Profiler.isEnabled()) {
Profiler.enter("Decode connector state "
+ Util.getSimpleName(connector));
}
JSONObject stateJson = new JSONObject(
states.getJavaScriptObject(connectorId));
if (connector instanceof HasJavaScriptConnectorHelper) {
((HasJavaScriptConnectorHelper) connector)
.getJavascriptConnectorHelper()
.setNativeState(
stateJson.getJavaScriptObject());
}
SharedState state = connector.getState();
Profiler.enter("updateConnectorState decodeValue");
JsonDecoder.decodeValue(new Type(state.getClass()
.getName(), null), stateJson, state,
ApplicationConnection.this);
Profiler.leave("updateConnectorState decodeValue");
if (Profiler.isEnabled()) {
Profiler.leave("Decode connector state "
+ Util.getSimpleName(connector));
}
Profiler.enter("updateConnectorState create event");
boolean isNewConnector = remainingNewConnectors
.contains(connectorId);
if (isNewConnector) {
remainingNewConnectors.remove(connectorId);
}
StateChangeEvent event = new StateChangeEvent(
connector, stateJson, isNewConnector);
events.add(event);
Profiler.leave("updateConnectorState create event");
Profiler.leave("updateConnectorState inner loop");
}
} catch (final Throwable e) {
VConsole.error(e);
}
}
Profiler.enter("updateConnectorState newWithoutState");
// Fire events for properties using the default value for newly
// created connectors even if there were no state changes
JsArrayString dump = remainingNewConnectors.dump();
int length = dump.length();
for (int i = 0; i < length; i++) {
String connectorId = dump.get(i);
ServerConnector connector = connectorMap
.getConnector(connectorId);
StateChangeEvent event = new StateChangeEvent(connector,
new JSONObject(), true);
events.add(event);
}
Profiler.leave("updateConnectorState newWithoutState");
Profiler.leave("updateConnectorState");
return events;
}
/**
* 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 and a list of all
* connectors for which the parent has changed
*/
private ConnectorHierarchyUpdateResult updateConnectorHierarchy(
ValueMap json) {
ConnectorHierarchyUpdateResult result = new ConnectorHierarchyUpdateResult();
VConsole.log(" * Updating connector hierarchy");
if (!json.containsKey("hierarchy")) {
return result;
}
Profiler.enter("updateConnectorHierarchy");
FastStringSet maybeDetached = FastStringSet.create();
ValueMap hierarchies = json.getValueMap("hierarchy");
JsArrayString hierarchyKeys = hierarchies.getKeyArray();
for (int i = 0; i < hierarchyKeys.length(); i++) {
try {
Profiler.enter("updateConnectorHierarchy hierarchy entry");
String connectorId = hierarchyKeys.get(i);
ServerConnector parentConnector = connectorMap
.getConnector(connectorId);
JsArrayString childConnectorIds = hierarchies
.getJSStringArray(connectorId);
int childConnectorSize = childConnectorIds.length();
Profiler.enter("updateConnectorHierarchy find new connectors");
List<ServerConnector> newChildren = new ArrayList<ServerConnector>();
List<ComponentConnector> newComponents = new ArrayList<ComponentConnector>();
for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) {
String childConnectorId = childConnectorIds
.get(connectorIndex);
ServerConnector childConnector = connectorMap
.getConnector(childConnectorId);
if (childConnector == null) {
VConsole.error("Hierarchy claims that "
+ childConnectorId
+ " is a child for "
+ connectorId
+ " ("
+ parentConnector.getClass().getName()
+ ") but no connector with id "
+ childConnectorId
+ " has been registered. "
+ "More information might be available in the server-side log if assertions are enabled");
continue;
}
newChildren.add(childConnector);
if (childConnector instanceof ComponentConnector) {
newComponents
.add((ComponentConnector) childConnector);
} else if (!(childConnector instanceof AbstractExtensionConnector)) {
throw new IllegalStateException(
Util.getConnectorString(childConnector)
+ " is not a ComponentConnector nor an AbstractExtensionConnector");
}
if (childConnector.getParent() != parentConnector) {
childConnector.setParent(parentConnector);
result.parentChangedIds.add(childConnectorId);
// Not detached even if previously removed from
// parent
maybeDetached.remove(childConnectorId);
}
}
Profiler.leave("updateConnectorHierarchy find new connectors");
// 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
List<ServerConnector> oldChildren = parentConnector
.getChildren();
boolean actuallyChanged = !Util.collectionsEquals(
oldChildren, newChildren);
if (!actuallyChanged) {
continue;
}
Profiler.enter("updateConnectorHierarchy handle HasComponentsConnector");
if (parentConnector instanceof HasComponentsConnector) {
HasComponentsConnector ccc = (HasComponentsConnector) parentConnector;
List<ComponentConnector> oldComponents = ccc
.getChildComponents();
if (!Util.collectionsEquals(oldComponents,
newComponents)) {
// Fire change event if the hierarchy has
// changed
ConnectorHierarchyChangeEvent event = GWT
.create(ConnectorHierarchyChangeEvent.class);
event.setOldChildren(oldComponents);
event.setConnector(parentConnector);
ccc.setChildComponents(newComponents);
result.events.add(event);
}
} else if (!newComponents.isEmpty()) {
VConsole.error("Hierachy claims "
+ Util.getConnectorString(parentConnector)
+ " has component children even though it isn't a HasComponentsConnector");
}
Profiler.leave("updateConnectorHierarchy handle HasComponentsConnector");
Profiler.enter("updateConnectorHierarchy setChildren");
parentConnector.setChildren(newChildren);
Profiler.leave("updateConnectorHierarchy setChildren");
Profiler.enter("updateConnectorHierarchy find removed children");
/*
* Find children removed from this parent and mark for
* removal unless they are already attached to some
* other parent.
*/
for (ServerConnector oldChild : oldChildren) {
if (oldChild.getParent() != parentConnector) {
// Ignore if moved to some other connector
continue;
}
if (!newChildren.contains(oldChild)) {
/*
* Consider child detached for now, will be
* cleared if it is later on added to some other
* parent.
*/
maybeDetached.add(oldChild.getConnectorId());
}
}
Profiler.leave("updateConnectorHierarchy find removed children");
} catch (final Throwable e) {
VConsole.error(e);
} finally {
Profiler.leave("updateConnectorHierarchy hierarchy entry");
}
}
Profiler.enter("updateConnectorHierarchy detach removed connectors");
/*
* Connector is in maybeDetached at this point if it has been
* removed from its parent but not added to any other parent
*/
JsArrayString maybeDetachedArray = maybeDetached.dump();
for (int i = 0; i < maybeDetachedArray.length(); i++) {
ServerConnector removed = connectorMap
.getConnector(maybeDetachedArray.get(i));
recursivelyDetach(removed, result.events);
}
Profiler.leave("updateConnectorHierarchy detach removed connectors");
Profiler.leave("updateConnectorHierarchy");
return result;
}
private void recursivelyDetach(ServerConnector connector,
JsArrayObject<ConnectorHierarchyChangeEvent> events) {
/*
* Reset state in an attempt to keep it consistent with the
* hierarchy. No children and no parent is the initial situation
* for the hierarchy, so changing the state to its initial value
* is the closest we can get without data from the server.
* #10151
*/
Profiler.enter("ApplicationConnection recursivelyDetach reset state");
try {
Profiler.enter("ApplicationConnection recursivelyDetach reset state - getStateType");
Type stateType = AbstractConnector.getStateType(connector);
Profiler.leave("ApplicationConnection recursivelyDetach reset state - getStateType");
// Empty state instance to get default property values from
Profiler.enter("ApplicationConnection recursivelyDetach reset state - createInstance");
Object defaultState = stateType.createInstance();
Profiler.leave("ApplicationConnection recursivelyDetach reset state - createInstance");
if (connector instanceof AbstractConnector) {
// optimization as the loop setting properties is very
// slow, especially on IE8
replaceState((AbstractConnector) connector,
defaultState);
} else {
SharedState state = connector.getState();
Profiler.enter("ApplicationConnection recursivelyDetach reset state - properties");
JsArrayObject<Property> properties = stateType
.getPropertiesAsArray();
int size = properties.size();
for (int i = 0; i < size; i++) {
Property property = properties.get(i);
property.setValue(state,
property.getValue(defaultState));
}
Profiler.leave("ApplicationConnection recursivelyDetach reset state - properties");
}
} catch (NoDataException e) {
throw new RuntimeException("Can't reset state for "
+ Util.getConnectorString(connector), e);
} finally {
Profiler.leave("ApplicationConnection recursivelyDetach reset state");
}
Profiler.enter("ApplicationConnection recursivelyDetach perform detach");
/*
* Recursively detach children to make sure they get
* setParent(null) and hierarchy change events as needed.
*/
for (ServerConnector child : connector.getChildren()) {
/*
* Server doesn't send updated child data for removed
* connectors -> ignore child that still seems to be a child
* of this connector although it has been moved to some part
* of the hierarchy that is not detached.
*/
if (child.getParent() != connector) {
continue;
}
recursivelyDetach(child, events);
}
Profiler.leave("ApplicationConnection recursivelyDetach perform detach");
/*
* Clear child list and parent
*/
Profiler.enter("ApplicationConnection recursivelyDetach clear children and parent");
connector
.setChildren(Collections.<ServerConnector> emptyList());
connector.setParent(null);
Profiler.leave("ApplicationConnection recursivelyDetach clear children and parent");
/*
* Create an artificial hierarchy event for containers to give
* it a chance to clean up after its children if it has any
*/
Profiler.enter("ApplicationConnection recursivelyDetach create hierarchy event");
if (connector instanceof HasComponentsConnector) {
HasComponentsConnector ccc = (HasComponentsConnector) connector;
List<ComponentConnector> oldChildren = ccc
.getChildComponents();
if (!oldChildren.isEmpty()) {
/*
* HasComponentsConnector has a separate child component
* list that should also be cleared
*/
ccc.setChildComponents(Collections
.<ComponentConnector> emptyList());
// Create event and add it to the list of pending events
ConnectorHierarchyChangeEvent event = GWT
.create(ConnectorHierarchyChangeEvent.class);
event.setConnector(connector);
event.setOldChildren(oldChildren);
events.add(event);
}
}
Profiler.leave("ApplicationConnection recursivelyDetach create hierarchy event");
}
private native void replaceState(AbstractConnector connector,
Object defaultState)
/*-{
connector.@com.vaadin.client.ui.AbstractConnector::state = defaultState;
}-*/;
private void handleRpcInvocations(ValueMap json) {
if (json.containsKey("rpc")) {
Profiler.enter("handleRpcInvocations");
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);
rpcManager.parseAndApplyInvocation(rpcCall,
ApplicationConnection.this);
} catch (final Throwable e) {
VConsole.error(e);
}