You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/17 12:45:36 UTC

[12/52] [abbrv] incubator-taverna-workbench git commit: taverna-ui-impl/

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
new file mode 100644
index 0000000..f97d36c
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/EditManagerImpl.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.lang.observer.MultiCaster;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Implementation of {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class EditManagerImpl implements EditManager {
+	private static Logger logger = Logger.getLogger(EditManagerImpl.class);
+
+	private MultiCaster<EditManagerEvent> multiCaster = new MultiCaster<>(this);
+	private Map<WorkflowBundle, DataflowEdits> editsForDataflow = new HashMap<>();
+
+	@Override
+	public void addObserver(Observer<EditManagerEvent> observer) {
+		multiCaster.addObserver(observer);
+	}
+
+	@Override
+	public boolean canRedoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		return edits.canRedo();
+	}
+
+	@Override
+	public boolean canUndoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		return edits.canUndo();
+	}
+
+	@Override
+	public void doDataflowEdit(WorkflowBundle dataflow, Edit<?> edit)
+			throws EditException {
+		// We do the edit before we notify the observers
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		synchronized (edits) {
+			// Make sure the edits are in the order they were performed
+			edit.doEdit();
+			edits.addEdit(edit);
+		}
+		multiCaster.notify(new DataflowEditEvent(dataflow, edit));
+	}
+
+	@Override
+	public List<Observer<EditManagerEvent>> getObservers() {
+		return multiCaster.getObservers();
+	}
+
+	@Override
+	public void redoDataflowEdit(WorkflowBundle dataflow) throws EditException {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		Edit<?> edit;
+		synchronized (edits) {
+			if (!edits.canRedo())
+				return;
+			edit = edits.getLastUndo();
+			edit.doEdit();
+			edits.addRedo(edit);
+		}
+		multiCaster.notify(new DataFlowRedoEvent(dataflow, edit));
+	}
+
+	@Override
+	public void removeObserver(Observer<EditManagerEvent> observer) {
+		multiCaster.removeObserver(observer);
+	}
+
+	@Override
+	public void undoDataflowEdit(WorkflowBundle dataflow) {
+		DataflowEdits edits = getEditsForDataflow(dataflow);
+		Edit<?> edit;
+		synchronized (edits) {
+			if (!edits.canUndo())
+				return;
+			edit = edits.getLastEdit();
+			edit.undo();
+			edits.addUndo(edit);
+		}
+		logger.info("Undoing an edit");
+		multiCaster.notify(new DataFlowUndoEvent(dataflow, edit));
+	}
+
+	/**
+	 * Get the set of edits for a given dataflow, creating if neccessary.
+	 *
+	 * @param dataflow
+	 *            Dataflow the edits relate to
+	 * @return A {@link DataflowEdits} instance to keep edits for the given
+	 *         dataflow
+	 */
+	protected synchronized DataflowEdits getEditsForDataflow(WorkflowBundle dataflow) {
+		DataflowEdits edits = editsForDataflow.get(dataflow);
+		if (edits == null) {
+			edits = new DataflowEdits();
+			editsForDataflow.put(dataflow, edits);
+		}
+		return edits;
+	}
+
+	/**
+	 * A set of edits and undoes for a {@link Dataflow}
+	 *
+	 * @author Stian Soiland-Reyes
+	 *
+	 */
+	public class DataflowEdits {
+		/**
+		 * List of edits that have been performed and can be undone.
+		 */
+		private List<Edit<?>> edits = new ArrayList<>();
+		/**
+		 * List of edits that have been undone and can be redone
+		 */
+		private List<Edit<?>> undoes = new ArrayList<>();
+
+		/**
+		 * Add an {@link Edit} that has been done by the EditManager.
+		 * <p>
+		 * This can later be retrieved using {@link #getLastEdit()}. After
+		 * calling this {@link #canRedo()} will be false.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been undone
+		 */
+		public synchronized void addEdit(Edit<?> edit) {
+			addEditOrRedo(edit, false);
+		}
+
+		/**
+		 * Add an {@link Edit} that has been redone by the EditManager.
+		 * <p>
+		 * The {@link Edit} must be the same as the last undo returned through
+		 * {@link #getLastUndo()}.
+		 * <p>
+		 * This method works like {@link #addEdit(Edit)} except that instead of
+		 * removing all possible redoes, only the given {@link Edit} is removed.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been redone
+		 */
+		public synchronized void addRedo(Edit<?> edit) {
+			addEditOrRedo(edit, true);
+		}
+
+		/**
+		 * Add an {@link Edit} that has been undone by the EditManager.
+		 * <p>
+		 * After calling this method {@link #canRedo()} will be true, and the
+		 * edit can be retrieved using {@link #getLastUndo()}.
+		 * </p>
+		 * <p>
+		 * The {@link Edit} must be the last edit returned from
+		 * {@link #getLastEdit()}, after calling this method
+		 * {@link #getLastEdit()} will return the previous edit or
+		 * {@link #canUndo()} will be false if there are no more edits.
+		 *
+		 * @param edit
+		 *            {@link Edit} that has been undone
+		 */
+		public synchronized void addUndo(Edit<?> edit) {
+			int lastIndex = edits.size() - 1;
+			if (lastIndex < 0 || !edits.get(lastIndex).equals(edit))
+				throw new IllegalArgumentException("Can't undo unknown edit "
+						+ edit);
+			undoes.add(edit);
+			edits.remove(lastIndex);
+		}
+
+		/**
+		 * True if there are undone events that can be redone.
+		 *
+		 * @return <code>true</code> if there are undone events
+		 */
+		public boolean canRedo() {
+			return !undoes.isEmpty();
+		}
+
+		/**
+		 * True if there are edits that can be undone and later added with
+		 * {@link #addUndo(Edit)}.
+		 *
+		 * @return <code>true</code> if there are edits that can be undone
+		 */
+		public boolean canUndo() {
+			return !edits.isEmpty();
+		}
+
+		/**
+		 * Get the last edit that can be undone. This edit was the last one to
+		 * be added with {@link #addEdit(Edit)} or {@link #addRedo(Edit)}.
+		 *
+		 * @return The last added {@link Edit}
+		 * @throws IllegalStateException
+		 *             If there are no more edits (Check with {@link #canUndo()}
+		 *             first)
+		 *
+		 */
+		public synchronized Edit<?> getLastEdit() throws IllegalStateException {
+			if (edits.isEmpty())
+				throw new IllegalStateException("No more edits");
+			int lastEdit = edits.size() - 1;
+			return edits.get(lastEdit);
+		}
+
+		/**
+		 * Get the last edit that can be redone. This edit was the last one to
+		 * be added with {@link #addUndo(Edit)}.
+		 *
+		 * @return The last undone {@link Edit}
+		 * @throws IllegalStateException
+		 *             If there are no more edits (Check with {@link #canRedo()}
+		 *             first)
+		 *
+		 */
+		public synchronized Edit<?> getLastUndo() throws IllegalStateException {
+			if (undoes.isEmpty())
+				throw new IllegalStateException("No more undoes");
+			int lastUndo = undoes.size() - 1;
+			return undoes.get(lastUndo);
+		}
+
+		/**
+		 * Add an edit or redo. Common functionallity called by
+		 * {@link #addEdit(Edit)} and {@link #addRedo(Edit)}.
+		 *
+		 * @see #addEdit(Edit)
+		 * @see #addRedo(Edit)
+		 * @param edit
+		 *            The {@link Edit} to add
+		 * @param isRedo
+		 *            True if this is a redo
+		 */
+		protected void addEditOrRedo(Edit<?> edit, boolean isRedo) {
+			edits.add(edit);
+			if (undoes.isEmpty())
+				return;
+			if (isRedo) {
+				// It's a redo, remove only the last one
+				int lastUndoIndex = undoes.size() - 1;
+				Edit<?> lastUndo = undoes.get(lastUndoIndex);
+				if (!edit.equals(lastUndo))
+					throw new IllegalArgumentException(
+							"Can only redo last undo");
+				undoes.remove(lastUndoIndex);
+			} else
+				// It's a new edit, remove all redos
+				undoes.clear();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
new file mode 100644
index 0000000..97d14a6
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/AbstractUndoAction.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * Copyright (C) 2012 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static java.awt.Toolkit.getDefaultToolkit;
+import static java.awt.event.KeyEvent.VK_Y;
+import static java.awt.event.KeyEvent.VK_Z;
+import static javax.swing.KeyStroke.getKeyStroke;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.redoIcon;
+import static net.sf.taverna.t2.workbench.icons.WorkbenchIcons.undoIcon;
+
+import java.awt.event.ActionEvent;
+
+import javax.swing.AbstractAction;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.lang.observer.SwingAwareObserver;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.AbstractDataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+import net.sf.taverna.t2.workbench.selection.events.PerspectiveSelectionEvent;
+import net.sf.taverna.t2.workbench.selection.events.SelectionManagerEvent;
+import net.sf.taverna.t2.workbench.selection.events.WorkflowBundleSelectionEvent;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public abstract class AbstractUndoAction extends AbstractAction {
+	protected EditManager editManager;
+	private SelectionManager selectionManager;
+
+	public AbstractUndoAction(String label, EditManager editManager) {
+		super(label);
+		this.editManager = editManager;
+		if (label.equals("Undo")) {
+			this.putValue(SMALL_ICON, undoIcon);
+			this.putValue(SHORT_DESCRIPTION, "Undo an action");
+			putValue(
+					ACCELERATOR_KEY,
+					getKeyStroke(VK_Z, getDefaultToolkit()
+							.getMenuShortcutKeyMask()));
+		} else if (label.equals("Redo")) {
+			this.putValue(SMALL_ICON, redoIcon);
+			this.putValue(SHORT_DESCRIPTION, "Redo an action");
+			putValue(
+					ACCELERATOR_KEY,
+					getKeyStroke(VK_Y, getDefaultToolkit()
+							.getMenuShortcutKeyMask()));
+		}
+		editManager.addObserver(new EditManagerObserver());
+		updateStatus();
+	}
+
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		WorkflowBundle workflowBundle = getCurrentDataflow();
+		if (workflowBundle != null)
+			performUndoOrRedo(workflowBundle);
+	}
+
+	/**
+	 * Check if action should be enabled or disabled and update its status.
+	 */
+	public void updateStatus() {
+		WorkflowBundle workflowBundle = getCurrentDataflow();
+		if (workflowBundle == null)
+			setEnabled(false);
+		setEnabled(isActive(workflowBundle));
+	}
+
+	/**
+	 * Retrieve the current dataflow from the {@link ModelMap}, or
+	 * <code>null</code> if no workflow is active.
+	 * 
+	 * @return The current {@link Dataflow}
+	 */
+	protected WorkflowBundle getCurrentDataflow() {
+		if (selectionManager == null)
+			return null;
+		return selectionManager.getSelectedWorkflowBundle();
+	}
+
+	/**
+	 * Return <code>true</code> if the action should be enabled when the given
+	 * {@link Dataflow} is the current, ie. if it's undoable or redoable.
+	 * 
+	 * @param dataflow
+	 *            Current {@link Dataflow}
+	 * @return <code>true</code> if the action should be enabled.
+	 */
+	protected abstract boolean isActive(WorkflowBundle workflowBundle);
+
+	/**
+	 * Called by {@link #actionPerformed(ActionEvent)} when the current dataflow
+	 * is not <code>null</code>.
+	 * 
+	 * @param dataflow
+	 *            {@link Dataflow} on which to undo or redo
+	 */
+	protected abstract void performUndoOrRedo(WorkflowBundle workflowBundle);
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (selectionManager != null)
+			selectionManager.addObserver(new SelectionManagerObserver());
+	}
+
+	/**
+	 * Update the status if there's been an edit done on the current workflow.
+	 * 
+	 */
+	protected class EditManagerObserver implements Observer<EditManagerEvent> {
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			if (!(message instanceof AbstractDataflowEditEvent))
+				return;
+			AbstractDataflowEditEvent dataflowEdit = (AbstractDataflowEditEvent) message;
+			if (dataflowEdit.getDataFlow().equals(dataflowEdit.getDataFlow()))
+				// It's an edit that could effect our undoability
+				updateStatus();
+		}
+	}
+
+	private final class SelectionManagerObserver extends
+			SwingAwareObserver<SelectionManagerEvent> {
+		private static final String DESIGN_PERSPECTIVE_ID = "net.sf.taverna.t2.ui.perspectives.design.DesignPerspective";
+
+		@Override
+		public void notifySwing(Observable<SelectionManagerEvent> sender,
+				SelectionManagerEvent message) {
+			if (message instanceof WorkflowBundleSelectionEvent)
+				updateStatus();
+			else if (message instanceof PerspectiveSelectionEvent) {
+				PerspectiveSelectionEvent perspectiveSelectionEvent = (PerspectiveSelectionEvent) message;
+				if (DESIGN_PERSPECTIVE_ID.equals(perspectiveSelectionEvent
+						.getSelectedPerspective().getID()))
+					updateStatus();
+				else
+					setEnabled(false);
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
new file mode 100644
index 0000000..2abc139
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/RedoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditException;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Redo the previous {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class RedoMenuAction extends AbstractMenuAction {
+	private static Logger logger = Logger.getLogger(RedoMenuAction.class);
+	private final EditManager editManager;
+	private SelectionManager selectionManager;
+	private AbstractUndoAction undoAction;
+
+	public RedoMenuAction(EditManager editManager) {
+		super(UNDO_SECTION_URI, 20);
+		this.editManager = editManager;
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		undoAction = new AbstractUndoAction("Redo", editManager) {
+			@Override
+			protected boolean isActive(WorkflowBundle workflowBundle) {
+				return editManager.canRedoDataflowEdit(workflowBundle);
+			}
+
+			@Override
+			protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+				try {
+					editManager.redoDataflowEdit(workflowBundle);
+				} catch (EditException | RuntimeException e) {
+					logger.warn("Could not redo for " + workflowBundle, e);
+					showMessageDialog(null, "Could not redo for workflow "
+							+ workflowBundle + ":\n" + e, "Could not redo",
+							ERROR_MESSAGE);
+				}
+			}
+		};
+		undoAction.setSelectionManager(selectionManager);
+		return undoAction;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (undoAction != null)
+			undoAction.setSelectionManager(selectionManager);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
new file mode 100644
index 0000000..e1242b3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuAction.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.showMessageDialog;
+import static net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection.UNDO_SECTION_URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.selection.SelectionManager;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Undo the last {@link Edit} done on the current workflow using the
+ * {@link EditManager}.
+ * 
+ * @author Stian Soiland-Reyes
+ * 
+ */
+public class UndoMenuAction extends AbstractMenuAction {
+	private static Logger logger = Logger.getLogger(UndoMenuAction.class);
+	private final EditManager editManager;
+	private SelectionManager selectionManager;
+	private AbstractUndoAction undoAction;
+
+	public UndoMenuAction(EditManager editManager) {
+		super(UNDO_SECTION_URI, 10);
+		this.editManager = editManager;
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected Action createAction() {
+		undoAction = new AbstractUndoAction("Undo", editManager) {
+			@Override
+			protected boolean isActive(WorkflowBundle workflowBundle) {
+				return editManager.canUndoDataflowEdit(workflowBundle);
+			}
+
+			@Override
+			protected void performUndoOrRedo(WorkflowBundle workflowBundle) {
+				try {
+					editManager.undoDataflowEdit(workflowBundle);
+				} catch (RuntimeException e) {
+					logger.warn("Could not undo for " + workflowBundle, e);
+					showMessageDialog(null, "Could not undo for workflow "
+							+ workflowBundle + ":\n" + e, "Could not undo",
+							ERROR_MESSAGE);
+				}
+			}
+		};
+		undoAction.setSelectionManager(selectionManager);
+		return undoAction;
+	}
+
+	public void setSelectionManager(SelectionManager selectionManager) {
+		this.selectionManager = selectionManager;
+		if (undoAction != null)
+			undoAction.setSelectionManager(selectionManager);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
new file mode 100644
index 0000000..b83a650
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/menu/UndoMenuSection.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.menu;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+/**
+ * A section of the Edit menu that contains {@link UndoMenuSection undo} and
+ * {@link RedoMenuAction redo}.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class UndoMenuSection extends AbstractMenuSection {
+	public static final URI UNDO_SECTION_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/edits#undoSection");
+	public static final URI EDIT_MENU_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#edit");
+
+	public UndoMenuSection() {
+		super(EDIT_MENU_URI, 10, UNDO_SECTION_URI);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
new file mode 100644
index 0000000..9eea85a
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/EditToolbarSection.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.ui.menu.DefaultToolBar.DEFAULT_TOOL_BAR;
+
+import java.net.URI;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuSection;
+
+public class EditToolbarSection extends AbstractMenuSection {
+	public static final URI EDIT_TOOLBAR_SECTION = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarSection");
+
+	public EditToolbarSection() {
+		super(DEFAULT_TOOL_BAR, 60, EDIT_TOOLBAR_SECTION);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
new file mode 100644
index 0000000..09c0058
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/RedoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction;
+
+public class RedoToolbarAction extends AbstractMenuAction {
+	private static final URI EDIT_TOOLBAR_REDO_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarRedo");
+	private final RedoMenuAction redoMenuAction;
+
+	public RedoToolbarAction(RedoMenuAction redoMenuAction) {
+		super(EDIT_TOOLBAR_SECTION, 20, EDIT_TOOLBAR_REDO_URI);
+		this.redoMenuAction = redoMenuAction;
+	}
+
+	@Override
+	protected Action createAction() {
+		return redoMenuAction.getAction();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
new file mode 100644
index 0000000..8e31ed3
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/java/net/sf/taverna/t2/workbench/edits/impl/toolbar/UndoToolbarAction.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl.toolbar;
+
+import static net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection.EDIT_TOOLBAR_SECTION;
+
+import java.net.URI;
+
+import javax.swing.Action;
+
+import net.sf.taverna.t2.ui.menu.AbstractMenuAction;
+import net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction;
+
+public class UndoToolbarAction extends AbstractMenuAction {
+	private static final URI EDIT_TOOLBAR_UNDO_URI = URI
+			.create("http://taverna.sf.net/2008/t2workbench/menu#editToolbarUndo");
+	private final UndoMenuAction undoMenuAction;
+
+	public UndoToolbarAction(UndoMenuAction undoMenuAction) {
+		super(EDIT_TOOLBAR_SECTION, 10, EDIT_TOOLBAR_UNDO_URI);
+		this.undoMenuAction = undoMenuAction;
+	}
+
+	@Override
+	protected Action createAction() {
+		return undoMenuAction.getAction();
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
new file mode 100644
index 0000000..6938308
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.ui.menu.MenuComponent
@@ -0,0 +1,6 @@
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection
+net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection
+net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction
+net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
new file mode 100644
index 0000000..92ee088
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/services/net.sf.taverna.t2.workbench.edits.EditManager
@@ -0,0 +1 @@
+net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
new file mode 100644
index 0000000..8eb7041
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context-osgi.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:beans="http://www.springframework.org/schema/beans"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd
+                      http://www.springframework.org/schema/osgi
+                      http://www.springframework.org/schema/osgi/spring-osgi.xsd">
+
+	<service ref="UndoMenuSection" auto-export="interfaces" />
+	<service ref="UndoMenuAction" auto-export="interfaces" />
+	<service ref="RedoMenuAction" auto-export="interfaces" />
+	<service ref="EditToolbarSection" auto-export="interfaces" />
+	<service ref="UndoToolbarAction" auto-export="interfaces" />
+	<service ref="RedoToolbarAction" auto-export="interfaces" />
+
+	<service ref="EditManagerImpl" interface="net.sf.taverna.t2.workbench.edits.EditManager" />
+
+	<reference id="selectionManager" interface="net.sf.taverna.t2.workbench.selection.SelectionManager" cardinality="0..1" />
+
+</beans:beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
new file mode 100644
index 0000000..33f0b7b
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/main/resources/META-INF/spring/edits-impl-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<bean id="UndoMenuSection" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuSection" />
+	<bean id="UndoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.UndoMenuAction">
+		<constructor-arg name="editManager">
+			<ref local="EditManagerImpl" />
+		</constructor-arg>
+		<property name="selectionManager" ref="selectionManager" />
+	</bean>
+	<bean id="RedoMenuAction" class="net.sf.taverna.t2.workbench.edits.impl.menu.RedoMenuAction">
+		<constructor-arg name="editManager">
+			<ref local="EditManagerImpl" />
+		</constructor-arg>
+		<property name="selectionManager" ref="selectionManager" />
+	</bean>
+	<bean id="EditToolbarSection" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.EditToolbarSection" />
+	<bean id="UndoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.UndoToolbarAction">
+		<constructor-arg>
+			<ref local="UndoMenuAction" />
+		</constructor-arg>
+	</bean>
+	<bean id="RedoToolbarAction" class="net.sf.taverna.t2.workbench.edits.impl.toolbar.RedoToolbarAction">
+		<constructor-arg>
+			<ref local="RedoMenuAction" />
+		</constructor-arg>
+	</bean>
+
+	<bean id="EditManagerImpl" class="net.sf.taverna.t2.workbench.edits.impl.EditManagerImpl" />
+
+</beans>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
new file mode 100644
index 0000000..9123671
--- /dev/null
+++ b/taverna-workbench-edits-impl/src/test/java/net/sf/taverna/t2/workbench/edits/impl/TestEditManagerImpl.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.edits.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.sf.taverna.t2.lang.observer.Observable;
+import net.sf.taverna.t2.lang.observer.Observer;
+import net.sf.taverna.t2.workbench.edits.Edit;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowRedoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataFlowUndoEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.DataflowEditEvent;
+import net.sf.taverna.t2.workbench.edits.EditManager.EditManagerEvent;
+import net.sf.taverna.t2.workflow.edits.AddProcessorEdit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Processor;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+public class TestEditManagerImpl {
+
+	private Workflow dataflow;
+
+	private EditManagerObserver editManagerObserver = new EditManagerObserver();
+
+	private Processor processor;
+
+	@Test
+	public void addProcessor() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		assertFalse("Edit was already applied", edit.isApplied());
+		assertTrue("Did already add processor", dataflow.getProcessors()
+				.isEmpty());
+
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+		assertTrue("Edit was not applied", edit.isApplied());
+		assertEquals("Did not add processor", processor, dataflow.getProcessors().first());
+
+		// Should have received the edit event
+		assertEquals("Incorrect number of events", 1,
+				editManagerObserver.events.size());
+		EditManagerEvent event = editManagerObserver.events.get(0);
+		assertTrue("Event was not a DataflowEditEvent",
+				event instanceof DataflowEditEvent);
+		DataflowEditEvent dataEditEvent = (DataflowEditEvent) event;
+		assertEquals("Event did not have correct workflow", dataflow,
+				dataEditEvent.getDataFlow().getWorkflows().first());
+		assertEquals("Event did not have correct edit", edit, dataEditEvent
+				.getEdit());
+
+	}
+
+	@Test
+	public void undoAddProcessor() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+		assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+		assertTrue("Did not undo add processor", dataflow.getProcessors()
+				.isEmpty());
+
+		// Should have received the undo event
+		assertEquals("Incorrect number of events", 2,
+				editManagerObserver.events.size());
+		EditManagerEvent event = editManagerObserver.events.get(1);
+		assertTrue("Event was not a DataflowEditEvent",
+				event instanceof DataFlowUndoEvent);
+		DataFlowUndoEvent dataEditEvent = (DataFlowUndoEvent) event;
+		assertEquals("Event did not have correct workflow", dataflow,
+				dataEditEvent.getDataFlow().getWorkflows().first());
+		assertEquals("Event did not have correct edit", edit, dataEditEvent
+				.getEdit());
+		assertFalse("Edit was still applied", edit.isApplied());
+	}
+
+	@Test
+	public void multipleUndoesRedoes() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Workflow dataflowA = createDataflow();
+		Workflow dataflowB = createDataflow();
+		Workflow dataflowC = createDataflow();
+
+		Processor processorA1 = createProcessor();
+		Processor processorA2 = createProcessor();
+		Processor processorA3 = createProcessor();
+		Processor processorB1 = createProcessor();
+		Processor processorC1 = createProcessor();
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflowA, processorA1);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowB, processorB1);
+		editManager.doDataflowEdit(dataflowB.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowA, processorA2);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowC, processorC1);
+		editManager.doDataflowEdit(dataflowC.getParent(), edit);
+
+		edit = new AddProcessorEdit(dataflowA, processorA3);
+		editManager.doDataflowEdit(dataflowA.getParent(), edit);
+
+
+
+		assertFalse("Did not add processors", dataflowA.getProcessors().isEmpty());
+		assertEquals(3, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(2, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(1, dataflowA.getProcessors().size());
+		editManager.undoDataflowEdit(dataflowA.getParent());
+		assertEquals(0, dataflowA.getProcessors().size());
+
+		assertEquals(1, dataflowB.getProcessors().size());
+		assertEquals(1, dataflowC.getProcessors().size());
+
+		assertTrue(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+		editManager.undoDataflowEdit(dataflowC.getParent());
+		assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+		editManager.undoDataflowEdit(dataflowC.getParent()); // extra one
+		assertFalse(editManager.canUndoDataflowEdit(dataflowC.getParent()));
+
+
+		assertEquals(1, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+
+		editManager.undoDataflowEdit(dataflowB.getParent());
+		assertEquals(0, dataflowA.getProcessors().size());
+		assertEquals(0, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(1, dataflowA.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(2, dataflowA.getProcessors().size());
+
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(3, dataflowA.getProcessors().size());
+
+		// does not affect it
+		editManager.redoDataflowEdit(dataflowA.getParent());
+		assertEquals(3, dataflowA.getProcessors().size());
+		assertEquals(0, dataflowB.getProcessors().size());
+		assertEquals(0, dataflowC.getProcessors().size());
+	}
+
+	@Test
+	public void emptyUndoDoesNotFail() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+		editManager.undoDataflowEdit(dataflow.getParent());
+	}
+
+	@Test
+	public void extraUndoesDoesNotFail() throws Exception {
+		EditManager editManager = new EditManagerImpl();
+		editManager.addObserver(editManagerObserver);
+
+		Edit<Workflow> edit = new AddProcessorEdit(dataflow, processor);
+		editManager.doDataflowEdit(dataflow.getParent(), edit);
+
+		assertFalse("Did not add processor", dataflow.getProcessors().isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+		assertTrue("Did not undo add processor", dataflow.getProcessors()
+				.isEmpty());
+		editManager.undoDataflowEdit(dataflow.getParent());
+	}
+
+	@Before
+	public void makeDataflow() {
+		dataflow = createDataflow();
+	}
+
+	protected Workflow createDataflow() {
+		WorkflowBundle workflowBundle = new WorkflowBundle();
+		Workflow workflow = new Workflow();
+		workflow.setParent(workflowBundle);
+		return workflow;
+	}
+
+	protected Processor createProcessor() {
+		Processor processor = new Processor();
+		processor.setName("proc-" + UUID.randomUUID());
+		return processor;
+	}
+
+	@Before
+	public void makeProcessor() {
+		processor = createProcessor();
+	}
+
+	private class EditManagerObserver implements Observer<EditManagerEvent> {
+
+		public List<EditManagerEvent> events = new ArrayList<>();
+
+		@Override
+		public void notify(Observable<EditManagerEvent> sender,
+				EditManagerEvent message) throws Exception {
+			events.add(message);
+			if (message instanceof DataflowEditEvent) {
+				DataflowEditEvent dataflowEdit = (DataflowEditEvent) message;
+				assertTrue("Edit was not applied on edit event", dataflowEdit
+						.getEdit().isApplied());
+			} else if (message instanceof DataFlowUndoEvent) {
+				DataFlowUndoEvent dataflowUndo = (DataFlowUndoEvent) message;
+				assertFalse("Edit was applied on undo event", dataflowUndo
+						.getEdit().isApplied());
+			} else if (message instanceof DataFlowRedoEvent) {
+				DataFlowRedoEvent dataflowEdit = (DataFlowRedoEvent) message;
+				assertTrue("Edit was not applied on edit event", dataflowEdit
+						.getEdit().isApplied());
+			} else {
+				fail("Unknown event: " + message);
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-file-impl/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-workbench-file-impl/pom.xml b/taverna-workbench-file-impl/pom.xml
new file mode 100644
index 0000000..98dcce7
--- /dev/null
+++ b/taverna-workbench-file-impl/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>ui-impl</artifactId>
+		<version>2.0-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.ui-impl</groupId>
+	<artifactId>file-impl</artifactId>
+	<packaging>bundle</packaging>
+	<name>File opening implementation</name>
+	<description>
+		Implementation for doing file (i.e. workflow) open/save in the
+		workbench.
+	</description>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>file-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>edits-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>helper-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>menu-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-api</groupId>
+			<artifactId>workbench-api</artifactId>
+			<version>${t2.ui.api.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>observer</artifactId>
+			<version>${t2.lang.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.configuration</groupId>
+			<artifactId>taverna-app-configuration-api</artifactId>
+			<version>${taverna.configuration.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-api</artifactId>
+			<version>${scufl2.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>com.springsource.org.apache.commons.lang</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jdom</groupId>
+			<artifactId>com.springsource.org.jdom</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.taverna.t2.ui-impl</groupId>
+			<artifactId>edits-impl</artifactId>
+			<version>${project.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-t2flow</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>uk.org.taverna.scufl2</groupId>
+			<artifactId>scufl2-rdfxml</artifactId>
+			<version>${scufl2.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
new file mode 100644
index 0000000..86bc091
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowFromDataflowPersistenceHandler.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.taverna.t2.workbench.file.AbstractDataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+import net.sf.taverna.t2.workbench.file.exceptions.OpenException;
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+import uk.org.taverna.scufl2.api.core.Workflow;
+
+/**
+ * @author alanrw
+ */
+public class DataflowFromDataflowPersistenceHandler extends
+		AbstractDataflowPersistenceHandler implements
+		DataflowPersistenceHandler {
+	private static final WorkflowBundleFileType WORKFLOW_BUNDLE_FILE_TYPE = new WorkflowBundleFileType();
+
+	@Override
+	public DataflowInfo openDataflow(FileType fileType, Object source)
+			throws OpenException {
+		if (!getOpenFileTypes().contains(fileType))
+			throw new IllegalArgumentException("Unsupported file type "
+					+ fileType);
+
+		WorkflowBundle workflowBundle = (WorkflowBundle) source;
+		Date lastModified = null;
+		Object canonicalSource = null;
+		return new DataflowInfo(WORKFLOW_BUNDLE_FILE_TYPE, canonicalSource,
+				workflowBundle, lastModified);
+	}
+
+	@Override
+	public List<FileType> getOpenFileTypes() {
+		return Arrays.<FileType> asList(WORKFLOW_BUNDLE_FILE_TYPE);
+	}
+
+	@Override
+	public List<Class<?>> getOpenSourceTypes() {
+		return Arrays.<Class<?>> asList(Workflow.class);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
new file mode 100644
index 0000000..39117e9
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/DataflowPersistenceHandlerRegistry.java
@@ -0,0 +1,238 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import static org.apache.commons.collections.map.LazyMap.decorate;
+import static org.apache.commons.lang.ClassUtils.getAllInterfaces;
+import static org.apache.commons.lang.ClassUtils.getAllSuperclasses;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sf.taverna.t2.workbench.file.DataflowPersistenceHandler;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.commons.collections.Factory;
+
+// TODO: Cache lookups / build one massive structure
+public class DataflowPersistenceHandlerRegistry {
+	private static final MapFactory MAP_FACTORY = new MapFactory();
+	private static final SetFactory SET_FACTORY = new SetFactory();
+
+	@SuppressWarnings("unchecked")
+	protected static List<Class<?>> findAllParentClasses(
+			final Class<?> sourceClass) {
+		List<Class<?>> superClasses = new ArrayList<>();
+		superClasses.add(sourceClass);
+		superClasses.addAll(getAllSuperclasses(sourceClass));
+		superClasses.addAll(getAllInterfaces(sourceClass));
+		return superClasses;
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> openClassToHandlers;
+	private Map<Class<?>, Set<FileType>> openClassToTypes;
+	private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> openFileClassToHandler;
+	private Map<FileType, Set<DataflowPersistenceHandler>> openFileToHandler;
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> saveClassToHandlers;
+	private Map<Class<?>, Set<FileType>> saveClassToTypes;
+	private Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> saveFileClassToHandler;
+	private Map<FileType, Set<DataflowPersistenceHandler>> saveFileToHandler;
+
+	private List<DataflowPersistenceHandler> dataflowPersistenceHandlers;
+
+	public DataflowPersistenceHandlerRegistry() {
+	}
+
+	public Set<FileType> getOpenFileTypes() {
+		return getOpenFileClassToHandler().keySet();
+	}
+
+	public Set<FileType> getOpenFileTypesFor(Class<?> sourceClass) {
+		Set<FileType> fileTypes = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			fileTypes.addAll(getOpenClassToTypes().get(candidateClass));
+		return fileTypes;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+			Class<? extends Object> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenClassToHandlers().get(candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersFor(
+			FileType fileType, Class<? extends Object> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getOpenHandlersForType(
+			FileType fileType) {
+		return getOpenFileToHandler().get(fileType);
+	}
+
+	public synchronized Set<DataflowPersistenceHandler> getOpenHandlersForType(
+			FileType fileType, Class<?> sourceClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(sourceClass))
+			handlers.addAll(getOpenFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	public Set<FileType> getSaveFileTypes() {
+		return getSaveFileClassToHandler().keySet();
+	}
+
+	public Set<FileType> getSaveFileTypesFor(Class<?> destinationClass) {
+		Set<FileType> fileTypes = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			fileTypes.addAll(getSaveClassToTypes().get(candidateClass));
+		return fileTypes;
+	}
+
+	public Set<DataflowPersistenceHandler> getSaveHandlersFor(
+			Class<? extends Object> destinationClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			handlers.addAll(getSaveClassToHandlers().get(candidateClass));
+		return handlers;
+	}
+
+	public Set<DataflowPersistenceHandler> getSaveHandlersForType(
+			FileType fileType, Class<?> destinationClass) {
+		Set<DataflowPersistenceHandler> handlers = new LinkedHashSet<>();
+		for (Class<?> candidateClass : findAllParentClasses(destinationClass))
+			handlers.addAll(getSaveFileClassToHandler().get(fileType).get(
+					candidateClass));
+		return handlers;
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	private synchronized void createCollections() {
+		openFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+		openFileToHandler = decorate(new HashMap(), SET_FACTORY);
+		openClassToTypes = decorate(new HashMap(), SET_FACTORY);
+		openClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+
+		saveFileClassToHandler = decorate(new HashMap(), MAP_FACTORY);
+		saveFileToHandler = decorate(new HashMap(), SET_FACTORY);
+		saveClassToTypes = decorate(new HashMap(), SET_FACTORY);
+		saveClassToHandlers = decorate(new HashMap(), SET_FACTORY);
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> getOpenClassToHandlers() {
+		return openClassToHandlers;
+	}
+
+	private synchronized Map<Class<?>, Set<FileType>> getOpenClassToTypes() {
+		return openClassToTypes;
+	}
+
+	private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getOpenFileClassToHandler() {
+		return openFileClassToHandler;
+	}
+
+	private Map<FileType, Set<DataflowPersistenceHandler>> getOpenFileToHandler() {
+		return openFileToHandler;
+	}
+
+	private Map<Class<?>, Set<DataflowPersistenceHandler>> getSaveClassToHandlers() {
+		return saveClassToHandlers;
+	}
+
+	private synchronized Map<Class<?>, Set<FileType>> getSaveClassToTypes() {
+		return saveClassToTypes;
+	}
+
+	private synchronized Map<FileType, Map<Class<?>, Set<DataflowPersistenceHandler>>> getSaveFileClassToHandler() {
+		return saveFileClassToHandler;
+	}
+
+	/**
+	 * Bind method for SpringDM.
+	 * 
+	 * @param service
+	 * @param properties
+	 */
+	public void update(Object service, Map<?, ?> properties) {
+		if (dataflowPersistenceHandlers != null)
+			updateColletions();
+	}
+
+	public synchronized void updateColletions() {
+		createCollections();
+		for (DataflowPersistenceHandler handler : dataflowPersistenceHandlers) {
+			for (FileType openFileType : handler.getOpenFileTypes()) {
+				Set<DataflowPersistenceHandler> set = openFileToHandler
+						.get(openFileType);
+				set.add(handler);
+				for (Class<?> openClass : handler.getOpenSourceTypes()) {
+					openFileClassToHandler.get(openFileType).get(openClass)
+							.add(handler);
+					openClassToTypes.get(openClass).add(openFileType);
+				}
+			}
+			for (Class<?> openClass : handler.getOpenSourceTypes())
+				openClassToHandlers.get(openClass).add(handler);
+
+			for (FileType saveFileType : handler.getSaveFileTypes()) {
+				saveFileToHandler.get(saveFileType).add(handler);
+				for (Class<?> saveClass : handler.getSaveDestinationTypes()) {
+					saveFileClassToHandler.get(saveFileType).get(saveClass)
+							.add(handler);
+					saveClassToTypes.get(saveClass).add(saveFileType);
+				}
+			}
+			for (Class<?> openClass : handler.getSaveDestinationTypes())
+				saveClassToHandlers.get(openClass).add(handler);
+		}
+	}
+
+	public void setDataflowPersistenceHandlers(
+			List<DataflowPersistenceHandler> dataflowPersistenceHandlers) {
+		this.dataflowPersistenceHandlers = dataflowPersistenceHandlers;
+	}
+
+	private static class MapFactory implements Factory {
+		@Override
+		@SuppressWarnings("rawtypes")
+		public Object create() {
+			return decorate(new HashMap(), SET_FACTORY);
+		}
+	}
+
+	private static class SetFactory implements Factory {
+		@Override
+		public Object create() {
+			return new LinkedHashSet<Object>();
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/72850d5a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
----------------------------------------------------------------------
diff --git a/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
new file mode 100644
index 0000000..89ae39c
--- /dev/null
+++ b/taverna-workbench-file-impl/src/main/java/net/sf/taverna/t2/workbench/file/impl/FileDataflowInfo.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.workbench.file.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import net.sf.taverna.t2.workbench.file.DataflowInfo;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.file.FileType;
+
+import org.apache.log4j.Logger;
+
+import uk.org.taverna.scufl2.api.container.WorkflowBundle;
+
+/**
+ * Information about an open dataflow that was opened from or saved to a
+ * {@link File}.
+ * 
+ * @see DataflowInfo
+ * @see FileManager
+ * @author Stian Soiland-Reyes
+ */
+public class FileDataflowInfo extends DataflowInfo {
+	private static Logger logger = Logger.getLogger(FileDataflowInfo.class);
+
+	public FileDataflowInfo(FileType fileType, File source,
+			WorkflowBundle workflowBundle) {
+		super(fileType, canonicalFile(source), workflowBundle,
+				lastModifiedFile(source));
+	}
+
+	protected static Date lastModifiedFile(File file) {
+		long lastModifiedLong = file.lastModified();
+		if (lastModifiedLong == 0)
+			return null;
+		return new Date(lastModifiedLong);
+	}
+
+	public static File canonicalFile(File file) {
+		try {
+			return file.getCanonicalFile();
+		} catch (IOException e) {
+			logger.warn("Could not find canonical file for " + file);
+			return file;
+		}
+	}
+}