blob: ac97208d27fa40726c876c45dd9bc8a574ba9048 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2012 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
*
* @author Bob Brodt
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.adapters;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.bpmn2.ExtensionAttributeValue;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
/**
* This adapter will insert a new value into its container feature when the
* owning object's content changes. This allows the UI to construct new objects
* without inserting them into their container unless the user changes some
* feature in the new object. Thus, an empty EObject is available for use by the
* UI for rendering only, without creating an EMF transaction, and hence, a
* useless entry on the undo stack.
*/
public class InsertionAdapter extends EContentAdapter implements IResourceProvider {
protected Resource resource;
protected EObject object;
protected EStructuralFeature feature;
protected EObject value;
private InsertionAdapter(EObject object, EStructuralFeature feature, EObject value) {
this.resource = object.eResource();
this.object = object;
this.feature = feature;
this.value = value;
}
private InsertionAdapter(EObject object, String featureName, EObject value) {
this(object, object.eClass().getEStructuralFeature(featureName), value);
}
/**
* Create an InsertionAdapter that will add the value into the given
* object's containment feature as soon as some feature in the value is
* changed by the user.
* <p>
* In order for this to work, the object being adapted must be contained in
* a Resource, the value must <b>not yet</b> be contained in a Resource, and
* the value must be an instance of the feature's EType.
*
* @param object the object being adapted
* @param feature a containment feature of the object
* @param value the value to be inserted
* @return the value to be inserted
*/
public static EObject add(EObject object, EStructuralFeature feature, EObject value) {
if (object!=null) {
value.eAdapters().add(
new InsertionAdapter(object, feature, value));
}
return value;
}
/**
* Convenience method for creating an InsertionAdapter given a feature name.
*
* @param object the object being adapted
* @param featureName the name of a containment feature of the object
* @param value the value to be inserted
* @return the value to be inserted
*/
public static EObject add(EObject object, String featureName, EObject value) {
if (object!=null) {
value.eAdapters().add(
new InsertionAdapter(object, featureName, value));
}
return value;
}
/* (non-Javadoc)
* @see org.eclipse.emf.ecore.util.EContentAdapter#notifyChanged(org.eclipse.emf.common.notify.Notification)
*/
public void notifyChanged(Notification notification) {
if (notification.getNotifier() == value && !(notification.getOldValue() instanceof InsertionAdapter)) {
// execute if an attribute in the new value has changed
execute();
}
else if (notification.getNotifier()==object && notification.getNewValue()==value) {
// if the new value has been added to the object, we can remove this adapter
object.eAdapters().remove(this);
}
}
private void executeChildren(List list) {
for (Object o : list) {
if (o instanceof List) {
executeChildren((List)o);
}
else if (o instanceof EObject) {
executeIfNeeded((EObject)o);
}
}
}
private void executeChildren(EObject value) {
// allow other adapters to execute first
for (EStructuralFeature f : value.eClass().getEAllStructuralFeatures()) {
try {
Object v = value.eGet(f);
if (v instanceof List) {
executeChildren((List)v);
}
else if (v instanceof EObject) {
executeIfNeeded((EObject)v);
}
}
catch (Exception e) {
// some getters may throw exceptions - ignore those
}
}
executeIfNeeded(value);
}
@SuppressWarnings("unchecked")
private void execute() {
// if the object into which this value is being added has other adapters execute those first
executeIfNeeded(object);
// remove this adapter from the value - this adapter is a one-shot deal!
value.eAdapters().remove(this);
try {
Object o = object.eGet(feature);
}
catch (Exception e1) {
try {
if (value.eClass().getEStructuralFeature(feature.getName())!=null) {
Object o = value.eGet(feature);
// this is the inverse add of object into value
o = value;
value = object;
object = (EObject)o;
}
}
catch (Exception e2) {
}
}
// if there are any EObjects contained or referenced by this value, execute those adapters first
executeChildren(value);
// set the value in the object
boolean valueChanged = false;
final EList<EObject> list = feature.isMany() ? (EList<EObject>)object.eGet(feature) : null;
if (list==null) {
try {
valueChanged = object.eGet(feature)!=value;
}
catch (Exception e) {
// feature does not exist, it's a dynamic feature
valueChanged = true;
}
}
else
valueChanged = !list.contains(value) || value instanceof ExtensionAttributeValue;
if (valueChanged) {
ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(object);
if (adapter!=null) {
adapter.getFeatureDescriptor(feature).setValue(value);
}
}
}
/**
* Adds the value to the object's containment feature.
*
* @param value
*/
public static void executeIfNeeded(EObject value) {
if (value!=null) {
List<InsertionAdapter> allAdapters = new ArrayList<InsertionAdapter>();
for (Adapter adapter : value.eAdapters()) {
if (adapter instanceof InsertionAdapter) {
allAdapters.add((InsertionAdapter)adapter);
}
}
value.eAdapters().removeAll(allAdapters);
for (InsertionAdapter adapter : allAdapters)
adapter.execute();
}
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.adapters.IResourceProvider#getResource()
*/
@Override
public Resource getResource() {
if (resource==null) {
Resource res = object.eResource();
if (res!=null)
return res;
InsertionAdapter insertionAdapter = AdapterUtil.adapt(object, InsertionAdapter.class);
if (insertionAdapter!=null)
return insertionAdapter.getResource();
}
return resource;
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.adapters.IResourceProvider#setResource(org.eclipse.emf.ecore.resource.Resource)
*/
@Override
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Gets the EMF Resource that contains the given object. If the object has been adapted
* for InsertionAdapter, the Resource defined by that adapter is returned.
*
* @param object the object.
* @return an EMF Resource or null.
*/
public static Resource getResource(EObject object) {
InsertionAdapter adapter = AdapterUtil.adapt(object, InsertionAdapter.class);
if (adapter!=null) {
return adapter.getResource();
}
if (object!=null)
return object.eResource();
return null;
}
/**
* Gets the object managed by this InsertionAdapter.
*
* @return the object
*/
public EObject getObject() {
return object;
}
/**
* Gets the object's containment feature managed by this InsertionAdapter
*
* @return the containment feature
*/
public EStructuralFeature getFeature() {
return feature;
}
/**
* Gets the object to be inserted into the containment feature.
*
* @return the value
*/
public EObject getValue() {
return value;
}
/* (non-Javadoc)
* @see org.eclipse.emf.edit.domain.IEditingDomainProvider#getEditingDomain()
*/
@Override
public EditingDomain getEditingDomain() {
getResource();
if (resource!=null)
return AdapterFactoryEditingDomain.getEditingDomainFor(resource);
return null;
}
}