/**
* <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: AdapterFactoryEditingDomain.java,v 1.26 2008/08/29 16:13:24 emerks Exp $
*/
package net.enilink.komma.edit.domain;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.core.runtime.IAdaptable;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.util.Modules;
import net.enilink.vocab.rdfs.Resource;
import net.enilink.komma.common.adapter.IAdapter;
import net.enilink.komma.common.adapter.IAdapterFactory;
import net.enilink.komma.common.command.ExtendedCompositeCommand;
import net.enilink.komma.common.command.ICommand;
import net.enilink.komma.common.command.ICommandStack;
import net.enilink.komma.common.command.UnexecutableCommand;
import net.enilink.komma.common.util.AbstractTreeIterator;
import net.enilink.komma.common.util.ICollector;
import net.enilink.komma.common.util.ITreeIterator;
import net.enilink.komma.edit.command.CommandParameter;
import net.enilink.komma.edit.command.CopyToClipboardCommand;
import net.enilink.komma.edit.command.CreateChildCommand;
import net.enilink.komma.edit.command.CutToClipboardCommand;
import net.enilink.komma.edit.command.DeleteCommand;
import net.enilink.komma.edit.command.IOverrideableCommand;
import net.enilink.komma.edit.command.PasteFromClipboardCommand;
import net.enilink.komma.edit.command.RemoveCommand;
import net.enilink.komma.edit.command.ReplaceCommand;
import net.enilink.komma.edit.provider.IEditingDomainItemProvider;
import net.enilink.komma.edit.provider.IWrapperItemProvider;
import net.enilink.komma.edit.provider.ItemProviderAdapter;
import net.enilink.komma.em.util.UnitOfWork;
import net.enilink.komma.model.IModel;
import net.enilink.komma.model.IModelSet;
import net.enilink.komma.model.IModelSetFactory;
import net.enilink.komma.model.IObject;
import net.enilink.komma.model.IURIConverter;
import net.enilink.komma.model.MODELS;
import net.enilink.komma.model.ModelPlugin;
import net.enilink.komma.model.ModelSetModule;
import net.enilink.komma.model.change.ChangeRecorder;
import net.enilink.komma.core.IEntity;
import net.enilink.komma.core.IUnitOfWork;
import net.enilink.komma.core.URIs;
/**
* This class implements an editing domain by delegating to adapters that
* implement {@link net.enilink.komma.edit.provider.IEditingDomainItemProvider}.
*/
public class AdapterFactoryEditingDomain implements IEditingDomain,
IEditingDomain.Internal {
/**
* This implements an tree iterator that iterates over an object, it's
* domain children, their domain children, and so on.
*/
public static class DomainTreeIterator<E> extends AbstractTreeIterator<E> {
private static final long serialVersionUID = 1L;
/**
* This is the domain that defines the tree structured.
*/
protected IEditingDomain domain;
/**
* This constructs tree iterator that iterates over an object, it's
* domain children, their domain children, and so on.
*/
public DomainTreeIterator(IEditingDomain domain, E object) {
super(object);
this.domain = domain;
}
/**
* This constructs tree iterator that iterates over an object (but only
* if includeRoot is true), it's domain children, their domain children,
* and so on.
*/
public DomainTreeIterator(IEditingDomain domain, Object object,
boolean includeRoot) {
super(object, includeRoot);
this.domain = domain;
}
@SuppressWarnings("unchecked")
@Override
protected Iterator<E> getChildren(Object o) {
return (Iterator<E>) domain.getChildren(o).iterator();
}
}
/**
* This returns the editing domain for the given arbitrary object, or null,
* if it can't be determined. It is recommended that you always work
* directly with an EditingDomain instance whenever possible. This is
* implemented to check if the object itself implements
* {@link net.enilink.komma.edit.domain.IEditingDomainProvider} and returns
* that result. Otherwise it checks if it is valid to call
* {@link #getEditingDomainFor(org.eclipse.emf.ecore.EObject)
* getEditingDomainFor(EObject)} and returns that result or null.
*
* <p>
* It is recommended that you always keep an editing domain instance
* available through some other means; this should only be used to implement
* things such as a global popup action for some object; in such a cases
* such as that the editing domain returned here may well be one that
* belongs to some editor you know nothing about, which is what you want.
*/
static public IEditingDomain getEditingDomainFor(Object object) {
if (object instanceof IEditingDomainProvider) {
IEditingDomain editingDomain = ((IEditingDomainProvider) object).getEditingDomain();
return editingDomain;
} else if (object instanceof IWrapperItemProvider) {
return getEditingDomainFor(((IWrapperItemProvider) object).getValue());
} else {
IEditingDomainProvider provider = null;
if (object instanceof IObject) {
provider = (IEditingDomainProvider) ((IObject) object).getModel().getModelSet().adapters()
.getAdapter(IEditingDomainProvider.class);
} else if (object instanceof IAdaptable) {
provider = ((IAdaptable) object).getAdapter(IEditingDomainProvider.class);
}
if (provider != null) {
return provider.getEditingDomain();
}
}
return null;
}
/**
* Returns whether the object is, contains, or wraps something that likely
* represents a stale {@link Resource#unload() unloaded}
* {@link EObject#eIsProxy() object}. It's best to stop using unloaded
* objects entirely because they ought to be garbage collected and should be
* replaced by their {@link AdapterFactoryEditingDomain#resolve(Collection)
* resolved} result.
*/
static public boolean isStale(Object object) {
if (object instanceof IWrapperItemProvider) {
IWrapperItemProvider wrapper = (IWrapperItemProvider) object;
return isStale(wrapper.getValue()) || isStale(wrapper.getOwner());
} else if (object instanceof Collection<?>) {
for (Object item : (Collection<?>) object) {
if (isStale(item)) {
return true;
}
}
return false;
} else if (object instanceof Object[]) {
for (Object item : (Object[]) object) {
if (isStale(item)) {
return true;
}
}
return false;
} else if (object instanceof IEntity) {
return ((IEntity) object).getEntityManager() == null
|| !((IEntity) object).getEntityManager().isOpen();
} else if (object == null) {
return false;
} else {
// This handles IStructuredSelection.
Class<?> objectClass = object.getClass();
try {
Method method = objectClass.getMethod("toArray");
return isStale(method.invoke(object));
} catch (Exception exception) {
return false;
}
}
}
public static Object unwrap(Object object) {
while (object instanceof IWrapperItemProvider) {
object = ((IWrapperItemProvider) object).getValue();
}
return object;
}
private class EditingDomainProviderAdapter implements IAdapter,
IEditingDomainProvider {
@Override
public void addTarget(Object adapted) {
}
@Override
public boolean isAdapterForType(Object type) {
return IEditingDomainProvider.class.equals(type);
}
@Override
public void removeTarget(Object adapted) {
}
@Override
public IEditingDomain getEditingDomain() {
return AdapterFactoryEditingDomain.this;
}
}
/**
* This is the adapter factory used to create the adapter to which calls are
* delegated.
*/
protected IAdapterFactory adapterFactory;
/**
* This is the current clipboard.
*/
protected Collection<Object> clipboard;
/**
* This is the command stack that was passed into the constructor.
*/
protected ICommandStack commandStack;
/**
* This is the resource set used to contain all created and loaded
* resources.
*/
protected IModelSet modelSet;
/**
* This controls whether the domain is read only.
*/
protected Map<IModel, Boolean> modelToReadOnlyMap;
private EditingDomainProviderAdapter domainProvider;
private ChangeRecorder recorder;
private IModelSet clipboardModelSet;
/**
* Create an instance from the adapter factory, the specialized command
* stack, and the specialized resource set. If the resource set's context is
* null, one will be created here; otherwise, the existing context should
* implement {@link net.enilink.komma.edit.domain.IEditingDomainProvider}.
*/
public AdapterFactoryEditingDomain(IAdapterFactory adapterFactory,
ICommandStack commandStack, IModelSet modelSet) {
this.adapterFactory = adapterFactory;
this.commandStack = commandStack;
this.modelSet = modelSet;
registerDomainProviderAdapter();
initialize();
}
/**
* Register an {@link IEditingDomainProvider} as adapter on the model set.
*/
protected void registerDomainProviderAdapter() {
domainProvider = new EditingDomainProviderAdapter();
this.modelSet.adapters().add(domainProvider);
}
/**
* May be overridden by subclasses to create a custom change recorder
* implementation. Just creates a change recorder on the specified resource
* set and returns it.
*
* @param modelSet
* a model set in which to record changes
*
* @return the new change recorder
*/
protected ChangeRecorder createChangeRecorder(IModelSet modelSet) {
return new ChangeRecorder(modelSet);
}
/**
* This delegates to
* {@link net.enilink.komma.edit.provider.IEditingDomainItemProvider#createCommand
* IEditingDomainItemProvider.createCommand}.
*/
public ICommand createCommand(Class<? extends ICommand> commandClass,
CommandParameter commandParameter) {
// If the owner parameter is set, we delegate to the owner's adapter
Object owner = commandParameter.getOwner();
if (commandClass == CopyToClipboardCommand.class) {
return new CopyToClipboardCommand(this,
commandParameter.getCollection());
} else if (commandClass == PasteFromClipboardCommand.class) {
return new PasteFromClipboardCommand(this,
commandParameter.getOwner(),
commandParameter.getProperty(), commandParameter.getIndex());
} else if (commandClass == CutToClipboardCommand.class) {
return new CutToClipboardCommand(this, RemoveCommand.create(this,
commandParameter.getOwner(),
commandParameter.getProperty(),
commandParameter.getCollection()));
} else if (commandClass == DeleteCommand.class) {
return new DeleteCommand(this, commandParameter.getCollection());
} else if (owner != null) {
// If there is an adapter of the correct type...
IEditingDomainItemProvider editingDomainItemProvider = (IEditingDomainItemProvider) adapterFactory
.adapt(owner, IEditingDomainItemProvider.class);
return editingDomainItemProvider != null ? editingDomainItemProvider
.createCommand(owner, this, commandClass, commandParameter)
: new ItemProviderAdapter(null).createCommand(owner, this,
commandClass, commandParameter);
} else {
// If command has no owner specified
if (commandClass == RemoveCommand.class) {
// For RemoveCommand, we will find the owner by calling
// EditingDomain.getParent() on the object(s) being removed.
ExtendedCompositeCommand removeCommand = new ExtendedCompositeCommand(
ExtendedCompositeCommand.MERGE_COMMAND_ALL);
List<Object> objects = new ArrayList<Object>(
commandParameter.getCollection());
while (!objects.isEmpty()) {
// We will iterate over the whole collection, removing some
// as we go.
ListIterator<Object> remainingObjects = objects
.listIterator();
// Take the first object, and remove it.
Object object = remainingObjects.next();
remainingObjects.remove();
// Determine the object's parent.
Object parent = getParent(object);
if (parent != null) {
// Now we want to find all the other objects with this
// same parent.
// So we can collection siblings together and give the
// parent control over their removal.
List<Object> siblings = new ArrayList<Object>();
siblings.add(object);
while (remainingObjects.hasNext()) {
// Get the next object and check if it has the same
// parent.
Object otherObject = remainingObjects.next();
Object otherParent = getParent(otherObject);
if (otherParent == parent) {
// Remove the object and add it as a sibling.
remainingObjects.remove();
siblings.add(otherObject);
}
}
// We will now create a command with this implied parent
removeCommand.add(createCommand(RemoveCommand.class,
new CommandParameter(parent, null, siblings)));
} else if (object != null) {
// The parent is null, which implies a top-level
// removal, so create a self-removing command.
removeCommand.add(createCommand(
RemoveCommand.class,
new CommandParameter(object, null, Collections
.singleton(object))));
}
}
return removeCommand.reduce();
} else if (commandClass == ReplaceCommand.class) {
Object obj = commandParameter.getValue();
Object parent = (obj == null) ? null : getParent(obj);
if (parent == null)
parent = obj;
return createCommand(ReplaceCommand.class,
new CommandParameter(parent, null, obj,
commandParameter.getCollection()));
} else if (commandClass == CreateChildCommand.class) {
// For CreateChildCommand, we will find the owner by calling
// EditingDomain.getParent() on the first selected object
Collection<?> sel = commandParameter.getCollection();
Object parent = sel == null ? null : getParent(sel.iterator()
.next());
if (parent == null) {
return UnexecutableCommand.INSTANCE;
}
return createCommand(
CreateChildCommand.class,
new CommandParameter(parent, commandParameter
.getProperty(), commandParameter.getValue(),
commandParameter.getCollection(),
commandParameter.getIndex()));
}
}
try {
Constructor<? extends ICommand> constructor = commandClass
.getConstructor(IEditingDomain.class,
CommandParameter.class);
ICommand command = constructor.newInstance(new Object[] { this,
commandParameter });
return command;
} catch (IllegalAccessException exception) {
// Ignore.
} catch (InstantiationException exception) {
// Ignore.
} catch (NoSuchMethodException exception) {
// Ignore.
} catch (InvocationTargetException exception) {
// Ignore.
}
return UnexecutableCommand.INSTANCE;
}
/**
* This just returns null, since this is an optional feature that we don't
* support here.
*/
public ICommand createOverrideCommand(IOverrideableCommand command) {
return null;
}
@Override
public void dispose() {
if (recorder != null) {
recorder.dispose();
recorder = null;
}
if (domainProvider != null) {
modelSet.adapters().remove(domainProvider);
domainProvider = null;
}
}
/**
* This returns the adapter factory used by this domain.
*/
public IAdapterFactory getAdapterFactory() {
return adapterFactory;
}
@Override
public ChangeRecorder getChangeRecorder() {
return recorder;
}
/**
* This delegates to
* {@link net.enilink.komma.edit.provider.IEditingDomainItemProvider#getChildren
* IEditingDomainItemProvider.getChildren}.
*/
public Collection<?> getChildren(Object object) {
// If there is an adapter of the correct type...
//
IEditingDomainItemProvider editingDomainItemProvider = (IEditingDomainItemProvider) adapterFactory
.adapt(object, IEditingDomainItemProvider.class);
return editingDomainItemProvider != null ? editingDomainItemProvider
.getChildren(object) : Collections.emptyList();
}
/**
* This returns the clipboard of the editing domain.
*/
public Collection<Object> getClipboard() {
return clipboard;
}
@Override
public synchronized IModel getClipboardModel() {
// TODO use a shared clipboard model set for the whole application
if (clipboardModelSet == null) {
IModelSetFactory factory = Guice.createInjector(
Modules.override(
new ModelSetModule(ModelPlugin
.createModelSetModule(getClass()
.getClassLoader()))).with(
new AbstractModule() {
@Override
protected void configure() {
bind(UnitOfWork.class).toInstance(
(UnitOfWork) getModelSet()
.getUnitOfWork());
bind(IUnitOfWork.class).toInstance(
getModelSet().getUnitOfWork());
}
})).getInstance(IModelSetFactory.class);
clipboardModelSet = factory.createModelSet(URIs
.createURI(MODELS.NAMESPACE + "MemoryModelSet" //
));
}
IModel model = clipboardModelSet.getModel(
URIs.createURI(CLIPBOARD_URI), false);
if (model != null) {
return model;
}
return clipboardModelSet.createModel(URIs.createURI(CLIPBOARD_URI));
}
/**
* This returns the command stack provided in the constructor.
*/
public ICommandStack getCommandStack() {
return commandStack;
}
/**
* This returns the model set used to contain all created and loaded models.
*/
public IModelSet getModelSet() {
return modelSet;
}
/**
* Returns the map of resource to a Boolean value indicating whether the
* resource is read only.
*/
public Map<IModel, Boolean> getModelToReadOnlyMap() {
return modelToReadOnlyMap;
}
/**
* This delegates to
* {@link net.enilink.komma.edit.provider.IEditingDomainItemProvider#getNewChildDescriptors
* IEditingDomainItemProvider.getNewChildDescriptors}.
*/
public void getNewChildDescriptors(Object object, Object sibling,
ICollector<Object> descriptors) {
// If no object is specified, but an existing sibling is, the object is
// its parent.
if (object == null) {
object = getParent(sibling);
}
if (object == null) {
return;
}
// If there is an adapter of the correct type...
IEditingDomainItemProvider editingDomainItemProvider = (IEditingDomainItemProvider) adapterFactory
.adapt(object, IEditingDomainItemProvider.class);
if (editingDomainItemProvider != null) {
editingDomainItemProvider.getNewChildDescriptors(object, this,
sibling, descriptors);
}
}
/**
* This delegates to
* {@link net.enilink.komma.edit.provider.IEditingDomainItemProvider#getParent
* IEditingDomainItemProvider.getParent}.
*/
public Object getParent(Object object) {
// If there is an adapter of the correct type...
IEditingDomainItemProvider editingDomainItemProvider = (IEditingDomainItemProvider) adapterFactory
.adapt(object, IEditingDomainItemProvider.class);
return editingDomainItemProvider != null ? editingDomainItemProvider
.getParent(object) : null;
}
public Object getRoot(Object object) {
Object result = object;
for (Object parent = getParent(object); parent != null; parent = getParent(parent)) {
result = parent;
}
return result;
}
public Object getWrapper(Object object) {
if (object != null) {
for (Iterator<?> i = treeIterator(getRoot(object)); i.hasNext();) {
Object element = i.next();
Object elementValue = element;
while (elementValue instanceof IWrapperItemProvider) {
elementValue = ((IWrapperItemProvider) elementValue)
.getValue();
}
if (elementValue == object) {
return element;
}
}
}
return object;
}
/**
* Initializes my state.
*/
protected void initialize() {
recorder = createChangeRecorder(modelSet);
}
@Override
public boolean isReadOnly(IModel model) {
if (modelToReadOnlyMap == null) {
return false;
} else {
Boolean result = modelToReadOnlyMap.get(model);
if (result == null && model != null) {
Map<String, ?> attributes = (model.getModelSet() == null ? modelSet
: model.getModelSet()).getURIConverter().getAttributes(
model.getURI(), null);
result = Boolean.TRUE.equals(attributes
.get(IURIConverter.ATTRIBUTE_READ_ONLY));
modelToReadOnlyMap.put(model, result);
}
return Boolean.TRUE.equals(result);
}
}
@Override
public boolean isReadOnly(IEntity entity) {
if (!(entity instanceof IObject)) {
return false;
}
return isReadOnly(((IObject) entity).getModel());
}
/**
* This sets the adapter factory after the domain is already created.
*/
public void setAdapterFactory(IAdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
}
/**
* This sets the clipboard of the editing domain.
*/
public void setClipboard(Collection<Object> clipboard) {
this.clipboard = clipboard;
}
/**
* Set the map of resource to a Boolean value indicating whether the
* resource is read only.
*/
public void setModelToReadOnlyMap(Map<IModel, Boolean> modelToReadOnlyMap) {
this.modelToReadOnlyMap = modelToReadOnlyMap;
}
/**
* This returns a tree iterator that will yield the object, the children of
* the object, their children, and so on.
*/
public ITreeIterator<?> treeIterator(Object object) {
return new DomainTreeIterator<Object>(this, object);
}
}