/*******************************************************************************
* Copyright (c) 2012, 2014 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.ui.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.te.runtime.callback.Callback;
import org.eclipse.tcf.te.runtime.events.EventManager;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.persistence.interfaces.IURIPersistenceService;
import org.eclipse.tcf.te.runtime.services.ServiceManager;
import org.eclipse.tcf.te.runtime.services.ServiceUtils;
import org.eclipse.tcf.te.runtime.statushandler.StatusHandlerUtil;
import org.eclipse.tcf.te.runtime.utils.StatusHelper;
import org.eclipse.tcf.te.tcf.core.events.DeletedEvent;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode;
import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNodeProperties;
import org.eclipse.tcf.te.tcf.locator.interfaces.services.IPeerModelRefreshService;
import org.eclipse.tcf.te.tcf.locator.model.ModelManager;
import org.eclipse.tcf.te.tcf.ui.help.IContextHelpIds;
import org.eclipse.tcf.te.tcf.ui.nls.Messages;
import org.eclipse.tcf.te.ui.interfaces.handler.IDeleteHandlerDelegate;
import org.eclipse.tcf.te.ui.views.Managers;
import org.eclipse.tcf.te.ui.views.ViewsUtil;
import org.eclipse.tcf.te.ui.views.interfaces.ICategory;
import org.eclipse.tcf.te.ui.views.interfaces.IUIConstants;
import org.eclipse.tcf.te.ui.views.interfaces.categories.ICategorizable;
import org.eclipse.tcf.te.ui.views.interfaces.categories.ICategoryManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;
/**
* Delete handler implementation.
*/
public class DeleteHandler extends AbstractHandler {
// Remember the shell from the execution event
private Shell shell = null;
/* (non-Javadoc)
* @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
*/
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
// Get the shell
shell = HandlerUtil.getActiveShell(event);
// Get the current selection
ISelection selection = getSelection(event);
// Delete the selection
if (selection != null) {
delete(selection, new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
// Refresh the view
ViewsUtil.refresh(IUIConstants.ID_EXPLORER);
}
});
}
// Reset the shell
shell = null;
return null;
}
protected ISelection getSelection(ExecutionEvent event) {
return HandlerUtil.getCurrentSelection(event);
}
/**
* Tests if this delete handler can delete the elements of the given
* selection.
*
* @param selection The selection. Must not be <code>null</code>.
* @return <code>True</code> if the selection can be deleted by this handler, <code>false</code> otherwise.
*/
public boolean canDelete(ISelection selection) {
Assert.isNotNull(selection);
boolean canDelete = false;
if (!(selection instanceof ITreeSelection) && selection instanceof IStructuredSelection && !selection.isEmpty()) {
Iterator<?> it = ((IStructuredSelection)selection).iterator();
List<TreePath> treePathes = new ArrayList<TreePath>();
while (it.hasNext()) {
Object sel = it.next();
treePathes.add(new TreePath(new Object[]{sel}));
}
selection = new TreeSelection(treePathes.toArray(new TreePath[treePathes.size()]));
}
// The selection must be a tree selection and must not be empty
if (selection instanceof ITreeSelection && !selection.isEmpty()) {
// Assume the selection to be deletable
canDelete = true;
// Iterate the selection. All elements must be of type IPeerNode
for (TreePath treePath : ((ITreeSelection)selection).getPaths()) {
// Get the element
Object element = treePath.getLastSegment();
// This handler will take care of peer model nodes only
if (!(element instanceof IPeerNode)) {
canDelete = false;
break;
}
// Check if there is a delete handler delegate for the element
IDeleteHandlerDelegate delegate = ServiceUtils.getUIServiceDelegate(element, element, IDeleteHandlerDelegate.class);
// If a delegate is available, ask the handler first if the given element is currently deletable
if (delegate != null) canDelete = delegate.canDelete(treePath);
if (!canDelete) {
break;
}
}
}
return canDelete;
}
/**
* Internal helper class to describe the delete operation to perform.
*/
private static class Operation {
// The operation types
public enum TYPE { Remove, Unlink }
// The parent delete handler
public DeleteHandler parentHandler;
// The element to operate on
public IPeerNode node;
// The operation type to perform
public TYPE type;
// In case of an "unlink" operation, the parent category
// is required.
public ICategory parentCategory;
/**
* Constructor.
*/
public Operation() {
}
/**
* Executes the operation.
*
* @throws Exception if the operation fails.
*/
public void execute() throws Exception {
Assert.isNotNull(node);
Assert.isNotNull(type);
if (TYPE.Remove.equals(type)) {
// Remove the node from the persistence storage
IURIPersistenceService service = ServiceManager.getInstance().getService(IURIPersistenceService.class);
if (service == null) throw new IOException("Persistence service instance unavailable."); //$NON-NLS-1$
service.delete(node, null);
if (parentCategory != null) {
ViewsUtil.setSelection(IUIConstants.ID_EXPLORER, new StructuredSelection(parentCategory));
}
// Mark the peer node as deleted
Protocol.invokeAndWait(new Runnable() {
@Override
public void run() {
node.setProperty(IPeerNodeProperties.PROPERTY_IS_DELETED, true);
}
});
// Check if there is a delete handler delegate for the element
IDeleteHandlerDelegate delegate = ServiceUtils.getUIServiceDelegate(node, node, IDeleteHandlerDelegate.class);
// If a delegate is available, signal the execution of the remove
if (delegate != null) delegate.postDelete(node);
// Send the peer node deleted event to also delete the launch configuration
DeletedEvent event = new DeletedEvent(parentHandler, node);
EventManager.getInstance().fireEvent(event);
}
else if (TYPE.Unlink.equals(type)) {
Assert.isNotNull(parentCategory);
ICategoryManager manager = Managers.getCategoryManager();
Assert.isNotNull(manager);
ICategorizable categorizable = (ICategorizable)node.getAdapter(ICategorizable.class);
if (categorizable == null) {
categorizable = (ICategorizable)Platform.getAdapterManager().getAdapter(node, ICategorizable.class);
}
Assert.isNotNull(categorizable);
manager.remove(parentCategory.getId(), categorizable.getId());
ViewsUtil.setSelection(IUIConstants.ID_EXPLORER, new StructuredSelection(parentCategory));
}
}
}
/**
* Deletes all elements from the given selection and invokes the
* given callback once done.
*
* @param selection The selection. Must not be <code>null</code>.
* @param callback The callback. Must not be <code>null</code>.
*/
public void delete(ISelection selection, final ICallback callback) {
Assert.isNotNull(selection);
Assert.isNotNull(callback);
// The callback needs to be invoked in any case. However, if called
// from an asynchronous callback, set this flag to false.
boolean invokeCallback = true;
if (!(selection instanceof ITreeSelection) && selection instanceof IStructuredSelection && !selection.isEmpty()) {
Iterator<?> it = ((IStructuredSelection)selection).iterator();
List<TreePath> treePathes = new ArrayList<TreePath>();
while (it.hasNext()) {
Object sel = it.next();
treePathes.add(new TreePath(new Object[]{sel}));
}
selection = new TreeSelection(treePathes.toArray(new TreePath[treePathes.size()]));
}
// The selection must be a tree selection and must not be empty
if (selection instanceof ITreeSelection && !selection.isEmpty()) {
// Determine the operations to perform for each of the selected elements
Operation[] operations = selection2operations((ITreeSelection)selection);
// Seek confirmation for the "remove" operations. If the user deny it,
// everything, including the "unlink" operations are cancelled.
boolean confirmed = confirmDelete(operations);
// Execute the operations
if (confirmed) {
// If one of the operation is a "remove" operation, the locator
// model needs to be refreshed
boolean refreshModel = false;
try {
for (Operation op : operations) {
if (Operation.TYPE.Remove.equals(op.type)) {
refreshModel = true;
}
op.execute();
}
} catch (Exception e) {
String template = NLS.bind(Messages.DeleteHandler_error_deleteFailed, Messages.PossibleCause);
StatusHandlerUtil.handleStatus(StatusHelper.getStatus(e), selection, template,
Messages.DeleteHandler_error_title, IContextHelpIds.MESSAGE_DELETE_FAILED, this, null);
}
if (refreshModel) {
// Trigger a refresh of the model
invokeCallback = false;
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
IPeerModelRefreshService service = ModelManager.getPeerModel().getService(IPeerModelRefreshService.class);
// Refresh the model now (must be executed within the TCF dispatch thread)
if (service != null) service.refresh(new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
// Invoke the callback
callback.done(DeleteHandler.this, Status.OK_STATUS);
}
});
}
});
}
}
}
if (invokeCallback) {
callback.done(this, Status.OK_STATUS);
}
}
/**
* Analyze the given selection and convert it to an list of operations
* to perform.
*
* @param selection The selection. Must not be <code>null</code>.
* @return The list of operations.
*/
private Operation[] selection2operations(ITreeSelection selection) {
Assert.isNotNull(selection);
List<Operation> operations = new ArrayList<Operation>();
// Iterate the selection. All elements must be of type IPeerNode
for (TreePath treePath : selection.getPaths()) {
// Get the element
Object element = treePath.getLastSegment();
Assert.isTrue(element instanceof IPeerNode);
IPeerNode node = (IPeerNode)element;
ICategory category = treePath.getFirstSegment() instanceof ICategory ? (ICategory)treePath.getFirstSegment() : null;
Operation op = new Operation();
if (category != null && IUIConstants.ID_CAT_FAVORITES.equals(category.getId())) {
op.type = Operation.TYPE.Unlink;
}
else {
op.type = Operation.TYPE.Remove;
}
op.node = node;
op.parentCategory = category;
op.parentHandler = this;
operations.add(op);
}
return operations.toArray(new Operation[operations.size()]);
}
/**
* Confirm the deletion with the user.
*
* @param state The state of delegation handler.
* @return true if the user agrees to delete or it has confirmed previously.
*/
private boolean confirmDelete(Operation[] operations) {
Assert.isNotNull(operations);
boolean confirmed = false;
// Find all elements to remove
List<Operation> toRemove = new ArrayList<Operation>();
for (Operation op : operations) {
if (Operation.TYPE.Remove.equals(op.type)) {
toRemove.add(op);
}
}
// If there are node to remove -> ask for confirmation
if (!toRemove.isEmpty()) {
String question = getConfirmQuestion(toRemove);
Shell parent = shell != null ? shell : PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
confirmed = MessageDialog.openQuestion(parent, Messages.DeleteHandlerDelegate_DialogTitle, question);
} else {
confirmed = true;
}
return confirmed;
}
/**
* Get confirmation question displayed in the confirmation dialog.
*
* @param toRemove The list of nodes to remove.
* @return The question to ask the user.
*/
private String getConfirmQuestion(List<Operation> toRemove) {
Assert.isNotNull(toRemove);
String question;
if (toRemove.size() == 1) {
final Operation op = toRemove.get(0);
final AtomicReference<String> name = new AtomicReference<String>();
Runnable runnable = new Runnable() {
@Override
public void run() {
name.set(op.node.getPeer().getName());
}
};
if (Protocol.isDispatchThread()) {
runnable.run();
}
else {
Protocol.invokeAndWait(runnable);
}
question = NLS.bind(Messages.DeleteHandlerDelegate_MsgDeleteOnePeer, name.get());
}
else {
question = NLS.bind(Messages.DeleteHandlerDelegate_MsgDeleteMultiplePeers, Integer.valueOf(toRemove.size()));
}
return question;
}
}