/** * <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: WrapperItemProvider.java,v 1.15 2008/05/02 11:27:39 emerks Exp $ */ package net.enilink.komma.edit.provider; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import net.enilink.vocab.xmlschema.XMLSCHEMA; import net.enilink.komma.common.adapter.IAdapterFactory; import net.enilink.komma.common.command.CommandResult; 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.util.ICollector; import net.enilink.komma.common.util.IResourceLocator; import net.enilink.komma.common.util.Log; import net.enilink.komma.edit.KommaEditPlugin; import net.enilink.komma.edit.command.AbstractOverrideableCommand; import net.enilink.komma.edit.command.CommandParameter; import net.enilink.komma.edit.command.CopyCommand; import net.enilink.komma.edit.command.DragAndDropCommand; import net.enilink.komma.edit.command.SetCommand; import net.enilink.komma.edit.domain.IEditingDomain; import net.enilink.komma.em.concepts.IProperty; import net.enilink.komma.em.concepts.IResource; import net.enilink.komma.core.IReference; /** * A basic implementation of <code>IWrapperProvider</code> from which others can * extend. This class provides all the methods required to implement the * following item provider interfaces: * <ul> * <li>{@link IStructuredItemContentProvider} * <li>{@link ITreeItemContentProvider} * <li>{@link IItemLabelProvider} * <li>{@link IItemFontProvider} * <li>{@link IItemColorProvider} * <li>{@link IItemPropertySource} * <li>{@link IEditingDomainItemProvider} * </ul> * <p> * Subclasses should declare which of these interfaces they are meant to * implement, and override methods as needed. In addition, a partial * implementation for {@link IUpdateableItemText} is provided, along with * additional methods and classes that are useful in implementing multiple * subclasses. */ public class WrapperItemProvider implements IWrapperItemProvider { /** * The wrapped value. */ protected Object value; /** * The object that owns the value. */ protected Object owner; /** * The structural feature, if applicable, through which the value can be set * and retrieved. */ protected IReference property; /** * The index at which the value is located. If {@link #property} is * non-null, this index is within that feature. */ protected int index; /** * The adapter factory for the owner's item provider. */ protected IAdapterFactory adapterFactory; /** * Creates an instance. The adapter factory of the owner's item provider may * be needed for echoing notifications and providing property descriptors. */ public WrapperItemProvider(Object value, Object owner, IReference property, int index, IAdapterFactory adapterFactory) { this.value = value; this.owner = owner; this.property = property; this.index = index; this.adapterFactory = adapterFactory; } /** * Disposes the wrapper by deactivating any notification that this wrapper * may provide. Since this implementation does not provide any notification, * this method does nothing. */ public void dispose() { // Do nothing. } /** * Returns the wrapped value. */ public Object getValue() { return value; } /** * Returns the object that owns the value. */ public Object getOwner() { return owner; } /** * Returns the structural feature through which the value can be set and * retrieved, or null if the feature is unknown or not applicable. */ public IReference getProperty() { return property; } /** * The index at which the value is located, or * {@link net.enilink.komma.edit.command.CommandParameter#NO_INDEX} if * the index isn't known to the wrapper. If {@link #property} is non-null, * this index is within that feature. */ public int getIndex() { return index; } /** * Sets the index. Has no effect if the index isn't known to the wrapper. */ public void setIndex(int index) { this.index = index; } /** * {@link IStructuredItemContentProvider#getElements * IStructuredItemContentProvider.getElements} is implemented by forwarding * the call to {@link #getChildren getChildren}. */ public Collection<?> getElements(Object object) { return getChildren(object); } /** * {@link ITreeItemContentProvider#getChildren * ITreeItemContentProvider.getChildren} is implemented to return an empty * list. Subclasses may override it to return something else. */ public Collection<?> getChildren(Object object) { return Collections.emptyList(); } /** * {@link ITreeItemContentProvider#hasChildren * ITreeItemContentProvider.hasChildren} is implemented by testing whether * the collection returned by {@link #getChildren getChildren} is non-empty. */ public boolean hasChildren(Object object) { return !getChildren(object).isEmpty(); } /** * {@link ITreeItemContentProvider#getParent * ITreeItemContentProvider.getParent} is implemented by returning the * {@link #owner}. */ public Object getParent(Object object) { return owner; } /** * {@link net.enilink.komma.edit.provider.IItemLabelProvider#getText * IItemLabelProvider.getText} is implemented by returning a non-null value, * as a string, or "null". */ public String getText(Object object) { return value != null ? value.toString() : "null"; } /** * {@link net.enilink.komma.edit.provider.IItemLabelProvider#getImage * IItemLabelProvider.getImage} is implemented by returning the default icon * for an EMF.Edit item. */ public Object getImage(Object object) { return KommaEditPlugin.INSTANCE.getImage("full/obj16/Item"); } /** * {@link net.enilink.komma.edit.provider.IItemFontProvider#getFont * IItemFontProvider.getFont} is implemented by returning null. */ public Object getFont(Object object) { return null; } /** * {@link net.enilink.komma.edit.provider.IItemColorProvider#getForeground * IItemColorProvider.getForeground} is implemented by returning null. */ public Object getForeground(Object object) { return null; } /** * {@link net.enilink.komma.edit.provider.IItemColorProvider#getBackground * IItemColorProvider.getBackground} is implemented by returning null. */ public Object getBackground(Object object) { return null; } /** * {@link IUpdateableItemText#getUpdateableText * IUpdateableItemText.getUpdateableText} is implemented by forwarding the * call to {@link #getText getText}. */ public String getUpdateableText(Object object) { return getText(object); } /** * {@link IItemPropertySource#getPropertyDescriptors * IItemPropertySource.getPropertyDescriptors} is implemented to return an * empty list. Subclasses may override it to return something else. */ public List<IItemPropertyDescriptor> getPropertyDescriptors(Object object) { return Collections.emptyList(); } /** * {@link IItemPropertySource#getPropertyDescriptor * IItemPropertySource.getPropertyDescriptor} is implemented by iterating * over the descriptors returned by {@link #getPropertyDescriptors * getPropertyDescriptors}, and returning the first descriptor whose * {@link IItemPropertyDescriptor#getId(Object) ID} or * {@link IItemPropertyDescriptor#getProperty(Object) feature} matches the * specified ID, or <code>null</code> if none match. */ public IItemPropertyDescriptor getPropertyDescriptor(Object object, Object propertyId) { for (IItemPropertyDescriptor descriptor : getPropertyDescriptors(object)) { if (propertyId.equals(descriptor.getId(object)) || propertyId.equals(descriptor.getProperty(object))) { return descriptor; } } return null; } /** * {@link IItemPropertySource#getEditableValue * IItemPropertySource.getEditableValue} is implemented to return the value, * itself. */ public Object getEditableValue(Object object) { return value; } /** * Returns a name for a value's single property. Subclasses may use this in * creating a property descriptor, and user subclasses may override it to * provide a specific name. */ protected String getPropertyName() { return KommaEditPlugin.INSTANCE.getString("_UI_ValueProperty_name"); } /** * Returns a description for a value's single property. Subclasses may use * this in creating a property descriptor, and user subclasses may override * it to provide a specific name. */ protected String getPropertyDescription() { return KommaEditPlugin.INSTANCE .getString("_UI_ValueProperty_description"); } /** * Returns whether a value's single property is settable. By default, this * returns whether the structural feature is * {@link org.eclipse.emf.ecore.EStructuralFeature#isChangeable changeable}. * Subclasses may use this in creating a property descriptor, and user * subclasses may override it to restrict or allow setting of the property. */ protected boolean isPropertySettable() { return true; // return property.isChangeable(); } /** * Returns whether value's single property consists of multi-line text. By * default, false is returned. Subclasses may use this in creating a * property descriptor, and user subclasses may override it to enable * multi-line text editing. */ protected boolean isPropertyMultiLine() { return false; } /** * Returns whether value's single property should sort its choices for * selection. By default, false is returned. Subclasses may use this in * creating a property descriptor, and user subclasses may override it to * enable sorting. */ protected boolean isPropertySortChoices() { return false; } /** * Returns an image for a value's single property. By default, a standard * property icon is selected based on the type of the structural feature. * Subclasses may use this in creating a property descriptor, and user * subclasses may override it to select a different icon. */ protected Object getPropertyImage() { IProperty property = (IProperty) ((IResource) getOwner()) .getEntityManager().find(this.property); Set<String> ranges = new HashSet<String>(); for (net.enilink.vocab.rdfs.Class rangeClass : property .getRdfsRanges()) { ranges.add(rangeClass.getURI().toString()); } return getPropertyImage(ranges); } /** * Returns the property image for the specified type. Implementations of * {@link #getPropertyImage() getPropertyImage} typically call this method. */ protected Object getPropertyImage(Collection<String> ranges) { if (ranges.contains(XMLSCHEMA.TYPE_BOOLEAN.toString())) { return ItemPropertyDescriptor.BOOLEAN_VALUE_IMAGE; } else if (ranges.contains(XMLSCHEMA.TYPE_BYTE.toString()) || ranges.contains(XMLSCHEMA.TYPE_INTEGER.toString()) || ranges.contains(XMLSCHEMA.TYPE_LONG.toString()) || ranges.contains(XMLSCHEMA.TYPE_SHORT.toString())) { return ItemPropertyDescriptor.INTEGRAL_VALUE_IMAGE; } else if (ranges.contains(XMLSCHEMA.TYPE_STRING.toString())) { return ItemPropertyDescriptor.TEXT_VALUE_IMAGE; } else if (ranges.contains(XMLSCHEMA.TYPE_DOUBLE.toString()) || ranges.contains(XMLSCHEMA.TYPE_FLOAT.toString())) { return ItemPropertyDescriptor.REAL_VALUE_IMAGE; } return ItemPropertyDescriptor.GENERIC_VALUE_IMAGE; } /** * Returns a category for a value's single property. By default, null is * returned. Subclasses may use this in creating a property descriptor, and * user subclasses may override it to actually provide a category. */ protected String getPropertyCategory() { return null; } /** * Returns filter flags for a value's single property. By default, null is * returned. Subclasses may use this in creating a property descriptor, and * user subclasses may override it to actually provide filter flags. */ protected String[] getPropertyFilterFlags() { return null; } /** * {@link IEditingDomainItemProvider#getNewChildDescriptors * IEditingDomainItemProvider.getNewChildDescriptors} is implemented to * return an empty list. Subclasses may override it to return something * else. */ public void getNewChildDescriptors(Object object, IEditingDomain editingDomain, Object sibling, ICollector<Object> descriptors) { } /** * {IEditingDomainItemProvider#createCommand * IEditingDomainItemProvider.createCommand} is implemented via * {@link #baseCreateCommand baseCreateCommand} to create set, copy, and * drag-and-drop commands, only. */ public ICommand createCommand(Object object, IEditingDomain domain, Class<? extends ICommand> commandClass, CommandParameter commandParameter) { return baseCreateCommand(object, domain, commandClass, commandParameter); } /** * Implements creation of a set, copy, or drag-and-drop command by calling * out to {@link #createSetCommand createSetCommand}, * {@link #createCopyCommand createCopyCommand}, or * {@link #createDragAndDropCommand createDragAndDropCommand}. */ public ICommand baseCreateCommand(Object object, IEditingDomain domain, Class<? extends ICommand> commandClass, CommandParameter commandParameter) { if (commandClass == SetCommand.class) { return createSetCommand(domain, commandParameter.getOwner(), commandParameter.getProperty(), commandParameter.getValue(), commandParameter.getIndex()); } else if (commandClass == CopyCommand.class) { return createCopyCommand(domain, commandParameter.getOwner(), (CopyCommand.Helper) commandParameter.getValue()); } else if (commandClass == DragAndDropCommand.class) { DragAndDropCommand.Detail detail = (DragAndDropCommand.Detail) commandParameter .getProperty(); return createDragAndDropCommand(domain, commandParameter.getOwner(), detail.location, detail.operations, detail.operation, commandParameter.getCollection()); } else { return UnexecutableCommand.INSTANCE; } } /** * Return an * {@link net.enilink.komma.common.command.UnexecutableCommand}. * Subclasses should override this to map this into a real set on a model * object. */ protected ICommand createSetCommand(IEditingDomain domain, Object owner, Object feature, Object value, int index) { return UnexecutableCommand.INSTANCE; } /** * Returns an * {@link net.enilink.komma.common.command.UnexecutableCommand}. An * ordinary {@link net.enilink.komma.edit.command.CopyCommand} is only * useful for copying model objects, so it would be inappropriate here. * Subclasses should override it to return something more useful, like a * concrete subclass of a {@link SimpleCopyCommand} or * {@link WrappingCopyCommand}. */ protected ICommand createCopyCommand(IEditingDomain domain, Object owner, CopyCommand.Helper helper) { return UnexecutableCommand.INSTANCE; } /** * Creates a {@link net.enilink.komma.edit.command.DragAndDropCommand} * . */ protected ICommand createDragAndDropCommand(IEditingDomain domain, Object owner, float location, int operations, int operation, Collection<?> collection) { return new DragAndDropCommand(domain, owner, location, operations, operation, collection); } /** * A label for copy command inner classes, the same one used by * {@link net.enilink.komma.edit.command.CopyCommand}. */ protected static final String COPY_COMMAND_LABEL = KommaEditPlugin.INSTANCE .getString("_UI_CopyCommand_label"); /** * A description for copy command inner classes, the same as in * {@link net.enilink.komma.edit.command.CopyCommand}. */ protected static final String COPY_COMMAND_DESCRIPTION = KommaEditPlugin.INSTANCE .getString("_UI_CopyCommand_description"); /** * A command base class for copying a simple value and the wrapper. This is * useful when the value isn't able provide an adapter to return a copy * command, itself. This class just provides the scaffolding; concrete * subclasses must implement {@link #copy copy} to do the copying. */ protected abstract class SimpleCopyCommand extends AbstractOverrideableCommand { protected Collection<?> affectedObjects; /** * Creates an instance for the given domain. */ public SimpleCopyCommand(IEditingDomain domain) { super(domain, COPY_COMMAND_LABEL, COPY_COMMAND_DESCRIPTION); } /** * Returns true; this command can requires now preparation and can * always be executed. */ @Override protected boolean prepare() { return true; } /** * Calls {@link #copy} to do the copying, {@link IDisposable#dispose * disposes} the copy, and sets it to be the result of the command. * Since the copy has not been created within the viewed model, it * should never do any kind of notification, which is why it is * immediately disposed. */ @Override protected CommandResult doExecuteWithResult( IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException { IWrapperItemProvider copy = copy(); copy.dispose(); return CommandResult.newOKCommandResult(Collections .singletonList(copy)); } /** * Concrete subclasses must implement this to copy and return the value * and wrapper. */ public abstract IWrapperItemProvider copy(); /** * Returns a list containing only the original wrapper itself. */ @Override public Collection<?> doGetAffectedObjects() { if (affectedObjects == null) { affectedObjects = Collections .singletonList(WrapperItemProvider.this); } return affectedObjects; } } /** * A command base class for copying the wrapper for a value that is partly * copied by another command. This is useful when the value includes a model * object that is able provide an adapter to return a copy command, but also * includes an element that is not adaptable, such as a feature map entry. * This command copies the non-adapter element and the wrapper, which * ensures the copy can be copied again. */ protected abstract class WrappingCopyCommand extends CommandWrapper { protected Collection<?> affectedObjects; /** * Creates an instance where some adaptable value is copied by the given * command. */ public WrappingCopyCommand(ICommand command) { super(command); } /** * Executes the adaptable-value-copying command, then calls * {@link #copy copy} to copy the rest of the value and the wrapper, * {@link IDisposable#dispose disposes} the copy, and sets it to be the * result of the command. Since the copy has not been created within the * viewed model, it should never do any kind of notification, which is * why it is immediately disposed. */ @Override protected CommandResult doExecuteWithResult( IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException { super.doExecuteWithResult(progressMonitor, info); IWrapperItemProvider copy = copy(); copy.dispose(); return CommandResult.newOKCommandResult(Collections .singletonList(copy)); } /** * Concrete subclasses must implement this to copy and return the value * and wrapper. The result of the adaptable-value-copying command is * available from <code>getCommand().getResult()</code>. */ public abstract IWrapperItemProvider copy(); /** * Returns a list containing only the original wrapper itself. */ @Override public Collection<?> getAffectedObjects() { if (affectedObjects == null) { affectedObjects = Collections .singletonList(WrapperItemProvider.this); } return affectedObjects; } } /** * Returns the {@link #adapterFactory}, if non-composeable, otherwise, * returns its root adapter factory. */ protected IAdapterFactory getRootAdapterFactory() { return adapterFactory instanceof IComposeableAdapterFactory ? ((IComposeableAdapterFactory) adapterFactory) .getRootAdapterFactory() : adapterFactory; } /** * An item property descriptor for the single property of a wrapper for a * simple value. This extends the base implementation and substitutes the * wrapper's owner for the selected object (the wrapper itself) in the call * to {@link #getPropertyValue getPropertyValue}. Thus, the owner must be an * EObject to use this class. The property's name, description, settable * flag, static image, category, and filter flags are obtained by calling * out to various template methods, so can be easily changed by deriving a * subclass. */ protected class WrapperItemPropertyDescriptor extends ItemPropertyDescriptor { public WrapperItemPropertyDescriptor(IResourceLocator resourceLocator, IReference property) { super(WrapperItemProvider.this.adapterFactory, resourceLocator, getPropertyName(), getPropertyDescription(), property, isPropertySettable(), isPropertyMultiLine(), isPropertySortChoices(), getPropertyImage(), getPropertyCategory(), getPropertyFilterFlags()); } /** * Substitutes the wrapper owner for the selected object and invokes the * base implementation. The actual value returned depends on the * implementation of {@link #getValue getValue}. */ @Override public Object getPropertyValue(Object object) { return super.getPropertyValue(owner); } /** * Substitutes the wrapper owner for the selected object and invokes the * base implementation. */ @Override public boolean canSetProperty(Object object) { return super.canSetProperty(owner); } /** * Returns <code>true</code>, as the property of a value wrapper is * always considered to be set. */ @Override public boolean isPropertySet(Object object) { return true; } /** * Does nothing, as resetting the property of a value wrapper is not * meaningful. */ @Override public void resetPropertyValue(Object object) { // Do nothing } /** * Sets the property value. If an editing domain can be obtained, the * command returned by {@link #createSetCommand createSetcommand} is * executed; otherwise, {@link #setValue setValue} is called to set the * value. */ @Override public void setPropertyValue(Object object, Object value) { IEditingDomain editingDomain = getEditingDomain(owner); if (editingDomain == null) { setValue((IResource) object, property, value); } else { try { editingDomain.getCommandStack().execute( createSetCommand(editingDomain, (IResource) object, property, value), null, null); } catch (ExecutionException e) { Log.error(KommaEditPlugin.getPlugin(), 0, "Error while setting property value", e); } } } /** * Returns a value from a model object. If the feature is multi-valued, * only the single value that the wrapper represents is returned. */ @Override protected Object getValue(IResource object, IReference property) { // When the value is changed, the property sheet page doesn't update // the property sheet viewer input // before refreshing, and this gets called on the obsolete wrapper. // So, we need to read directly from the // model object. // // return value; Object result = object.get(property); if (object.getApplicableCardinality(property).getSecond() != 1) { // If the last object was deleted and the selection was in the // property sheet view, the obsolete wrapper will // reference past the end of the list. // List<?> list = (List<?>) result; result = index >= 0 && index < list.size() ? list.get(index) : value; } return result; } /** * Sets a value on a model object. If the feature is multi-valued, only * the single value that the wrapper represents is set. */ protected void setValue(IResource object, IReference property, Object value) { if (object.getApplicableCardinality(property).getSecond() != 1) { @SuppressWarnings("unchecked") List<Object> list = ((List<Object>) object.get(property)); list.set(index, value); } else { object.set(property, value); } } /** * Returns a command that will set the value on the model object. The * wrapper is used as the owner of the command, unless overridden, so * that it can specialize the command that eventually gets created. */ protected ICommand createSetCommand(IEditingDomain domain, Object owner, Object feature, Object value) { return SetCommand.create(domain, getCommandOwner(WrapperItemProvider.this), null, value); } /** * Returns <code>false</code>, as the property only represents a single * value, even if the feature is multi-valued. */ @Override public boolean isMany(Object object) { return false; } /** * Substitutes the wrapper owner for the selected object and invokes the * base implementation. */ @Override public Collection<?> getChoiceOfValues(Object object) { return super.getChoiceOfValues(owner); } } /** * A <code>ReplacementAffectedObjectCommand</code> wraps another command to * return as its affected objects the single wrapper that replaces this * wrapper. That is, it obtains the children of the wrapper's owner, and * returns a collection containing the first wrapper whose feature and index * match this one's. */ protected class ReplacementAffectedObjectCommand extends CommandWrapper { public ReplacementAffectedObjectCommand(ICommand command) { super(command); } /** * Obtains the children of the wrapper's owner, and returns a collection * containing the first wrapper whose feature and index match this * one's. */ @Override public Collection<?> getAffectedObjects() { Collection<?> children = Collections.EMPTY_LIST; // Either the IEditingDomainItemProvider or ITreeItemContentProvider // item provider interface can give us // the children. // Object adapter = adapterFactory.adapt(owner, IEditingDomainItemProvider.class); if (adapter instanceof IEditingDomainItemProvider) { children = ((IEditingDomainItemProvider) adapter) .getChildren(owner); } else { adapter = adapterFactory.adapt(owner, ITreeItemContentProvider.class); if (adapter instanceof ITreeItemContentProvider) { children = ((ITreeItemContentProvider) adapter) .getChildren(owner); } } for (Object child : children) { if (child instanceof IWrapperItemProvider) { IWrapperItemProvider wrapper = (IWrapperItemProvider) child; if (wrapper.getProperty() == property && wrapper.getIndex() == index) { return Collections.singletonList(child); } } } return Collections.EMPTY_LIST; } } @Override public boolean isInferred() { return false; } }