newChildMap);
/**
* Detaches the component from its parent. After calling this method
* getParent(component) and getChildMap(parent).get(name) should return null where
* parent is the old parent of the component and name the name of the component.
*
*
It is important that after detaching, the method {@link #getComponentByUid}
* still returns the component.
*/
protected abstract void detachFromParent(Component component);
/**
* Try to find the component by given uid. If it is not found, returns null.
*/
protected abstract Component getComponentByUid(String uid);
/**
* Creates a new component with the given uid. The method may
* assume that there is no existing component with the same uid.
* After the method returns, getComponentByUid must return the
* same component with the given uid.
*/
protected abstract Component createComponent(String uid);
/**
* Removes the component. The method may assume that the component
* has already been detached from its parent.
*/
protected abstract void removeComponent(Component component);
/**
* Calls connectionResolvedCallback with the result of resolving the given connection point
* of the given component. The method is called only for components whose updating is completed.
*/
protected abstract void resolveConnectionPointLocally(Component component, String connectionPointName,
ConnectionResolvedCallback connectionResolvedCallback);
/**
*
Updates the component based on the given name, type, properties and connections.
*
*
When updating has been finished (this may happen after this method has been finished)
* the method {@link #componentUpdated} must be called.
*
*
The method may use {@link resolveReference} to resolve connection point references.
*/
protected abstract void updateComponent(Component component, String name,
String typeId, Collection properties,
Collection connections);
/**
* The component that synchronizer is currently updating.
* If currentComponent=null, beginComponent method is not yet called
* or endComponent is called matching the first beginComponent call.
*/
private Component currentComponent;
/**
* The set of components which or whose children may still be
* updated by calling beginComponent during this synchronization.
*/
private THashSet mayBeUpdated = new THashSet();
/**
* The key set of this map contains all components in {@link #mayBeUpdated} and additionally
* components whose updating has began by beginComponent call, but that are not yet completely
* updated (for example because they are waiting other unresolved components).
*/
private THashMap>> pendingResolves =
new THashMap>>();
/**
* This set contains all components that currently have no parents and will be removed
* at the end of synchronization.
*/
private THashSet pendingRemoval = new THashSet();
private static class PendingResolve {
public final Component component;
public final String connectionPoint;
public final ConnectionResolvedCallback connectionResolvedCallback;
public PendingResolve(Component component, String connectionPoint,
ConnectionResolvedCallback connectionResolvedCallback) {
this.component = component;
this.connectionPoint = connectionPoint;
this.connectionResolvedCallback = connectionResolvedCallback;
}
@Override
public String toString() {
return connectionPoint + "->" + connectionResolvedCallback;
}
}
@Override
public void beginSynchronization() {
currentComponent = null;
}
@Override
public void endSynchronization() {
if(currentComponent != null)
throw new SynchronizationException("beginComponent is called more often than endComponent at the end of synchronization.");
for(Component component : pendingRemoval)
removeComponent(component);
pendingRemoval.clear();
}
@Override
public void beginComponent(String name, String typeId,
Collection properties,
Collection connections, Collection children)
throws SynchronizationException {
if(currentComponent == null) {
currentComponent = getConfigurationRoot();
if(currentComponent == null)
throw new SynchronizationException("getConfiguration root returned null.");
}
else {
currentComponent = getChildMap(currentComponent).get(name);
if(currentComponent == null)
throw new SynchronizationException("Didn't find '"+name+"'. "
+ "It should have been mentioned as a child in the parent beginComponent method.");
}
// Handle children
if(!getChildMap(currentComponent).isEmpty() || !children.isEmpty()){
THashMap newChildMap =
new THashMap();
for(ChildInfo info : children) {
// Detach from the existing configuration the children with
// the right uids or create new components if uid is unknown.
Component component = getComponentByUid(info.uid);
if(component == null)
component = createComponent(info.uid);
else {
pendingRemoval.remove(component);
detachFromParent(component);
}
newChildMap.put(info.name, component);
componentMayBeUpdated(component);
}
// Put old children not detached in the previous phase
// to the pending removal set. They might have been
// moved somewhere else.
Map oldChildMap = getChildMap(currentComponent);
for(Component component : oldChildMap.values()) {
detachFromParent(component);
pendingRemoval.add(component);
}
setChildMap(currentComponent, newChildMap);
}
// Update/create component itself
mayBeUpdated.remove(currentComponent);
updateComponent(currentComponent, name, typeId, properties, connections);
}
@Override
public void endComponent() {
if(currentComponent == null)
throw new SynchronizationException("endComponent is called more often than beginComponent.");
for(Component child : getChildMap(currentComponent).values())
if(mayBeUpdated.remove(child))
// clear pending status of components which will not change
// during synchronization
componentUpdated(child);
currentComponent = getParent(currentComponent);
}
private void componentMayBeUpdated(Component component) {
mayBeUpdated.add(component);
pendingResolves.put(component, new ArrayList>(2));
}
/**
* Signals that the component is updated so far that its connection may be resolved
* (with resolveConnectionPointLocally).
*/
public void componentUpdated(Component component) {
ArrayList> resolves = pendingResolves.remove(component);
if(resolves != null)
for(PendingResolve resolve : resolves) {
resolveConnectionPoint(resolve.component,
resolve.connectionPoint,
resolve.connectionResolvedCallback);
}
}
/**
* Resolves relative connection point reference starting from the given component and calls
* ConnectionResolvedCallback with the resolved reference.
*/
public void resolveConnectionPoint(Component component, String connectionPoint, ConnectionResolvedCallback connectionResolvedCallback) {
int pos = 0;
while(true) {
char c = connectionPoint.charAt(pos++);
switch(c) {
case '.':
component = getParent(component);
break;
case '/': {
int endPos = pos;
while(true) {
c = connectionPoint.charAt(endPos);
if(c == '/' || c == '#')
break;
++endPos;
}
String segment = URIStringUtils.unescape(connectionPoint.substring(pos, endPos));
pos = endPos;
component = getChildMap(component).get(segment);
if(component == null) {
String message = "Couldn't resolve " + connectionPoint +
", because child " + segment + " does not exist.";
reportProblem(message);
return;
}
ArrayList> pendingList = pendingResolves.get(component);
if(pendingList != null) {
pendingList.add(
new PendingResolve(
component, connectionPoint.substring(pos), connectionResolvedCallback));
return;
}
} break;
case '#': {
String segment = connectionPoint.substring(pos);
resolveConnectionPointLocally(component, segment, connectionResolvedCallback);
} return;
}
}
}
}