org.eclipse.bpel.common.ui.editmodel.EditModelCommandStack.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.bpel.common.ui.editmodel.EditModelCommandStack.java

Source

/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.common.ui.editmodel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.bpel.common.ui.CommonUIPlugin;
import org.eclipse.bpel.common.ui.Messages;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchWindow;

/**
 * Implements the CommandStack API, adds extra notifications,
 * supports validateEdit and update the resource's dirty state.
 * 
 * WDG: TODO: the GEF CommandStack has been extended with extra notifications.
 * However, how are we supposed to use it since everything is still private?
 */
public class EditModelCommandStack extends CommandStack {

    protected int saveLocation = 0;
    protected int fCurrentLocation = 0;

    protected Set<Resource> dirtyUntilSave = new HashSet<Resource>();

    protected List<Context> fContexts = new ArrayList<Context>(30);

    /**
     * Brand new shiny EditModelCommandStack.
     */
    public EditModelCommandStack() {
        super();
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#getUndoCommand()
     */
    @Override
    public Command getUndoCommand() {
        if (fCurrentLocation < 1) {
            return null;
        }
        return fContexts.get(fCurrentLocation - 1).fCommand;
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#canUndo()
     */
    @Override
    public boolean canUndo() {
        Command c = getUndoCommand();
        return (c != null) && c.canUndo();
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#getRedoCommand()
     */
    @Override
    public Command getRedoCommand() {
        if (fCurrentLocation >= fContexts.size())
            return null;
        return fContexts.get(fCurrentLocation).fCommand;
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#canRedo()
     */
    @Override
    public boolean canRedo() {
        return (getRedoCommand() != null);
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#execute(org.eclipse.gef.commands.Command)
     */
    @Override
    public void execute(Command command) {
        SharedCommandStackChangedEvent event = notifyListeners(SharedCommandStackListener.EVENT_START_EXECUTE);
        if (!event.doit)
            return;

        if (getUndoCommand() instanceof PlaceHolderCommand) {
            // This should never happen because the EditModelCommandFramework should
            // remove the placeholder during the notifyListeners() call above.
            throw new IllegalStateException();
        }
        if (command == null)
            return;
        if (!validateEdit(command))
            return;
        if (!command.canExecute())
            return;
        drop(fCurrentLocation, fContexts.size());

        // Paranoia check
        if (getUndoCommand() instanceof PlaceHolderCommand) {
            // This should never happen because the EditModelCommandFramework should
            // remove the placeholder during the notifyListeners() call above.
            throw new IllegalStateException();
        }
        command.execute();
        if (getUndoCommand() instanceof PlaceHolderCommand) {
            // This should never happen because the EditModelCommandFramework should
            // remove the placeholder during the notifyListeners() call above.
            throw new IllegalStateException();
        }
        int limit = getUndoLimit();
        if (limit > 0)
            while (fCurrentLocation >= limit) {
                if (saveLocation == 0)
                    saveLocation = -1;
                drop(0);
                notifyListeners(SharedCommandStackListener.EVENT_DROP_LAST_UNDO_STACK_ENTRY);
            }
        if (getUndoCommand() instanceof PlaceHolderCommand) {
            // This should never happen because the EditModelCommandFramework should
            // remove the placeholder during the notifyListeners() call above.
            throw new IllegalStateException();
        }
        Resource[] resources = getModifiedResources(command);
        if ((resources.length > 0) || (command instanceof PlaceHolderCommand)) {
            Context c = new Context(command, resources);
            fContexts.add(c);
            fCurrentLocation = fContexts.size();
            // mark resources as dirty/clean as appropriate.
            c.setModifiedFlags(true);
            //System.out.println("execute - markModified.  currentLocation="+currentLocation+", saveLocation="+saveLocation);
        }
        notifyListeners(SharedCommandStackListener.EVENT_FINISH_EXECUTE);
    }

    /*
     * call VCM validateEdit;
     * open the dialog to ask the user if he/she wants to procede
     *       in case the file is readonly;
     * return true if the command should be executed otherwise returns false;
     */
    protected boolean validateEdit(Command command) {
        Resource[] resources = getResources(command);
        if (resources.length == 0)
            return true;
        boolean disposeShell = false;
        Shell shell;
        IWorkbenchWindow win = CommonUIPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow();
        if (win != null) {
            shell = win.getShell();
        } else {
            disposeShell = true;
            shell = new Shell();
        }
        try {
            IFile[] files = new IFile[resources.length];
            StringBuffer filesString = new StringBuffer();
            for (int i = 0; i < resources.length; i++) {
                Resource resource = resources[i];
                files[i] = EditModel.getIFileForURI(resource.getURI());
                filesString.append(files[i].getName());
                if (i < resources.length - 1)
                    filesString.append(", "); //$NON-NLS-1$
            }
            IStatus stat = ResourcesPlugin.getWorkspace().validateEdit(files, shell);
            if (stat.getSeverity() == IStatus.CANCEL) {
                return false;
            } else if (!stat.isOK()) {
                String[] buttons = { IDialogConstants.OK_LABEL }; //
                String msg;
                if (files.length == 1)
                    msg = NLS.bind(Messages.EditModelCommandStack_validateEdit_message0,
                            (new String[] { filesString.toString(), stat.getMessage() }));
                else
                    msg = NLS.bind(Messages.EditModelCommandStack_validateEdit_message1,
                            (new String[] { filesString.toString(), stat.getMessage() }));
                MessageDialog dialog = new MessageDialog(shell, Messages.EditModelCommandStack_validateEdit_title,
                        null, // accept the default windowing system icon
                        msg, MessageDialog.WARNING, buttons, 0);
                dialog.open();
                return false;
            }
        } finally {
            if (disposeShell)
                shell.dispose();
        }
        return true;
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#dispose()
     */
    @Override
    public void dispose() {
        drop(0, fContexts.size());
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#flush()
     */
    @Override
    public void flush() {
        SharedCommandStackChangedEvent event = notifyListeners(SharedCommandStackListener.EVENT_START_FLUSH);
        if (!event.doit)
            return;

        drop(0, fContexts.size());
        fContexts.clear();
        saveLocation = -1;
        fCurrentLocation = 0;
        // TODO: should we mark all resources as clean?
        notifyListeners(SharedCommandStackListener.EVENT_FINISH_FLUSH);
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#getCommands()
     */
    @Override
    public Object[] getCommands() {
        Object[] commands = new Object[fContexts.size()];
        for (int i = 0; i < fContexts.size(); i++) {
            commands[i] = fContexts.get(i).fCommand;
        }
        return commands;
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#isDirty()
     */
    @Override
    public boolean isDirty() {
        //System.out.println("isDirty: C="+currentLocation+"  S="+saveLocation+"  dus="+dirtyUntilSave.size());
        return (fCurrentLocation != saveLocation) || !dirtyUntilSave.isEmpty();
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#markSaveLocation()
     */
    @Override
    public void markSaveLocation() {
        //      // mark all the resources we know about as clean!
        //      for (int i = 0; i<contexts.size(); i++) {
        //         Context c = (Context)contexts.get(i);
        //         c.setModifiedFlags(false);
        //      }
        //      // that includes ones that fell off the bottom of the undo stack. 
        //      for (Iterator it = dirtyUntilSave.iterator(); it.hasNext(); ) {
        //         setResourceModified((Resource)it.next(), false);
        //      }
        dirtyUntilSave.clear();

        saveLocation = fCurrentLocation;
        notifyListeners(SharedCommandStackListener.EVENT_MARK_SAVED);
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#undo()
     */
    @Override
    public void undo() {
        SharedCommandStackChangedEvent event = notifyListeners(SharedCommandStackListener.EVENT_START_UNDO);
        if (!event.doit)
            return;

        if (!canUndo())
            return;
        Context c = fContexts.get(fCurrentLocation - 1);
        c.fCommand.undo();
        fCurrentLocation--;
        // mark resources as dirty/clean as appropriate.
        if (fCurrentLocation < saveLocation) {
            // moving away from save location --> resources can only become dirty
            c.setModifiedFlags(true);
            // System.out.println("undo - markModified.  currentLocation="+currentLocation+", saveLocation="+saveLocation);
        } else {
            // moving towards save location --> resources can only become clean
            updateModifiedFlags();
        }

        notifyListeners();
        notifyListeners(SharedCommandStackListener.EVENT_FINISH_UNDO);
    }

    /**
     * @see org.eclipse.gef.commands.CommandStack#redo()
     */
    @Override
    public void redo() {
        SharedCommandStackChangedEvent event = notifyListeners(SharedCommandStackListener.EVENT_START_REDO);
        if (!event.doit)
            return;

        if (!canRedo())
            return;
        Context c = fContexts.get(fCurrentLocation);
        c.fCommand.redo();
        fCurrentLocation++;
        // mark resources as dirty/clean as appropriate.
        if (fCurrentLocation > saveLocation) {
            // moving away from save location --> resources can only become dirty
            c.setModifiedFlags(true);
            //System.out.println("redo - markModified.  currentLocation="+currentLocation+", saveLocation="+saveLocation);
        } else {
            // moving towards save location --> resources can only become clean
            updateModifiedFlags();
        }

        if (getUndoCommand() instanceof PlaceHolderCommand) {
            // This should never happen
            throw new IllegalStateException();
        }
        notifyListeners();
        notifyListeners(SharedCommandStackListener.EVENT_FINISH_REDO);
    }

    /**
     * Sends notification to all {@link CommandStackListener}s.
     */
    protected SharedCommandStackChangedEvent notifyListeners(int property) {
        SharedCommandStackChangedEvent event = new SharedCommandStackChangedEvent(this);
        event.property = property;

        for (Object next : listeners) {
            CommandStackListener csl = (CommandStackListener) next;
            csl.commandStackChanged(event);
        }

        return event;
    }

    /*
     * Helper to remove a command from any point in the stack.
     */
    protected void drop(int pos) {
        //System.out.println("  (drop "+pos+") C="+currentLocation+" S="+saveLocation);
        if ((pos < 0) || pos >= fContexts.size()) {
            throw new IllegalArgumentException();
        }
        Context c = fContexts.get(pos);
        int a = Math.min(saveLocation, fCurrentLocation);
        int b = Math.max(saveLocation, fCurrentLocation);
        if ((a <= pos) && (pos < b)) {
            // we're dropping something between current and save point.
            dirtyUntilSave.addAll(Arrays.asList(c.fResources));
            //System.out.println("dus = "+dirtyUntilSave);
        }
        c.fCommand.dispose();
        fContexts.remove(pos);
        if (fCurrentLocation > pos) {
            fCurrentLocation--;
        }
        if (saveLocation > pos) {
            saveLocation--;
        }
    }

    /*
     * Helper to remove a range of commands from anywhere in the stack.
     */
    protected void drop(int from, int to) {
        if (to < from) {
            int a = to;
            to = from;
            from = a;
        }
        //System.out.println("drop: "+to+".."+from);
        while (to > from) {
            drop(from);
            to--;
        }
    }

    /*
     * Helper to mark a resource as clean or dirty (mostly for ease of debugging)
     */
    protected static void setResourceModified(Resource r, boolean modified) {
        if (r.isModified() != modified) {
            //System.out.println("> "+modified+" : "+r);
            r.setModified(modified);
        }
    }

    /*
     * Helper to calculate which resources should be marked as dirty
     */
    protected void updateModifiedFlags() {
        //System.out.println("calculateModifiedState()");

        Set<Resource> cleanResources = new HashSet<Resource>();
        // for starters, treat everything as clean
        for (Context c : fContexts) {
            cleanResources.addAll(Arrays.asList(c.fResources));
        }

        // mark things that fell off the bottom of the undo stack as dirty 
        for (Resource resource : dirtyUntilSave) {
            cleanResources.remove(resource);
            setResourceModified(resource, true);
        }
        // mark things modified between saveLocation and currentLocation as dirty
        int a = Math.min(fCurrentLocation, saveLocation);
        int b = Math.max(fCurrentLocation, saveLocation);

        for (int i = Math.max(a, 0); i < b; i++) {
            Context c = fContexts.get(i);
            cleanResources.removeAll(Arrays.asList(c.fResources));
            c.setModifiedFlags(true);
        }
        // mark anything we still consider clean as clean
        for (Resource resource : cleanResources) {
            setResourceModified(resource, false);
        }
    }

    /**
     * SharedCommandStackListener
     * 
     */
    public static interface SharedCommandStackListener extends CommandStackListener {

        public static final int EVENT_START_EXECUTE = 1;
        public static final int EVENT_FINISH_EXECUTE = 2;
        public static final int EVENT_START_UNDO = 3;
        public static final int EVENT_FINISH_UNDO = 4;
        public static final int EVENT_START_REDO = 5;
        public static final int EVENT_FINISH_REDO = 6;
        public static final int EVENT_START_FLUSH = 7;
        public static final int EVENT_FINISH_FLUSH = 8;
        public static final int EVENT_START_MARK_SAVED = 9;
        public static final int EVENT_FINISH_MARK_SAVED = 10;

        public static final int EVENT_DROP_LAST_UNDO_STACK_ENTRY = 11;
        public static final int EVENT_MARK_SAVED = 12;

    }

    public static class SharedCommandStackChangedEvent extends EventObject {
        int property;
        public boolean doit = true;

        SharedCommandStackChangedEvent(Object source) {
            super(source);
        }

        public EditModelCommandStack getStack() {
            return (EditModelCommandStack) getSource();
        }

        public int getProperty() {
            return property;
        }
    }

    protected static Resource[] EMPTY_RESOURCE_ARRAY = new Resource[0];

    // TODO: should this be in a utility class?  Can it be made extensible?
    public static Resource[] getResources(Command command) {
        if (command instanceof IEditModelCommand) {
            return ((IEditModelCommand) command).getResources();
        }
        if (command instanceof CompoundCommand) {
            CompoundCommand ccmd = (CompoundCommand) command;

            Set<Resource> set = new HashSet<Resource>();
            for (Object n : ccmd.getChildren()) {
                for (Resource r : getResources((Command) n)) {
                    set.add(r);
                }
            }
            if (set.isEmpty()) {
                return EMPTY_RESOURCE_ARRAY;
            }
            return set.toArray(EMPTY_RESOURCE_ARRAY);
        }

        throw new IllegalArgumentException();
    }

    // TODO: should this be in a utility class?  Can it be made extensible?
    public static Resource[] getModifiedResources(Command command) {

        if (command instanceof IEditModelCommand) {
            return ((IEditModelCommand) command).getModifiedResources();
        }
        if (command instanceof CompoundCommand) {
            CompoundCommand ccmd = (CompoundCommand) command;

            Set<Resource> set = new HashSet<Resource>();
            for (Object n : ccmd.getChildren()) {
                for (Resource r : getModifiedResources((Command) n)) {
                    set.add(r);
                }
            }
            if (set.isEmpty()) {
                return EMPTY_RESOURCE_ARRAY;
            }
            return set.toArray(EMPTY_RESOURCE_ARRAY);
        }

        throw new IllegalArgumentException();
    }

    protected static class Context {

        public Command fCommand;
        public Resource[] fResources;

        public Context(Command command, Resource[] resources) {
            this.fCommand = command;
            this.fResources = resources;
        }

        public void setModifiedFlags(boolean value) {
            for (Resource r : fResources) {
                setResourceModified(r, value);
            }
        }

    }

}