Package org.locationtech.udig.catalog.ui.workflow

Source Code of org.locationtech.udig.catalog.ui.workflow.Workflow$WorkflowRunner

/*
*    uDig - User Friendly Desktop Internet GIS client
*    http://udig.refractions.net
*    (C) 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.catalog.ui.workflow;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.locationtech.udig.catalog.ui.CatalogUIPlugin;
import org.locationtech.udig.catalog.ui.internal.Messages;
import org.locationtech.udig.core.Pair;
import org.locationtech.udig.ui.OffThreadProgressMonitor;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;

/**
* Basically is a state machine. It has a set of states and handles the stepping through the states
* when next, previous or run are called.
* <p>
* This is an active object IE it has a thread and most method calls to the object are ran in that
* thread. The method calls are blocking and if the call is in the display thread it is blocked in a
* "nice" manner. IE the Display.readAndDispatch method is called.
*
* @author justin
* @since 1.0
*/
public class Workflow {

    /** set of primary states */
    private State[] states;

    /** map of class to objects for states */
    private Map<Class<State>, State> lookup;

    /** queue of primary states* */
    private LinkedList<State> queue;

    /** current state * */
    private State current;

    /** listeners * */
    private Set<Listener> listeners = new CopyOnWriteArraySet<Listener>();

    /** flag to indicate wither the pipe is started/finished * */
    boolean started = false;
    private boolean finished = false;

    /** context object, states use this object as a seed to perform work * */
    private Object context;

    private ThreadingStrategy threading;

    /**
     * Creates an empty workflow. When using this constructor the states of the workflow must be set
     * before the workflow can be started.
     */
    public Workflow(ThreadingStrategy threading) {
        threading.init();
        this.threading = threading;
    }

    /**
     * Creates a workflow from a set of workflow states.
     *
     * @param states The states of the workflow.
     */
    public Workflow( ThreadingStrategy strategy, State[] states ) {
        this(strategy);
        setStates(states);
    }

    /**
     * Creates an empty workflow. When using this constructor the states of the workflow must be set
     * before the workflow can be started.
     */
    public Workflow() {
        this(new DefaultThreading());
    }

    /**
     * Creates a workflow from a set of workflow states.
     *
     * @param states The states of the workflow.
     */
    public Workflow( State[] states ) {
        this(new DefaultThreading(), states);
    }

    /**
     * Adds a listener to the workflow. The listener collection is a set which prevents duplicates.
     * For this resason clients may call this method multiple times with the same object.
     *
     * @param l The listening object.
     */
    public void addListener( Listener l ) {
        listeners.add(l);
    }

    public void removeListener( Listener l ) {
        listeners.remove(l);
    }

    public void shutdown() {
        threading.shutdown();
    }

    /**
     * Returns an object representing a context for which the states can feed off of. The context is
     * often provided via a workbench selection.
     *
     * @return The context object, or null if none has been set
     */
    public Object getContext() {
        return context;
    }

    /**
     * Sets the object representing a context for which states can feed off of. The context is often
     * provided via a workbench selection.
     *
     * @param context The context object to set.
     */
    public void setContext( Object context ) {
        this.context = context;
    }

    /**
     * Sets the primary set of states of the workflow.
     *
     * @param states An array of states.
     */
    @SuppressWarnings("unchecked")
    public void setStates( State[] states ) {
        int i2 = 0;
        if (states != null)
            i2 = states.length;
        State[] s = new State[i2];
        if (states != null)
            System.arraycopy(states, 0, s, 0, s.length);

        this.states = s;
        queue = new LinkedList<State>();
        lookup = new HashMap<Class<State>, State>();
        for( int i = 0; i < s.length; i++ ) {
            s[i].setWorkflow(this);
            queue.addLast(s[i]);
            lookup.put((Class<State>) s[i].getClass(), s[i]);
        }
    }

    /**
     * @return the primary set of states of the workflow.
     */
    public State[] getStates() {
        State[] s = new State[states.length];
        System.arraycopy(states, 0, s, 0, s.length);
        return s;
    }

    /**
     * Goes through the lookup values and find the first
     * match for the provided c.
     *
     * @param <T> The type of the state.
     * @param c The class of the state.
     * @return The state instance, or null if none exists.
     */
    public <T> T getState( Class<T> c ) {
       
        State state = lookup.get(c);
        if( state == null ){
            // see if we have a subclass of the type
            for( State current : lookup.values() ) {
                if( c.isAssignableFrom(current.getClass())) {
                    state = current;
                    break;
                }
            }
        }
        return c.cast(state);
    }

    /**
     * Starts the workflow by moving to the first state. This method must only be called once. This
     * method executes asynchronously performing work in a seperate thread and does not block.
     */
    public void start() {
        start(new NullProgressMonitor());
    }

    /**
     * Starts the workflow by moving to the first state. This method must only be called once. This
     * method executes synchronously performing work in the current thread and blocks.
     */
    public void start( final IProgressMonitor monitor ) {
        final IProgressMonitor progressMonitor = checkMonitor(monitor);

        threading.init();

        Runnable request = new Runnable(){
            public void run() {
                try {
                    // move to first state
                    current = queue.removeFirst();
                    current.setPrevious(null);
                    current.init(progressMonitor);

                    started = true;
                    dispatchStarted(current);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public String toString() {
                return "Workflow.java start() task"; //$NON-NLS-1$
            }
        };

        threading.run(request);
    }

    /**
     * This is an estimate of whether or not the Workflow can be run to completion.
     *
     * @return true if it is likely that the workflow can be run to completion.
     */
    public boolean dryRun() {
        Queue<State> copiedQueue = new LinkedList<State>(queue);
        State state = getCurrentState();

        while( state != null ) {
            Pair<Boolean, State> dryRunResult = state.dryRun();
            if (!dryRunResult.getLeft()) {
                // the dry run predicts failure
                return false;
            }

            state = dryRunResult.getRight();
            if (state == null) {
                state = copiedQueue.poll();
            }
        }

        // we finished with no failures
        return true;
    }
    /**
     * Moves the workflow to the next state. This method executes asynchronously performing work in
     * a seperate thread and does not block.
     */
    public void next() {
        next(new NullProgressMonitor());
    }

    /**
     * Moves the workflow to the next state. This method executes synchronously performing work in
     * the current thread and blocks.
     *
     * @return True if the state
     */
    public void next( final IProgressMonitor monitor ) {

        final IProgressMonitor progressMonitor = checkMonitor(monitor);

        Runnable request = new Runnable(){
            public void run() {
                doNextInternal(progressMonitor);
            }

            @Override
            public String toString() {
                return "Workflow.java run() task"; //$NON-NLS-1$
            }
        };

        threading.run(request);
    }
    private IProgressMonitor checkMonitor( final IProgressMonitor monitor ) {
        if (monitor == null) {
            throw new NullPointerException("monitor is null"); //$NON-NLS-1$
        }

        final IProgressMonitor progressMonitor;
        if (monitor instanceof OffThreadProgressMonitor) {
            progressMonitor = monitor;
        } else {
            progressMonitor = new OffThreadProgressMonitor(monitor);
        }
        return progressMonitor;
    }

    @SuppressWarnings("unchecked")
    private void doNextInternal( IProgressMonitor monitor ) {

        try {
            assertStarted();
            assertNotFinished();

            String name = getCurrentState().getName();
            String string = name != null ? name : Messages.Workflow_busy;
            monitor.beginTask(string, 20);
            monitor.setTaskName(string);

            if (queue == null) {
                String msg = "No states"; //$NON-NLS-1$
                throw new IllegalStateException(msg);
            }

            // run it
            boolean ok = false;
            SubProgressMonitor subProgressMonitor = new SubProgressMonitor(monitor, 10);
            try {
                ok = current.run(subProgressMonitor) && !monitor.isCanceled();
            } catch (Throwable t) {
                CatalogUIPlugin.log(t.getLocalizedMessage(), t);
                if( Platform.inDevelopmentMode() ){
                  System.out.println( "Could not "+current.getName()+":"+t.getLocalizedMessage() );
                  t.printStackTrace();
                }
            } finally {
                subProgressMonitor.done();
            }

            if (ok) {
                // dispatch the event
                dispatchPassed(current);

                // grab the next state, try pulling one from the current state
                State next = current.next();
                if (next == null) {
                    // try pulling from the queue
                    if (!queue.isEmpty())
                        next = queue.removeFirst();
                } else {
                    // add to lookup tables
                    lookup.put((Class<State>) next.getClass(), next);
                }

                if (next != null) {
                    // set the back pointer and call lifecyclmutexe events
                    next.setWorkflow(this);
                    next.setPrevious(current);
                    try {
                        subProgressMonitor = new SubProgressMonitor(monitor, 10);
                        next.init(subProgressMonitor);
                    } finally {
                        subProgressMonitor.done();
                    }
                    State prev = current;
                    current = next;

                    dispatchForward(current, prev);
                } else {
                    // no more states, we are finished
                    State last = current;
                    current = null;

                    finished = true;
                    threading.shutdown();
                    dispatchFinished(last);
                }
            } else {
                // run did not succeed
                dispatchFailed(current);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Moves the workflow to the previous state. This method executes asynchronously performing work
     * in a seperate thread and does not block.
     */
    public void previous() {
        previous(new NullProgressMonitor());
    }

    /**
     * Moves the workflow to the previous state. This method executes synchronously performing work
     * in the current thread and blocks.
     */
    public void previous( final IProgressMonitor monitor ) {
        final IProgressMonitor progressMonitor = checkMonitor(monitor);

        Runnable request = new Runnable(){
            public void run() {
                try {
                    assertStarted();
                    assertNotFinished();

                    if (current.getPreviousState() != null) {
                        // if this state is a "primary" state, place back in front of queue
                        if (isPrimaryState(current))
                            queue.addFirst(current);

                        State next = current;
                        current = current.getPreviousState();

                        // renitialize the state and dispatch the started event
                        current.init(progressMonitor);
                        dispatchBackward(current, next);

                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public String toString() {
                return "Workflow.java run() task"; //$NON-NLS-1$
            }
        };

        threading.run(request);
    }

    /**
     * @return True if the workflow has been started with a call to #start().
     */
    public boolean isStarted() {
        return started;
    }

    /**
     * @return True if the workflow has been finished. The workflow is considered finished after the
     *         call to #next(), while in the final state.
     */
    public boolean isFinished() {
        return finished;
    }

    /**
     * @return the current state of the workflow.
     */
    public State getCurrentState() {
        return current;
    }

    /**
     * Determines if the workflow has more states. It is important to note that this method may not
     * 100% accurate depending on the behaviour of states dynamically creating new states.
     *
     * @return True if there are more states, otherwise false.
     */
    public boolean hasMoreStates() {
        // if the queue is not empty, we definitely have more states
        if (!queue.isEmpty())
            return true;

        // ask the current state
        if (current != null)
            return current.hasNext();

        return false;
    }
    /**
     * Runs the workflow from its current state. The workflow will continue to walk through the
     * states while the state is finished.
     *
     * @param monitor A progress monitor.
     * @return True if the pipe was able to run to completion, otherwise false.
     */
    public boolean run( IProgressMonitor monitor ) {
        if (monitor == null) {
            throw new NullPointerException("monitor is null"); //$NON-NLS-1$
        }

        WorkflowRunner runner = new WorkflowRunner(this);
        return runner.run(monitor);
    }

    /**
     * Resets the workflow. This method may only be called if the workflow is in a finished state.
     * Once reset the workflow lifecycle starts again with a call to
     *
     * @see DataPipeline#start().
     */
    public void reset() {
        assertFinished();

        started = finished = false;
        setStates(states);
    }

    protected void assertStarted() {
        if (!started) {
            String msg = "Not started"; //$NON-NLS-1$
            throw new IllegalStateException(msg);
        }
    }

    protected void assertNotStarted() {
        if (started) {
            String msg = "Already started"; //$NON-NLS-1$
            throw new IllegalStateException(msg);
        }
    }

    protected void assertFinished() {
        if (!finished) {
            String msg = "Not finished"; //$NON-NLS-1$
            throw new IllegalStateException(msg);
        }
    }

    protected void assertNotFinished() {
        if (finished) {
            String msg = "Already finished"; //$NON-NLS-1$
            throw new IllegalStateException(msg);
        }
    }

    protected boolean isPrimaryState( State state ) {
        for( int i = 0; i < states.length; i++ ) {
            if (states[i].equals(state))
                return true;
        }

        return false;
    }

    protected void dispatchStarted( final State start ) {
        for( final Listener l : listeners ) {
            l.started(start);
        }
    }

    protected void dispatchForward( final State current, final State prev ) {
        for( final Listener l : listeners ) {
            l.forward(current, prev);
        }
    }

    protected void dispatchBackward( final State current, final State next ) {
        for( final Listener l : listeners ) {
            l.backward(current, next);
        }
    }

    protected void dispatchPassed( final State state ) {
        for( final Listener l : listeners ) {
            l.statePassed(state);
        }
    }

    protected void dispatchFailed( final State state ) {
        for( final Listener l : listeners ) {
            l.stateFailed(state);
        }
    }

    protected void dispatchFinished( final State last ) {
        for( final Listener l : listeners ) {
            l.finished(last);
        }
    }
   
    @Override
    public String toString() {
      StringBuffer text = new StringBuffer();
      text.append("Workflow: Start ");
      if( started ){
        text.append(" -> ");
      }
      else {
        text.append( " -- " );       
      }
      for( State state : states ){
        if( state == current){
          text.append("[");
        }
        text.append( state.getName() );
        if( state == current){
          text.append("]");
        }
        if( this.queue.contains( state )){
          text.append(" -- ");
          }
          else {
            text.append( " -> " );       
          }
      }
      if( this.finished ){
        text.append( "[Finish]" );
      }
      else {
        text.append( "Finish" );
      }
      return text.toString();
    }
    /**
     * Listens to the workflow as it runs; will run each stage in turn.
     */
    public static class WorkflowRunner implements Listener {
        Workflow pipe;
        boolean stopped;

        WorkflowRunner( Workflow pipe ) {
            this.pipe = pipe;
        }

        /**
         * Will run the workflow; and return true if the workflow if completed
         *
         * @param monitor
         * @return true if completed
         */
        public boolean run( final IProgressMonitor monitor ) {
            final boolean[] result = new boolean[1];

            // run in the Workflow thread
            pipe.threading.run(new Runnable(){
                public void run() {
                    result[0] = runInternal(monitor);
                }
            });
            return result[0];
        }

        /**
         * Carefully runs the workflow step by step until finished or stoppped.
         *
         * @param monitor
         * @return true if completed; false if stopped
         */
        private boolean runInternal( IProgressMonitor monitor ) {
            try {
                monitor.beginTask(Messages.Workflow_task_name, IProgressMonitor.UNKNOWN);
                stopped = false;
                pipe.addListener(this);

                // first check if the pipe is already finished
                if (pipe.isFinished())
                    return true;

                // may need to start
                if (!pipe.isStarted()) {
                    pipe.start(monitor);
                }

                while( !stopped && !pipe.isFinished() ) {
                    pipe.next(new SubProgressMonitor(monitor, 10,
                            SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
                }

                pipe.removeListener(this);
                return !stopped;
            } finally {
                monitor.done();
            }
        }
        /**
         * We are not interesed in the workflow moving forward
         */
        public void forward( State current, State prev ) {
            // do nothing
        }

        /**
         * We are not interested in the workflow moving backward
         */
        public void backward( State current, State next ) {
            // do nothing
        }

        /**
         * We are not interested in a state validating
         */
        public void statePassed( State state ) {
            // do nothing
        }

        /**
         * If the state has failed our workflow has stopped - and we need human intervention
         */
        public void stateFailed( State state ) {
            stopped = true;
        }

        /**
         * We are not interested in starting
         */
        public void started( State first ) {
            // do nothing
        }

        /**
         * We are not interested in the workflow finishing
         */
        public void finished( State last ) {
            // do nothing
        }
    }

}
TOP

Related Classes of org.locationtech.udig.catalog.ui.workflow.Workflow$WorkflowRunner

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.