/*
* org.openmicroscopy.shoola.util.concur.tasks.CompositeTask
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.concur.tasks;
//Java imports
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//Third-party libraries
//Application-internal dependencies
/**
* Aggregates computations in a computation tree.
* <p>Each leaf node is a computation to carry out and can be an instance of
* {@link java.lang.Runnable}, {@link Invocation}, or {@link MultiStepTask}.
* The root node and all internal nodes are <code>CompositeTask</code> objects
* which are used to {@link #add(MultiStepTask) build} the tree.</p>
* <p>After building the tree, pass the root node to the {@link CmdProcessor}
* for execution. This class enforces an execution algorithm which consists in
* running all computations in the leaf nodes <i>sequentially</i> and in the
* order imposed by the depth-first algorithm the children of every node
* are kept in the same order as they were {@link #add(MultiStepTask) added} to
* the node. For example, say you create a root node <i>R</i>, an internal node
* <i>I</i> and {@link #add(MultiStepTask) add} <i>I</i> to <i>R</i>. You then
* create a {@link MultiStepTask} <i>L1</i> and {@link #add(MultiStepTask) add}
* it to <i>R</i>. Finally, you create two {@link Invocation} objects <i>L2,
* L3</i> and add them to <i>I</i>. Here's what your tree looks like then:</p>
* <pre>
* R
* |--- I
* | |--- L2
* | |--- L3
* |
* |--- L1
* </pre>
* <p>The resulting execution order is <i>L2, L3, L1</i>.</p>
* <p>You can add a new node while the tree is being computed however, be
* careful about concurrency issues when doing so. If <i>not all</i> children
* of the parent node to which you're adding the new node have already been
* visited by the execution algorithm, then the leaf nodes of the new sub-tree
* will be executed. Otherwise, an exception will be thrown.<br>
* Back to the previous example, you could add a new node <i>II</i> containing
* a {@link java.lang.Runnable} <i>L4</i> to <i>I</i>. So your tree would now
* be:</p>
* <pre>
* R
* |--- I
* | |--- L2
* | |--- L3
* | |--- II
* | |--- L4
* |--- L1
* </pre>
* <p>If you added <i>before L3</i> has finished executing, then <i>L4</i> would
* be executed (after <i>L3</i> in this case). If you didn't, you would get an
* exception.</p>
* <p>A <code>CompositeTask</code> object can't take part in more than one
* execution. This implies that the root node and any internal node can't be
* reused after the first execution and have to be discarded. Another
* implication is that you shouldn't share the same <code>CompositeTask</code>
* object across any two trees that are executed concurrently. Probably the
* best strategy to avoid this altogether is to follow an hand-off protocol:
* create a new tree, pass it to the {@link CmdProcessor}, dereference the root
* node and any other internal node.</p>
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author <br>Andrea Falconi
* <a href="mailto:a.falconi@dundee.ac.uk">
* a.falconi@dundee.ac.uk</a>
* @version 2.2
* <small>
* (<b>Internal version:</b> $Revision$ $Date$)
* </small>
* @since OME2.2
*/
public class CompositeTask
implements MultiStepTask
{
/**
* Flag to denote the <i>Adding</i> state.
* When in this state, the tree is being built. The {@link #curIndex} is
* <code>0</code> and {@link #curChild} is <code>null</code>. It is
* possible to {@link #add(MultiStepTask) add} new nodes, but attempting to
* call {@link #doStep()} will result in an exception. A call to
* {@link #isDone()} will result in the object transitioning to the
* {@link #ITERATING} state if some nodes have been added and not all of
* them are done (that is, there exists at least one child for which its
* <code>isDone</code> method will return <code>false</code>).
* Otherwise, the object will transition to the {@link #DONE} state.
*/
public static final int ADDING = 0;
/**
* Flag to denote the <i>Iterating</i> state.
* When in this state, the tree is being executed by the
* {@link CmdProcessor} and the execution algorithm is processing this node.
* The {@link #curChild} is not <code>null</code> and {@link #curIndex} is
* in <code>[0, sz)</code> <code>sz</code> being the size of
* {@link #children}. Moreover, <nobr><code>
* {@link #curChild} == {@link #children}.get({@link #curIndex})
* </code></nobr>. While in this state, it is still possible to
* {@link #add(MultiStepTask) add} new nodes. The iteration will proceed
* until all children are done (that is, until the <code>isDone</code>
* method of every child will return <code>true</code>). At which point the
* object will transition to the {@link #DONE} state.
*/
public static final int ITERATING = 1;
/**
* Flag to denote the <i>Done</i> state.
* When in this state, this node has already been executed this
* implies the {@link #isDone() isDone} method has been called at least
* once. The {@link #curChild} is <code>null</code> and {@link #curIndex}
* equals the number of children (may be <code>0</code>). Any attempt to
* {@link #add(MultiStepTask) add} new nodes or to call {@link #doStep()}
* will result in an exception. The {@link #isDone() isDone} method will
* always return <code>true</code> from now on.
*/
public static final int DONE = 2;
/** Keeps track of the current state. */
private int state;
/**
* Stores the children nodes of this node.
* Although this field is never <code>null</code>, the list may be empty.
* If not empty, the list contains instances of {@link MultiStepTask}
* <code>null</code>s are not allowed.
*/
private List children;
/**
* The index of {@link #curChild} in {@link #children}.
* Only relevant to the {@link #ITERATING} state.
*/
private int curIndex;
/**
* The child currently processed by the execution algorithm.
* Only relevant to the {@link #ITERATING} state.
*/
private MultiStepTask curChild;
/**
* Finds the first child which is not done, starting from {@link #curChild}.
* That is, the first element <code>c</code> in {@link #children} whose
* <code>isDone</code> method will return <code>false</code>. If found,
* {@link #curChild} will be set to <code>c</code> and {@link #curIndex} to
* the index of <code>c</code> in {@link #children}. Otherwise,
* {@link #curChild} will be set to <code>null</code> and {@link #curIndex}
* to the size of {@link #children}.
*
* @return The new value of {@link #curIndex}.
*/
private int next()
{
int m = children.size();
curChild = null; //Discard previous one, if any.
for (int k = curIndex; k < m; ++k) {
curChild = (MultiStepTask) children.get(k);
if (!curChild.isDone()) { //Found, could well be old curChild.
m = k;
break;
}
//Else it is either a CompositeTask with no children or a
//task with no steps to execute. Both cases are probably
//symptoms of bugs. (*)
}
curIndex = m;
return m;
}
//(*) NOTE: We might want to revisit this and throw an exception instead.
//However in the case of a CompositeTask, we can't throw an exception from
//the add method b/c the client may first add an empty node and then
//populate it. So exceptions will have to be thrown during execution.
//This is inconvenient b/c execution doesn't happen in the client's thread.
/**
* Creates a new instance which could serve either as a root or internal
* node.
*/
public CompositeTask()
{
children = new ArrayList();
state = ADDING;
}
/**
* Just forwards the call to the child currently processed by the execution
* algorithm.
* @throws IllegalStateException If not invoked in the {@link #ITERATING}
* state.
* @see MultiStepTask#doStep()
*/
public Object doStep()
throws Exception
{
if (state != ITERATING) throw new IllegalStateException();
return curChild.doStep();
}
/**
* Tells whether all children within this node have been processed.
* If the current child has been processed, then this method advances
* to the next child.
* @see MultiStepTask#isDone()
*/
public boolean isDone()
{
if (state == DONE) return true;
//Else Active state, either ADDING or ITERATING
int m = next(); //Update the curChild and curIndex.
if (m == children.size()) //sz=0 or all children are done.
state = DONE;
else //m<sz and curChild is first child not done.
if (state == ADDING) state = ITERATING;
//Else we're already iterating, do nothing.
return (state == DONE);
}
/**
* Adds a new child node to this node.
* The child node will be treated as a leaf node unless it is an instance
* of <code>CompositeTask</code>. In which case, the new node will just
* be an internal node.
*
* @param mst The child node. Mustn't be <code>null</code>.
* @throws IllegalStateException If invoked in the {@link #DONE} state.
*/
public void add(MultiStepTask mst)
{
if (state == DONE) throw new IllegalStateException();
if (mst == null) throw new NullPointerException("No task.");
children.add(mst);
}
/**
* Adds a new leaf node to this node.
*
* @param task The leaf node. Mustn't be <code>null</code>.
* @throws IllegalStateException If invoked in the {@link #DONE} state.
*/
public void add(Runnable task)
{
add(new TaskAdapter(task));
//TaskAdapter checks for null and throws NPE.
}
/**
* Adds a new leaf node to this node.
*
* @param call The leaf node. Mustn't be <code>null</code>.
* @throws IllegalStateException If invoked in the {@link #DONE} state.
*/
public void add(Invocation call)
{
add(new InvocationAdapter(call));
//InvocationAdapter checks for null and throws NPE.
}
/**
* Returns a <i>read-only</i> view of the children nodes.
* Attempts to modify the contents of the returned list will fail and an
* exception will be thrown. The list contains no <code>null</code>
* elements; however its elements may differ from the objects that were
* initially added. In fact, whenever you add a {@link Runnable} or
* {@link Invocation} object, this is turned into a {@link MultiStepTask}
* object before adding it to the list. {@link MultiStepTask} objects
* remains untouched though.
*
* @return See above.
*/
public List getChildren()
{
return Collections.unmodifiableList(children);
}
/**
* Returns the child node that is currently processed by the execution
* algorithm. The returned object may differ from the object that was
* originally added. In fact, whenever you add a {@link Runnable} or
* {@link Invocation} object, this is turned into a {@link MultiStepTask}
* object before adding it to the list. {@link MultiStepTask} objects
* remains untouched though.
*
* @return See above.
*/
public MultiStepTask getCurChild()
{
if (isDone()) return null;
return curChild;
}
/**
* Returns a flag denoting the current state.
*
* @return One of the state flags defined by this class.
*/
public int getState() { return state; }
}