blob: bb19c472dd96a02f89773daf4906102f74c7f171 [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.ui.adapters.properties;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import org.eclipse.bpmn2.Activity;
import org.eclipse.bpmn2.Bpmn2Package;
import org.eclipse.bpmn2.CallableElement;
import org.eclipse.bpmn2.DataAssociation;
import org.eclipse.bpmn2.DataInput;
import org.eclipse.bpmn2.DataInputAssociation;
import org.eclipse.bpmn2.DataObject;
import org.eclipse.bpmn2.DataObjectReference;
import org.eclipse.bpmn2.DataOutput;
import org.eclipse.bpmn2.DataOutputAssociation;
import org.eclipse.bpmn2.DataStore;
import org.eclipse.bpmn2.DataStoreReference;
import org.eclipse.bpmn2.DocumentRoot;
import org.eclipse.bpmn2.Event;
import org.eclipse.bpmn2.FlowElementsContainer;
import org.eclipse.bpmn2.InputOutputSpecification;
import org.eclipse.bpmn2.ItemAwareElement;
import org.eclipse.bpmn2.Process;
import org.eclipse.bpmn2.Property;
import org.eclipse.bpmn2.di.BPMNShape;
import org.eclipse.bpmn2.modeler.core.adapters.AdapterUtil;
import org.eclipse.bpmn2.modeler.core.adapters.ExtendedPropertiesAdapter;
import org.eclipse.bpmn2.modeler.core.adapters.FeatureDescriptor;
import org.eclipse.bpmn2.modeler.core.features.ContextConstants;
import org.eclipse.bpmn2.modeler.core.model.Bpmn2ModelerFactory;
import org.eclipse.bpmn2.modeler.core.preferences.ModelEnablements;
import org.eclipse.bpmn2.modeler.core.utils.AnchorUtil;
import org.eclipse.bpmn2.modeler.core.utils.BusinessObjectUtil;
import org.eclipse.bpmn2.modeler.core.utils.GraphicsUtil;
import org.eclipse.bpmn2.modeler.core.utils.ModelUtil;
import org.eclipse.bpmn2.modeler.ui.features.flow.DataAssociationFeatureContainer;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.IDeleteFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.IReconnectionFeature;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.features.context.impl.DeleteContext;
import org.eclipse.graphiti.features.context.impl.ReconnectionContext;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.ui.editor.DiagramEditor;
/**
* @author Bob Brodt
*
*/
public class DataAssociationPropertiesAdapter extends ExtendedPropertiesAdapter<DataAssociation> {
/**
* @param adapterFactory
* @param object
*/
public DataAssociationPropertiesAdapter(AdapterFactory adapterFactory, DataAssociation object) {
super(adapterFactory, object);
EStructuralFeature ref;
ref = Bpmn2Package.eINSTANCE.getDataAssociation_SourceRef();
setFeatureDescriptor(ref, new SourceTargetFeatureDescriptor(adapterFactory,object,ref));
setProperty(ref, UI_CAN_EDIT_INLINE, Boolean.FALSE);
setProperty(ref, UI_CAN_EDIT, Boolean.FALSE);
setProperty(ref, UI_CAN_CREATE_NEW, Boolean.FALSE);
setProperty(ref, UI_IS_MULTI_CHOICE, Boolean.TRUE);
ref = Bpmn2Package.eINSTANCE.getDataAssociation_TargetRef();
setFeatureDescriptor(ref, new SourceTargetFeatureDescriptor(adapterFactory,object,ref));
setProperty(ref, UI_CAN_EDIT_INLINE, Boolean.FALSE);
setProperty(ref, UI_CAN_EDIT, Boolean.FALSE);
setProperty(ref, UI_CAN_CREATE_NEW, Boolean.FALSE);
setProperty(ref, UI_IS_MULTI_CHOICE, Boolean.TRUE);
}
public class SourceTargetFeatureDescriptor extends FeatureDescriptor<DataAssociation> {
public SourceTargetFeatureDescriptor(AdapterFactory adapterFactory, DataAssociation object, EStructuralFeature feature) {
super(adapterFactory, object, feature);
}
@Override
public String getLabel(Object context) {
Object object = adopt(context);
if (object instanceof DataInputAssociation)
return Messages.DataAssociationPropertiesAdapter_Source;
return Messages.DataAssociationPropertiesAdapter_Target;
}
@Override
public Hashtable<String, Object> getChoiceOfValues(Object context) {
List<EObject> values = new ArrayList<EObject>();
// search for all Properties and DataStores
// Properties are contained in the nearest enclosing Process or Event;
// DataStores are contained in the DocumentRoot
DataAssociation association = adopt(context);
EObject container = ModelUtil.getContainer(association);
values.addAll( ModelUtil.collectAncestorObjects(container, "properties", new Class[] {Activity.class}) ); //$NON-NLS-1$
values.addAll( ModelUtil.collectAncestorObjects(container, "properties", new Class[] {Process.class}) ); //$NON-NLS-1$
values.addAll( ModelUtil.collectAncestorObjects(container, "properties", new Class[] {Event.class}) ); //$NON-NLS-1$
values.addAll( ModelUtil.collectAncestorObjects(container, "dataStore", new Class[] {DocumentRoot.class}) ); //$NON-NLS-1$
values.addAll( ModelUtil.collectAncestorObjects(container, "flowElements", new Class[] {FlowElementsContainer.class}, new Class[] {ItemAwareElement.class})); //$NON-NLS-1$
Activity activity = (Activity)ModelUtil.findNearestAncestor(container, new Class[] {Activity.class});
if (activity!=null) {
InputOutputSpecification ioSpec = activity.getIoSpecification();
if (ioSpec!=null) {
if (association instanceof DataInputAssociation)
values.addAll(ioSpec.getDataInputs());
else
values.addAll(ioSpec.getDataOutputs());
}
}
CallableElement callable = (CallableElement)ModelUtil.findNearestAncestor(container, new Class[] {CallableElement.class});
if (callable!=null) {
InputOutputSpecification ioSpec = callable.getIoSpecification();
if (ioSpec!=null) {
if (association instanceof DataInputAssociation)
values.addAll(ioSpec.getDataInputs());
else
values.addAll(ioSpec.getDataOutputs());
}
}
super.setChoiceOfValues(values);
return super.getChoiceOfValues(context);
}
@Override
public EObject createFeature(Resource resource, Object context, EClass eClass) {
DataAssociation association = adopt(context);
// what kind of object should we create? Property or DataStore?
if (eClass==null) {
if (ModelUtil.findNearestAncestor(association, new Class[] {Process.class, Event.class}) != null)
// nearest ancestor is a Process or Event, so new object will be a Property
eClass = Bpmn2Package.eINSTANCE.getProperty();
else if(ModelUtil.findNearestAncestor(association, new Class[] {DocumentRoot.class}) != null)
eClass = Bpmn2Package.eINSTANCE.getDataStore();
}
if (eClass!=null) {
return Bpmn2ModelerFactory.create(resource, eClass);
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public void setValue(Object context, Object value) {
final DataAssociation association = adopt(context);
EObject container = null;
EStructuralFeature containerFeature = null;
if (value instanceof Property) {
if (((Property)value).eContainer()==null) {
// this Property isn't owned by anything yet - figure out who the owner is
container = ModelUtil.findNearestAncestor(association, new Class[] {Activity.class});
if (container==null)
container = ModelUtil.findNearestAncestor(association, new Class[] {Event.class});
if (container==null)
container = ModelUtil.findNearestAncestor(association, new Class[] {Process.class});
containerFeature = container.eClass().getEStructuralFeature("properties"); //$NON-NLS-1$
}
}
else if (value instanceof DataStore) {
if (((DataStore)value).eContainer()==null) {
// this DataStore isn't owned by anything yet - figure out who the owner is
container = ModelUtil.findNearestAncestor(association, new Class[] {DocumentRoot.class});
containerFeature = container.eClass().getEStructuralFeature("dataStore"); //$NON-NLS-1$
}
}
else if (value instanceof String) {
// first check if a property with this name already exists
Hashtable<String, Object> choices = getChoiceOfValues(object);
Property property = (Property)choices.get(value);
if (property==null) {
// need to create a new one!
DiagramEditor editor = ModelUtil.getEditor(object);
ModelEnablements modelEnablement =
(ModelEnablements)editor.getAdapter(ModelEnablements.class);
// find nearest element that can contain a Property and create one
container = association;
for (;;) {
container = ModelUtil.findNearestAncestor(container, new Class[] {Activity.class, Event.class, Process.class});
if (container==null)
return;
containerFeature = container.eClass().getEStructuralFeature("properties"); //$NON-NLS-1$
if (modelEnablement.isEnabled(container.eClass(), containerFeature))
break;
}
containerFeature = container.eClass().getEStructuralFeature("properties"); //$NON-NLS-1$
property = Bpmn2ModelerFactory.create(Property.class);
ExtendedPropertiesAdapter adapter = ExtendedPropertiesAdapter.adapt(property);
adapter.getObjectDescriptor().setDisplayName((String)value);
}
value = property;
}
final EObject c = container;
final EStructuralFeature cf = containerFeature;
final ItemAwareElement v = (ItemAwareElement)value;
TransactionalEditingDomain editingDomain = getEditingDomain(association);
if (feature == Bpmn2Package.eINSTANCE.getDataAssociation_SourceRef()) {
if (editingDomain == null) {
setSourceRef(association, v, c, cf);
} else {
editingDomain.getCommandStack().execute(new RecordingCommand(editingDomain) {
@Override
protected void doExecute() {
setSourceRef(association, v, c, cf);
}
});
}
}
else {
if (editingDomain == null) {
setTargetRef(association, v, c, cf);
} else {
editingDomain.getCommandStack().execute(new RecordingCommand(editingDomain) {
@Override
protected void doExecute() {
setTargetRef(association, v, c, cf);
}
});
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void setSourceRef(DataAssociation association, ItemAwareElement value, EObject container, EStructuralFeature containerFeature) {
if (association.getSourceRef().size()==0) {
if (container!=null) {
if (containerFeature.isMany())
((List)container.eGet(containerFeature)).add(value);
else
container.eSet(containerFeature, value);
}
association.getSourceRef().add(value);
}
else {
if (container!=null) {
if (containerFeature.isMany())
((List)container.eGet(containerFeature)).add(value);
else
container.eSet(containerFeature, value);
}
updateConnectionIfNeeded(association, value);
association.getSourceRef().set(0,value);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void setTargetRef(DataAssociation association, ItemAwareElement value, EObject container, EStructuralFeature containerFeature) {
if (container!=null) {
if (containerFeature.isMany())
((List)container.eGet(containerFeature)).add(value);
else
container.eSet(containerFeature, value);
}
updateConnectionIfNeeded(association, value);
association.setTargetRef(value);
}
private void updateConnectionIfNeeded(DataAssociation association, ItemAwareElement value) {
DiagramEditor diagramEditor = ModelUtil.getDiagramEditor(association);
Diagram diagram = diagramEditor.getDiagramTypeProvider().getDiagram();
IFeatureProvider fp = diagramEditor.getDiagramTypeProvider().getFeatureProvider();
Shape owner = null;
EObject container = association.eContainer();
if (container instanceof Activity || container instanceof Event) {
for (PictogramElement pe : Graphiti.getLinkService().getPictogramElements(diagram, container)) {
if (pe instanceof Shape && BusinessObjectUtil.getFirstElementOfType(pe, BPMNShape.class)!=null)
owner = (Shape) pe;
}
}
PictogramElement pe = null;
if (value instanceof DataObject ||
value instanceof DataObjectReference ||
value instanceof DataStore ||
value instanceof DataStoreReference ||
value instanceof DataInput ||
value instanceof DataOutput) {
List<PictogramElement> pes = Graphiti.getLinkService().getPictogramElements(diagram, (EObject)value);
for (PictogramElement p : pes) {
if (BusinessObjectUtil.getFirstElementOfType(p, BPMNShape.class)!=null) {
pe = p;
break;
}
}
}
Connection connection = DataAssociationFeatureContainer.findDataAssociation(diagram, association);
if (connection!=null) {
// There's an existing DataAssociation connection which needs to
// either be reconnected or deleted, depending on what the combobox
// selection is.
if (pe!=null) {
// need to reconnect the DataAssociation
ReconnectionContext rc = null;
if (association instanceof DataOutputAssociation) {
Point p = GraphicsUtil.createPoint(connection.getStart());
Anchor a = AnchorUtil.findNearestAnchor((AnchorContainer) pe, p);
rc = new ReconnectionContext(connection, connection.getStart(), a, null);
rc.setTargetPictogramElement(pe);
rc.setTargetLocation(Graphiti.getPeService().getLocationRelativeToDiagram(a));
rc.setReconnectType(ReconnectionContext.RECONNECT_TARGET);
}
else {
Point p = GraphicsUtil.createPoint(connection.getEnd());
Anchor a = AnchorUtil.findNearestAnchor(owner, p);
rc = new ReconnectionContext(connection, a, connection.getEnd(), null);
rc.setTargetPictogramElement(pe);
rc.setTargetLocation(Graphiti.getPeService().getLocationRelativeToDiagram(a));
rc.setReconnectType(ReconnectionContext.RECONNECT_SOURCE);
}
IReconnectionFeature rf = fp.getReconnectionFeature(rc);
if (rf.canReconnect(rc)) {
rf.reconnect(rc);
}
}
else {
// need to delete the DataAssociation connection
DeleteContext dc = new DeleteContext(connection);
connection.getLink().getBusinessObjects().remove(0);
IDeleteFeature df = fp.getDeleteFeature(dc);
df.delete(dc);
}
}
else if (pe!=null){
// There is no existing DataAssociation connection, but the newly selected source or target
// is some kind of data object shape, so we need to create a connection between the Activity
// (or Throw/Catch Event) that owns the DataAssociation, and the new data object shape.
Point p = GraphicsUtil.createPoint((AnchorContainer) pe);
Anchor ownerAnchor = AnchorUtil.findNearestAnchor(owner, p);
p = GraphicsUtil.createPoint(owner);
Anchor peAnchor = AnchorUtil.findNearestAnchor((AnchorContainer) pe, p);
AddConnectionContext ac = null;
if (association instanceof DataOutputAssociation) {
ac = new AddConnectionContext(ownerAnchor, peAnchor);
}
else {
ac = new AddConnectionContext(peAnchor, ownerAnchor);
}
ac.putProperty(ContextConstants.BUSINESS_OBJECT, association);
ac.setNewObject(association);
IAddFeature af = fp.getAddFeature(ac);
if (af.canAdd(ac)) {
af.add(ac);
}
}
}
}
}