/**
* <copyright>
*
* Copyright (c) 2002, 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: AddCommand.java,v 1.12 2008/04/22 19:46:16 emerks Exp $
*/
package net.enilink.komma.edit.command;
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.rdfs.Class;
import net.enilink.komma.common.command.CommandResult;
import net.enilink.komma.common.command.ICommand;
import net.enilink.komma.edit.KommaEditPlugin;
import net.enilink.komma.edit.domain.AdapterFactoryEditingDomain;
import net.enilink.komma.edit.domain.IEditingDomain;
import net.enilink.komma.em.concepts.IClass;
import net.enilink.komma.em.concepts.IProperty;
import net.enilink.komma.em.concepts.IResource;
import net.enilink.komma.model.IModel;
import net.enilink.komma.model.IObject;
import net.enilink.komma.core.IReference;
/**
* The add command logically acts upon an owner object that has a
* collection-based feature to which other objects can be added. The static
* create methods delegate command creation to
* {@link IEditingDomain#createCommand EditingDomain.createCommand}, which may
* or may not result in the actual creation of an instance of this class.
*
* <p>
* The implementation of this class is low-level and EMF specific; it allows one
* or more objects to be added to a many-valued feature of an owner, i.e., it is
* equivalent of the call
*
* <pre>
* ((EList) ((EObject) owner).eGet((EStructuralFeature) feature)).addAll(index,
* (Collection) collection);
* </pre>
*
* <p>
* It can also be used as an equivalent to the call
*
* <pre>
* ((EList) extent).addAll(index, (Collection) collection);
* </pre>
*
* which is how root objects are added into the contents of a resource. Like all
* the low-level commands in this package, the add command is undoable.
*
* <p>
* An add command is an {@link IOverrideableCommand}.
*/
public class AddCommand extends AbstractOverrideableCommand {
/**
* This caches the description.
*/
protected static final String DESCRIPTION = KommaEditPlugin.INSTANCE
.getString("_UI_AddCommand_description");
/**
* This caches the description for a list-based addition.
*/
protected static final String DESCRIPTION_FOR_LIST = KommaEditPlugin.INSTANCE
.getString("_UI_AddCommand_description_for_list");
/**
* This caches the label.
*/
protected static final String LABEL = KommaEditPlugin.INSTANCE
.getString("_UI_AddCommand_label");
/**
* This creates a command to add a collection of values to the specified
* feature of the owner. The feature will often be null because the domain
* will deduce it.
*/
public static ICommand create(IEditingDomain domain, Object owner,
Object property, Collection<?> collection) {
return domain.createCommand(AddCommand.class, new CommandParameter(
owner, property, collection, CommandParameter.NO_INDEX));
}
/**
* This creates a command to insert a collection of values at a particular
* index in the specified feature of the owner. The feature will often be
* null because the domain will deduce it.
*/
public static ICommand create(IEditingDomain domain, Object owner,
Object property, Collection<?> collection, int index) {
return domain.createCommand(AddCommand.class, new CommandParameter(
owner, property, collection, index));
}
/**
* This creates a command to add a particular value to the specified feature
* of the owner. The feature will often be null because the domain will
* deduce it.
*/
public static ICommand create(IEditingDomain domain, Object owner,
Object property, Object value) {
return create(domain, owner, property, Collections.singleton(value),
CommandParameter.NO_INDEX);
}
/**
* This creates a command to insert particular value at a particular index
* in the specified feature of the owner. The feature will often be null
* because the domain will deduce it.
*/
public static ICommand create(IEditingDomain domain, Object owner,
Object property, Object value, int index) {
return create(domain, owner, property, Collections.singleton(value),
index);
}
/**
* This is the value returned by {@link ICommand#getAffectedObjects}. The
* affected objects are different after an execute than after an undo, so we
* record it.
*/
protected Collection<?> affectedObjects;
/**
* This is the collection of objects being added to the owner list.
*/
protected Collection<?> collection;
/**
* This is the position at which the objects will be inserted.
*/
protected int index;
/**
* This is the owner object upon which the command will act. It could be
* null in the case that we are dealing with an
* {@link net.enilink.komma.rmf.common.util.EList}.
*/
protected IResource owner;
/**
* This is the list to which the command will add the collection.
*/
protected Collection<Object> ownerList;
/**
* This is the feature of the owner object upon the command will act. It
* could be null, in the case that we are dealing with an
* {@link net.enilink.komma.rmf.common.util.EList}.
*/
protected IReference property;
/**
* This constructs a primitive command to insert a collection of values into
* the specified extent.
*/
public AddCommand(IEditingDomain domain, Collection<?> list,
Collection<?> collection) {
this(domain, list, collection, CommandParameter.NO_INDEX);
}
/**
* This constructs a primitive command to insert a collection of values into
* the specified extent.
*/
public AddCommand(IEditingDomain domain, Collection<?> list,
Collection<?> collection, int index) {
super(domain, LABEL, DESCRIPTION_FOR_LIST);
this.collection = collection;
this.index = index;
@SuppressWarnings("unchecked")
Collection<Object> untypedList = (Collection<Object>) list;
ownerList = untypedList;
}
/**
* This constructs a primitive command to add a particular value into the
* specified extent.
*/
public AddCommand(IEditingDomain domain, Collection<?> list, Object value) {
this(domain, list, Collections.singleton(value),
CommandParameter.NO_INDEX);
}
/**
* This constructs a primitive command to insert particular value into the
* specified extent.
*/
public AddCommand(IEditingDomain domain, Collection<?> list, Object value,
int index) {
this(domain, list, Collections.singleton(value), index);
}
/**
* This constructs a primitive command to add a collection of values to the
* specified many-valued feature of the owner.
*/
public AddCommand(IEditingDomain domain, IResource owner,
IReference property, Collection<?> collection) {
this(domain, owner, property, collection, CommandParameter.NO_INDEX);
}
/**
* This constructs a primitive command to insert a collection of values into
* the specified many-valued feature of the owner.
*/
public AddCommand(IEditingDomain domain, IResource owner,
IReference property, Collection<?> collection, int index) {
super(domain, LABEL, DESCRIPTION);
this.owner = owner;
this.property = property;
this.collection = collection;
this.index = index;
ownerList = getOwnerList(owner, property);
}
/**
* This constructs a primitive command to add a particular value to the
* specified many-valued feature of the owner.
*/
public AddCommand(IEditingDomain domain, IResource owner,
IReference property, Object value) {
this(domain, owner, property, Collections.singleton(value),
CommandParameter.NO_INDEX);
}
/**
* This constructs a primitive command to insert particular value into the
* specified many-valued feature of the owner.
*/
public AddCommand(IEditingDomain domain, IResource owner,
IReference property, Object value, int index) {
this(domain, owner, property, Collections.singleton(value), index);
}
@Override
protected CommandResult doExecuteWithResult(
IProgressMonitor progressMonitor, IAdaptable info)
throws ExecutionException {
boolean transactionWasActive = owner.getEntityManager()
.getTransaction().isActive();
if (!transactionWasActive) {
owner.getEntityManager().getTransaction().begin();
}
try {
Class listClass = getListType();
if (listClass != null) {
@SuppressWarnings("unchecked")
List<Object> newList = (List<Object>) ((IClass) listClass)
.newInstance();
ownerList = newList;
}
// Simply add the collection to the list.
if (ownerList instanceof List<?>
&& index != CommandParameter.NO_INDEX) {
((List<Object>) ownerList).addAll(index, collection);
} else {
ownerList.addAll(collection);
}
// We'd like the collection of things added to be selected after
// this
// command completes.
affectedObjects = collection;
if (!transactionWasActive) {
owner.getEntityManager().getTransaction().commit();
}
} catch (Throwable e) {
if (!transactionWasActive) {
owner.getEntityManager().getTransaction().rollback();
}
throw new ExecutionException("Error while adding element", e);
}
return CommandResult.newOKCommandResult(collection);
}
@Override
public Collection<?> doGetAffectedObjects() {
return affectedObjects;
}
@Override
public Collection<?> doGetAffectedResources(Object type) {
if (IModel.class.equals(type)
&& (owner != null || ownerList != null || collection != null)) {
Collection<Object> affected = new HashSet<Object>(
super.doGetAffectedResources(type));
if (owner instanceof IObject) {
affected.add(((IObject) owner).getModel());
}
if (ownerList != null) {
for (Object element : ownerList) {
Object object = AdapterFactoryEditingDomain.unwrap(element);
if (object instanceof IObject) {
affected.add(((IObject) object).getModel());
}
}
}
if (collection != null) {
for (Object element : collection) {
Object object = AdapterFactoryEditingDomain.unwrap(element);
if (object instanceof IObject) {
affected.add(((IObject) object).getModel());
}
}
}
return affected;
}
return super.doGetAffectedResources(type);
}
@Override
protected CommandResult doRedoWithResult(IProgressMonitor progressMonitor,
IAdaptable info) throws ExecutionException {
CommandResult result = super.doRedoWithResult(progressMonitor, info);
// We'd like the collection of things added to be selected after this
// command completes.
affectedObjects = collection;
return result;
}
@Override
protected CommandResult doUndoWithResult(IProgressMonitor progressMonitor,
IAdaptable info) throws ExecutionException {
CommandResult result = super.doUndoWithResult(progressMonitor, info);
// We'd like the owner selected after this undo completes.
affectedObjects = owner == null ? Collections.EMPTY_SET : Collections
.singleton(owner);
return result;
}
/**
* This returns the collection of objects being added.
*/
public Collection<?> getCollection() {
return collection;
}
/**
* This returns the position at which the objects will be added.
*/
public int getIndex() {
return index;
}
protected Class getListType() {
if (ownerList == null) {
IProperty property = owner.getEntityManager().find(this.property,
IProperty.class);
for (Class clazz : property.getNamedRanges(owner, true)) {
if (clazz.getURI() != null) {
return clazz;
}
}
}
return null;
}
/**
* This returns the owner object upon which the command will act. It could
* be null in the case that we are dealing with an
* {@link net.enilink.komma.rmf.common.util.EList}.
*/
public IResource getOwner() {
return owner;
}
/**
* This returns the list to which the command will add.
*/
public Collection<?> getOwnerList() {
return ownerList;
}
/**
* This returns the feature of the owner object upon the command will act.
* It could be null, in the case that we are dealing with an
* {@link net.enilink.komma.rmf.common.util.EList}.
*/
public IReference getProperty() {
return property;
}
@Override
protected boolean prepare() {
// If there is no list to add to, no collection or an empty collection
// from which to add, or the index is out of range...
if (ownerList == null && getListType() == null || collection == null
|| collection.size() == 0 || index != CommandParameter.NO_INDEX
&& (// ownerList != null && !(ownerList instanceof List<?>) ||
index < 0 || ownerList != null && index > ownerList.size())) {
return false;
}
if (property != null) {
IProperty property = owner.getEntityManager().find(this.property,
IProperty.class);
// Check each object...
for (Object object : collection) {
boolean containment = false;
// Check type of object.
//
// if (!property..isInstance(object)) {
// return false;
// }
// Check that the object isn't already in a unique list.
//
// if (property.isUnique() && ownerList.contains(object)) {
// return false;
// }
// Check to see if a container is being put into a contained
// object.
containment |= property.isContainment();
if (containment) {
// use seen to prevent infinite loops due to invalid usage
// of komma:contains
Set<IResource> seen = new HashSet<IResource>();
for (IResource container = owner; container != null; container = container
.getContainer()) {
if (!seen.add(container) || object.equals(container)) {
return false;
}
}
}
}
}
if (owner != null && getDomain().isReadOnly(owner)) {
return false;
}
return true;
}
/**
* This gives an abbreviated name using this object's own class' name,
* without package qualification, followed by a space separated list of
* <tt>field:value</tt> pairs.
*/
@Override
public String toString() {
StringBuffer result = new StringBuffer(super.toString());
result.append(" (owner: " + owner + ")");
result.append(" (feature: " + property + ")");
result.append(" (ownerList: " + ownerList + ")");
result.append(" (collection: " + collection + ")");
result.append(" (index: " + index + ")");
result.append(" (affectedObjects:" + affectedObjects + ")");
return result.toString();
}
}