/**
* <copyright>
*
* Copyright (c) 2004, 2009 IBM Corporation 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:
* IBM - Initial API and implementation
*
* </copyright>
*
* $Id: DelegatingWrapperItemProvider.java,v 1.11 2008/05/07 19:08:46 emerks Exp $
*/
package net.enilink.komma.edit.provider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import net.enilink.komma.common.adapter.IAdapterFactory;
import net.enilink.komma.common.command.CommandWrapper;
import net.enilink.komma.common.command.ICommand;
import net.enilink.komma.common.command.UnexecutableCommand;
import net.enilink.komma.common.notify.INotification;
import net.enilink.komma.common.notify.INotificationBroadcaster;
import net.enilink.komma.common.notify.INotificationListener;
import net.enilink.komma.common.notify.INotifier;
import net.enilink.komma.common.notify.IPropertyNotification;
import net.enilink.komma.common.notify.NotificationFilter;
import net.enilink.komma.common.notify.NotificationSupport;
import net.enilink.komma.common.util.ICollector;
import net.enilink.komma.edit.command.CommandParameter;
import net.enilink.komma.edit.command.DragAndDropCommand;
import net.enilink.komma.edit.command.ICommandActionDelegate;
import net.enilink.komma.edit.command.SetCommand;
import net.enilink.komma.edit.domain.IEditingDomain;
import net.enilink.komma.edit.provider.IItemPropertyDescriptor.OverrideableCommandOwner;
import net.enilink.komma.model.event.IStatementNotification;
import net.enilink.komma.core.IReference;
/**
* A wrapper for model objects and other wrappers. This handles most of the item
* provider methods by delegating to the item provider returned by adapting on
* the value, but it returns the
* {@link net.enilink.komma.edit.provider.WrapperItemProvider#getParent(Object)
* owner} as the parent, and it has to decorate the children, property
* descriptors, and commands that it returns.
*/
public class DelegatingWrapperItemProvider extends WrapperItemProvider
implements IStructuredItemContentProvider, ITreeItemContentProvider,
IItemLabelProvider, IItemFontProvider, IItemColorProvider,
ITableItemLabelProvider, ITableItemFontProvider,
ITableItemColorProvider, IItemPropertySource,
IEditingDomainItemProvider, INotifier<INotification>,
INotificationBroadcaster<INotification>,
INotificationListener<INotification> {
/**
* The wrapped value's item provider, to which most methods are delegated.
*/
protected Object delegateItemProvider;
/**
* The wrapped children are cached here, keyed by the children returned by
* the delegate item provider.
*/
protected Map<Object, IWrapperItemProvider> childrenMap;
/**
* The collection of children last returned by the delegate item provider is
* cached here.
*/
protected Collection<?> delegateChildren;
/**
* The decorated property descriptors are cached here.
*/
protected List<IItemPropertyDescriptor> propertyDescriptors;
/**
* Records any listeners for this wrapper and fires notifications to them.
*/
protected NotificationSupport<INotification> changeNotifier;
/**
* Creates an instance for the given value. A decorator for the object's
* item provider is created, and set up to repeat notifications, decorating
* them, so that they will update this wrapper, rather than the model object
* they originate from. If the adapter factory is an {@link IChangeNotifier}
* , a listener is added to it, so it's important to call {@link #dispose()}
* .
*
* @exception IllegalArgumentException
* If the specified value is null.
*/
@SuppressWarnings("unchecked")
public DelegatingWrapperItemProvider(Object value, Object owner,
IReference property, int index, IAdapterFactory adapterFactory) {
super(value, owner, property, index, adapterFactory);
if (value == null) {
throw new IllegalArgumentException("value=null");
}
Object delegateValue = getDelegateValue();
if (delegateValue != null) {
delegateItemProvider = getRootAdapterFactory().adapt(delegateValue,
IStructuredItemContentProvider.class);
if (delegateItemProvider instanceof INotifier) {
((INotifier<INotification>) delegateItemProvider)
.addListener(this);
}
}
}
/**
* Deactivates notification repeating and disposes any wrappers it is
* maintaining for its children.
*/
@Override
@SuppressWarnings("unchecked")
public void dispose() {
if (delegateItemProvider instanceof INotifier) {
((INotifier<INotification>) delegateItemProvider)
.removeListener(this);
}
if (childrenMap != null) {
for (IDisposable object : childrenMap.values()) {
object.dispose();
}
}
}
/**
* Returns the value from which to obtain and which to pass to a delegate
* item provider. If this returns null, no delegate item provider should
* ever be obtained. This implementation simply returns the value of the
* wrapper, though subclasses may override it to return something else.
*/
protected Object getDelegateValue() {
return value;
}
/**
* Uses the delegate item provider to return the delegate value's elements.
*/
@Override
public Collection<?> getElements(Object object) {
return delegateItemProvider instanceof IStructuredItemContentProvider ? ((IStructuredItemContentProvider) delegateItemProvider)
.getElements(getDelegateValue()) : Collections.emptyList();
}
/**
* Uses the delegate item provider to return the delegate value's children,
* with appropriate wrappers to ensure that this wrapper is considered their
* parent. Each child is replaced by the corresponding wrapper from
* {@link #childrenMap}, after updating it by calling
* {@link #updateChildren updateChildren}.
*/
@Override
public Collection<?> getChildren(Object object) {
updateChildren();
Collection<Object> result = new ArrayList<Object>(
delegateChildren.size());
for (Object delegateChild : delegateChildren) {
result.add(childrenMap.get(delegateChild));
}
return result;
}
/**
* Uses the delegate item provider to get the delegate value's children,
* assigning the collection to {@link #delegateChildren}, and to update the
* {@link #childrenMap}. New children are wrapped by calling
* {@link #createWrapper createWrapper} and added to the map; Wrappers for
* children that have been removed are disposed.
*/
protected void updateChildren() {
if (delegateItemProvider instanceof ITreeItemContentProvider) {
boolean changed = false;
Set<Object> oldDelegateChildren = delegateChildren != null ? new HashSet<Object>(
delegateChildren) : Collections.emptySet();
delegateChildren = ((ITreeItemContentProvider) delegateItemProvider)
.getChildren(getDelegateValue());
if (childrenMap == null && !delegateChildren.isEmpty()) {
childrenMap = new HashMap<Object, IWrapperItemProvider>();
}
// Wrap any new children and add them to the map. Remove each
// current child from the set of old children.
//
for (Object child : delegateChildren) {
if (!childrenMap.containsKey(child)) {
IWrapperItemProvider wrapper = createWrapper(child, this,
adapterFactory);
childrenMap.put(child, wrapper);
changed = true;
}
oldDelegateChildren.remove(child);
}
// Remove and dispose any wrappers for remaining old children.
//
if (!oldDelegateChildren.isEmpty()) {
changed = true;
for (Object child : oldDelegateChildren) {
IWrapperItemProvider wrapper = childrenMap.remove(child);
if (wrapper != null) {
wrapper.dispose();
}
}
}
// If any children were added or removed, reset the indices.
if (changed) {
int index = 0;
for (Object delegateChild : delegateChildren) {
childrenMap.get(delegateChild).setIndex(index);
}
}
} else {
delegateChildren = Collections.emptyList();
}
}
/**
* Creates a new instance of this wrapper for the given value, owner, and
* adapter factory.
*/
protected IWrapperItemProvider createWrapper(Object value, Object owner,
IAdapterFactory adapterFactory) {
return new DelegatingWrapperItemProvider(value, owner, getProperty(),
CommandParameter.NO_INDEX, adapterFactory);
}
/**
* Uses the delegate item provider to test whether the delegate value has
* children.
*/
@Override
public boolean hasChildren(Object object) {
return delegateItemProvider instanceof ITreeItemContentProvider ? ((ITreeItemContentProvider) delegateItemProvider)
.hasChildren(getDelegateValue()) : false;
}
/**
* Uses the delegate item provider to return the delegate value's text.
*/
@Override
public String getText(Object object) {
return delegateItemProvider instanceof IItemLabelProvider ? ((IItemLabelProvider) delegateItemProvider)
.getText(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's image.
*/
@Override
public Object getImage(Object object) {
return delegateItemProvider instanceof IItemLabelProvider ? ((IItemLabelProvider) delegateItemProvider)
.getImage(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's font.
*/
@Override
public Object getFont(Object object) {
return delegateItemProvider instanceof IItemFontProvider ? ((IItemFontProvider) delegateItemProvider)
.getFont(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's foreground
* color.
*/
@Override
public Object getForeground(Object object) {
return delegateItemProvider instanceof IItemColorProvider ? ((IItemColorProvider) delegateItemProvider)
.getForeground(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's background
* color.
*/
@Override
public Object getBackground(Object object) {
return delegateItemProvider instanceof IItemColorProvider ? ((IItemColorProvider) delegateItemProvider)
.getBackground(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's column
* text.
*/
public String getColumnText(Object object, int columnIndex) {
return delegateItemProvider instanceof ITableItemLabelProvider ? ((ITableItemLabelProvider) delegateItemProvider)
.getColumnText(getDelegateValue(), columnIndex)
: getText(object);
}
/**
* Uses the delegate item provider to return the delegate value's column
* image.
*/
public Object getColumnImage(Object object, int columnIndex) {
return delegateItemProvider instanceof ITableItemLabelProvider ? ((ITableItemLabelProvider) delegateItemProvider)
.getColumnImage(getDelegateValue(), columnIndex)
: getImage(object);
}
/**
* Uses the delegate item provider to return the delegate value's font.
*/
public Object getFont(Object object, int columnIndex) {
return delegateItemProvider instanceof ITableItemFontProvider ? ((ITableItemFontProvider) delegateItemProvider)
.getFont(getDelegateValue(), columnIndex) : getFont(object);
}
/**
* Uses the delegate item provider to return the delegate value's foreground
* color.
*/
public Object getForeground(Object object, int columnIndex) {
return delegateItemProvider instanceof ITableItemColorProvider ? ((ITableItemColorProvider) delegateItemProvider)
.getForeground(getDelegateValue(), columnIndex)
: getFont(object);
}
/**
* Uses the delegate item provider to return the delegate value's background
* color.
*/
public Object getBackground(Object object, int columnIndex) {
return delegateItemProvider instanceof ITableItemColorProvider ? ((ITableItemColorProvider) delegateItemProvider)
.getBackground(getDelegateValue(), columnIndex)
: getFont(object);
}
/**
* Wraps the property descriptors returned by the delegate item provider,
* caching and returning them.
*/
@Override
public List<IItemPropertyDescriptor> getPropertyDescriptors(Object object) {
if (propertyDescriptors == null) {
if (delegateItemProvider instanceof IItemPropertySource) {
List<IItemPropertyDescriptor> l = ((IItemPropertySource) delegateItemProvider)
.getPropertyDescriptors(getDelegateValue());
propertyDescriptors = new ArrayList<IItemPropertyDescriptor>(
l.size());
for (IItemPropertyDescriptor desc : l) {
propertyDescriptors
.add(new DelegatingWrapperItemPropertyDescriptor(
getDelegateValue(), desc));
}
} else {
propertyDescriptors = Collections.emptyList();
}
}
return propertyDescriptors;
}
/**
* Uses the delegate item provider to return an editable value.
*/
@Override
public Object getEditableValue(Object object) {
return delegateItemProvider instanceof IItemPropertySource ? ((IItemPropertySource) delegateItemProvider)
.getEditableValue(getDelegateValue()) : null;
}
/**
* Uses the delegate item provider to return the delegate value's new child
* descriptors.
*/
@Override
public void getNewChildDescriptors(Object object,
IEditingDomain editingDomain, Object sibling,
ICollector<Object> descriptors) {
if (delegateItemProvider instanceof IEditingDomainItemProvider) {
((IEditingDomainItemProvider) delegateItemProvider)
.getNewChildDescriptors(getDelegateValue(), editingDomain,
sibling, descriptors);
}
}
/**
* Uses the delegate item provider to create a command for the delegate
* value, and then calls {@link #wrapCommand wrapCommand} to return an
* appropriate wrapper-substituting command wrapper for it. Drag and drop
* commands are created directly by calling
* {@link WrapperItemProvider#createDragAndDropCommand
* createDragAndDropCommand}.
*/
@Override
public ICommand createCommand(Object object, IEditingDomain domain,
Class<? extends ICommand> commandClass,
CommandParameter commandParameter) {
if (commandClass == DragAndDropCommand.class) {
DragAndDropCommand.Detail detail = (DragAndDropCommand.Detail) commandParameter
.getProperty();
return createDragAndDropCommand(domain,
commandParameter.getOwner(), detail.location,
detail.operations, detail.operation,
commandParameter.getCollection());
}
if (delegateItemProvider instanceof IEditingDomainItemProvider) {
Object commandOwner = getDelegateValue();
ICommand result = null;
// A SetCommand needs to go through SetCommand.create() to ensure it
// can execute and undo.
//
if (commandClass == SetCommand.class) {
Object feature = commandParameter.getProperty();
result = SetCommand.create(domain, commandOwner, feature,
commandParameter.getValue(),
commandParameter.getIndex());
// A set command without a feature sets the value of this
// wrapper, hence replacing it with a new wrapper. So,
// we need a special command wrapper that selects this new
// wrapper as the affected object.
//
if (feature == null) {
return new ReplacementAffectedObjectCommand(result);
}
} else {
commandParameter.setOwner(commandOwner);
result = ((IEditingDomainItemProvider) delegateItemProvider)
.createCommand(commandOwner, domain, commandClass,
commandParameter);
}
return wrapCommand(result, commandClass);
}
return UnexecutableCommand.INSTANCE;
}
/**
* Wraps the given command in an appropriate command that will substitute
* the delegating wrapper for its value and child wrappers for their
* corresponding values, whenever they appear in the affected objects. This
* implementation returns an {@link AffectedObjectsWrappingCommand} or an
* {@link AffectedObjectsWrappingCommandActionDelegate}, depending on
* whether the given command implements {@link ICommandActionDelegate}.
*/
protected ICommand wrapCommand(ICommand command,
Class<? extends ICommand> commandClass) {
return command instanceof ICommandActionDelegate ? new AffectedObjectsWrappingCommandActionDelegate(
(ICommandActionDelegate) command)
: new AffectedObjectsWrappingCommand(command);
}
/**
* An <code>AffectedObjectsWrappingCommand</code> wraps another command to
* substitute this wrapper for its value and child wrappers for their
* corresponding child values, whenever they appear in the affected objects.
*/
protected class AffectedObjectsWrappingCommand extends CommandWrapper {
public AffectedObjectsWrappingCommand(ICommand command) {
super(command);
}
@Override
public Collection<?> getAffectedObjects() {
List<Object> result = new ArrayList<Object>(
super.getAffectedObjects());
updateChildren();
for (ListIterator<Object> i = result.listIterator(); i.hasNext();) {
Object object = i.next();
if (object == getDelegateValue()) {
i.set(DelegatingWrapperItemProvider.this);
} else if (childrenMap != null) {
Object wrapper = childrenMap.get(object);
if (wrapper != null) {
i.set(wrapper);
}
}
}
return result;
}
}
/**
* An <code>AffectedObjectsWrappingCommandActionDelegate</code> wraps
* another command that also implements <code>CommandActionDelegate</code>,
* to substitute this wrapper for its value and child wrappers for their
* corresponding child values, whenever they appear in the affected objects.
* Action delegate methods are delegated directly to the wrapped command.
*/
protected class AffectedObjectsWrappingCommandActionDelegate extends
AffectedObjectsWrappingCommand implements ICommandActionDelegate {
ICommandActionDelegate commandActionDelegate;
/**
* Returns a new
* <code>AffectedObjectsWrappingCommandActionDelegate</code> for the
* given command.
*
* @exception ClassCastException
* If the specified command does not implement
* {@link net.enilink.komma.common.command.ICommand} .
*/
public AffectedObjectsWrappingCommandActionDelegate(
ICommandActionDelegate command) {
super((ICommand) command);
commandActionDelegate = command;
}
@Override
public boolean canExecute() {
return commandActionDelegate.canExecute();
}
public Object getImage() {
return commandActionDelegate.getImage();
}
public String getText() {
return commandActionDelegate.getText();
}
@Override
public String getDescription() {
return commandActionDelegate.getDescription();
}
public String getToolTipText() {
return commandActionDelegate.getToolTipText();
}
}
@SuppressWarnings("unchecked")
public void fireNotifications(
Collection<? extends INotification> notifications) {
if (adapterFactory instanceof INotificationBroadcaster) {
INotificationBroadcaster<INotification> adapterFactoryChangeNotifier = (INotificationBroadcaster<INotification>) adapterFactory;
adapterFactoryChangeNotifier.fireNotifications(notifications);
}
if (changeNotifier != null) {
changeNotifier.fireNotifications(notifications);
}
}
public void addListener(INotificationListener<INotification> listener) {
if (changeNotifier == null) {
changeNotifier = new NotificationSupport<INotification>();
}
changeNotifier.addListener(listener);
}
public void removeListener(INotificationListener<INotification> listener) {
if (changeNotifier != null) {
changeNotifier.removeListener(listener);
}
}
/**
* Called by {@link #delegateItemProvider} when it normally fires a
* notification to it's adapter factory; if the notification originated from
* the delegate value, this repeats the notification, using
* {@link #wrapNotification wrapNotification} to substitute this wrapper as
* the operative object.
*/
public void notifyChanged(Collection<? extends INotification> notifications) {
for (INotification notification : notifications) {
if (getRefreshElement(notification).equals(getDelegateValue())) {
fireNotifications(Arrays.asList(wrapNotification(notification)));
}
}
}
/**
* Returns the operative object of this notification, from which the viewer
* would be refreshed. If the notification is an {@link IViewerNotification}
* , the {@link IViewerNotification#getElement element} is returned.
* Otherwise, the
* {@link net.enilink.komma.PropertyNotification.common.notify.Notification#getNotifier
* notifier} is returned.
*/
protected Object getRefreshElement(INotification notification) {
if (notification instanceof IViewerNotification) {
return ((IViewerNotification) notification).getElement();
}
if (notification instanceof IPropertyNotification) {
return ((IPropertyNotification) notification).getSubject();
}
if (notification instanceof IStatementNotification) {
return ((IStatementNotification) notification).getSubject();
}
return null;
}
/**
* Wraps the given notification, substituting this wrapper as the operative
* object, by calling {@link ViewerNotification#wrapNotification
* ViewerNotification.wrapNotification}.
*/
protected INotification wrapNotification(INotification notification) {
return ViewerNotification.wrapNotification(notification, this);
}
/**
* A <code>DelegatingWrapperItemPropertyDescriptor</code> decorates an
* <code>ItemPropertyDescriptor</code> and manages a command owner override.
* If its command owner is non-null, it ensures that the decorated
* descriptor, if it also implements <code>OverrideableCommandOwner</code>,
* will have its command owner set to the same object when
* {@link #resetPropertyValue resetPropertyValue} or
* {@link #setPropertyValue setPropertyValue} is called. If its command
* owner is null, then the decorated descriptors's command owner will be set
* to this wrapper item provider.
*/
protected class DelegatingWrapperItemPropertyDescriptor extends
ItemPropertyDescriptorDecorator implements OverrideableCommandOwner {
protected Object commandOwner;
public DelegatingWrapperItemPropertyDescriptor(Object object,
IItemPropertyDescriptor itemPropertyDescriptor) {
super(object, itemPropertyDescriptor);
}
/**
* Sets the override command owner and, if the decorated descriptor also
* implements {@link IItemPropertyDescriptor.OverrideableCommandOwner
* OverrideableCommandOwner}, updates its command owner.
*/
public void setCommandOwner(Object commandOwner) {
this.commandOwner = commandOwner;
if (itemPropertyDescriptor instanceof OverrideableCommandOwner) {
((OverrideableCommandOwner) itemPropertyDescriptor)
.setCommandOwner(commandOwner);
}
}
/**
* Returns the override command owner.
*/
public Object getCommandOwner() {
return commandOwner;
}
/**
* Updates the decorated descriptor's command owner and invokes
* <code>resetPropertyValue</code> on it.
*/
@Override
public void resetPropertyValue(Object thisObject) {
boolean hasCommandOwner = commandOwner != null;
if (!hasCommandOwner) {
setCommandOwner(DelegatingWrapperItemProvider.this);
}
itemPropertyDescriptor.resetPropertyValue(object);
if (!hasCommandOwner) {
setCommandOwner(null);
}
}
/**
* Updates the decorated descriptor's command owner and invokes
* <code>setPropertyValue</code> on it.
*/
@Override
public void setPropertyValue(Object thisObject, Object value) {
boolean hasCommandOwner = commandOwner != null;
if (!hasCommandOwner) {
setCommandOwner(DelegatingWrapperItemProvider.this);
}
itemPropertyDescriptor.setPropertyValue(object, value);
if (!hasCommandOwner) {
setCommandOwner(null);
}
}
}
@Override
public NotificationFilter<INotification> getFilter() {
return null;
}
}