package org.korsakow.ide.ui.controller.action; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.dsrg.soenea.domain.MapperException; import org.dsrg.soenea.domain.command.CommandException; import org.korsakow.domain.CommandExecutor; import org.korsakow.domain.command.DeleteResourceCommand; import org.korsakow.domain.command.FindResourcesReferencingCommand; import org.korsakow.domain.command.RemoveReferencesToResourceCommand; import org.korsakow.domain.command.Request; import org.korsakow.domain.command.Response; import org.korsakow.domain.interf.IResource; import org.korsakow.domain.mapper.input.ProjectInputMapper; import org.korsakow.domain.mapper.input.ResourceInputMapper; import org.korsakow.ide.Application; import org.korsakow.ide.lang.LanguageBundle; import org.korsakow.ide.ui.ResourceEditor; import org.korsakow.ide.ui.components.tree.FolderNode; import org.korsakow.ide.ui.components.tree.KNode; import org.korsakow.ide.ui.components.tree.NodeVisitor; import org.korsakow.ide.ui.components.tree.ResourceNode; import org.korsakow.ide.ui.controller.ResourceBrowserController; import org.korsakow.ide.ui.dialogs.EditingConflictDialog; import org.korsakow.ide.ui.resourceexplorer.ResourceTreeTable; import org.korsakow.ide.ui.resourceexplorer.ResourceTreeTableModel; import org.korsakow.ide.util.UIUtil; public class DeleteAction implements ActionListener, KeyListener { private static class NodeContext { final public KNode parent; final public Integer childIndex; final public IResource child; public NodeContext(KNode parent, Integer childIndex, IResource child) { this.parent = parent; this.childIndex = childIndex; this.child = child; } } private static class UseContext { private final Collection<IResource> references; private final ResourceEditor editor; public UseContext(ResourceEditor editor) { this(editor, null); } public UseContext(Collection<IResource> references) { this(null, references); } public UseContext(ResourceEditor editor, Collection<IResource> references) { this.editor = editor; this.references = references; } public ResourceEditor getEditor() { return editor; } public Collection<IResource> getReferences() { return references; } } private final ResourceTreeTable treeTable; public DeleteAction(ResourceTreeTable treeTable) { this.treeTable = treeTable; } public void actionPerformed(ActionEvent event) { Application app = Application.getInstance(); try { app.stopCommonTasks(); app.beginBusyOperation(); List<? extends KNode> selectedNodes = treeTable.getSelectedNodes(); deleteNodes(treeTable.getTreeTableModel(), selectedNodes); } catch (CommandException e) { Application.getInstance().showUnhandledErrorDialog(LanguageBundle.getString("general.errors.uncaughtexception.title"), e); } catch (MapperException e) { Application.getInstance().showUnhandledErrorDialog(e); } finally { app.endBusyOperation(); app.startCommonTasks(); } } public static void deleteNodes(final ResourceTreeTableModel model, Collection<? extends KNode> nodesToDelete1) throws CommandException, MapperException { final Collection<KNode> nodesToDelete = new ArrayList<KNode>(); for (KNode node: nodesToDelete1) { if ( node instanceof ResourceNode) { IResource resource = ResourceInputMapper.map( ((ResourceNode)node).getResourceId() ); if ( resource.equals( ProjectInputMapper.find().getDefaultInterface() ) ) { Application.getInstance().showAlertDialog( LanguageBundle.getString("general.messages.cannotdeletedefaultinterface.title"), LanguageBundle.getString("general.messages.cannotdeletedefaultinterface.message") ); continue; } } nodesToDelete.add(node); } Map<IResource, UseContext> resourcesInUse = new HashMap<IResource, UseContext>(); // master list Collection<ResourceNode> resourceNodesToDelete = ResourceBrowserController.filterResourceNodes(nodesToDelete); Set<IResource> resourcesToDelete = ResourceNode.getResources(resourceNodesToDelete); Set<ResourceNode> resourcesToAutomaticallyBreakLinks = new HashSet<ResourceNode>(); for (ResourceNode node : resourceNodesToDelete) { if (!testDelete(node, resourcesInUse)) { int inUseAndAlsoToDeleteCount = 0; final IResource resource = ResourceInputMapper.map(node.getResourceId()); final UseContext references = resourcesInUse.get(resource); if ( references != null && references.references != null) { for (IResource inUse : references.references) { if ( resourcesToDelete.contains(inUse) ) { ++inUseAndAlsoToDeleteCount; } } if (inUseAndAlsoToDeleteCount == references.references.size()) { resourcesToAutomaticallyBreakLinks.add(node); } } } } for (ResourceNode node : resourcesToAutomaticallyBreakLinks) { final IResource resource = ResourceInputMapper.map(node.getResourceId()); Request request = new Request(); request.set("id", node.getResourceId()); Response response = CommandExecutor.executeCommand( RemoveReferencesToResourceCommand.class, request ); if ( response.getCollection("lockedBy", IResource.class).isEmpty() ) { resourcesInUse.remove(resource); } } if (!resourcesInUse.isEmpty()) { showConflictsDialog(resourcesInUse, model, nodesToDelete); } else { for (ResourceNode node: resourceNodesToDelete) { IResource resource = ResourceInputMapper.map( node.getResourceId() ); // delete should not fail at this point since we've checked for in-use resources, but... if ( delete(resource) ) { for ( IResource key : resourcesInUse.keySet() ) { UseContext ctx = resourcesInUse.get(key); ctx.references.remove(resource); } // removing unrooted nodes via the model is... problematic (throws a NPE) if (UIUtil.isRooted(model.getRoot(), node)) model.removeNodeFromParent(node); } } // cleanup folder nodes that are now empty for (KNode toDelete: nodesToDelete) { if (toDelete instanceof FolderNode == false) continue; KNode.visitDepthFirstLeafFirst(toDelete, new NodeVisitor() { public void visit(KNode node) { if (node.isLeaf() && node instanceof FolderNode) { if (node != node.getRoot()) if (UIUtil.isRooted(model.getRoot(), node)) model.removeNodeFromParent(node); } } }); } } } private static boolean testDelete(ResourceNode removeNode, Map<IResource, UseContext> resourcesInUse) throws CommandException, MapperException { Long id = (removeNode).getResourceId(); assert id != null; IResource resource = ResourceInputMapper.map( id ); if (resource == null) resource = null; assert resource != null; ResourceEditor editor = Application.getInstance().getOpenEditor(ResourceInputMapper.map(resource.getId())); if (editor != null) { resourcesInUse.put(resource, new UseContext(editor)); return false; } Collection<IResource> references = findReferences(resource); if (!references.isEmpty()) resourcesInUse.put(resource, new UseContext(references)); return references.isEmpty(); } private static Collection<IResource> findReferences(IResource resource) throws CommandException { Request request = new Request(); request.set(FindResourcesReferencingCommand.ID, resource.getId()); Response response = new Response(); CommandExecutor.executeCommand(FindResourcesReferencingCommand.class, request, response); Collection<IResource> references = response.getCollection(FindResourcesReferencingCommand.REFERENCES, IResource.class); return references; } private static boolean delete(IResource resource) throws CommandException { Request request = new Request(); request.set(DeleteResourceCommand.ID, resource.getId()); Response response = new Response(); CommandExecutor.executeCommand(DeleteResourceCommand.class, request, response); boolean inUse = (response.has("resourceInUse") && response.getBoolean("resourceInUse")); if (!inUse) { Application.getInstance().registerDeleted( resource ); } return !inUse; } private static void showConflictsDialog(final Map<IResource, UseContext> resourcesInUse, final ResourceTreeTableModel model, final Collection<? extends KNode> nodesToDelete) { final EditingConflictDialog dialog = Application.getInstance().showEditingConflictDialog(); Collection<IResource> keys = resourcesInUse.keySet(); dialog.setBreakLinksAction(new BreakLinksAction(dialog, resourcesInUse, nodesToDelete, model)); dialog.setMessage(LanguageBundle.getString("general.errors.resourceinuse.message")); for (IResource resource : keys) { UseContext ctx = resourcesInUse.get(resource); dialog.addConflictItem(resource, ctx.getReferences()); } dialog.setVisible(true); } private static class BreakLinksAction implements ActionListener { private final EditingConflictDialog dialog; private final Map<IResource, UseContext> resourcesInUse; private final Collection<? extends KNode> nodesToDelete; private final ResourceTreeTableModel model; public BreakLinksAction(EditingConflictDialog dialog, Map<IResource, UseContext> resourcesInUse, Collection<? extends KNode> nodesToDelete, ResourceTreeTableModel model) { this.dialog = dialog; this.resourcesInUse = resourcesInUse; this.nodesToDelete = nodesToDelete; this.model = model; } public void actionPerformed(ActionEvent event) { Collection<IResource> locked = new HashSet<IResource>(); try { for (IResource resource : resourcesInUse.keySet()) { UseContext ctx = resourcesInUse.get(resource); if (ctx.getEditor() != null) { if (!Application.getInstance().showOKCancelDialog(dialog, LanguageBundle.getString("confirm.resourcehasopeneditor.title"), LanguageBundle.getString("confirm.resourcehasopeneditor.message", resource.getName()))) continue; ctx.getEditor().dispose(); } if (ctx.getReferences() != null) { Request request = new Request(); request.set("id", resource.getId()); Response response = CommandExecutor.executeCommand( RemoveReferencesToResourceCommand.class, request ); if ( !response.getCollection("lockedBy", IResource.class).isEmpty() ) { locked.add( resource ); } else { for (IResource modified : response.getCollection("references", IResource.class)) Application.getInstance().notifyResourceModified(modified); } } } if ( !locked.isEmpty() ) { Application.getInstance().showAlertDialog( LanguageBundle.getString("general.messages.cannotbreaklinks_locked.title"), LanguageBundle.getString("general.messages.cannotbreaklinks_locked.message") ); } else { dialog.dispose(); deleteNodes(model, nodesToDelete); } } catch (Exception e) { Application.getInstance().showUnhandledErrorDialog(LanguageBundle.getString("general.errors.resourceinuse.message"), e); } } } public void keyPressed(KeyEvent e){} public void keyReleased(KeyEvent e) { if (UIUtil.isPlatformCommandKeyDown(e) && e.getKeyCode()==KeyEvent.VK_BACK_SPACE) actionPerformed(null); } public void keyTyped(KeyEvent e){} }