package com.sap.ide.treeprovider;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.ui.views.properties.IPropertySource;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import com.sap.ide.treeprovider.internal.Activator;
public class GenericRefObjectNode extends TreeNodeRefObject<EObject> implements INodeExplorer, ITreeNodeTransferHandler, IModelTransferPreparer {
private static MOINModelBrowserPropertySourceManager propManager = new MOINModelBrowserPropertySourceManager();
/**
* This inner class combines an object with an optional role name that is to
* be displayed in front of the object's label in the tree. If the
* {@link #roleName} is <tt>null</tt>, only the object will be shown.
* <p>
*
* If the object is a {@link EObject}, a class in this package named
* <tt>NodeX</tt> with <tt>X</tt> being replaced by the unqualified name of
* the {@link EObject}'s class will be looked up (example:
* <tt>NodeSapClass</tt> for class <tt>data.classes.SapClass</tt>). If
* found, a two-argument constructor is called, using the parent object and
* the {@link EObject} as arguments. Note, that the tree node class must
* implement the {@link INodeExplorer} interface. If no such class is found,
* the class {@link GenericRefObjectNode} is used to show the
* {@link EObject} in the tree.
* <p>
*
* @author Axel Uhl D043530
*
*/
public static class RoleNameAndObject {
private final String roleName;
private final Object theObject;
public RoleNameAndObject(String roleName, Object refObject) {
this.roleName = roleName;
this.theObject = refObject;
}
public String getRoleName() {
return roleName;
}
public Object getObject() {
return theObject;
}
public EObject getTreeNode(EObject parent) {
EObject node = null;
if (getObject() instanceof EObject) {
node = getTreeNodeForRefObject(parent, node);
}
return node;
}
private EObject getTreeNodeForRefObject(EObject parent, EObject node) {
try {
Class<?> nodeClass = Class.forName("com.sap.ide.treeprovider.internal.explorer.nodes.Node"
+ ((EClass) ((EObject) getObject()).refMetaObject()).getName());
for (Constructor<?> c : nodeClass.getDeclaredConstructors()) {
if (c.getParameterTypes().length == 2 && c.getParameterTypes()[0].isAssignableFrom(parent.getClass())
&& c.getParameterTypes()[1].isAssignableFrom(getObject().getClass())) {
node = (EObject) c.newInstance(parent, getObject());
break;
}
}
if (node == null) {
node = (EObject) new GenericRefObjectNode(parent, ((EObject) getObject()), getRoleName());
}
} catch (ClassNotFoundException cnfe) {
// in this case, use this generic node type
node = (EObject) new GenericRefObjectNode(parent, ((EObject) getObject()), getRoleName());
} catch (Exception e) {
throw new RuntimeException(e);
}
return node;
}
}
/**
* If non-<tt>null</tt>, specifies a name to be used as a prefix in the
* label displaying this node in the tree; would typically identify an
* attribute/reference/feature/association end name for the value
* represented by this node.
*/
private String roleName;
public String getRoleName() {
return roleName;
}
public GenericRefObjectNode(EObject parent, EObject modelElement) {
super(parent, modelElement);
}
public GenericRefObjectNode(EObject parent, EObject modelElement, String roleName) {
this(parent, modelElement);
this.roleName = roleName;
}
protected Collection<RoleNameAndObject> getChildRefObjects() {
JmiHelper jmiHelper = getValue().get___Connection().getJmiHelper();
List<RoleNameAndObject> kids = new ArrayList<RoleNameAndObject>();
for (EReference ae : jmiHelper.getAssociationEnds((EClass) getValue().refMetaObject(), /* includeSupertypes */
true)) {
EReference a = (EReference) ae.eContainer();
if (ae.getAggregation() == EEnum.COMPOSITE) {
EReference refAssoc = jmiHelper.getRefAssociationForAssociation(a);
boolean isSingle = ae.getEOpposite().getUpperBound() == 1;
String roleName = isSingle ? ae.getEOpposite().getName() : null;
Collection<EObject> localKids = refAssoc.refQuery(ae, getValue());
for (EObject localKid : localKids) {
kids.add(new RoleNameAndObject(roleName, localKid));
}
}
}
return kids;
}
/**
* For a given class <tt>of</tt>, finds all associations where instances of
* <tt>of</tt> can act as composite parent. For the respective opposite end,
* finds all concrete classes whose instances can act as composite children
* on that opposite end. Those classes are returned.
*/
public static Map<EReference, Set<EClass>> getConcreteCompositeChildClasses(EClass of) {
Map<EReference, Set<EClass>> result = new LinkedHashMap<EReference, Set<EClass>>();
JmiHelper jmiHelper = of.get___Connection().getJmiHelper();
for (EReference ae : jmiHelper.getAssociationEnds(of, /* includeSupertypes */true)) {
Set<EClass> resultsForEnd = new LinkedHashSet<EClass>();
if (ae.getAggregation().equals(EEnum.COMPOSITE)) {
EClass opposite = (EClass) ae.getEOpposite().getEType();
if (!opposite.isAbstract()) {
resultsForEnd.add(opposite);
}
for (EClass ge : jmiHelper.getAllSubtypes(opposite)) {
EClass mc = (EClass) ge;
if (!mc.isAbstract()) {
resultsForEnd.add(mc);
}
}
result.put(ae.getEOpposite(), resultsForEnd);
}
}
return result;
}
public EObject[] getChildren() {
List<EObject> nodes = new ArrayList<EObject>();
Collection<RoleNameAndObject> kids = getChildRefObjects();
for (RoleNameAndObject o : kids) {
EObject node = o.getTreeNode(this);
nodes.add(node);
}
return nodes.toArray();
}
public boolean hasChildren() {
return getChildRefObjects().size() > 0;
}
/**
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public EObject getAdapter(EClass adapter) {
if (adapter == IPropertySource.class) {
return propManager.getPropertySource(this.getValue());
}
return super.getAdapter(adapter);
}
/**
* This method is invoked when the user either releases the mouse (drops)
* some objects or when the user presses ctrl + v.
*
* It requests all affected editors with unsaved changes to be saved.
* If this was successful then the composite parent of all dragged elements
* are replaced with the drag target.
*
* If there are more then one possible new composite parent associations, then the
* user is asked to select one.
*
* @param target
* The object where the selection has been dropped or where ctrl +
* v has been pressed
* @param transfer
* All selected objects
* @return True, if transfer was successful
* @see TransferOperationEnum
*/
@Override
public boolean handleTransfer(Object target, ITreeNodeTransfer transfer) {
assert target == this;
ResourceSet co = getConnection();
JmiHelper helper = co.getJmiHelper();
EObject[] objects = transfer.getRefBaseObjects(co);
EObject actualTarget = (EObject) getAdapter(EObject.class);
// Save all editors with unsaved changes to prevent locking errors
IStatus status = ModelManagerUI.getCommandManager().prepareExecution(
co, getAffectedPartitionsForDND(actualTarget, objects));
if (status == Status.CANCEL_STATUS) {
return false; // user has canceled
}
CommandStack stk = co.getCommandStack();
stk.openGroup("Handle DND Transfer");
// Remove existing links to old composite parents
for (EObject obj : objects) {
if (obj instanceof EObject) {
EObject source = (EObject) obj;
EObject parent = (EObject) source.refImmediateComposite();
for (EReference assoc : helper.getCompositeAssociations((EClass)
parent.refMetaObject(), (EClass) source.refMetaObject())) {
EReference refAssoc = helper.getRefAssociationForAssociation(assoc);
EReference compAssocEnd = co.getJmiHelper().getCompositeAssociationEnd(assoc);
if (helper.isFirstAssociationEnd(assoc, compAssocEnd) &&
refAssoc.refLinkExists(parent, source)) {
refAssoc.refRemoveLink(parent, source);
} else if (helper.isFirstAssociationEnd(assoc, compAssocEnd.otherEnd()) &&
refAssoc.refLinkExists(source, parent)) {
refAssoc.refRemoveLink(source, parent);
}
}
}
}
// Link to new root
DefaultModelTransferTarget dmth = new DefaultModelTransferTarget();
dmth.handleTransfer(actualTarget, objects, null);
stk.closeGroup();
try {
co.save();
} catch(Exception e) {
Activator.logError(e, true);
return false;
}
return true;
}
@Override
public boolean isTransferAllowed(Object target, ITreeNodeTransfer transfer) {
assert target == this;
EObject[] objects = transfer.getRefBaseObjects(getConnection());
DefaultModelTransferTarget dmth = new DefaultModelTransferTarget();
EObject actualTarget = (EObject) getAdapter(EObject.class);
return dmth.isTransferAllowed(actualTarget, objects);
}
private Collection<EOperation> getAffectedPartitionsForDND(EObject target, EObject[] objects) {
Collection<EOperation> partitions = new HashSet<EOperation>();
partitions.add(new EOperation(EOperation.EDIT, target.get___Partition().getPri()));
for (EObject obj : objects) {
partitions.add(new EOperation(EOperation.EDIT, obj.get___Partition().getPri()));
if (obj instanceof EObject) {
EObject source = (EObject) obj;
EObject parent = source.refImmediateComposite();
// TODO only add partitions with storage
partitions.add(new EOperation(EOperation.EDIT, parent.get___Partition().getPri()));
}
}
return partitions;
}
@Override
public void handlePostCopy(ResourceSet targetConnection, EObject[] srcObjects, DeepCopyResultSet copyResult) {
enableEventListeners(targetConnection);
}
public void handlePreCopy(ResourceSet targetConnection, EObject[] srcObjects) {
disableEventListeners(targetConnection);
}
/**
* Deactivate event listeners on the given connection for save,
* uninterrupted copying
*
* @param co
*/
private void disableEventListeners(ResourceSet co) {
BundleContext context = Activator.getDefault().getBundle().getBundleContext();
ServiceReference ref = context.getServiceReference(GlobalEventListenerRegistry.class.getName());
GlobalEventListenerRegistry registry = (GlobalEventListenerRegistry) context.getService(ref);
registry.deregisterFilters(co.getSession());
}
/**
* Restore deactivated event listeners on the given connection
*
* @param co
*/
private void enableEventListeners(ResourceSet co) {
BundleContext context = Activator.getDefault().getBundle().getBundleContext();
ServiceReference ref = context.getServiceReference(GlobalEventListenerRegistry.class.getName());
GlobalEventListenerRegistry registry = (GlobalEventListenerRegistry) context.getService(ref);
registry.registerFilters(co.getSession());
}
}