Bug 559267 - ConditionalDeleteService assumes containment references
- add a canRemove(...) API to the ConditionalDeleteService
- use it instead of canDelete(...) where appropriate
Change-Id: I488060ca9f4ce2217722292fc8d16600c136e96b
Signed-off-by: Christian W. Damus <give.a.damus@gmail.com>
diff --git a/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/ConditionalDeleteService.java b/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/ConditionalDeleteService.java
index b65f506..a74c0d3 100644
--- a/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/ConditionalDeleteService.java
+++ b/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/ConditionalDeleteService.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2019 Christian W. Damus and others.
+ * Copyright (c) 2019-2020 Christian W. Damus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -51,14 +51,49 @@
boolean canDelete(Iterable<?> objects);
/**
+ * Query whether an {@code object} in some {@code reference} feature of an {@code owner} of that
+ * feature can be removed from the {@code reference} feature. In the case of containment references,
+ * this amounts to {@linkplain #canDelete(Object) deletion} of the {@code object}.
+ *
+ * @param owner the owner of a {@code reference} to some {@code object}
+ * @param reference a reference feature of the {@code owner}
+ * @param object an object referenced by the {@code owner} from which it is to be removed
+ *
+ * @return whether the {@code object} may be removed
+ *
+ * @since 1.24
+ */
+ default boolean canRemove(Object owner, Object reference, Object object) {
+ return canRemove(owner, reference, Collections.singleton(object));
+ }
+
+ /**
+ * Query whether <em>all</em> of the given {@code objects} in some {@code reference} feature of an {@code owner} of
+ * that
+ * feature can be removed from the {@code reference} feature. In the case of containment references,
+ * this amounts to {@linkplain #canDelete(Iterable) deletion} of the {@code object}.
+ *
+ * @param owner the owner of a {@code reference} to some {@code object}
+ * @param reference a reference feature of the {@code owner}
+ * @param objects a group of objects referenced by the {@code owner} from which they are to be removed
+ *
+ * @return whether the {@code objects} may be removed
+ *
+ * @since 1.24
+ */
+ default boolean canRemove(Object owner, Object reference, Iterable<?> objects) {
+ return canDelete(objects);
+ }
+
+ /**
* Obtain a conditional delete service for a given {@code context}, if necessary
* {@linkplain #adapt(DeleteService) adapting} its service implementation.
*
* @param context a form context
* @return the delete service, or an adapter if the actual delete service implementation does not provide
- * the {@link ConditionalDeleteService} protocol or if there isn't a delete service at all (in which case nothing can
- * be deleted)
- *
+ * the {@link ConditionalDeleteService} protocol or if there isn't a delete service at all (in which case
+ * nothing can be deleted)
+ *
* @see #adapt(DeleteService)
*/
static ConditionalDeleteService getDeleteService(EMFFormsViewContext context) {
@@ -67,8 +102,8 @@
/**
* Obtain a view of a given {@code service} as conditional delete service, if necessary adapting a service
- * implementation that does not provide the {@link ConditionalDeleteService} protocol with a default deletion condition.
- * In that case, an object is assumed to be deletable if it is not {@code null} and it is an
+ * implementation that does not provide the {@link ConditionalDeleteService} protocol with a default deletion
+ * condition. In that case, an object is assumed to be deletable if it is not {@code null} and it is an
* {@link EObject} that has some container from which it can be removed (root elements not logically
* being deletable).
*
diff --git a/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/EMFDeleteServiceImpl.java b/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/EMFDeleteServiceImpl.java
index dad5963..0d067ff 100644
--- a/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/EMFDeleteServiceImpl.java
+++ b/bundles/org.eclipse.emf.ecp.edit/src/org/eclipse/emf/ecp/edit/spi/EMFDeleteServiceImpl.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,7 +10,7 @@
*
* Contributors:
* Johannes Faltermeier - initial API and implementation
- * Christian W. Damus - bug 552385
+ * Christian W. Damus - bugs 552385, 559267
******************************************************************************/
package org.eclipse.emf.ecp.edit.spi;
@@ -21,10 +21,12 @@
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.edit.command.ChangeCommand;
import org.eclipse.emf.edit.command.DeleteCommand;
+import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
@@ -123,13 +125,7 @@
boolean result;
if (editingDomain != null) {
- Collection<?> collection;
- if (objects instanceof Collection<?>) {
- collection = (Collection<?>) objects;
- } else {
- collection = StreamSupport.stream(objects.spliterator(), false).collect(Collectors.toList());
- }
-
+ final Collection<?> collection = asCollection(objects);
final Command deleteCommand = DeleteCommand.create(editingDomain, collection);
result = deleteCommand.canExecute();
} else {
@@ -148,4 +144,41 @@
return result;
}
+ private static <T> Collection<T> asCollection(Iterable<T> iterable) {
+ Collection<T> result;
+
+ if (iterable instanceof Collection<?>) {
+ result = (Collection<T>) iterable;
+ } else {
+ result = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean canRemove(Object owner, Object feature, Iterable<?> objects) {
+ if (!(feature instanceof EReference)) {
+ return canDelete(objects);
+ }
+
+ final EReference reference = (EReference) feature;
+ if (reference.isContainment()) {
+ return canDelete(objects);
+ }
+
+ boolean result;
+
+ if (editingDomain != null) {
+ final Collection<?> collection = asCollection(objects);
+ final Command removeCommand = RemoveCommand.create(editingDomain, owner, reference, collection);
+ result = removeCommand.canExecute();
+ } else {
+ // No commands? Then there's nothing to say 'no'
+ result = true;
+ }
+
+ return result;
+ }
+
}
diff --git a/bundles/org.eclipse.emf.ecp.view.control.multireference/src/org/eclipse/emf/ecp/view/internal/control/multireference/MultiReferenceSWTRenderer.java b/bundles/org.eclipse.emf.ecp.view.control.multireference/src/org/eclipse/emf/ecp/view/internal/control/multireference/MultiReferenceSWTRenderer.java
index 7b3830a..da538b4 100644
--- a/bundles/org.eclipse.emf.ecp.view.control.multireference/src/org/eclipse/emf/ecp/view/internal/control/multireference/MultiReferenceSWTRenderer.java
+++ b/bundles/org.eclipse.emf.ecp.view.control.multireference/src/org/eclipse/emf/ecp/view/internal/control/multireference/MultiReferenceSWTRenderer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -12,7 +12,7 @@
* Eugen Neufeld - initial API and implementation
* Lucas Koehler - use data binding services
* Martin Fleck - bug 487101
- * Christian W. Damus - bugs 527736, 548592, 552385
+ * Christian W. Damus - bugs 527736, 548592, 552385, 559267
******************************************************************************/
package org.eclipse.emf.ecp.view.internal.control.multireference;
@@ -140,6 +140,11 @@
private Optional<EObject> cachedContainer;
/**
+ * The structural feature presented in this multi reference control.
+ */
+ private Optional<EStructuralFeature> cachedFeature;
+
+ /**
* A user-presentable display name for the reference, used in tool-tips.
*/
private String referenceDisplayName;
@@ -781,8 +786,10 @@
private void enableDeleteButton(boolean baseEnable, int listSize, int selectionIndex) {
if (btnDelete != null && showDeleteButton()) {
btnDelete.setEnabled(baseEnable && listSize > 0 && selectionIndex != -1
+ && getContainer().isPresent() && cachedFeature.isPresent()
&& ConditionalDeleteService.getDeleteService(getViewModelContext())
- .canDelete(tableViewer.getStructuredSelection().toList()));
+ .canRemove(getContainer().get(), cachedFeature.get(),
+ tableViewer.getStructuredSelection().toList()));
}
}
@@ -822,6 +829,7 @@
final Composite buttonComposite = new Composite(parent, SWT.NONE);
final EStructuralFeature structuralFeature = getEStructuralFeature();
+ cachedFeature = Optional.ofNullable(structuralFeature);
int nrButtons = 0;
diff --git a/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java b/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
index 6e92318..8faba07 100644
--- a/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
+++ b/bundles/org.eclipse.emf.ecp.view.table.ui.swt/src/org/eclipse/emf/ecp/view/spi/table/swt/TableControlSWTRenderer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -11,7 +11,7 @@
* Contributors:
* Eugen Neufeld - initial API and implementation
* Johannes Faltermeier - refactorings
- * Christian W. Damus - bugs 544116, 544537, 545686, 530314, 547271, 547787, 548592, 552385
+ * Christian W. Damus - bugs 544116, 544537, 545686, 530314, 547271, 547787, 548592, 552385, 559267
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.table.swt;
@@ -638,8 +638,10 @@
@Override
public boolean canExecute() {
+ final Setting setting = getActionContext().getSetting();
return super.canExecute() && ConditionalDeleteService.getDeleteService(getViewModelContext())
- .canDelete(getActionContext().getViewer().getStructuredSelection().toList());
+ .canRemove(setting.getEObject(), setting.getEStructuralFeature(),
+ getActionContext().getViewer().getStructuredSelection().toList());
}
};
diff --git a/tests/org.eclipse.emf.ecp.edit.swt.test/src/org/eclipse/emf/ecp/edit/internal/swt/util/EMFDeleteSerivceImpl_PTest.java b/tests/org.eclipse.emf.ecp.edit.swt.test/src/org/eclipse/emf/ecp/edit/internal/swt/util/EMFDeleteSerivceImpl_PTest.java
index 0344274..b0c3c97 100644
--- a/tests/org.eclipse.emf.ecp.edit.swt.test/src/org/eclipse/emf/ecp/edit/internal/swt/util/EMFDeleteSerivceImpl_PTest.java
+++ b/tests/org.eclipse.emf.ecp.edit.swt.test/src/org/eclipse/emf/ecp/edit/internal/swt/util/EMFDeleteSerivceImpl_PTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2011-2020 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,7 +10,7 @@
*
* Contributors:
* jfaltermeier - initial API and implementation
- * Christian W. Damus - bug 552385
+ * Christian W. Damus - bugs 552385, 559267
******************************************************************************/
package org.eclipse.emf.ecp.edit.internal.swt.util;
@@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
@@ -46,6 +47,7 @@
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.emfstore.bowling.BowlingFactory;
+import org.eclipse.emf.emfstore.bowling.BowlingPackage;
import org.eclipse.emf.emfstore.bowling.Game;
import org.eclipse.emf.emfstore.bowling.League;
import org.eclipse.emf.emfstore.bowling.Player;
@@ -202,4 +204,58 @@
assertThat("can delete a root object", deleteService.canDelete(league), is(false));
}
+ @Test
+ public void canRemoveElement_containment() {
+ assertThat("can delete returns false",
+ deleteService.canRemove(league, BowlingPackage.Literals.LEAGUE__PLAYERS, player1), is(true));
+
+ denyDeletion = player1;
+ assertThat("can delete returns true",
+ deleteService.canRemove(league, BowlingPackage.Literals.LEAGUE__PLAYERS, player1), is(false));
+ }
+
+ @Test
+ public void canRemoveElement_crossReferenced() {
+ assertThat("cannot remove a referenced object",
+ deleteService.canRemove(tournament, BowlingPackage.Literals.TOURNAMENT__PLAYERS, player1), is(true));
+ }
+
+ @Test
+ public void canRemoveElement_notReferenced() {
+ tournament.getPlayers().remove(player3);
+
+ assertThat("cannot remove a referenced object",
+ deleteService.canRemove(tournament, BowlingPackage.Literals.TOURNAMENT__PLAYERS, player3), is(false));
+ }
+
+ @Test
+ public void canRemoveElement_crossReferenced_readOnly() {
+ final Resource readOnly = resource.getResourceSet().createResource(URI.createURI("READ_ONLY"));
+ final League others = BowlingFactory.eINSTANCE.createLeague();
+ others.setName("The Others");
+ others.getPlayers().add(player3);
+ domain.setResourceToReadOnlyMap(new HashMap<>());
+ domain.getResourceToReadOnlyMap().put(readOnly, true);
+
+ assertThat("cannot remove a referenced object",
+ deleteService.canRemove(tournament, BowlingPackage.Literals.TOURNAMENT__PLAYERS, player3), is(true));
+ }
+
+ @Test
+ public void testRemove_noEditingDomain() {
+ // Disconnect everything from the editing domain
+ resource.getContents().clear();
+ resource.getResourceSet().eAdapters().clear();
+ resource.getResourceSet().getResources().clear();
+ resource.unload();
+ resource.eAdapters().clear();
+
+ // Re-initialize to forget the editing domain
+ deleteService.dispose();
+ deleteService.instantiate(context);
+
+ assertThat("cannot remove a referenced object",
+ deleteService.canRemove(tournament, BowlingPackage.Literals.TOURNAMENT__PLAYERS, player1), is(true));
+ }
+
}