Package org.locationtech.udig.project.command

Source Code of org.locationtech.udig.project.command.CommandManager$Executor

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004-2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.project.command;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;

import org.locationtech.udig.project.internal.Messages;
import org.locationtech.udig.project.internal.ProjectPlugin;
import org.locationtech.udig.project.internal.commands.edit.RollbackCommand;
import org.locationtech.udig.project.internal.commands.selection.CommitCommand;
import org.locationtech.udig.project.internal.impl.MapImpl.MapCommandListener;
import org.locationtech.udig.project.preferences.PreferenceConstants;
import org.locationtech.udig.ui.PlatformGIS;
import org.locationtech.udig.ui.ProgressMonitorTaskNamer;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;

/**
* A commands Manager executes commands in a seperate thread, either synchronously or a
* synchronously.
*
* @author Jesse
* @since 1.0.0
*/
public class CommandManager implements CommandStack, NavCommandStack {

    private static final String TRACE_ID = "org.locationtech.udig.project/debug/commands/manager/trace"; //$NON-NLS-1$
    /**
     * If -1 then Synchronous commands will wait indefinately. Otherwise they will try for this many
     * milliseconds.
     */
    private long timeout = -1;

    Set<ErrorHandler> handlers = new CopyOnWriteArraySet<ErrorHandler>();
    Set<CommandListener> completionHandlers = new CopyOnWriteArraySet<CommandListener>();

    Executor commandExecutor;
    private final String managerName;

    /**
     * Creates a new instance of CommandManager
     *
     * @param handler an error handler to use to handle thrown exceptions.
     */
    public CommandManager( String managerName, ErrorHandler handler, CommandListener commandCompletionListener ) {
        this.managerName=managerName;
        handlers.add(handler);
        if( commandCompletionListener!=null )
            completionHandlers.add(commandCompletionListener);
    }
    /**
     * Creates a new instance of CommandManager
     *
     * @param handler an error handler to use to handle thrown exceptions.
     */
    public CommandManager( String managerName, ErrorHandler handler ) {
        this(managerName, handler, null);
    }
    /**
     * Creates a new instance of CommandManager
     *
     * @param handler an error handler to use to handle thrown exceptions.
     */
    public CommandManager( String managerName, ErrorHandler handler, CommandListener commandCompletionListener,
            long timeout2 ) {
        this(managerName, handler, commandCompletionListener);
        this.timeout = timeout2;
       
    }

    /**
     * Executes a command. Calls the Errorhandler if an exception is thrown.
     *
     * @param command The command to execute
     * @param async flag indicating wether command should be executed sync vs async.
     * @return true if no problems were encountered while queueing command. Problems will typically
     *         occur when the command is synchronous and it times out or is interrupted.
     */
    public boolean execute( final Command command, boolean async ) {
        int type = Request.RUN;
        return doMakeRequest(command, async, type);

    }
   
    /**
     * @param command command to perform
     * @param async whether to do request synchronously
     * @param type type of request (REDO UNOD RUN )
     * @return
     */
    private boolean doMakeRequest( final Command command, boolean async, int type ) {
        Request request = new Request(type, command, async, Display.getCurrent() != null);
        synchronized (this) {
            if (commandExecutor == null) {
                commandExecutor = new Executor(managerName);
            }
        }
        commandExecutor.addRequest(request);
        if (request.isSync()) {

            // synchronous execution, current thread needs to wait
            // it is unlikely that the command will be complete by the time
            // we get here but better to be safe than sorry
            try {
                Display current = Display.getCurrent();
                if (current != null)
                    waitInDisplay(current, request);
                else
                    waitOnRequest(request);
                if (!request.completed) {
                    ProjectPlugin.trace(TRACE_ID, getClass(), "Request didn't complete", null); //$NON-NLS-1$
                    commandExecutor.removeCommand(request);
                    return false;
                }
                ProjectPlugin.trace(TRACE_ID, getClass(), "Request completed", null); //$NON-NLS-1$
            } catch (InterruptedException e) {
                ProjectPlugin.log("Error running commands synchronously", e); //$NON-NLS-1$
                return false;
            }
        }
        return true;
    }

    private long waitOnRequest( Request request ) throws InterruptedException {
        ProjectPlugin.trace(TRACE_ID,getClass(),
                "synchronous command NOT in display thread\nTimout=" + timeout, null); //$NON-NLS-1$
        long tries = 0;

        while( mustWait(request, tries) ) {
            tries += 500;
            synchronized (request) {
                request.wait(500);
            }
        }
        return tries;
    }
    /**
     * This method is special wait command that ensures that the display does not block. It executes
     * the jobs waiting for display.
     *
     * @param current the current display
     * @param request
     * @throws InterruptedException
     * @see Display#readAndDispatch()
     */
    private long waitInDisplay( Display current, Request request ) throws InterruptedException {
        ProjectPlugin.trace(TRACE_ID,getClass(),
                "synchronous command IN display thread\nTimout=" + timeout, null); //$NON-NLS-1$
        long start = System.currentTimeMillis();

        while( mustWait(request, System.currentTimeMillis() - start) ) {
            // run a display event continue if there is more work todo.
            if (current.readAndDispatch()) {
                continue;
            }

            // no more work to do in display thread, wait on request if request has not
            // finished
            if (!mustWait(request, System.currentTimeMillis() - start))
                return System.currentTimeMillis() - start;

            synchronized (request) {
                request.wait(300);
            }
        }
        return System.currentTimeMillis() - start;
    }
    private boolean mustWait( Request request, long tries ) {
        ProjectPlugin
                .trace(
                        TRACE_ID, getClass(),
                        "timeout :" + timeout + ", tries: " + tries + ", completed:" + request.completed, null); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        return !request.completed && (tries < timeout || timeout == -1);
    }
    /**
     * Executes the last undone command, if there are any commands to undo.
     *
     * @param runAsync true to run undo asynchronously
     */
    public void redo( boolean runAsync ) {
        doMakeRequest(null, runAsync, Request.REDO);
    }

    /**
     * Undoes the last command if possible.
     *
     * @param runAsync true to run undo asynchronously
     */
    public void undo( boolean runAsync ) {
        doMakeRequest(null, runAsync, Request.UNDO);
    }

    /**
     * Adds an Errorhandler to the list of error handlers
     *
     * @param handler the error handler to add.
     * @see ErrorHandler
     */
    public void addErrorHandler( ErrorHandler handler ) {
        handlers.add(handler);
    }

    /**
     * Removes an Errorhandler from the list of error handlers
     *
     * @param handler the error handler to remove.
     * @see ErrorHandler
     */
    public void removeErrorHandler( ErrorHandler handler ) {
        handlers.remove(handler);
    }

    /**
     * @see org.locationtech.udig.project.command.CommandStack#canUndo()
     */
    public boolean canUndo() {
        if( commandExecutor ==null )
            return false;
       
        Command c;
        if (!commandExecutor.history.isEmpty()) {
            c = (Command) commandExecutor.history.peek();
            if (c instanceof UndoableCommand)
                return true;
        }
        return false;
    }

    /**
     * @see org.locationtech.udig.project.command.CommandStack#canRedo()
     */
    public boolean canRedo() {
        if (commandExecutor!=null && !commandExecutor.undone.isEmpty()) {
            return true;
        }
        return false;
    }

    /**
     * @see org.locationtech.udig.project.command.NavCommandStack#hasBackHistory()
     */
    public boolean hasBackHistory() {
        return canUndo();
    }

    /**
     * @see org.locationtech.udig.project.command.NavCommandStack#hasForwardHistory()
     */
    public boolean hasForwardHistory() {
        return canRedo();
    }

    /**
     * Executes commands in a seperate thread from the requesting thread. JONES: Should support
     * force kill of a command.
     *
     * @author Jesse
     * @since 1.0.0
     */
    public class Executor extends Job {
        LinkedList<Command> history = new LinkedList<Command>();

        LinkedList<Command> undone = new LinkedList<Command>();

        Queue<Request> commands = new ConcurrentLinkedQueue<Request>();

        /**
         * Construct <code>Executor</code>.
         *
         * @param name the name of the job
         * @param type the type of the executor. (RUN, UNDO, REDO)
         */
        public Executor( String name ) {
            super(name);
            setSystem(false);
        }

        IProgressMonitor progressMonitor;
        Request currentRequest;
        @Override
        protected IStatus run( IProgressMonitor monitor ) {
            monitor.beginTask(Messages.CommandManager_ProgressMonitor, IProgressMonitor.UNKNOWN);
            while( !getThread().isInterrupted() ) {
               
                synchronized (this) {
                    currentRequest = commands.poll();
                    if( currentRequest==null )
                        return Status.OK_STATUS;
                }
                progressMonitor = new ProgressMonitorTaskNamer(monitor, 10);
                run(progressMonitor, currentRequest);

                if (currentRequest.isSync()) {
                    // notify those wating for command to finish
                    synchronized (currentRequest) {
                        currentRequest.notifyAll();
                    }
                }
            }
            return Status.OK_STATUS;
        }
        private void run( IProgressMonitor monitor, Request request ) {
            switch( request.type ) {
            case Request.RUN:
                execute(request.command, monitor);
                break;
            case Request.UNDO:
                undo(monitor);
                break;
            case Request.REDO:
                redo(monitor);
                break;
            case Request.RERUN:
                rerunCommands(monitor);
                break;
            }

            request.completed = true;
        }

        /**
         * Adds a command to the stack of commands that needs to be executed.
         *
         * @param request
         */
        public void addRequest( Request request ) {
            if (getThread() == Thread.currentThread() || isDisplayThreadDeadlockDetected(request)) {
                run(progressMonitor, request);
            } else {
                synchronized( this ){
                    commands.offer(request);
                    schedule();
                }
                Thread.yield();
            }
        }
        private boolean isDisplayThreadDeadlockDetected( Request request ) {
            return (Display.getCurrent() != null && currentRequest != null
                    && currentRequest.requestByDisplay && currentRequest.isSync());
        }

        /**
         * This method is only called by {@linkplain CommandManager#execute(Command, boolean)}
         */
        synchronized void removeCommand( Request request ) {
            if (commands.remove(request))
                return;

            if (currentRequest == request) {
                // JONES interrupt running command if command is running
            }

            // If it has already been executed then don't worry.
        }

        /**
         * Executes a command. Calls the Errorhandler if an exception is thrown.
         *
         * @param command The command to execute
         */
        private void execute( final Command command, IProgressMonitor monitor ) {

            long time = System.currentTimeMillis();
            if( command.getName()!=null )
            monitor.beginTask(command.getName(), IProgressMonitor.UNKNOWN);
            try {
                final boolean runCommand = openWarning(command);
                if (!runCommand)
                    return;

                if (command instanceof PostDeterminedEffectCommand) {

                    PostDeterminedEffectCommand c = (PostDeterminedEffectCommand) command;
                    if (c.execute(new SubProgressMonitor(monitor, 1000))) {
                        undone.clear();
                        addToHistory(command);
                    }

                } else {
                    command.run(new SubProgressMonitor(monitor, 1000));

                    undone.clear();
                    addToHistory(command);

                }

                if( ProjectPlugin.isDebugging(TRACE_ID) ){
                long l = (System.currentTimeMillis()-time);
                    if( l>100){
                        System.out.println(command.toString()+"--"+l); //$NON-NLS-1$
                    }
                }
                if (history.size() > getMaxHistorySize())
                    history.removeFirst();
                notifyOwner(command);

            } catch (Throwable e) {
                undone.clear();
                handleError(command, e);
            }

        }
        private void addToHistory( final Command command ) {
            if( history.size()>ProjectPlugin.getPlugin().getPreferenceStore().getInt(PreferenceConstants.P_MAX_UNDO))
                history.removeFirst();
            history.addLast(command);
        }
       
        private boolean openWarning( final Command command ) {
            final boolean[] runCommand=new boolean[1];
            if (!(command instanceof UndoableCommand)
                    && ProjectPlugin.getPlugin().getUndoableCommandWarning()) {
                final IPreferenceStore preferenceStore = ProjectPlugin.getPlugin()
                        .getPreferenceStore();
                if (!preferenceStore.getBoolean(PreferenceConstants.P_WARN_IRREVERSIBLE_COMMAND)) {
                    return preferenceStore
                            .getBoolean(PreferenceConstants.P_IRREVERSIBLE_COMMAND_VALUE);
                }
                PlatformGIS.syncInDisplayThread(new Runnable(){
                    public void run() {
                        String string = Messages.CommandManager_warning + command.getName();
                        if ( command instanceof RollbackCommand ||
                                command instanceof CommitCommand )
                            string += "?"; //$NON-NLS-1$
                        else
                            string+=Messages.CommandManager_warning2;
                        MessageDialogWithToggle dialog = MessageDialogWithToggle
                                .openOkCancelConfirm(
                                        PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                                .getShell(),
                                        Messages.CommandManager_warningTitle, string, 
                                        Messages.CommandManager_toggleMessage, false, preferenceStore, PreferenceConstants.P_WARN_IRREVERSIBLE_COMMAND);
                        runCommand[0] = dialog.getReturnCode() == IDialogConstants.OK_ID;
                        if (dialog.getToggleState()) {
                            preferenceStore
                                    .setValue(PreferenceConstants.P_IRREVERSIBLE_COMMAND_VALUE,
                                            runCommand[0]);
                        }
                    }
                });
            }else{
                return true;
            }
            return runCommand[0];
        }

        /**
         * Notifies the owner that the command has been executed.
         */
        private void notifyOwner( Command command ) {
           
            for( CommandListener listener : completionHandlers ) {
                if (command instanceof NavCommand) {
                    listener.commandExecuted(MapCommandListener.NAV_COMMAND);
                } else {
                    listener.commandExecuted(MapCommandListener.COMMAND);
                }
               
            }

        }

        /**
         * Executes the last undone command, if there are any commands to undo.
         */
        private void redo( IProgressMonitor monitor ) {
            if( undone.isEmpty() )
                return;
            Command command = undone.removeLast();
            monitor.beginTask(Messages.CommandManager_redo + command.getName(), 1000);
            try {
                if (command instanceof PostDeterminedEffectCommand) {
                    PostDeterminedEffectCommand post = (PostDeterminedEffectCommand) command;
                    post.execute(new SubProgressMonitor(monitor, 1000));
                } else {
                    command.run(new SubProgressMonitor(monitor, 1000));
                }
                addToHistory(command);
                notifyOwner(command);
            } catch (Exception e) {
                handleError(command, e);
            }
        }

        /**
         * Undoes the last command if possible.
         */
        private void undo( IProgressMonitor monitor ) {
            Command c;
            // First check if there's a command on the commands stack that hasn't
            // been executed and should just be removed
            if (!commands.isEmpty()) {
                Request r;
                synchronized (this) {
                    r = commands.peek();
                    if( r.type!=Request.UNDO ){
                    commands.remove(0);
                    c = r.command;
                    if (c instanceof UndoableCommand) {
                        // We've already popped it off...so we're done
                    } else {
                        throw new RuntimeException(
                                "Undoing Commands (No Undoable Command) is not handled " //$NON-NLS-1$
                                        + "yet because it involves rolling back the current transaction and redoing all " //$NON-NLS-1$
                                        + "the commands in the stack"); //$NON-NLS-1$
                    }                    return;
                    }
                }
            }
            // Nothing on the Command stack, we'll actually undo something
            if (!history.isEmpty()) {
                c = history.removeLast();
                if (c instanceof UndoableCommand) {
                    UndoableCommand command = (UndoableCommand) c;
                    monitor.beginTask(Messages.CommandManager_undo + command.getName(), 1000);
                    try {
                        command.rollback(new SubProgressMonitor(monitor, 1000));
                        addToUndone(command);
                    } catch (Throwable e) {
                        handleRollbackError(command, e);
                    }
                } else {
                    throw new RuntimeException(
                            "Undoing Commands (No Undoable Command) is not handled " //$NON-NLS-1$
                                    + "yet because it involves rolling back the current transaction and redoing all " //$NON-NLS-1$
                                    + "the commands in the stack"); //$NON-NLS-1$
                }
                notifyOwner(c);
            }
        }
        private void addToUndone( UndoableCommand command ) {
            if( undone.size()>ProjectPlugin.getPlugin().getPreferenceStore().getInt(PreferenceConstants.P_MAX_UNDO) )
                undone.removeFirst();
            undone.add(command);
        }

        private void handleError( Command command, Throwable e ) {
            for( ErrorHandler handler : handlers ) {
                handler.handleError(command, e);
            }
        }

        private void handleRollbackError( UndoableCommand command, Throwable e ) {
            for( ErrorHandler handler : handlers ) {
                handler.handleRollbackError(command, e);
            }
        }

        /**
         * Executes all the commands in the history again.
         */
        public void rerunCommands( IProgressMonitor monitor ) {
            Queue<Command> q = history;
            history = new LinkedList<Command>();
            for( Iterator<Command> iter = q.iterator(); iter.hasNext(); ) {
                Command command = (Command) iter.next();
                execute(command, monitor);
            }
        }
    }
    /**
     * TODO Purpose of org.locationtech.udig.project.command
     * <p>
     * </p>
     *
     * @author Jesse
     * @since 1.0.0
     */
    public static class Request {
        /** <code>RUN</code> field */
        public static final int RUN = 0;
        /** <code>UNDO</code> field */
        public static final int UNDO = 1;
        /** <code>REDO</code> field */
        public static final int REDO = 2;
        /** <code>RERUN</code> field */
        public static final int RERUN = 4;

        /** the type of request */
        public final int type;
        /** sync/async * */
        public final boolean async;
        /** the command to be done/undone/redone */
        public final Command command;
        /** flag to signal wether command is complete * */
        public volatile boolean completed;
        public final boolean requestByDisplay;

        /**
         * Construct <code>Request</code>.
         *
         * @param type the type of request
         * @param command the command to be done/undone/redone
         */
        public Request( int type, Command command, boolean async, boolean requestByDisplay2 ) {
            this.requestByDisplay = requestByDisplay2;
            this.command = command;
            this.type = type;
            this.async = async;
            completed = false;
        }

        /**
         * Determines if the request is synchronous.
         *
         * @return
         */
        public boolean isSync() {
            return !async;
        }
    }
    /**
     * Execute Command syncrounously. IE wait until command is complete before returning.
     *
     * @return true if no problems were encountered while queueing command. Problems will typically
     *         occur when the command is synchronous and it times out or is interrupted.
     */
    public boolean syncExecute( Command command ) {
        return execute(command, false);
    }
    public int getMaxHistorySize() {
        return 20;
    }
    /**
     * Execute Command asyncrounously. IE return immediately, do not wait until command is complete
     * before returning.
     *
     * @return true if no problems were encountered while queueing command. Problems will typically
     *         occur when the command is synchronous and it times out or is interrupted.
     */
    public boolean aSyncExecute( Command command ) {
        return execute(command, true);
    }
   
}
TOP

Related Classes of org.locationtech.udig.project.command.CommandManager$Executor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.