/*******************************************************************************
* Copyright (c) 2012, 2014 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.processes.core.model.runtime.services;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IProcesses;
import org.eclipse.tcf.services.ISysMonitor;
import org.eclipse.tcf.te.runtime.model.interfaces.IContainerModelNode;
import org.eclipse.tcf.te.runtime.model.interfaces.IModelNode;
import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx;
import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx.QueryState;
import org.eclipse.tcf.te.runtime.model.interfaces.contexts.IAsyncRefreshableCtx.QueryType;
import org.eclipse.tcf.te.runtime.services.ServiceUtils;
import org.eclipse.tcf.te.tcf.core.model.services.AbstractModelService;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNode;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.IProcessContextNodeProperties;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModel;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelRefreshService;
import org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelUpdateService;
/**
* Runtime model update service implementation.
*/
public class RuntimeModelUpdateService extends AbstractModelService<IRuntimeModel> implements IRuntimeModelUpdateService {
/**
* Constructor.
*
* @param model The parent model. Must not be <code>null</code>.
*/
public RuntimeModelUpdateService(IRuntimeModel model) {
super(model);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelUpdateService#add(org.eclipse.tcf.te.runtime.model.interfaces.IModelNode)
*/
@Override
public void add(IModelNode node) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(node);
getModel().add(node);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelUpdateService#remove(org.eclipse.tcf.te.runtime.model.interfaces.IModelNode)
*/
@Override
public void remove(IModelNode node) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(node);
Assert.isNotNull(node.getParent());
node.getParent().remove(node, false);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.processes.core.model.interfaces.runtime.IRuntimeModelUpdateService#updateChildren(org.eclipse.tcf.te.runtime.model.interfaces.IContainerModelNode, org.eclipse.tcf.te.runtime.model.interfaces.IContainerModelNode)
*/
@Override
public void updateChildren(IContainerModelNode dst, IContainerModelNode src) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(dst);
Assert.isNotNull(src);
boolean dstNodeChanged = __updateChildren(dst, src);
// Fire a properties changed event if the destination node changed
if (dstNodeChanged) {
dst.fireChangeEvent(IContainerModelNode.NOTIFY_CHANGED, null, dst.getProperties());
}
}
/**
* Lookup a node with the given id in the given list.
*
* @param id The node id. Must not be <code>null</code>.
* @param list The list. Must not be <code>null</code>.
*
* @return The matching process context node or <code>null</code> if not found.
*/
/* default */ IProcessContextNode findInList(String id, List<IProcessContextNode> list) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(id);
Assert.isNotNull(list);
IProcessContextNode node = null;
for (IProcessContextNode candidate : list) {
if (id.equals(candidate.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID))) {
node = candidate;
break;
}
}
return node;
}
/**
* Update the child tree of the destination container from the given source container.
*
* @param dst The destination container. Must not be <code>null</code>.
* @param src The source container. Must not be <code>null</code>.
*
* @return <code>True</code> if the destination container changed, <code>false</code> otherwise.
*/
/* default */ boolean __updateChildren(IContainerModelNode dst, IContainerModelNode src) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(dst);
Assert.isNotNull(src);
boolean dstNodeChanged = false;
// Get the asynchronous refreshable contexts of the nodes
IAsyncRefreshableCtx dstRefreshable = (IAsyncRefreshableCtx)dst.getAdapter(IAsyncRefreshableCtx.class);
Assert.isNotNull(dstRefreshable);
IAsyncRefreshableCtx srcRefreshable = (IAsyncRefreshableCtx)src.getAdapter(IAsyncRefreshableCtx.class);
Assert.isNotNull(srcRefreshable);
// Synchronize the refreshable states
Assert.isTrue(srcRefreshable.getQueryState(QueryType.CONTEXT) != QueryState.IN_PROGRESS, "Context query of node '" + src.getName() + "' in progress while updating model."); //$NON-NLS-1$ //$NON-NLS-2$
if (srcRefreshable.getQueryState(QueryType.CONTEXT) == QueryState.DONE && dstRefreshable.getQueryState(QueryType.CONTEXT) != QueryState.DONE) {
dstRefreshable.setQueryState(QueryType.CONTEXT, QueryState.DONE);
dstNodeChanged |= true;
}
Assert.isTrue(srcRefreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.IN_PROGRESS, "Child list query of node '" + src.getName() + "' in progress while updating model."); //$NON-NLS-1$ //$NON-NLS-2$
if (srcRefreshable.getQueryState(QueryType.CHILD_LIST) == QueryState.DONE && dstRefreshable.getQueryState(QueryType.CHILD_LIST) != QueryState.DONE) {
dstRefreshable.setQueryState(QueryType.CHILD_LIST, QueryState.DONE);
dstNodeChanged |= true;
}
// If the refreshable state of the source container is PENDING, than we are done here
if (srcRefreshable.getQueryState(QueryType.CHILD_LIST) == QueryState.PENDING) {
return dstNodeChanged;
}
// Get the list of old children (update node instances where possible)
final List<IProcessContextNode> oldChildren = dst.getChildren(IProcessContextNode.class);
// Disable notifications while updating the child list
boolean eventEnablementChanged = dst.setChangeEventsEnabled(false);
// Get the list of new children
final List<IProcessContextNode> newChildren = src.getChildren(IProcessContextNode.class);
// Loop the list of new children and lookup a matching node in the list of old children
for (IProcessContextNode candidate : newChildren) {
String id = candidate.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID);
if (id == null) continue;
// If the context node got invalid while refreshing the tree, skip the
// context. The context will be removed from the tree as a result, if
// the context had been added to the tree before. If the context was not
// in the tree before, it will not be added at all.
if (candidate.isProperty(IProcessContextNodeProperties.PROPERTY_INVALID_CTX, true)) continue;
// Find the old process context node
IProcessContextNode oldNode = findInList(id, oldChildren);
if (oldNode != null) {
// Remove the old node from the old children list
oldChildren.remove(oldNode);
// Update the properties of the old node from the new node
dstNodeChanged |= __updateProperties(oldNode, candidate);
// If the child list of the new node is valid, update the child list
IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)candidate.getAdapter(IAsyncRefreshableCtx.class);
Assert.isNotNull(refreshable);
if (refreshable.getQueryState(QueryType.CONTEXT) == QueryState.DONE) {
// Update the child tree of the old node from the new node
dstNodeChanged |= __updateChildren(oldNode, candidate);
}
} else {
if (candidate.getParent() == null) {
// Parent not set -> Add the new child node
dstNodeChanged |= dst.add(candidate);
} else {
// Parent set -> Create a copy of the new node
IProcessContextNode copy = getModel().getFactory().newInstance(IProcessContextNode.class);
__updateProperties(copy, candidate);
IAsyncRefreshableCtx refreshable = (IAsyncRefreshableCtx)candidate.getAdapter(IAsyncRefreshableCtx.class);
Assert.isNotNull(refreshable);
if (refreshable.getQueryState(QueryType.CONTEXT) == QueryState.DONE) {
__updateChildren(copy, candidate);
}
// Add the copy of the new node
dstNodeChanged |= dst.add(copy);
}
}
}
// If there are remaining old children, remove them (non-recursive)
for (IProcessContextNode oldChild : oldChildren) {
dstNodeChanged |= dst.remove(oldChild, false);
}
// Re-enable the change events
if (eventEnablementChanged) dst.setChangeEventsEnabled(true);
return dstNodeChanged;
}
/**
* Update the destination node properties from the given source node.
* <p>
* <b>Note:</b> This method does not update the child tree. Use {@link #updateChildren(IContainerModelNode, IContainerModelNode)}
* for updating the child tree.
*
* @param dst The destination node. Must not be <code>null</code>.
* @param src The source node. Must not be <code>null</code>.
*
* @return <code>True</code> if the properties of the destination node changed, <code>false</code> otherwise.
*/
/* default */ boolean __updateProperties(IProcessContextNode dst, IProcessContextNode src) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(dst);
Assert.isNotNull(src);
boolean eventEnablementChanged = dst.setChangeEventsEnabled(false);
boolean dstNodeChanged = false;
// Update the properties of the destination node from the source node
for (String key : src.getProperties().keySet()) {
dstNodeChanged |= dst.setProperty(key, src.getProperty(key));
}
// Make sure that old properties are removed from the destination node.
// Collect the list of property names to check for removal
List<String> managedPropertyNames = new ArrayList<String>();
managedPropertyNames.add(IProcessContextNodeProperties.PROPERTY_ID);
managedPropertyNames.add(IProcessContextNodeProperties.PROPERTY_NAME);
// Determine if a delegate is registered
IRuntimeModelRefreshService.IDelegate delegate = ServiceUtils.getDelegateServiceDelegate(dst, dst, IRuntimeModelRefreshService.IDelegate.class);
if (delegate == null && getModel().getService(IRuntimeModelRefreshService.class) instanceof RuntimeModelRefreshService) {
delegate = ((RuntimeModelRefreshService)getModel().getService(IRuntimeModelRefreshService.class)).defaultDelegate;
}
if (delegate != null) {
String[] candidates = delegate.getManagedPropertyNames();
if (candidates != null) managedPropertyNames.addAll(Arrays.asList(candidates));
}
// Clean up the destination node
for (String managedPropertyName : managedPropertyNames) {
if (src.isProperty(managedPropertyName, null)) {
dstNodeChanged |= dst.setProperty(managedPropertyName, null);
}
}
// Update the system monitor context object (if necessary)
ISysMonitor.SysMonitorContext s1 = dst.getSysMonitorContext();
ISysMonitor.SysMonitorContext s2 = src.getSysMonitorContext();
if ((s1 == null && s2 != null) || (s1 != null && s2 == null) || (s1 != null && !s1.equals(s2))) {
dst.setSysMonitorContext(src.getSysMonitorContext());
dstNodeChanged |= true;
}
// Update the process context object (if necessary)
IProcesses.ProcessContext p1 = dst.getProcessContext();
IProcesses.ProcessContext p2 = src.getProcessContext();
if ((p1 == null && p2 != null) || (p1 != null && p2 == null) || (p1 != null && !p1.equals(p2))) {
dst.setProcessContext(src.getProcessContext());
dstNodeChanged |= true;
}
// Update the node type (if necessary)
if (dst.getType() != src.getType()) {
dst.setType(src.getType());
dstNodeChanged |= true;
}
// Re-enable the change events
if (eventEnablementChanged) dst.setChangeEventsEnabled(true);
return dstNodeChanged;
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.core.model.interfaces.services.IModelUpdateService#update(org.eclipse.tcf.te.runtime.model.interfaces.IModelNode, org.eclipse.tcf.te.runtime.model.interfaces.IModelNode)
*/
@Override
public void update(IModelNode dst, IModelNode src) {
Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
Assert.isNotNull(dst);
Assert.isNotNull(src);
// The nodes to update must be process context nodes
if (!(dst instanceof IProcessContextNode) || !(src instanceof IProcessContextNode)) {
return;
}
// Update the nodes only if the id's are matching
String dstContextId = dst.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID);
String srcContextId = src.getStringProperty(IProcessContextNodeProperties.PROPERTY_ID);
if ((dstContextId == null && srcContextId != null) || (dstContextId != null && srcContextId == null) || (dstContextId != null && !dstContextId.equals(srcContextId))) {
return;
}
boolean dstNodeChanged = __updateProperties((IProcessContextNode)dst, (IProcessContextNode)src);
if (dst instanceof IContainerModelNode && src instanceof IContainerModelNode) {
dstNodeChanged |= __updateChildren((IContainerModelNode)dst, (IContainerModelNode)src);
}
// Fire a properties changed event if the destination node changed
if (dstNodeChanged) {
dst.fireChangeEvent(IContainerModelNode.NOTIFY_CHANGED, null, null);
}
}
}