From b7b9ee7c1327a2561b4ba28d3d12c0a4dbf06cc7 Mon Sep 17 00:00:00 2001 From: Stephane Dallongeville <stephane.dallongeville@pasteur.fr> Date: Thu, 3 Dec 2020 18:35:10 +0100 Subject: [PATCH] Merged Protocols SDK and Protocols plugins as they were inter dependant --- .../adufour/blocks/lang/ArrayLoop.java | 9 + .../plugins/adufour/blocks/lang/Batch.java | 155 ++ .../plugins/adufour/blocks/lang/Block.java | 33 + .../adufour/blocks/lang/BlockDescriptor.java | 1121 +++++++++++ .../adufour/blocks/lang/FileBatch.java | 130 ++ .../adufour/blocks/lang/FolderLoop.java | 9 + .../plugins/adufour/blocks/lang/Link.java | 126 ++ .../plugins/adufour/blocks/lang/Loop.java | 278 +++ .../adufour/blocks/lang/RangeLoop.java | 98 + .../blocks/lang/SequenceFileBatch.java | 56 + .../blocks/lang/SequenceSeriesBatch.java | 110 ++ .../plugins/adufour/blocks/lang/WorkFlow.java | 1175 +++++++++++ .../adufour/blocks/tools/Accumulator.java | 142 ++ .../plugins/adufour/blocks/tools/Display.java | 52 + .../plugins/adufour/blocks/tools/Indexer.java | 61 + .../adufour/blocks/tools/Iterator.java | 68 + .../adufour/blocks/tools/ListSize.java | 31 + .../plugins/adufour/blocks/tools/ReLoop.java | 68 + .../adufour/blocks/tools/ToolsBlock.java | 8 + .../adufour/blocks/tools/ij/CallIJMacro.java | 30 + .../adufour/blocks/tools/ij/CallIJPlugin.java | 61 + .../adufour/blocks/tools/ij/IJBlock.java | 13 + .../blocks/tools/ij/ImagePlusToSequence.java | 32 + .../blocks/tools/ij/SequenceToImagePlus.java | 32 + .../blocks/tools/ij/ShowImagePlus.java | 28 + .../adufour/blocks/tools/input/Boolean.java | 24 + .../adufour/blocks/tools/input/Decimal.java | 29 + .../adufour/blocks/tools/input/Decimals.java | 29 + .../adufour/blocks/tools/input/File.java | 34 + .../adufour/blocks/tools/input/Files.java | 34 + .../adufour/blocks/tools/input/Folder.java | 34 + .../adufour/blocks/tools/input/Folders.java | 34 + .../blocks/tools/input/InputBlock.java | 13 + .../adufour/blocks/tools/input/Integer.java | 30 + .../adufour/blocks/tools/input/Integers.java | 29 + .../adufour/blocks/tools/input/Sequence.java | 36 + .../adufour/blocks/tools/input/Sequences.java | 30 + .../adufour/blocks/tools/input/Text.java | 31 + .../blocks/tools/io/AppendFilePath.java | 58 + .../adufour/blocks/tools/io/CreateFile.java | 62 + .../adufour/blocks/tools/io/CreateFolder.java | 57 + .../adufour/blocks/tools/io/FileToPath.java | 31 + .../blocks/tools/io/FileToSequence.java | 56 + .../blocks/tools/io/FilesToSequence.java | 57 + .../blocks/tools/io/GetSequenceFolder.java | 40 + .../adufour/blocks/tools/io/IOBlock.java | 14 + .../adufour/blocks/tools/io/PathToFile.java | 34 + .../blocks/tools/io/SendToSwimmingPool.java | 47 + .../blocks/tools/io/SequenceToFile.java | 47 + .../blocks/tools/roi/AddROIToSequence.java | 56 + .../blocks/tools/roi/CropSequenceToROI.java | 114 ++ .../adufour/blocks/tools/roi/DilateROI.java | 265 +++ .../adufour/blocks/tools/roi/ErodeROI.java | 259 +++ .../blocks/tools/roi/GetROIFromSequence.java | 72 + .../adufour/blocks/tools/roi/MergeROI.java | 47 + .../adufour/blocks/tools/roi/MorphROI.java | 115 ++ .../adufour/blocks/tools/roi/ROIBlock.java | 14 + .../adufour/blocks/tools/roi/SubtractROI.java | 66 + .../blocks/tools/roi/TranslateROI.java | 83 + .../blocks/tools/sequence/SequenceBlock.java | 14 + .../tools/sequence/SequenceScreenshot.java | 67 + .../blocks/tools/sequence/ShowSequence.java | 35 + .../adufour/blocks/tools/text/AppendText.java | 53 + .../adufour/blocks/util/BlockAnnotations.java | 232 +++ .../adufour/blocks/util/BlockInfo.java | 23 + .../adufour/blocks/util/BlockListener.java | 20 + .../adufour/blocks/util/BlocksException.java | 29 + .../adufour/blocks/util/BlocksFinder.java | 752 +++++++ .../plugins/adufour/blocks/util/BlocksML.java | 1753 +++++++++++++++++ .../blocks/util/BlocksReloadedException.java | 10 + .../adufour/blocks/util/LinkCutException.java | 10 + .../adufour/blocks/util/LoopException.java | 18 + .../adufour/blocks/util/MenuItemListener.java | 7 + .../blocks/util/NoSuchBlockException.java | 25 + .../blocks/util/NoSuchLinkException.java | 12 + .../blocks/util/NoSuchVariableException.java | 24 + .../adufour/blocks/util/ScopeException.java | 13 + .../adufour/blocks/util/StopException.java | 10 + .../plugins/adufour/blocks/util/VarList.java | 334 ++++ .../adufour/blocks/util/VarListListener.java | 10 + .../blocks/util/VarVisibilityListener.java | 8 + .../adufour/blocks/util/WorkFlowListener.java | 85 + .../plugins/adufour/protocols/Protocols.java | 26 +- .../adufour/protocols/gui/MainFrame.java | 3 +- .../logo/icysoftware_icon-protocols-sdk.png | Bin 0 -> 6683 bytes 85 files changed, 9469 insertions(+), 11 deletions(-) create mode 100644 src/main/java/plugins/adufour/blocks/lang/ArrayLoop.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/Batch.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/Block.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/BlockDescriptor.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/FileBatch.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/FolderLoop.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/Link.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/Loop.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/RangeLoop.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/SequenceFileBatch.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/SequenceSeriesBatch.java create mode 100644 src/main/java/plugins/adufour/blocks/lang/WorkFlow.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/Accumulator.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/Display.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/Indexer.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/Iterator.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ListSize.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ReLoop.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ToolsBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/CallIJMacro.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/CallIJPlugin.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/IJBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/ImagePlusToSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/SequenceToImagePlus.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/ij/ShowImagePlus.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Boolean.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Decimal.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Decimals.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/File.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Files.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Folder.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Folders.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/InputBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Integer.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Integers.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Sequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Sequences.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/input/Text.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/AppendFilePath.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/CreateFile.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/CreateFolder.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/FileToPath.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/FileToSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/FilesToSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/GetSequenceFolder.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/IOBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/PathToFile.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/SendToSwimmingPool.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/io/SequenceToFile.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/AddROIToSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/CropSequenceToROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/DilateROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/ErodeROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/GetROIFromSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/MergeROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/MorphROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/ROIBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/SubtractROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/roi/TranslateROI.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/sequence/SequenceBlock.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/sequence/SequenceScreenshot.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/sequence/ShowSequence.java create mode 100644 src/main/java/plugins/adufour/blocks/tools/text/AppendText.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlockAnnotations.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlockInfo.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlockListener.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlocksException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlocksFinder.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlocksML.java create mode 100644 src/main/java/plugins/adufour/blocks/util/BlocksReloadedException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/LinkCutException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/LoopException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/MenuItemListener.java create mode 100644 src/main/java/plugins/adufour/blocks/util/NoSuchBlockException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/NoSuchLinkException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/NoSuchVariableException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/ScopeException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/StopException.java create mode 100644 src/main/java/plugins/adufour/blocks/util/VarList.java create mode 100644 src/main/java/plugins/adufour/blocks/util/VarListListener.java create mode 100644 src/main/java/plugins/adufour/blocks/util/VarVisibilityListener.java create mode 100644 src/main/java/plugins/adufour/blocks/util/WorkFlowListener.java create mode 100644 src/main/resources/logo/icysoftware_icon-protocols-sdk.png diff --git a/src/main/java/plugins/adufour/blocks/lang/ArrayLoop.java b/src/main/java/plugins/adufour/blocks/lang/ArrayLoop.java new file mode 100644 index 0000000..e11ef3e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/ArrayLoop.java @@ -0,0 +1,9 @@ +package plugins.adufour.blocks.lang; + +/** + * @deprecated use {@link Batch} instead. + * @author Alexandre Dufour + */ +@Deprecated +public class ArrayLoop extends Batch +{} \ No newline at end of file diff --git a/src/main/java/plugins/adufour/blocks/lang/Batch.java b/src/main/java/plugins/adufour/blocks/lang/Batch.java new file mode 100644 index 0000000..fa8ac3f --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/Batch.java @@ -0,0 +1,155 @@ +package plugins.adufour.blocks.lang; + +import java.lang.reflect.Array; +import java.util.List; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarArray; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.util.VarException; +import plugins.adufour.vars.util.VarListener; + +/** + * A batch is a particular type of work-flow that will execute repeatedly for every element of a + * list of items, allowing to process all list items with a same work-flow. This is the most generic + * type of batch available, however there are more user-friendly versions available, such as for + * instance a {@link FileBatch File batch} or a {@link SequenceFileBatch Sequence file batch}. + * + * @author Alexandre Dufour + */ +public class Batch extends Loop +{ + private VarMutableArray array; + + private VarMutable element; + + /** + * Defines the variable used to determine the list of objects to batch process.<br> + * By default, this variable is simply a {@link VarArray} containing the elements to process, + * but overriding classes may provide special functionalities. + * + * @see FileBatch#getBatchSource() + * @see SequenceFileBatch#getBatchSource() + * @see SequenceSeriesBatch#getBatchSource() + * @return the variable used to define the contents of the batch + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public Var<?> getBatchSource() + { + if (array == null) + { + array = new VarMutableArray("array", null); + array.addListener(new VarListener() + { + @Override + public void valueChanged(Var source, Object oldValue, Object newValue) + { + } + + @Override + public void referenceChanged(Var source, Var oldReference, Var newReference) + { + if (oldReference == null) + { + if (newReference != null) + { + element.setType(newReference.getType().getComponentType()); + } + } + else if (element.isReferenced()) + { + if (newReference != null && newReference.getType() != oldReference.getType()) + throw new IllegalAccessError("Cannot change the type of a linked mutable variable"); + } + else + { + if (newReference == null) + { + element.setType(null); + array.setType(null); + } + else + { + element.setType(newReference.getType().getComponentType()); + } + } + } + }); + } + return array; + } + + /** + * Defines the variable that will contain each element of the batch process, consecutively. + * + * @see FileBatch#getBatchElement() + * @see SequenceFileBatch#getBatchElement() + * @see SequenceSeriesBatch#getBatchElement() + * @return the inner variable that will contain each element to process + */ + public Var<?> getBatchElement() + { + if (element == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() below and VarList.add() + element = new VarMutable("element", null); + } + + return element; + } + + @Override + public void initializeLoop() + { + if (array == null || Array.getLength(array.getValue(true)) == 0) + throw new VarException(array, "Cannot loop on an empty array"); + } + + @Override + public void beforeIteration() + { + element.setValue(array.getElementAt(getIterationCounter().getValue())); + } + + @Override + public boolean isStopConditionReached() + { + return getIterationCounter().getValue() == Array.getLength(array.getValue()); + } + + @Override + public void declareInput(VarList inputMap) + { + super.declareInput(inputMap); + + Var<?> batchSource = getBatchSource(); + + inputMap.add(batchSource.getName(), getBatchSource()); + } + + @Override + public void declareOutput(VarList outputMap) + { + super.declareOutput(outputMap); + + Var<?> batchElement = getBatchElement(); + + outputMap.add(batchElement.getName(), batchElement); + } + + /** + * {@inheritDoc} <br> + * <br> + * The implementation provided in this class automatically declares the source and element of + * the batch as loop variables, therefore they don't need to be declared by overriding classes + */ + @Override + public void declareLoopVariables(List<Var<?>> loopVariables) + { + loopVariables.add(getBatchSource()); + loopVariables.add(getBatchElement()); + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/Block.java b/src/main/java/plugins/adufour/blocks/lang/Block.java new file mode 100644 index 0000000..55a27de --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/Block.java @@ -0,0 +1,33 @@ +package plugins.adufour.blocks.lang; + +import plugins.adufour.blocks.util.VarList; + +/** + * Interface indicating that implementing classes can be used in a block programming context + * + * @see WorkFlow + * @author Alexandre Dufour + */ +public interface Block extends Runnable +{ + /** + * Fills the specified map with all the necessary input variables + * + * @param inputMap + * the list of input variables to fill + */ + void declareInput(final VarList inputMap); + + /** + * Fills the specified map with all the necessary output variables + * + * @param outputMap + * the list of output variables to fill + */ + void declareOutput(final VarList outputMap); + + /** + * Main method + */ + void run(); +} diff --git a/src/main/java/plugins/adufour/blocks/lang/BlockDescriptor.java b/src/main/java/plugins/adufour/blocks/lang/BlockDescriptor.java new file mode 100644 index 0000000..b1b284f --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/BlockDescriptor.java @@ -0,0 +1,1121 @@ +package plugins.adufour.blocks.lang; + +import java.awt.Dimension; +import java.awt.Point; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import icy.file.xml.XMLPersistent; +import icy.gui.plugin.PluginErrorReport; +import icy.network.NetworkUtil; +import icy.plugin.PluginDescriptor; +import icy.plugin.PluginInstaller; +import icy.plugin.PluginLoader; +import icy.plugin.PluginRepositoryLoader; +import icy.plugin.abstract_.Plugin; +import icy.util.ClassUtil; +import icy.util.StringUtil; +import icy.util.XMLUtil; +import plugins.adufour.blocks.tools.Display; +import plugins.adufour.blocks.tools.input.InputBlock; +import plugins.adufour.blocks.util.*; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.util.MutableType; +import plugins.adufour.vars.util.VarListener; + +/** + * Class defining all the metadata associated to a {@link Block}. + * + * @author Alexandre Dufour + */ +@SuppressWarnings("rawtypes") +public class BlockDescriptor implements Runnable, VarListener, VarListListener, XMLPersistent +{ + public static enum BlockStatus + { + /** + * The result is not up-to-date. This status is active at startup and whenever a variable is + * changed + */ + DIRTY("This block is ready to run"), + /** + * The block is currently running + */ + RUNNING("This block is currently running..."), + /** + * The result is up-to-date and need not be recomputed (unless explicitly required) + */ + READY("This block is up to date"), + /** + * There was an error while running this block + */ + ERROR("This block did not run properly"); + + public final String defaultErrorMessage; + + private String optionalUserMessage = ""; + + private BlockStatus(String message) + { + defaultErrorMessage = message; + } + + public String getUserMessage() + { + return optionalUserMessage; + } + + public void setUserMessage(String message) + { + optionalUserMessage = (message != null ? message : ""); + } + + @Override + public String toString() + { + return defaultErrorMessage + (optionalUserMessage.isEmpty() ? "" : ":\n" + optionalUserMessage); + } + } + + private Integer id = hashCode(); + + private Block block; + + private WorkFlow container; + + private final HashSet<BlockListener> listeners = new HashSet<BlockListener>(); + + /** + * The input variables of this descriptor's block + */ + public final VarList inputVars; + + /** + * The output variables of this descriptor's block + */ + public final VarList outputVars; + + private final Point location = new Point(); + + private final Dimension dimension = new Dimension(0, 0); + + private boolean collapsed = false; + + private BlockStatus status = BlockStatus.DIRTY; + + private boolean finalBlock; + + private String definedName = null; + + private boolean keepResults = true; + + /** Command-line ID (used only for input blocks) */ + private String commandLineID = ""; + + public BlockDescriptor() + { + this.inputVars = new VarList(); + this.inputVars.addVarListListener(this); + this.outputVars = new VarList(); + this.outputVars.addVarListListener(this); + } + + public BlockDescriptor(int ID, Block block) + { + this(); + + this.block = block; + + try + { + block.declareInput(inputVars); + + block.declareOutput(outputVars); + } + catch (RuntimeException e) + { + String blockName = block.getClass().getName(); + + String devId = blockName.substring("plugins.".length()); + devId = devId.substring(0, devId.indexOf('.')); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.write("Unable to insert block: " + block.getClass().getName() + "\n"); + pw.write("Reason: " + e.getClass().getName() + ": " + e.getMessage() + "\n"); + pw.write("Stack trace:\n"); + e.printStackTrace(pw); + + PluginErrorReport.report(null, devId, sw.toString()); + } + + if (ID != -1) + id = ID; + } + + /** + * @deprecated use {@link #BlockDescriptor(int, Block)} instead + * @param ID + * Identifier + * @param block + * Block template instance. + * @param owner + * Workflow containing the new block. + * @param location + * Position of the new block. + */ + @Deprecated + public BlockDescriptor(int ID, Block block, WorkFlow owner, Point location) + { + this(ID, block); + + this.container = owner; + setLocation(location.x, location.y); + } + + /** + * Adds a new input variable to this block <u>after</u> it has been initialized. This method is + * called when the block is embedded inside another work flow, such that the block variables can + * be later exposed (and linked) outside the work flow. + * <p> + * NB: default (startup) block variables are not added here, they are automatically created in + * {@link #BlockDescriptor(int, Block)} via {@link Block#declareInput(VarList)} and + * {@link Block#declareOutput(VarList)}. + * + * @param uid + * a unique identifier for the variable + * @param variable + * the variable to add + * @throws IllegalArgumentException + * if a variable with same unique ID exists + */ + public void addInput(String uid, Var<?> variable) throws IllegalArgumentException + { + inputVars.add(uid, variable); + } + + /** + * Adds a new input variable to this block <u>after</u> it has been initialized. This method is + * called when the block is embedded inside another work flow, such that the block variables can + * be later exposed (and linked) outside the work flow. + * <p> + * NB: default (startup) block variables are not added here, they are automatically created in + * {@link #BlockDescriptor(int, Block)} via {@link Block#declareInput(VarList)} and + * {@link Block#declareOutput(VarList)}. + * + * @param uid + * a unique identifier for the variable + * @param variable + * the variable to add + * @throws IllegalArgumentException + * if a variable with same unique ID exists + */ + public void addOutput(String uid, Var<?> variable) throws IllegalArgumentException + { + outputVars.add(uid, variable); + } + + public void addBlockListener(BlockListener listener) + { + listeners.add(listener); + } + + public void addBlockPanelListener(BlockListener listener) + { + listeners.add(listener); + } + + public void removeBlockListener(BlockListener listener) + { + listeners.remove(listener); + } + + public void removeBlockPanelListener(BlockListener listener) + { + listeners.remove(listener); + } + + public Block getBlock() + { + return block; + } + + public WorkFlow getContainer() + { + return container; + } + + public Dimension getDimension() + { + return dimension; + } + + public Integer getID() + { + return id; + } + + /** + * @return the location of the block in the work flow (use only in graphical mode) + */ + public Point getLocation() + { + return location; + } + + /** + * @return The name of the block + */ + public String getName() + { + String blockName = block.getClass().getSimpleName(); + + if (block instanceof Plugin && ((Plugin) block).getDescriptor() != null) + { + String pluginName = ((Plugin) block).getDescriptor().getName(); + + if (!pluginName.equalsIgnoreCase(blockName)) + return pluginName; + } + + if (blockName.endsWith("block")) + { + blockName = blockName.substring(0, blockName.lastIndexOf("block")); + } + else if (blockName.endsWith("Block")) + { + blockName = blockName.substring(0, blockName.lastIndexOf("Block")); + } + + return BlocksFinder.getFlattened(blockName); + } + + public BlockStatus getStatus() + { + return status; + } + + /** + * Retrieves the unique ID of this variable. This method first searches in the list of input + * variables, and then in the list of output variables if necessary. + * + * @param variable + * The variable to retrieve. + * @return Identifier of the given variable. + * @throws NoSuchVariableException + * If the variable cannot be found in this block's input or output. + */ + public String getVarID(Var<?> variable) throws NoSuchVariableException + { + if (inputVars.contains(variable)) + { + String varID = inputVars.getID(variable); + + if (!isWorkFlow() || varID.contains(":")) + return varID; + + return ((WorkFlow) block).getInputVarID(variable); + } + + String varID = outputVars.getID(variable); + + if (!isWorkFlow() || varID.contains(":")) + return varID; + + return ((WorkFlow) block).getOutputVarID(variable); + } + + public boolean isCollapsed() + { + return collapsed; + } + + /** + * @return true if this block is the last block to execute in the work flow. If true, then the + * execution of the top-level work flow will stop after execution of this block. + */ + public boolean isFinalBlock() + { + return finalBlock; + } + + public boolean isLoop() + { + return block instanceof Loop; + } + + public boolean isSingleBlock() + { + return block instanceof Display || block instanceof InputBlock; + } + + public boolean isWorkFlow() + { + return block instanceof WorkFlow; + } + + public boolean isTopLevelWorkFlow() + { + // Stephane FIX: properly detect top level workflow + return isWorkFlow() && ((WorkFlow) block).isTopLevel(); + // return isWorkFlow() && container == null; + } + + public void setCollapsed(boolean collapsed) + { + if (this.collapsed == collapsed) + return; + + this.collapsed = collapsed; + + for (BlockListener l : listeners) + l.blockCollapsed(this, collapsed); + } + + public void setContainer(WorkFlow container) + { + this.container = container; + } + + /** + * Sets whether this block is the last block to execute in the work flow. If true, then the + * execution of the top-level work flow will stop after execution of this block. Note that if + * multiple blocks are marked as final, the execution will stop after the first block marked + * final, in order of execution. + * + * @param finalBlock + * true to stop the work flow after running this block, false otherwise. + */ + public void setFinalBlock(boolean finalBlock) + { + this.finalBlock = finalBlock; + } + + /** + * Sets the dimension of this block. + * + * @param width + * Width of the block in pixels. + * @param height + * Height of the block in pixels. + */ + public void setDimension(int width, int height) + { + if (dimension.width == width && dimension.height == height) + return; + + dimension.setSize(width, height); + + for (BlockListener l : listeners) + l.blockDimensionChanged(this, width, height); + } + + /** + * Sets the ID of this block. + * + * @param id + * Block unique identifier. + */ + public void setID(int id) + { + this.id = id; + } + + /** + * Sets the value of the specified input variable + * + * @param <T> + * Type of the value of the variable being set. + * @param varID + * The unique ID of the input variable to set. + * @param value + * The new variable value. + * @throws NoSuchVariableException + * If the variable is not in this block. + */ + public <T> void setInput(String varID, T value) throws NoSuchVariableException + { + Var<T> input = inputVars.get(varID); + if (input == null) + throw new NoSuchVariableException(this, varID); + + input.setValue(value); + } + + /** + * Stores the location of the block and notifies listeners. + * + * @param x + * X-axis position in pixels. + * @param y + * Y-axis position in pixels. + */ + public void setLocation(int x, int y) + { + if (location.x == x && location.y == y) + return; + + location.move(x, y); + for (BlockListener l : listeners) + l.blockLocationChanged(this, x, y); + } + + /** + * Sets the value of the specified output variable + * + * @param <T> + * Type of the value in the variable being set. + * @param varID + * the unique ID of the output variable to set + * @param value + * the new variable value + * @throws NoSuchVariableException + * if the variable is not in this block + */ + public <T> void setOutput(String varID, T value) throws NoSuchVariableException + { + Var<T> output = outputVars.get(varID); + if (output == null) + throw new NoSuchVariableException(this, varID); + + output.setValue(value); + } + + public void setStatus(BlockStatus newStatus) + { + if (this.status == newStatus) + return; + + this.status = newStatus; + for (BlockListener listener : listeners) + listener.blockStatusChanged(this, status); + + if (status == BlockStatus.DIRTY && container != null) + { + // propagate dirty status to the container (if it is not running) + BlockDescriptor wfDescriptor = container.getBlockDescriptor(); + + if (wfDescriptor.getStatus() != BlockStatus.RUNNING) + { + wfDescriptor.setStatus(BlockStatus.DIRTY); + } + } + } + + /** + * Removes the specified variable from the list of inputs + * + * @param inputVar + * Variable to be removed from the input variable list. + */ + public void removeInput(Var<?> inputVar) + { + if (!inputVars.contains(inputVar)) + return; + + inputVars.setVisible(inputVar, false); + inputVars.remove(inputVar); + } + + /** + * Removes the specified variable from the list of outputs + * + * @param outputVar + * Variable to be removed from the output variable list. + */ + public void removeOutput(Var<?> outputVar) + { + if (!outputVars.contains(outputVar)) + return; + + outputVars.setVisible(outputVar, false); + outputVars.remove(outputVar); + } + + /** + * Resets all output variables in this block to their default value + */ + @SuppressWarnings("unchecked") + public void reset() + { + setStatus(BlockStatus.DIRTY); + + for (Var var : outputVars) + var.setValue(var.getDefaultValue()); + + if (isWorkFlow()) + ((WorkFlow) block).reset(); + } + + public void run() + { + if (keepResults && status == BlockStatus.READY) + return; + + setStatus(BlockStatus.RUNNING); + + try + { + block.run(); + setStatus(keepResults ? BlockStatus.READY : BlockStatus.DIRTY); + } + catch (RuntimeException e) + { + BlockStatus error = BlockStatus.ERROR; + + error.setUserMessage(e.getMessage()); + + setStatus(error); + throw e; + } + } + + @SuppressWarnings("unchecked") + @Override + public void valueChanged(Var source, Object oldValue, Object newValue) + { + if (inputVars.contains(source)) + setStatus(BlockStatus.DIRTY); + + for (BlockListener listener : listeners) + listener.blockVariableChanged(this, source, newValue); + } + + @Override + public void referenceChanged(Var source, Var oldReference, Var newReference) + { + setStatus(BlockStatus.DIRTY); + } + + @Override + public String toString() + { + return getContainer().getBlockDescriptor().getName() + "." + getName(); + } + + @SuppressWarnings("unchecked") + @Override + public void variableAdded(VarList list, Var<?> variable) + { + variable.addListener(this); + + for (BlockListener listener : listeners) + listener.blockVariableAdded(this, variable); + } + + @SuppressWarnings("unchecked") + @Override + public void variableRemoved(VarList list, Var<?> variable) + { + variable.removeListener(this); + + // although we removed a variable, there is no "removed" notifier + // use the "added" notifier just for the sake of refreshing the GUI + for (BlockListener listener : listeners) + listener.blockVariableAdded(this, variable); + } + + /** + * Goes online, downloads and installs the plug-in containing the specified block + * + * @param blockType + * @return + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private Class<? extends Block> installRequiredBlock(String pluginClassName, String blockType) + throws ClassNotFoundException + { + Class<?> clazz = null; + + // check if the class exists, otherwise try downloading it (if online) + try + { + clazz = ClassUtil.findClass(blockType); + } + catch (ClassNotFoundException e) + { + if (!NetworkUtil.hasInternetAccess() || getClass().getClassLoader() == ClassLoader.getSystemClassLoader()) + { + throw new BlocksException("Plugin " + blockType + + " is missing, but no internet connection is available.\nTry again later", true); + } + + // String simpleName = + // ClassUtil.getSimpleClassName(ClassUtil.getBaseClassName(blockType)); + + // status.setValue("Downloading " + simpleName + "..."); + + PluginDescriptor pd = PluginRepositoryLoader.getPlugin(pluginClassName); + + if (pd == null) + { + // status.setValue("Couldn't find plugin online !"); + throw e; + } + + // status.setValue("Installing " + simpleName + "..."); + + PluginInstaller.install(pd, false); + + throw new BlocksReloadedException(); + } + + return (Class<? extends Block>) clazz; + } + + @Override + public boolean loadFromXML(Node node) + { + boolean noWarnings = true; + + Element blockNode = (Element) node; + + String blockType = XMLUtil.getAttributeValue(blockNode, "blockType", null); + + try + { + Class<? extends Block> blockClass = installRequiredBlock( + XMLUtil.getAttributeValue(blockNode, "className", null), blockType); + + block = blockClass.newInstance(); + + if (block == null) + throw new BlocksException("Couldn't create block from class " + blockClass.getName(), true); + + block.declareInput(inputVars); + block.declareOutput(outputVars); + + setID(XMLUtil.getAttributeIntValue(blockNode, "ID", -1)); + + int width = XMLUtil.getAttributeIntValue(blockNode, "width", 500); + int height = XMLUtil.getAttributeIntValue(blockNode, "height", 500); + setDimension(width, height); + + int xPos = XMLUtil.getAttributeIntValue(blockNode, "xLocation", -1); + int yPos = XMLUtil.getAttributeIntValue(blockNode, "yLocation", -1); + setLocation(xPos, yPos); + + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = inputVars.get(uid); + + if (var == null) + { + if (noWarnings) + { + System.err.println("Error(s) while loading protocol:"); + noWarnings = false; + } + System.err.println(new NoSuchVariableException(this, uid).getMessage()); + continue; + } + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + + if (type != null) + { + if (var instanceof VarMutable) + { + Class<?> mutableType = BlocksML.getPrimitiveType(type); + + if (mutableType == null) + mutableType = Class.forName(type); + + ((MutableType) var).setType(mutableType); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + + var.loadFromXML(varNode); + + inputVars.setVisible(var, XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + } + + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = outputVars.get(uid); + + if (var == null) + { + if (noWarnings) + { + System.err.println("Error(s) while loading protocol:"); + noWarnings = false; + } + System.err.println(new NoSuchVariableException(this, uid).getMessage()); + continue; + } + + outputVars.setVisible(var, XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + Class<?> mutableType = BlocksML.getPrimitiveType(type); + ((MutableType) var).setType(mutableType != null ? mutableType : Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + } + + } + catch (ClassNotFoundException e1) + { + throw new BlocksException("Cannot create block (" + e1.getMessage() + ") => class not found", true); + } + catch (InstantiationException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + + return noWarnings; + } + + @Override + public boolean saveToXML(Node node) + { + // TODO Auto-generated method stub + return false; + } + + public void setDefinedName(String s) + { + definedName = s; + } + + public String getDefinedName() + { + if (definedName == null || definedName.isEmpty()) + return getName(); + + return definedName; + } + + private BlockDescriptor getBlockDescriptor(int blockId, Set<BlockDescriptor> bds) + { + for (BlockDescriptor bd : bds) + if (bd.getID().intValue() == blockId) + return bd; + + return null; + } + + private String getNewVarId(String oldVarId, Map<BlockDescriptor, BlockDescriptor> copies) + { + // search for workflow shadow exposed variable + final int index = oldVarId.indexOf(":"); + + // direct variable id ? --> return it + if (index == -1) + return oldVarId; + + String result = ""; + + // get block Id + final int blockId = StringUtil.parseInt(oldVarId.substring(0, index), 0); + // get block descriptor from id + final BlockDescriptor bd = getBlockDescriptor(blockId, copies.keySet()); + // get corresponding new block + final BlockDescriptor nbd = copies.get(bd); + + if (nbd != null) + result = nbd.getID().toString(); + + // remaining var id + return result + ":" + getNewVarId(oldVarId.substring(index + 1), copies); + } + + public BlockDescriptor clone(boolean embedding) + { + return clone(embedding, new HashMap<BlockDescriptor, BlockDescriptor>()); + } + + @SuppressWarnings("unchecked") + public BlockDescriptor clone(boolean embedding, Map<BlockDescriptor, BlockDescriptor> copies) + { + Class<? extends Block> blockClass = PluginLoader.getPlugin(getBlock().getClass().getName()).getPluginClass() + .asSubclass(Block.class); + WorkFlow wf = null; + WorkFlow wfCpy = null; + BlockDescriptor cpy = null; + Block newBlock = null; + + try + { + newBlock = blockClass.newInstance(); + } + catch (InstantiationException e1) + { + e1.printStackTrace(); + } + catch (IllegalAccessException e1) + { + e1.printStackTrace(); + } + + cpy = (newBlock instanceof WorkFlow ? ((WorkFlow) newBlock).getBlockDescriptor() + : new BlockDescriptor(-1, newBlock)); + + cpy.setDefinedName(getDefinedName()); + cpy.setLocation(getLocation().x + 12, getLocation().y + 12); + cpy.setDimension(getDimension().width, getDimension().height); + cpy.setCollapsed(isCollapsed()); + + // Clone input variables + for (Var oldVar : inputVars) + { + final String oldID = inputVars.getID(oldVar); + + // FIX: shadow exposed variable from workflow ? --> don't copy it for now (Stephane) + if (oldID.contains(":") && (getBlock() instanceof WorkFlow)) + continue; + + Var newVar = cpy.inputVars.get(oldID); + // If newVar is null, then oldVar was a runtime variable + if (newVar == null) + { + newVar = new VarMutable(oldVar.getName(), oldVar.getType()); + cpy.inputVars.addRuntimeVariable(oldID, (VarMutable) newVar); + } + else + { + if (newVar instanceof VarMutable) + ((VarMutable) newVar).setType(oldVar.getType()); + newVar.setValue(oldVar.getValue()); + } + + // FIX: preserve visibility information (Stephane) + cpy.inputVars.setVisible(newVar, inputVars.isVisible(oldVar)); + } + + // Clone output variables + for (Var oldVar : outputVars) + { + final String oldID = outputVars.getID(oldVar); + + // FIX: shadow exposed variable from workflow ? --> don't copy it for now (Stephane) + if (oldID.contains(":") && (getBlock() instanceof WorkFlow)) + continue; + + Var newVar = cpy.outputVars.get(oldID); + // If newVar is null, then oldVar was a runtime variable + if (newVar == null) + { + newVar = new VarMutable(oldVar.getName(), oldVar.getType()); + cpy.outputVars.addRuntimeVariable(oldID, (VarMutable) newVar); + } + else + { + if (newVar instanceof VarMutable) + ((VarMutable) newVar).setType(oldVar.getType()); + newVar.setValue(oldVar.getValue()); + } + + // FIX: preserve visibility information (Stephane) + cpy.outputVars.setVisible(newVar, outputVars.isVisible(oldVar)); + } + + if (getBlock() instanceof WorkFlow) + { + wf = (WorkFlow) getBlock(); + wfCpy = (WorkFlow) cpy.getBlock(); + + BlockDescriptor tmp; + if (wf.getBlockSelection().isEmpty() || embedding) + { + for (BlockDescriptor bd : wf) + { + tmp = bd.clone(true, copies); + copies.put(bd, tmp); + wfCpy.addBlock(tmp); + wfCpy.selectBlock(tmp); + } + cloneLinks(wf.getLinksIterator(), copies, wfCpy); + } + else + { + for (BlockDescriptor bd : wf.getBlockSelection()) + { + tmp = bd.clone(true, copies); + copies.put(bd, tmp); + wfCpy.addBlock(tmp); + wfCpy.selectBlock(tmp); + } + cloneLinks(wf.getLinkSelection(), copies, wfCpy); + } + + for (Var oldVar : inputVars) + { + final String oldID = inputVars.getID(oldVar); + final int index = oldID.indexOf(":"); + + // shadow exposed variable ? --> try to recover it + if (index != -1) + { + // get block ID + final int blockID = StringUtil.parseInt(oldID.substring(0, index), 0); + // get var ID + final String varID = oldID.substring(index + 1); + // get new var ID + final String newVarID = getNewVarId(varID, copies); + // get block descriptor from ID + final BlockDescriptor bd = wf.getBlockByID(blockID); + // get corresponding new block + final BlockDescriptor nbd = copies.get(bd); + // get corresponding var for this block + final Var<Object> inputVar = nbd.inputVars.get(newVarID); + + // finally add the shadow exposed variable if not already done (can be done in WorkFlow.addBlock(..) method) ! + if (inputVar != null) + { + if (!cpy.inputVars.contains(inputVar)) + cpy.addInput(wfCpy.getInputVarID(inputVar), inputVar); + // and preserve visibility (very important) + cpy.inputVars.setVisible(inputVar, inputVars.isVisible(oldVar)); + } + } + } + + for (Var oldVar : outputVars) + { + final String oldID = outputVars.getID(oldVar); + final int index = oldID.indexOf(":"); + + // shadow exposed variable ? --> try to recover it + if (index != -1) + { + // get block ID + final int blockID = StringUtil.parseInt(oldID.substring(0, index), 0); + // get var ID + final String varID = oldID.substring(index + 1); + // get new var ID + final String newVarID = getNewVarId(varID, copies); + // get block descriptor from ID + final BlockDescriptor bd = wf.getBlockByID(blockID); + // get corresponding new block + final BlockDescriptor nbd = copies.get(bd); + // get corresponding var for this block + final Var<Object> outputVar = nbd.outputVars.get(newVarID); + + // finally add the shadow exposed variable if not already done (can be done in WorkFlow.addBlock(..) method) ! + if (outputVar != null) + { + if (!cpy.outputVars.contains(outputVar)) + cpy.addOutput(wfCpy.getInputVarID(outputVar), outputVar); + // and preserve visibility (very important) + cpy.outputVars.setVisible(outputVar, outputVars.isVisible(oldVar)); + } + } + } + } + + return cpy; + } + + private static void cloneLinks(Iterable<Link<?>> iterable, Map<BlockDescriptor, BlockDescriptor> copies, + WorkFlow dest) + { + for (Link<?> l : iterable) + { + if (l.srcBlock.getBlock() instanceof Loop || l.dstBlock.getBlock() instanceof Loop) + { + System.err.println("Warning : cannot copy a link to a loop variable"); + continue; + } + if (l.srcBlock.getBlock() instanceof WorkFlow || l.dstBlock.getBlock() instanceof WorkFlow) + { + System.err.println("Warning : cannot copy a link to an exposed variable"); + continue; + } + try + { + dest.addLink(copies.get(l.srcBlock), + copies.get(l.srcBlock).inputVars.get(l.srcBlock.inputVars.getID(l.srcVar)), + copies.get(l.dstBlock), + copies.get(l.dstBlock).inputVars.get(l.dstBlock.inputVars.getID(l.dstVar))); + } + catch (NoSuchVariableException nsve) + { + dest.addLink(copies.get(l.srcBlock), + copies.get(l.srcBlock).outputVars.get(l.srcBlock.outputVars.getID(l.srcVar)), + copies.get(l.dstBlock), + copies.get(l.dstBlock).inputVars.get(l.dstBlock.inputVars.getID(l.dstVar))); + } + } + } + + /** + * @return <code>true</code> if the block keeps results in memory, <code>false</code> otherwise + */ + public boolean keepsResults() + { + return keepResults; + } + + /** + * Sets whether the block should keep results in memory (and not need to recalculate it unless a + * parameter has changed) + * + * @param keep + * Flag to keep results in memory. + */ + public void keepResults(boolean keep) + { + this.keepResults = keep; + } + + /** + * (This method is used only if the block implements {@link InputBlock}) + * + * @return the command-line identifier for the input block's variable + */ + public String getCommandLineID() + { + return commandLineID; + } + + /** + * (This method is used only if the block implements {@link InputBlock}) + * Sets the command line identifier for this input block's variable + * + * @param id + * Block identifier when using the command line. + */ + public void setCommandLineID(String id) + { + commandLineID = id; + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/FileBatch.java b/src/main/java/plugins/adufour/blocks/lang/FileBatch.java new file mode 100644 index 0000000..827bb51 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/FileBatch.java @@ -0,0 +1,130 @@ +package plugins.adufour.blocks.lang; + +import java.io.File; +import java.io.FileFilter; +import java.util.List; + +import icy.file.FileUtil; +import icy.gui.frame.progress.AnnounceFrame; +import icy.main.Icy; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarString; +import plugins.adufour.vars.util.VarException; + +/** + * A file batch is a work-flow that will execute repeatedly on every file of a + * user-selected folder. Files can be retrieved from sub-folders if necessary, + * and filtered by extension. Note however that the files are given "as-is": it + * is up to the user to indicate inside the work-flow how these files should be + * read (for the particular case of image files, use the + * {@link SequenceFileBatch Sequence file batch} instead). + * + * @author Alexandre Dufour + */ +public class FileBatch extends Batch implements FileFilter +{ + // loop variables // + + private VarFile element; + + private VarFile folder; + + private VarString extension; + + private VarBoolean includeSubFolders; + + // local variables // + + /** + * The list of files containing the elements to process + */ + protected File[] files; + + @Override + public Var<?> getBatchSource() + { + if (folder == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() and VarList.add() + folder = new VarFile("folder", null); + folder.setDefaultEditorModel(new FileTypeModel(null, FileMode.FOLDERS, null, false)); + } + return folder; + } + + @Override + public boolean accept(File f) + { + String ext = extension.getValue(); + return f.isDirectory() || ext.isEmpty() || f.getPath().toLowerCase().endsWith(ext.toLowerCase()); + } + + @Override + public Var<?> getBatchElement() + { + if (element == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() and VarList.add() + element = new VarFile("file", null); + } + return element; + } + + @Override + public void initializeLoop() + { + if (folder.getValue() == null) + throw new VarException(folder, "No folder indicated"); + + File file = folder.getValue(); + if (!file.isDirectory()) + throw new VarException(folder, file.getAbsolutePath() + " is not a folder"); + + AnnounceFrame process = null; + + if (!Icy.getMainInterface().isHeadLess()) + process = new AnnounceFrame("Listing files..."); + + files = FileUtil.getFiles(file, this, includeSubFolders.getValue(), false, false); + + if (process != null) + process.close(); + } + + @Override + public void beforeIteration() + { + element.setValue(files[getIterationCounter().getValue()]); + } + + @Override + public boolean isStopConditionReached() + { + return getIterationCounter().getValue() == files.length; + } + + @Override + public void declareInput(VarList inputMap) + { + super.declareInput(inputMap); + + inputMap.add("extension", extension = new VarString("extension", "")); + inputMap.add("Include sub-folders", includeSubFolders = new VarBoolean("Include sub-folders", true)); + } + + @Override + public void declareLoopVariables(List<Var<?>> loopVariables) + { + super.declareLoopVariables(loopVariables); + + loopVariables.add(extension); + loopVariables.add(includeSubFolders); + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/FolderLoop.java b/src/main/java/plugins/adufour/blocks/lang/FolderLoop.java new file mode 100644 index 0000000..c02f5d3 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/FolderLoop.java @@ -0,0 +1,9 @@ +package plugins.adufour.blocks.lang; + +/** + * @deprecated use {@link FileBatch} instead. + * @author Alexandre Dufour + */ +@Deprecated +public class FolderLoop extends FileBatch +{} diff --git a/src/main/java/plugins/adufour/blocks/lang/Link.java b/src/main/java/plugins/adufour/blocks/lang/Link.java new file mode 100644 index 0000000..26cb755 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/Link.java @@ -0,0 +1,126 @@ +package plugins.adufour.blocks.lang; + +import icy.file.xml.XMLPersistent; +import icy.util.XMLUtil; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import plugins.adufour.blocks.util.BlocksException; +import plugins.adufour.blocks.util.BlocksML; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.util.MutableType; + +/** + * Class describing a link between two variables within a work flow + * + * @author Alexandre Dufour + */ +public class Link<T> implements XMLPersistent +{ + private final WorkFlow workFlow; + + public BlockDescriptor srcBlock; + + public Var<T> srcVar; + + public BlockDescriptor dstBlock; + + public Var<T> dstVar; + + /** + * Creates a new (empty) link in the specified work flow. This constructor is generally followed + * by a call to {@link #loadFromXML(Node)} to restore the link status from a previously saved + * state (e.g. XML file) + * + * @param workFlow + * Workflow containing the new link. + */ + public Link(WorkFlow workFlow) + { + this.workFlow = workFlow; + } + + public Link(WorkFlow workFlow, final BlockDescriptor srcBlock, final Var<T> output, final BlockDescriptor dstBlock, + final Var<T> input) + { + this(workFlow); + + this.srcBlock = srcBlock; + this.srcVar = output; + this.dstBlock = dstBlock; + this.dstVar = input; + } + + public Class<?> getType() + { + return srcVar.getType(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public boolean loadFromXML(Node node) + { + Element linkNode = (Element) node; + + int srcBlockID = XMLUtil.getAttributeIntValue(linkNode, "srcBlockID", -1); + String srcVarID = XMLUtil.getAttributeValue(linkNode, "srcVarID", null); + int dstBlockID = XMLUtil.getAttributeIntValue(linkNode, "dstBlockID", -1); + String dstVarID = XMLUtil.getAttributeValue(linkNode, "dstVarID", null); + + // load the source variable + BlockDescriptor theSrcBlock = workFlow.getBlockByID(srcBlockID); + Var theSrcVar = theSrcBlock.outputVars.get(srcVarID); + if (theSrcVar == null) + theSrcVar = theSrcBlock.inputVars.get(srcVarID); + + if (theSrcVar == null) + { + System.err.println("Cannot create a link from variable " + srcVarID + " (from block " + theSrcBlock + ")"); + return false; + } + + if (theSrcVar instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(linkNode, "srcVarType", null); + if (type != null) + { + try + { + if (theSrcVar instanceof VarMutable) + { + Class<?> mutableType = BlocksML.getPrimitiveType(type); + ((MutableType) theSrcVar).setType(mutableType != null ? mutableType : Class.forName(type)); + } + else if (theSrcVar instanceof VarMutableArray) + { + type = "[L" + type + ";"; + ((MutableType) theSrcVar).setType(Class.forName(type)); + } + } + catch (ClassNotFoundException e1) + { + throw new BlocksException("Cannot create link: unknown type " + type, true); + } + } + } + + // load the destination variable + BlockDescriptor theDstBlock = workFlow.getBlockByID(dstBlockID); + + Var theDstVar = theDstBlock.inputVars.get(dstVarID); + + workFlow.addLink(theSrcBlock, theSrcVar, theDstBlock, theDstVar); + + return true; + } + + @Override + public boolean saveToXML(Node node) + { + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/Loop.java b/src/main/java/plugins/adufour/blocks/lang/Loop.java new file mode 100644 index 0000000..b3edd5b --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/Loop.java @@ -0,0 +1,278 @@ +package plugins.adufour.blocks.lang; + +import icy.system.IcyHandledException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import plugins.adufour.blocks.lang.BlockDescriptor.BlockStatus; +import plugins.adufour.blocks.tools.ReLoop; +import plugins.adufour.blocks.util.BlocksException; +import plugins.adufour.blocks.util.LoopException; +import plugins.adufour.blocks.util.ScopeException; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarInteger; + +/** + * Special work flow that will repeat its contents forever until is it manually stopped by the user. + * + * @see Batch + * @see RangeLoop + * @see FileBatch + * @see SequenceFileBatch + * @see SequenceSeriesBatch + * @author Alexandre Dufour + */ +public class Loop extends WorkFlow +{ + private final ArrayList<Var<?>> loopVariables = new ArrayList<Var<?>>(); + + private VarInteger iterationCounter; + + public final VarBoolean stopOnFirstError = new VarBoolean("Stop on first error", false); + + public Loop() + { + super(false); + + declareLoopVariables(loopVariables); + + // always add the iteration counter in last position + loopVariables.add(iterationCounter); + } + + @Override + public BlockDescriptor getBlock(int blockID) + { + if (blockID == -1) + return getBlockDescriptor(); + + return super.getBlock(blockID); + } + + @Override + public BlockDescriptor getInputOwner(Var<?> var) + { + return loopVariables.contains(var) ? getBlockDescriptor() : super.getInputOwner(var); + } + + @Override + public BlockDescriptor getOutputOwner(Var<?> var) + { + return loopVariables.contains(var) ? getBlockDescriptor() : super.getOutputOwner(var); + } + + public String getInputVarID(Var<?> variable) + { + for (Var<?> innerVar : loopVariables) + if (variable == innerVar) + { + return getBlockDescriptor().inputVars.getID(variable); + } + + return super.getInputVarID(variable); + } + + public String getOutputVarID(Var<?> variable) + { + for (Var<?> innerVar : loopVariables) + if (variable == innerVar) + { + return getBlockDescriptor().outputVars.getID(variable); + } + + return super.getOutputVarID(variable); + } + + public VarInteger getIterationCounter() + { + return iterationCounter; + } + + /** + * Adds the specified variable to the list of inner loop variables, i.e., variables describing + * the parameters of the loop, which can be referenced from inside the loop by other blocks. + * + * @deprecated The process of declaring loop variables has changed. Override + * {@link #declareLoopVariables(List)} instead. + * @param loopVar + * the variable to register + */ + @Deprecated + protected void addLoopVariable(Var<?> loopVar) + { + loopVariables.add(loopVar); + } + + /** + * @param var + * the variable to look for + * @return true if <code>var</code> is a loop variable, false otherwise + */ + public boolean isLoopVariable(Var<?> var) + { + return loopVariables.contains(var); + } + + public List<Var<?>> getLoopVariables() + { + return loopVariables; + } + + @Override + public void run() + { + iterationCounter.setValue(0); + initializeLoop(); + + List<IcyHandledException> exceptions = new ArrayList<IcyHandledException>(); + + while (!Thread.currentThread().isInterrupted() && !isStopConditionReached()) + { + // FIXED (Stephane) + // we need to reset to dirty state for all contained blocks before the iteration + // as we never expect any contained block to keep value from previous iteration + final Iterator<BlockDescriptor> blockIt = iterator(); + + while (blockIt.hasNext()) + { + final BlockDescriptor block = blockIt.next(); + // put block back in dirty state + if (block.getStatus() == BlockStatus.READY) + block.setStatus(BlockStatus.DIRTY); + } + + beforeIteration(); + + try + { + super.run(); + } + catch (IcyHandledException e) + { + exceptions.add(e); + if (stopOnFirstError.getValue()) + break; + } + + iterationCounter.setValue(iterationCounter.getValue() + 1); + afterIteration(); + } + + if (exceptions.size() > 0) + { + String message = "The following errors occurred during the loop:\n\n"; + for (Exception e : exceptions) + message += " - " + e.getMessage() + "\n"; + throw new BlocksException(message, true); + } + } + + /** + * Initializes (or resets) internal loop structures such as iterators or counters. This method + * is called once before actually starting the loop. Note that the iteration counter is already + * reset before calling this method. + */ + public void initializeLoop() + { + // nothing to do in an infinite loop + } + + /** + * Called before the current iteration takes place (i.e. before calling the {@link #run()} + * method. This method is typically used to fetch array or iterator values necessary for the + * core code execution. + */ + public void beforeIteration() + { + // nothing to do in an infinite loop + } + + /** + * Notifies that the current iteration has finished, allowing the loop to handle custom + * increments or iterators to move forward before executing the next iteration. Note that the + * iteration counter is automatically increased before calling this method. + */ + public void afterIteration() + { + // release other threads for a very short time + // (especially the AWT, to receive user interruption and display stuff) + Thread.yield(); + + } + + /** + * NB: this method must be called by overriding methods to ensure that the loop can be + * interrupted correctly upon request + * + * @return true if the stopping condition is reached, false otherwise. + */ + public boolean isStopConditionReached() + { + // infinite loop + return false; + } + + @Override + protected <T> void checkScope(Link<T> link) throws ScopeException + { + try + { + super.checkScope(link); + } + catch (ScopeException e) + { + // authorize links coming from variable loops + if (!loopVariables.contains(link.srcVar)) + throw e; + } + } + + @Override + protected <T> void checkLoop(Link<T> link) throws LoopException + { + try + { + super.checkLoop(link); + } + catch (LoopException e) + { + // authorize loops only if the destination is a "ReLoop" block + if (link.dstBlock.getBlock() instanceof ReLoop) + { + ReLoop reLoop = (ReLoop) link.dstBlock.getBlock(); + + if (link.dstVar == reLoop.reloopValue) + return; + } + + throw e; + } + } + + @Override + public void declareOutput(VarList outputMap) + { + super.declareOutput(outputMap); + + iterationCounter = new VarInteger("iteration", 0); + iterationCounter.setEnabled(false); + outputMap.add("iteration", iterationCounter); + } + + /** + * Declares the necessary loop variables by adding them to the specified list. Loop variables + * describe the parameters of the loop, which can be referenced from inside the loop by other + * blocks. + * + * @param loopVars + * the variable to register + */ + public void declareLoopVariables(List<Var<?>> loopVars) + { + // a loop has no inner variable + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/RangeLoop.java b/src/main/java/plugins/adufour/blocks/lang/RangeLoop.java new file mode 100644 index 0000000..cf75e20 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/RangeLoop.java @@ -0,0 +1,98 @@ +package plugins.adufour.blocks.lang; + +import java.util.List; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarInteger; + +/** + * Particular type of loop that will repeats its contents for every number within the provided range + * of values + * + * @author Alexandre Dufour + */ +public class RangeLoop extends Loop +{ + private VarInteger startIndex; + private VarInteger endIndex; + private VarInteger step; + private VarInteger index; + + public VarInteger getEndIndex() + { + return endIndex; + } + + public VarInteger getIndex() + { + return index; + } + + public VarInteger getStartIndex() + { + return startIndex; + } + + public VarInteger getStep() + { + return step; + } + + @Override + public void initializeLoop() + { + index.setValue(startIndex.getValue()); + } + + @Override + public void beforeIteration() + { + // nothing to do here + } + + @Override + public void afterIteration() + { + index.setValue(index.getValue() + step.getValue()); + } + + @Override + public boolean isStopConditionReached() + { + if (super.isStopConditionReached()) return true; + + if (step.getValue() > 0) return index.getValue() >= endIndex.getValue(); + + return index.getValue() <= endIndex.getValue(); + } + + @Override + public void declareInput(VarList inputMap) + { + super.declareInput(inputMap); + + inputMap.add("start", startIndex = new VarInteger("start", 0)); + inputMap.add("step", step = new VarInteger("step", 1)); + inputMap.add("end", endIndex = new VarInteger("end", 10)); + } + + @Override + public void declareOutput(VarList outputMap) + { + super.declareOutput(outputMap); + + outputMap.add("index", index = new VarInteger("index", startIndex.getDefaultValue().intValue())); + } + + @Override + public void declareLoopVariables(List<Var<?>> loopVariables) + { + loopVariables.add(startIndex); + loopVariables.add(endIndex); + loopVariables.add(step); + loopVariables.add(index); + + super.declareLoopVariables(loopVariables); + } +} \ No newline at end of file diff --git a/src/main/java/plugins/adufour/blocks/lang/SequenceFileBatch.java b/src/main/java/plugins/adufour/blocks/lang/SequenceFileBatch.java new file mode 100644 index 0000000..c58c047 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/SequenceFileBatch.java @@ -0,0 +1,56 @@ +package plugins.adufour.blocks.lang; + +import java.io.File; + +import icy.file.Loader; +import plugins.adufour.vars.lang.VarSequence; + +/** + * Similar to the {@link FileBatch File batch}, a Sequence file batch will read + * all files of a user-defined folder, and additionally load and give access to + * the sequence contained within these files (non-imaging files will be + * skipped).<br> + * Note for multi-series imaging files (e.g. Leica .lif): when loading + * multi-series files, a dialog box will appear to let the user select the + * series to process. However only the first selected series will be loaded. To + * batch process entire multi-series files, use the {@link SequenceSeriesBatch + * Sequence series batch} instead. + * + * @author Alexandre Dufour + */ +public class SequenceFileBatch extends FileBatch +{ + VarSequence element; + + @Override + public VarSequence getBatchElement() + { + if (element == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() and VarList.add() + element = new VarSequence("Sequence", null); + } + return element; + } + + @Override + public boolean accept(File f) + { + return f.isDirectory() || super.accept(f) && Loader.isSupportedImageFile(f.getPath()); + } + + @Override + public void beforeIteration() + { + try + { + element.setValue(Loader.loadSequence(files[getIterationCounter().getValue()].getPath(), -1, false)); + Thread.yield(); + } + catch (Exception e) + { + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/SequenceSeriesBatch.java b/src/main/java/plugins/adufour/blocks/lang/SequenceSeriesBatch.java new file mode 100644 index 0000000..3b549f5 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/SequenceSeriesBatch.java @@ -0,0 +1,110 @@ +package plugins.adufour.blocks.lang; + +import java.io.File; +import java.io.IOException; + +import icy.common.exception.UnsupportedFormatException; +import icy.file.Loader; +import icy.file.SequenceFileImporter; +import icy.sequence.MetaDataUtil; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarSequence; +import plugins.adufour.vars.util.VarException; +import plugins.kernel.importer.LociImporterPlugin; + +/** + * A Sequence series batch will iteratively load all series contained in a multi-series (e.g. Leica + * .lif) file, allowing to process all the series with a single work-flow. It can be used in + * combination with the {@link FileBatch File batch} to batch process entire folders of multi-series + * files. + * + * @author Alexandre Dufour + */ +public class SequenceSeriesBatch extends Batch +{ + private VarFile multiSeriesFile; + + private int nbSeries; + + private VarSequence element; + + @Override + public VarFile getBatchSource() + { + if (multiSeriesFile == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() and VarList.add() + multiSeriesFile = new VarFile("Series file", null); + multiSeriesFile.setDefaultEditorModel(new FileTypeModel(null, FileMode.FILES, null, false)); + } + + return multiSeriesFile; + } + + @Override + public VarSequence getBatchElement() + { + if (element == null) + { + // WARNING: do *not* change the name of this variable + // why? see declareInput() and VarList.add() + element = new VarSequence("Sequence", null); + } + + return element; + } + + @Override + public void initializeLoop() + { + if (multiSeriesFile.getValue() == null) + throw new VarException(multiSeriesFile, "No file indicated"); + + File f = multiSeriesFile.getValue(); + if (f.isDirectory()) + throw new VarException(multiSeriesFile, f.getAbsolutePath() + " is not a file"); + + String path = f.getPath(); + + try + { + final SequenceFileImporter importer = Loader.getSequenceFileImporter(path, true); + + // FIX: force un-grouping to get number of series (Stephane) + if (importer instanceof LociImporterPlugin) + ((LociImporterPlugin) importer).setGroupFiles(false); + + nbSeries = MetaDataUtil.getNumSeries(Loader.getOMEXMLMetaData(importer, path)); + } + catch (UnsupportedFormatException e) + { + throw new VarException(multiSeriesFile, path + " is not an imaging file"); + } + catch (IOException e) + { + throw new VarException(multiSeriesFile, "Unable to read " + path); + } + } + + @Override + public void beforeIteration() + { + final String path = multiSeriesFile.getValue().getPath(); + final SequenceFileImporter importer = Loader.getSequenceFileImporter(path, true); + + // FIX: force un-grouping as we iterate over series (Stephane) + if (importer instanceof LociImporterPlugin) + ((LociImporterPlugin) importer).setGroupFiles(false); + + element.setValue(Loader.loadSequence(importer, path, getIterationCounter().getValue(), true)); + } + + @Override + public boolean isStopConditionReached() + { + return getIterationCounter().getValue() == nbSeries; + } +} diff --git a/src/main/java/plugins/adufour/blocks/lang/WorkFlow.java b/src/main/java/plugins/adufour/blocks/lang/WorkFlow.java new file mode 100644 index 0000000..499ac7d --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/lang/WorkFlow.java @@ -0,0 +1,1175 @@ +package plugins.adufour.blocks.lang; + +import java.awt.Point; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import icy.gui.dialog.ConfirmDialog; +import icy.main.Icy; +import icy.math.UnitUtil; +import icy.plugin.abstract_.Plugin; +import icy.system.IcyHandledException; +import icy.system.thread.ThreadUtil; +import icy.util.StringUtil; +import plugins.adufour.blocks.lang.BlockDescriptor.BlockStatus; +import plugins.adufour.blocks.util.BlockListener; +import plugins.adufour.blocks.util.BlocksException; +import plugins.adufour.blocks.util.LinkCutException; +import plugins.adufour.blocks.util.LoopException; +import plugins.adufour.blocks.util.NoSuchBlockException; +import plugins.adufour.blocks.util.NoSuchLinkException; +import plugins.adufour.blocks.util.NoSuchVariableException; +import plugins.adufour.blocks.util.ScopeException; +import plugins.adufour.blocks.util.StopException; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.blocks.util.VarListListener; +import plugins.adufour.blocks.util.WorkFlowListener; +import plugins.adufour.protocols.gui.MainFrame; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarObject; +import plugins.adufour.vars.util.VarException; + +public class WorkFlow extends Plugin implements Block, Iterable<BlockDescriptor>, BlockListener, WorkFlowListener +{ + private final BlockDescriptor descriptor = new BlockDescriptor(-1, this); + + private final HashSet<WorkFlowListener> listeners = new HashSet<WorkFlowListener>(); + + private final ArrayList<BlockDescriptor> orderedBlocks = new ArrayList<BlockDescriptor>(); + + private final boolean topLevel; + + private final ArrayList<Link<?>> links = new ArrayList<Link<?>>(); + + private final VarListListener inputVarListener = new VarListListener() + { + @Override + public void variableAdded(VarList list, Var<?> variable) + { + descriptor.addInput(getInputVarID(variable), variable); + } + + @Override + public void variableRemoved(VarList list, Var<?> variable) + { + descriptor.removeInput(variable); + } + }; + + private final VarListListener outputVarListener = new VarListListener() + { + @Override + public void variableAdded(VarList list, Var<?> variable) + { + descriptor.addOutput(getOutputVarID(variable), variable); + } + + @Override + public void variableRemoved(VarList list, Var<?> variable) + { + descriptor.removeOutput(variable); + } + }; + + private Thread executionThread; + + private boolean userInterruption = false; + + private ArrayList<BlockDescriptor> blockSelection = new ArrayList<BlockDescriptor>(); + private ArrayList<Link<?>> linkSelection = new ArrayList<Link<?>>(); + + private static final OutputStream nullStream = new OutputStream() + { + @Override + public void write(int b) throws IOException + { + } + }; + + /** + * The stream where the execution log is sent. + */ + private PrintStream logStream = new PrintStream(nullStream); + + public void setLogStream(PrintStream logStream) + { + this.logStream = logStream; + } + + /** + * Searches recursively for the top-level stream where the execution log will be sent + * + * @return The top-level stream where the log is sent + */ + public PrintStream getLogStream() + { + return descriptor.getContainer() == null ? logStream : descriptor.getContainer().getLogStream(); + } + + public WorkFlow() + { + this(false); + } + + public WorkFlow(boolean topLevel) + { + super(); + + this.topLevel = topLevel; + } + + public void addBlock(BlockDescriptor blockInfo) + { + blockInfo.setContainer(this); + + orderedBlocks.add(blockInfo); + blockInfo.addBlockListener(this); + + if (blockInfo.isWorkFlow()) + ((WorkFlow) blockInfo.getBlock()).addListener(this); + + // FIX: sometime getContainer() isn't yet initialized even for internal workflow (Stephane) + // if (descriptor.getContainer() != null) + if (!descriptor.isTopLevelWorkFlow()) + { + // Add the variables from the block to the work flow itself + // this is to allow variable exposing + + // Retrieve input variables + for (Var<?> inputVar : blockInfo.inputVars) + { + // FIX: avoid to duplicate variable (Stephane) + if (!descriptor.inputVars.contains(inputVar)) + descriptor.addInput(getInputVarID(inputVar), inputVar); + } + // Retrieve output variables + for (Var<?> outputVar : blockInfo.outputVars) + { + // FIX: avoid to duplicate variable (Stephane) + if (!descriptor.outputVars.contains(outputVar)) + descriptor.addOutput(getOutputVarID(outputVar), outputVar); + } + + blockInfo.inputVars.addVarListListener(inputVarListener); + blockInfo.outputVars.addVarListListener(outputVarListener); + } + + blockAdded(this, blockInfo); + } + + /** + * @deprecated Use {@link #addBlock(BlockDescriptor)} instead. + * @param block + * Block to add. + * @return Descriptor relative to this workflow of the added block. + */ + @Deprecated + public BlockDescriptor addBlock(Block block) + { + return addBlock(-1, block, new Point()); + } + + /** + * @deprecated Use {@link #addBlock(BlockDescriptor)} instead. + * @param ID + * Block identifier. + * @param block + * Block to be added. + * @param location + * Position to put the block at. + * @return Descriptor relative to this workflow of the added block. + */ + @SuppressWarnings("deprecation") + @Deprecated + public BlockDescriptor addBlock(int ID, Block block, Point location) + { + BlockDescriptor blockDescriptor; + + if (block instanceof WorkFlow) + { + blockDescriptor = ((WorkFlow) block).descriptor; + blockDescriptor.setContainer(this); + blockDescriptor.setLocation(location.x, location.y); + } + else + { + blockDescriptor = new BlockDescriptor(ID, block, this, location); + } + + addBlock(blockDescriptor); + + return blockDescriptor; + } + + /** + * Links the specified variables + * + * @param <T> + * Type of the variable to be linked. + * @param srcBlock + * the source block + * @param srcArgID + * the unique ID of the source (output) variable + * @param dstBlock + * the destination block + * @param dstArgID + * the unique ID of the destination (input) variable + * @return the newly created link + * @throws NoSuchBlockException + * if either the source or destination block is not in this work flow + * @throws NoSuchVariableException + * if either the source or destination variable is not in the corresponding block + */ + public <T> Link<T> addLink(BlockDescriptor srcBlock, String srcArgID, BlockDescriptor dstBlock, String dstArgID) + throws NoSuchBlockException, NoSuchVariableException, ClassCastException + { + if (!orderedBlocks.contains(srcBlock)) + throw new NoSuchBlockException(srcBlock); + if (!orderedBlocks.contains(dstBlock)) + throw new NoSuchBlockException(dstBlock); + + Var<T> srcVar = srcBlock.outputVars.get(srcArgID); + if (srcVar == null) + throw new NoSuchVariableException(srcBlock, srcArgID); + + Var<T> dstVar = dstBlock.inputVars.get(dstArgID); + if (dstVar == null) + throw new NoSuchVariableException(dstBlock, dstArgID); + + return addLink(srcBlock, srcVar, dstBlock, dstVar); + } + + /** + * Creates a link between the given variables and re-schedules the blocks accordingly + * + * @param <T> + * Type of the value of the link that is generated. + * @param srcBlock + * Start-point block descriptor of the link. + * @param srcVar + * Start-point variable instance of the link. + * @param dstBlock + * End-point block descriptor of the link. + * @param dstVar + * End-point variable instance of the link. + * @return Link instance connecting source and destination blocks. + * @throws LoopException + * if a loop is detected in the current scope + * @throws NoSuchVariableException + * if the variable cannot be found in the current scope + * @throws ClassCastException + * If the link is not valid, meaning that the two specified variables are of + * incompatible types. + */ + public <T> Link<T> addLink(BlockDescriptor srcBlock, Var<T> srcVar, BlockDescriptor dstBlock, Var<T> dstVar) + throws LoopException, ClassCastException + { + Link<T> link = checkLink(srcBlock, srcVar, dstBlock, dstVar); + + dstVar.setReference(srcVar); + + // re-order the boxes to make sure srcBlock runs before dstBlock + if (orderedBlocks.indexOf(link.srcBlock) > orderedBlocks.indexOf(link.dstBlock)) + reOrder(link.srcBlock, link.dstBlock); + + links.add(link); + + for (WorkFlowListener listener : listeners) + { + listener.linkAdded(this, link); + } + + return link; + } + + public void addListener(WorkFlowListener listener) + { + listeners.add(listener); + } + + public boolean isTopLevel() + { + return topLevel; + } + + public boolean contains(BlockDescriptor dstBlock) + { + return orderedBlocks.contains(dstBlock); + } + + /** + * Checks whether the specified variables can be linked, and if so returns the pair of + * {@link Block} objects to link + * + * @param <T> + * Type of value being transported through the link. + * @param srcBlock + * the {@link Block} to link from + * @param srcVar + * the {@link Var}iable to link from + * @param dstBlock + * the {@link Block} to link to + * @param dstVar + * the {@link Var}iable to link to + * @return The link instance connecting source and destination variables. + * @throws ClassCastException + * if the link is not valid, meaning that the two specified variables are of + * incompatible types + */ + public <T> Link<T> checkLink(final BlockDescriptor srcBlock, Var<T> srcVar, final BlockDescriptor dstBlock, + Var<T> dstVar) throws ClassCastException + { + Link<T> link = new Link<T>(this, srcBlock, srcVar, dstBlock, dstVar); + + checkScope(link); + + checkLoop(link); + + checkType(link); + + return link; + } + + protected <T> void checkScope(Link<T> link) throws ScopeException + { + if (contains(link.srcBlock) != contains(link.dstBlock)) + throw new ScopeException(); + + // special case: inner loop variables should not link to outside the loop + if (link.srcBlock.isLoop() && ((Loop) link.srcBlock.getBlock()).isLoopVariable(link.srcVar)) + throw new ScopeException(); + } + + protected <T> void checkLoop(Link<T> link) throws LoopException + { + if (link.srcBlock.equals(link.dstBlock) || depends(link.srcBlock, link.dstBlock)) + throw new LoopException(); + } + + protected <T> void checkType(Link<T> link) throws ClassCastException + { + // filter all valid cases + + if (link.dstVar.isAssignableFrom(link.srcVar)) + return; + + if (link.srcVar instanceof VarObject) + return; + + if (link.dstVar instanceof VarMutable) + return; + // { + // VarMutable dst = (VarMutable) link.dstVar; + // dst.setType(link.srcVar.getType()); + // return; + // } + + throw new ClassCastException("<html><h4>Variables \"" + link.dstVar.getName() + "\" and \"" + + link.srcVar.getName() + "\" are of different type and cannot be linked</h4></html>"); + } + + @Override + public void declareInput(VarList inputMap) + { + } + + @Override + public void declareOutput(VarList outputMap) + { + } + + private boolean depends(BlockDescriptor srcBlock, BlockDescriptor dstBlock) + { + boolean srcDependsOnDst = false; + + for (Link<?> link : links) + { + if (link.dstBlock == srcBlock) + { + // link points to srcBlock. + if (link.srcBlock == dstBlock) + return true; + + // else + srcDependsOnDst |= depends(link.srcBlock, dstBlock); + + // exit ASAP + if (srcDependsOnDst) + return true; + } + } + + return srcDependsOnDst; + } + + public BlockDescriptor getBlock(int index) + { + return orderedBlocks.get(index); + } + + public BlockDescriptor getBlockByID(int blockID) throws NoSuchBlockException + { + if (blockID == descriptor.getID()) + return descriptor; + + for (BlockDescriptor bd : orderedBlocks) + if (bd.getID() == blockID) + return bd; + + throw new NoSuchBlockException(blockID, this); + } + + /** + * @param variable + * The input variable to retrieve. + * @return A unique ID for the variable within this work flow (used for XML loading/saving). + */ + public String getInputVarID(Var<?> variable) + { + BlockDescriptor owner = getInputOwner(variable); + + return owner.getID() + ":" + owner.getVarID(variable); + } + + /** + * @param variable + * The input variable to retrieve. + * @return A unique ID for the variable within this work flow (used for XML loading/saving). + */ + public String getOutputVarID(Var<?> variable) + { + BlockDescriptor owner = getOutputOwner(variable); + + return owner.getID() + ":" + owner.getVarID(variable); + } + + public BlockDescriptor getBlockDescriptor() + { + return descriptor; + } + + public BlockDescriptor getInputOwner(Var<?> var) + { + for (BlockDescriptor blockInfo : orderedBlocks) + for (Var<?> inputVar : blockInfo.inputVars) + if (inputVar.equals(var)) + return blockInfo; + + return null; + } + + public BlockDescriptor getOutputOwner(Var<?> var) + { + for (BlockDescriptor blockInfo : orderedBlocks) + for (Var<?> outputVar : blockInfo.outputVars) + if (outputVar.equals(var)) + return blockInfo; + + return null; + } + + public Iterable<Link<?>> getLinksIterator() + { + return new Iterable<Link<?>>() + { + @Override + public Iterator<Link<?>> iterator() + { + return links.iterator(); + } + }; + } + + public int indexOf(BlockDescriptor blockInfo) + { + if (blockInfo == null) + return -1; + + return orderedBlocks.indexOf(blockInfo); + } + + @Override + public Iterator<BlockDescriptor> iterator() + { + // return orderedBlocks.iterator(); + return new Iterator<BlockDescriptor>() + { + private final Iterator<BlockDescriptor> orderedBlocksIt = orderedBlocks.iterator(); + + @Override + public boolean hasNext() + { + return orderedBlocksIt.hasNext(); + } + + @Override + public BlockDescriptor next() + { + return orderedBlocksIt.next(); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + }; + } + + /** + * Give the highest priority to the specified block, by placing giving it the smallest possible + * index in the list (i.e. after its direct dependencies). This method reorganizes the block execution order. + * + * @param blockDesc + * Descriptor of the block to be prioritized. + */ + public void prioritize(BlockDescriptor blockDesc) + { + // retrieve the current order + int currentOrder = indexOf(blockDesc); + // assume we can give the block maximum priority + int targetOrder = 0; + + // find its dependencies + HashSet<BlockDescriptor> dependencies = new HashSet<BlockDescriptor>(); + + for (Link<?> link : links) + if (link.dstBlock == blockDesc && contains(link.srcBlock)) + dependencies.add(link.srcBlock); + + // give piority to the dependencies first + for (BlockDescriptor dependency : dependencies) + prioritize(dependency); + + // calculate the final order + for (BlockDescriptor dependency : dependencies) + { + int order = indexOf(dependency) + 1; + if (order > targetOrder) + targetOrder = order; + } + + // assign the final order + + if (targetOrder != currentOrder) + { + orderedBlocks.add(targetOrder, orderedBlocks.remove(currentOrder)); + for (WorkFlowListener listener : listeners) + listener.workFlowReordered(this); + } + } + + /** + * Re-orders the specified blocks to ensure that prevBlock will execute before nextBlock.<br> + * NOTE: this method assumes that the specified blocks belong to the current work flow. If this + * is not the case, see the {@link #reOrder(Link)} method instead. + * + * @param prevBlock + * The block that should be executed right before this instance. + * @param nextBlock + * The block that should be executed right after this instance. + */ + private void reOrder(BlockDescriptor prevBlock, BlockDescriptor nextBlock) + { + orderedBlocks.remove(prevBlock); + orderedBlocks.add(orderedBlocks.indexOf(nextBlock), prevBlock); + + for (Link<?> link : links) + if (link.dstBlock == prevBlock && orderedBlocks.indexOf(link.srcBlock) > orderedBlocks.indexOf(prevBlock)) + { + reOrder(link.srcBlock, prevBlock); + break; // only do it once, even if multiple links exist + } + + for (WorkFlowListener listener : listeners) + listener.workFlowReordered(this); + } + + public void removeBlock(BlockDescriptor blockInfo, boolean checkSelection) + { + // pops out workflows' selections + if (blockInfo.getBlock() instanceof WorkFlow) + { + WorkFlow wf = ((WorkFlow) blockInfo.getBlock()); + if (checkSelection && !wf.getBlockSelection().isEmpty()) + try + { + // use location in main container instead of in the inner workflow + // avoids jumping toward the top left + for (BlockDescriptor bd : wf) + { + Point contLoc = bd.getContainer().getBlockDescriptor().getLocation(); + Point loc = bd.getLocation(); + bd.setLocation(contLoc.x + loc.x, contLoc.y + loc.y); + } + MainFrame.copySelection(wf, true); + MainFrame.pasteSelection(this, true); + } + catch (LinkCutException e) + { + if (ConfirmDialog.confirm("Warning", e.getMessage(), ConfirmDialog.OK_CANCEL_OPTION)) + { + MainFrame.pasteSelection(this, true); + } + else + return; + } + else + { + checkSelection = false; + } + } + + // 1) remove all links to/from this block + // this must be done first for self-adjusting blocks + // example: Accumulator may remove some variables as soon as they get unlinked + + // 1.a) links from this block elsewhere + for (int i = 0; i < links.size(); i++) + { + Link<?> link = links.get(i); + + if (blockInfo == link.srcBlock) + { + link.dstVar.setReference(null); + links.remove(i--); + + for (WorkFlowListener listener : listeners) + listener.linkRemoved(this, link); + } + } + + // 1.b) links from elsewhere to this block + for (int i = 0; i < links.size(); i++) + { + Link<?> link = links.get(i); + + if (blockInfo == link.dstBlock) + { + link.dstVar.setReference(null); + links.remove(i--); + + for (WorkFlowListener listener : listeners) + listener.linkRemoved(this, link); + } + } + + // 2) remove the block variables (this removes their exposure as well) + + // a work flow does not have variables of its own => nothing to remove + if (!blockInfo.isWorkFlow()) + { + // Remove input variables + for (Var<?> inputVar : blockInfo.inputVars) + this.descriptor.removeInput(inputVar); + + // Remove output variables + for (Var<?> outputVar : blockInfo.outputVars) + this.descriptor.removeOutput(outputVar); + } + + // 3) if the block is a work flow, remove its inner blocks + + // if (blockInfo.isWorkFlow()) + // { + // WorkFlow innerFlow = (WorkFlow) blockInfo.getBlock(); + // while (innerFlow.size() > 0) + // innerFlow.removeBlock(innerFlow.getBlock(0), checkSelection); + // } + + // 4) remove the block itself + + orderedBlocks.remove(blockInfo); + + blockInfo.removeBlockListener(this); + + // 5) Notify listeners... + + for (WorkFlowListener listener : listeners) + { + listener.blockRemoved(this, blockInfo); + listener.workFlowReordered(this); + } + + blockInfo.inputVars.removeVarListListener(inputVarListener); + blockInfo.outputVars.removeVarListListener(outputVarListener); + + blockSelection.remove(blockInfo); + } + + /** + * @param var + * the variable (input or output) to look for + * @return true if the specified variable is linked to another block, false otherwise + */ + public boolean isLinked(Var<?> var) + { + for (Link<?> link : links) + if (link.dstVar == var || link.srcVar == var) + return true; + return false; + } + + /** + * Removes the link to the specified variable, and returns false if this variable wasn't linked + * + * @param dstVar + * the destination variable of the link to remove + */ + public void removeLink(Var<?> dstVar) + { + for (Link<?> link : links) + if (link.dstVar == dstVar && links.remove(link)) + { + link.dstVar.setReference(null); + + for (WorkFlowListener listener : listeners) + listener.linkRemoved(this, link); + + return; + } + + // if code runs here, no link was found + // => check in the parent work flow for links to an "exposed" variable + + if (descriptor.getContainer() != null) + { + descriptor.getContainer().removeLink(dstVar); + } + else + { + throw new NoSuchLinkException("In method WorkFlow.removeLink():\nNo link points to " + + getInputOwner(dstVar).getName() + " > " + dstVar.getName()); + } + } + + public void removeListener(WorkFlowListener listener) + { + listeners.remove(listener); + } + + /** + * Resets all output variables of each block in this work flow to their default value + */ + public void reset() + { + for (BlockDescriptor block : orderedBlocks) + block.reset(); + } + + /** + * Resets and runs this work flow in an independent thread. + */ + public void runWorkFlow() + { + runWorkFlow(false); + } + + /** + * Resets and runs this work flow in an independent thread. + * + * @param waitForCompletion + * <code>true</code> if this method should wait for the work flow to finish before + * returning, or <code>false</code> if the method should return immediately (in this + * case, the status of the work flow can be accessed via {@link #getDescriptor()} + * {@link BlockDescriptor#getStatus() .getStatus()}) + */ + public void runWorkFlow(boolean waitForCompletion) + { + // this is the top-level work flow + // force it "dirty" to run it + this.descriptor.setStatus(BlockStatus.DIRTY); + executionThread = new Thread(this.descriptor, "Workflow"); + executionThread.start(); + + if (waitForCompletion) + { + while (descriptor.getStatus() == BlockStatus.RUNNING) + ThreadUtil.sleep(100); + } + } + + public void interrupt() + { + if (executionThread != null) + { + executionThread.interrupt(); + userInterruption = true; + } + else + { + // this is not the top-level workflow + // => look for it + descriptor.getContainer().interrupt(); + } + } + + private boolean isInterrupted() + { + if (Icy.getMainInterface().isHeadLess()) + return false; + + if (descriptor.getContainer() != null) + return descriptor.getContainer().isInterrupted(); + + return userInterruption || executionThread.isInterrupted(); + } + + /** + * DO NOT CALL DIRECTLY FROM CLIENT CODE: this method assumes running in its own thread, and may + * behave inconsistently if interrupted. Use the {@link #runWorkFlow()} method instead + */ + public void run() + { + if (orderedBlocks.size() == 0) + return; + + descriptor.setStatus(BlockStatus.RUNNING); + + BlockDescriptor runningBlock = null; + + String finalStatus = ""; + userInterruption = false; + + long startTime = 0, endTime = 0; + try + { + startTime = System.nanoTime(); + + String statusPrefix = getBlockDescriptor().getContainer() == null ? "" + : "\"" + getBlockDescriptor().getContainer().descriptor.getName() + "\"" + " => "; + + // get the log stream recursively to ensure it is always the top-level one + PrintStream log = getLogStream(); + + for (int blockIndex = 0; blockIndex < orderedBlocks.size(); blockIndex++) + { + if (isInterrupted()) + throw new StopException(); + + BlockDescriptor blockDescriptor = orderedBlocks.get(blockIndex); + + if (blockDescriptor.getStatus() != BlockStatus.READY) + { + runningBlock = blockDescriptor; + + String blockLog = "block #" + (blockIndex + 1) + " [" + blockDescriptor.getDefinedName() + "]"; + + // Adjust status + statusChanged(this, "Running block " + statusPrefix + "\"" + blockDescriptor.getDefinedName() + + "\" (#" + (blockIndex + 1) + ")..."); + + // Log should be indented + String indentation = ""; + // indent as many times as we go deep in the tree + WorkFlow container = this.descriptor.getContainer(); + while (container != null) + { + indentation += " "; + container = container.descriptor.getContainer(); + } + log.print(indentation + "Running " + blockLog); + + if (blockDescriptor.inputVars.size() > 0) + { + log.println(" with the following parameters:"); + for (Var<?> var : blockDescriptor.inputVars) + { + if (!blockDescriptor.inputVars.isVisible(var)) + continue; + + log.print(indentation + "- " + var.getName() + ": " + var.getValueAsString(true)); + + // If this variable points to another one, say so + Var<?> reference = var.getReference(); + if (reference != null) + { + // The owner of the reference is either... + + // 1) an output variable... + BlockDescriptor owner = getOutputOwner(reference); + // 2) a pass-through input... + if (owner == null) + owner = getInputOwner(reference); + // 3) a variable outside the work flow + if (owner == null) + { + log.println(" (from variable \"" + reference.getName() + + "\" defined outside this workflow)"); + } + else if (owner == this.descriptor) + { + log.println(" (from local loop or batch variable \"" + reference.getName() + "\")"); + } + else + { + log.println(" (from variable \"" + reference.getName() + "\" of block #" + + (indexOf(owner) + 1) + " [" + owner.getDefinedName() + "])"); + } + } + else + log.println(); + } + } + + // THIS IS WHERE IT HAPPENS + long tic = System.nanoTime(); + blockDescriptor.run(); + long tac = System.nanoTime(); + + String time = UnitUtil.displayTimeAsStringWithUnits((tac - tic) / 1000000, false); + log.println(indentation + "Finished " + blockLog + " in " + (time.isEmpty() ? "0 ms" : time)); + + runningBlock = null; + + if (blockDescriptor.isFinalBlock()) + { + blockDescriptor.setFinalBlock(false); + interrupt(); + } + } + } + + if (descriptor.getContainer() != null) + log.println(); + + finalStatus = "The workflow executed successfully"; + } + catch (StopException e) + { + // push the exception upstairs + if (descriptor.getContainer() != null) + throw e; + + finalStatus = "The workflow was interrupted"; + } + catch (RuntimeException e) + { + finalStatus = "The workflow did not execute properly"; + + boolean catchException = false; + + if (e instanceof IcyHandledException || e instanceof VarException) + { + catchException = true; + } + else if (e instanceof BlocksException) + { + catchException = ((BlocksException) e).catchException; + } + + if (catchException) + { + String blockName = runningBlock.getDefinedName(); + int blockID = indexOf(runningBlock) + 1; + throw new IcyHandledException( + "While running block \"" + blockName + "\" (" + blockID + "):\n" + e.getMessage()); + } + + // it's probably a real problem, re-throw + throw e; + } + finally + { + descriptor.setStatus(BlockStatus.READY); + endTime = System.nanoTime(); + double time = endTime - startTime; + finalStatus += " (total running time: " + StringUtil.toString(time / 1e9, 2) + " seconds)"; + statusChanged(this, finalStatus); + } + } + + public void setLocation(BlockDescriptor blockInfo, Point point) + { + blockInfo.setLocation(point.x, point.y); + } + + /** + * @return the number of blocks in this work flow + */ + public int size() + { + return orderedBlocks.size(); + } + + @Override + public void blockStatusChanged(BlockDescriptor blockInfo, BlockStatus status) + { + if (status == BlockStatus.DIRTY) + { + // "dirty-fy" following blocks + for (Link<?> link : links) + if (link.srcBlock == blockInfo) + link.dstBlock.setStatus(BlockStatus.DIRTY); + } + } + + @Override + public void blockVariableAdded(BlockDescriptor block, Var<?> variable) + { + + } + + @Override + public <T> void blockVariableChanged(BlockDescriptor block, Var<T> variable, T newValue) + { + blockVariableChanged(this, block, variable, newValue); + } + + @Override + public void blockCollapsed(BlockDescriptor block, boolean collapsed) + { + + } + + @Override + public void blockDimensionChanged(BlockDescriptor block, int newWidth, int newHeight) + { + blockDimensionChanged(this, block, newWidth, newHeight); + } + + @Override + public void blockLocationChanged(BlockDescriptor block, int newX, int newY) + { + blockLocationChanged(this, block, newX, newY); + } + + @Override + public void blockAdded(WorkFlow source, BlockDescriptor addedBlock) + { + for (WorkFlowListener listener : listeners) + listener.blockAdded(source, addedBlock); + } + + @Override + public void blockRemoved(WorkFlow source, BlockDescriptor removedBlock) + { + for (WorkFlowListener listener : listeners) + listener.blockRemoved(source, removedBlock); + } + + @Override + public void linkAdded(WorkFlow source, Link<?> addedLink) + { + for (WorkFlowListener listener : listeners) + listener.linkAdded(source, addedLink); + } + + @Override + public void linkRemoved(WorkFlow source, Link<?> removedLink) + { + for (WorkFlowListener listener : listeners) + listener.linkRemoved(source, removedLink); + } + + @Override + public void workFlowReordered(WorkFlow source) + { + for (WorkFlowListener listener : listeners) + listener.workFlowReordered(source); + } + + @Override + public void blockCollapsed(WorkFlow source, BlockDescriptor block, boolean collapsed) + { + for (WorkFlowListener listener : listeners) + listener.blockCollapsed(source, block, collapsed); + } + + @Override + public void blockDimensionChanged(WorkFlow source, BlockDescriptor block, int newWidth, int newHeight) + { + for (WorkFlowListener listener : listeners) + listener.blockDimensionChanged(this, block, newWidth, newHeight); + } + + @Override + public void blockLocationChanged(WorkFlow source, BlockDescriptor block, int newX, int newY) + { + for (WorkFlowListener listener : listeners) + listener.blockLocationChanged(this, block, newX, newY); + } + + @Override + public void blockStatusChanged(WorkFlow source, BlockDescriptor block, BlockStatus status) + { + // no need to propagate this event + } + + @Override + public void blockVariableAdded(WorkFlow source, BlockDescriptor block, Var<?> variable) + { + for (WorkFlowListener listener : listeners) + listener.blockVariableAdded(this, block, variable); + } + + @Override + public <T> void blockVariableChanged(WorkFlow source, BlockDescriptor block, Var<T> variable, T newValue) + { + for (WorkFlowListener listener : listeners) + listener.blockVariableChanged(this, block, variable, newValue); + } + + @Override + public void statusChanged(WorkFlow source, String message) + { + for (WorkFlowListener listener : listeners) + listener.statusChanged(source, message); + } + + public void newSelection() + { + blockSelection = new ArrayList<BlockDescriptor>(); + linkSelection = new ArrayList<Link<?>>(); + } + + public boolean isBlockSelected(BlockDescriptor bd) + { + return blockSelection.contains(bd); + } + + public boolean isLinkSelected(Link<?> l) + { + return linkSelection.contains(l); + } + + public void selectBlock(BlockDescriptor bd) + { + if (!isBlockSelected(bd)) + blockSelection.add(bd); + } + + public void selectLink(Link<?> l) + { + if (!isLinkSelected(l)) + linkSelection.add(l); + } + + public void unselectBlock(BlockDescriptor bd) + { + if (isBlockSelected(bd)) + blockSelection.remove(bd); + } + + public void unselectLink(Link<?> l) + { + if (isLinkSelected(l)) + linkSelection.remove(l); + } + + public ArrayList<BlockDescriptor> getBlockSelection() + { + return blockSelection; + } + + public ArrayList<Link<?>> getLinkSelection() + { + return linkSelection; + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/Accumulator.java b/src/main/java/plugins/adufour/blocks/tools/Accumulator.java new file mode 100644 index 0000000..01809e5 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/Accumulator.java @@ -0,0 +1,142 @@ +package plugins.adufour.blocks.tools; + +import java.lang.reflect.Array; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.blocks.util.VarListListener; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarTrigger; +import plugins.adufour.vars.util.TypeChangeListener; +import plugins.adufour.vars.util.VarReferencingPolicy; + +public class Accumulator extends Plugin implements ToolsBlock, TypeChangeListener +{ + Class<?> type = null; + + VarList inputMap; + + VarMutable output = new VarMutable("output", null); + + @Override + public void run() + { + int arrayLength = 0; + + for (Var<?> var : inputMap) + { + if (var.getValue() == null || var instanceof VarTrigger) continue; + + if (var.getValue().getClass().isArray()) + { + arrayLength += Array.getLength(var.getValue()); + } + else arrayLength++; + } + + Object array = Array.newInstance(type.getComponentType(), arrayLength); + + int i = 0; + for (Var<?> var : inputMap) + { + if (var.getValue() == null || var instanceof VarTrigger) continue; + + if (var.getValue().getClass().isArray()) + { + int length = Array.getLength(var.getValue()); + System.arraycopy(var.getValue(), 0, array, i, length); + i += length; + } + else Array.set(array, i++, var.getValue()); + } + + output.setValue(array); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void declareInput(final VarList theInputMap) + { + if (this.inputMap == null) + { + this.inputMap = theInputMap; + VarTrigger trigger = new VarTrigger("Add variable", new VarTrigger.TriggerListener() + { + @Override + public void valueChanged(Var<Integer> source, Integer oldValue, Integer newValue) + { + + } + + @Override + public void referenceChanged(Var<Integer> source, Var<? extends Integer> oldReference, Var<? extends Integer> newReference) + { + + } + + @Override + public void triggered(VarTrigger source) + { + final VarMutable var = new VarMutable("input", type) + { + public boolean isAssignableFrom(Var aVariable) + { + if (this.type == null) return true; + + if (super.isAssignableFrom(aVariable)) return true; + + return type.getComponentType().isAssignableFrom(aVariable.getType()); + }; + }; + var.addTypeChangeListener(Accumulator.this); + theInputMap.addRuntimeVariable("" + var.hashCode(), var); + } + }); + + trigger.setReferencingPolicy(VarReferencingPolicy.NONE); + + theInputMap.add("Add variable", trigger); + + theInputMap.addVarListListener(new VarListListener() + { + @Override + public void variableRemoved(VarList list, Var variable) + { + } + + @Override + public void variableAdded(VarList list, Var variable) + { + if (variable instanceof VarMutable) + { + ((VarMutable) variable).addTypeChangeListener(Accumulator.this); + } + } + }); + } + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output", output); + } + + @Override + public void typeChanged(Object source, Class<?> oldType, Class<?> newType) + { + if (type == null) + { + this.type = (newType.isArray() ? newType : Array.newInstance(newType, 0).getClass()); + + output.setType(type); + + for (Var<?> var : inputMap) + { + if (var instanceof VarMutable) ((VarMutable) var).setType(type); + } + } + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/Display.java b/src/main/java/plugins/adufour/blocks/tools/Display.java new file mode 100644 index 0000000..f77cbd1 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/Display.java @@ -0,0 +1,52 @@ +package plugins.adufour.blocks.tools; + +import icy.main.Icy; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.util.VarListener; + +public class Display extends Plugin implements Block +{ + private VarMutable object = new VarMutable("object", null); + + @Override + public void run() + { + if (Icy.getMainInterface().isHeadLess()) + { + // Print to the standard output stream + System.out.println(object.getValueAsString()); + } + } + + @Override + public void declareOutput(VarList outputMap) + { + + } + + @SuppressWarnings("unchecked") + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("object", object); + + object.addListener(new VarListener<Object>() + { + @Override + public void referenceChanged(Var<Object> source, Var<? extends Object> oldReference, Var<? extends Object> newReference) + { + if (newReference == null && !object.isReferenced()) object.setType(null); + } + + @Override + public void valueChanged(Var<Object> source, Object oldValue, Object newValue) + { + + } + }); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/Indexer.java b/src/main/java/plugins/adufour/blocks/tools/Indexer.java new file mode 100644 index 0000000..c88d8d2 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/Indexer.java @@ -0,0 +1,61 @@ +package plugins.adufour.blocks.tools; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.util.VarException; +import plugins.adufour.vars.util.VarListener; + +/** + * Utility block reading the specified index of an input array. This block uses mutable types to + * receive any type of input and adjust the output accordingly + * + * @author Alexandre Dufour + */ +public class Indexer extends Plugin implements ToolsBlock +{ + VarMutableArray array = new VarMutableArray("array", null); + VarInteger index = new VarInteger("index", 0); + + VarMutable element = new VarMutable("element", null); + + @Override + public void run() + { + if (index.getValue() >= array.size()) throw new VarException(index, "Index " + index.getValueAsString() + " does not exist (array size: " + array.size() + ")"); + + element.setValue(array.getElementAt(index.getValue())); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("array", array); + inputMap.add("index", index); + + array.addListener(new VarListener() + { + @Override + public void valueChanged(Var source, Object oldValue, Object newValue) + { + } + + @Override + public void referenceChanged(Var source, Var oldReference, Var newReference) + { + element.setType(newReference == null ? null : newReference.getType().getComponentType()); + } + }); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("element", element); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/Iterator.java b/src/main/java/plugins/adufour/blocks/tools/Iterator.java new file mode 100644 index 0000000..9e0c05f --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/Iterator.java @@ -0,0 +1,68 @@ +package plugins.adufour.blocks.tools; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.util.VarListener; + +/** + * Utility block reading the specified index of an input array. This block uses mutable types to + * receive any type of input and adjust the output accordingly + * + * @author Alexandre Dufour + */ +public class Iterator extends Plugin implements ToolsBlock +{ + VarMutableArray array = new VarMutableArray("array", null); + VarInteger index = new VarInteger("index", 0); + VarBoolean end = new VarBoolean("end", false); + VarMutable element = new VarMutable("element", null); + + @Override + public void run() + { + if (array.getValue() != null && index.getValue() < array.size()) + { + element.setValue(array.getElementAt(index.getValue())); + index.setValue(index.getValue() + 1); + } + else + { + end.setValue(true); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("array", array); + + array.addListener(new VarListener() + { + @Override + public void valueChanged(Var source, Object oldValue, Object newValue) + { + } + + @Override + public void referenceChanged(Var source, Var oldReference, Var newReference) + { + element.setType(newReference == null ? null : newReference.getType().getComponentType()); + } + }); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("element", element); + outputMap.add("index", index); + outputMap.add("end", end); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ListSize.java b/src/main/java/plugins/adufour/blocks/tools/ListSize.java new file mode 100644 index 0000000..998273c --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ListSize.java @@ -0,0 +1,31 @@ +package plugins.adufour.blocks.tools; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarMutableArray; + +public class ListSize extends Plugin implements ToolsBlock +{ + private final VarMutableArray list = new VarMutableArray("List", null); + + private final VarInteger size = new VarInteger("Size", 0); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("list", list); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("size", size); + } + + @Override + public void run() + { + size.setValue(list.size()); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ReLoop.java b/src/main/java/plugins/adufour/blocks/tools/ReLoop.java new file mode 100644 index 0000000..f8bbee0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ReLoop.java @@ -0,0 +1,68 @@ +package plugins.adufour.blocks.tools; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.util.TypeChangeListener; + +public class ReLoop extends Plugin implements ToolsBlock +{ + public final VarMutable initValue = new VarMutable("init", null); + + public final VarMutable reloopValue = new VarMutable("reloop", null); + + public final VarMutable output = new VarMutable("output", null); + + @Override + public void run() + { + // first run: output is null + if (output.getValue() == null) + { + output.setValue(initValue.getValue()); + } + else + { + output.setValue(reloopValue.getValue()); + } + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("init", initValue); + inputMap.add("reloop", reloopValue); + + initValue.addTypeChangeListener(new TypeChangeListener() + { + @Override + public void typeChanged(Object source, Class<?> oldType, Class<?> newType) + { + if (newType != null) + { + reloopValue.setType(newType); + output.setType(newType); + } + } + }); + + reloopValue.addTypeChangeListener(new TypeChangeListener() + { + @Override + public void typeChanged(Object source, Class<?> oldType, Class<?> newType) + { + if (newType != null) + { + initValue.setType(newType); + output.setType(newType); + } + } + }); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output", output); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ToolsBlock.java b/src/main/java/plugins/adufour/blocks/tools/ToolsBlock.java new file mode 100644 index 0000000..f447b96 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ToolsBlock.java @@ -0,0 +1,8 @@ +package plugins.adufour.blocks.tools; + +import plugins.adufour.blocks.lang.Block; + +public interface ToolsBlock extends Block +{ + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/CallIJMacro.java b/src/main/java/plugins/adufour/blocks/tools/ij/CallIJMacro.java new file mode 100644 index 0000000..fc5262f --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/CallIJMacro.java @@ -0,0 +1,30 @@ +package plugins.adufour.blocks.tools.ij; + +import icy.plugin.abstract_.Plugin; +import ij.IJ; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFile; + +public class CallIJMacro extends Plugin implements IJBlock +{ + VarFile macroFile = new VarFile("Macro file", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("Macro file", macroFile); + } + + @Override + public void declareOutput(VarList outputMap) + { + + } + + @Override + public void run() + { + IJ.runMacroFile(macroFile.getValue(true).getPath()); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/CallIJPlugin.java b/src/main/java/plugins/adufour/blocks/tools/ij/CallIJPlugin.java new file mode 100644 index 0000000..31330e0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/CallIJPlugin.java @@ -0,0 +1,61 @@ +package plugins.adufour.blocks.tools.ij; + +import icy.plugin.abstract_.Plugin; +import icy.system.IcyHandledException; +import ij.IJ; +import ij.ImagePlus; +import ij.WindowManager; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarImagePlus; +import plugins.adufour.vars.lang.VarString; + +public class CallIJPlugin extends Plugin implements IJBlock +{ + VarImagePlus varIp = new VarImagePlus("Input ImagePlus", null); + + VarString pluginName = new VarString("plug-in name", ""); + + VarString pluginParams = new VarString("parameters", ""); + + VarImagePlus varActiveIP = new VarImagePlus("Output (active) ImagePlus", null); + + @Override + public void run() + { + try + { + IJ.run(varIp.getValue(true), pluginName.getValue(true), pluginParams.getValue(true)); + // Set the output image (if available) with the following priority + ImagePlus output = WindowManager.getCurrentImage(); + if (output == null) + { + // Default to the current "temporary" image (if any) + output = WindowManager.getTempCurrentImage(); + } + if (output == null) + { + // Default to the input image (may have been modified "in-place" + output = varIp.getValue(); + } + varActiveIP.setValue(output); + } + catch (RuntimeException e) + { + throw new IcyHandledException(e.getLocalizedMessage()); + } + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("Input ImagePlus", varIp); + inputMap.add("ImageJ plug-in name", pluginName); + inputMap.add("ImageJ plug-in parameters", pluginParams); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("Output ImagePlus", varActiveIP); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/IJBlock.java b/src/main/java/plugins/adufour/blocks/tools/ij/IJBlock.java new file mode 100644 index 0000000..39c20dc --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/IJBlock.java @@ -0,0 +1,13 @@ +package plugins.adufour.blocks.tools.ij; + +import plugins.adufour.blocks.lang.Block; + +/** + * Interface used to flag ImageJ-related blocks + * + * @author Alexandre Dufour + * + */ +public interface IJBlock extends Block +{ +} \ No newline at end of file diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/ImagePlusToSequence.java b/src/main/java/plugins/adufour/blocks/tools/ij/ImagePlusToSequence.java new file mode 100644 index 0000000..ef66688 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/ImagePlusToSequence.java @@ -0,0 +1,32 @@ +package plugins.adufour.blocks.tools.ij; + +import icy.imagej.ImageJUtil; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarImagePlus; +import plugins.adufour.vars.lang.VarSequence; + +public class ImagePlusToSequence extends Plugin implements IJBlock +{ + VarImagePlus vip = new VarImagePlus("IJ ImagePlus", null); + VarSequence vs = new VarSequence("Icy Sequence", null); + + @Override + public void run() + { + vs.setValue(ImageJUtil.convertToIcySequence(vip.getValue(true), null)); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("IJ ImagePlus", vip); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("Icy Sequence", vs); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/SequenceToImagePlus.java b/src/main/java/plugins/adufour/blocks/tools/ij/SequenceToImagePlus.java new file mode 100644 index 0000000..57e5ff0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/SequenceToImagePlus.java @@ -0,0 +1,32 @@ +package plugins.adufour.blocks.tools.ij; + +import icy.imagej.ImageJUtil; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarImagePlus; +import plugins.adufour.vars.lang.VarSequence; + +public class SequenceToImagePlus extends Plugin implements IJBlock +{ + VarSequence vs = new VarSequence("Icy Sequence", null); + VarImagePlus vip = new VarImagePlus("ImagePlus", null); + + @Override + public void run() + { + vip.setValue(ImageJUtil.convertToImageJImage(vs.getValue(true), null)); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("Icy Sequence", vs); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("IJ ImagePlus", vip); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/ij/ShowImagePlus.java b/src/main/java/plugins/adufour/blocks/tools/ij/ShowImagePlus.java new file mode 100644 index 0000000..f18b07c --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/ij/ShowImagePlus.java @@ -0,0 +1,28 @@ +package plugins.adufour.blocks.tools.ij; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarImagePlus; + +public class ShowImagePlus extends Plugin implements IJBlock +{ + VarImagePlus ip = new VarImagePlus("ImagePlus", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("ImagePlus", ip); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + + @Override + public void run() + { + ip.getValue(true).show(); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Boolean.java b/src/main/java/plugins/adufour/blocks/tools/input/Boolean.java new file mode 100644 index 0000000..4eff08b --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Boolean.java @@ -0,0 +1,24 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarBoolean; + +public class Boolean extends Plugin implements InputBlock +{ + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("boolean", new VarBoolean("Boolean", false)); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + + @Override + public void run() + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Decimal.java b/src/main/java/plugins/adufour/blocks/tools/input/Decimal.java new file mode 100644 index 0000000..9e345aa --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Decimal.java @@ -0,0 +1,29 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarDouble; + +/** + * Input block reading a 64-bit double-precision floating-point value + * + * @author Alexandre Dufour + */ +public class Decimal extends Plugin implements InputBlock +{ + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("decimal", new VarDouble("decimal", 0.0)); + } + + @Override + public void run() + { + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Decimals.java b/src/main/java/plugins/adufour/blocks/tools/input/Decimals.java new file mode 100644 index 0000000..1ab76db --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Decimals.java @@ -0,0 +1,29 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarDoubleArrayNative; + +/** + * Utility block reading an array of double-precision floating-point values + * + * @author Alexandre Dufour + */ +public class Decimals extends Plugin implements InputBlock +{ + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("decimals", new VarDoubleArrayNative("decimals", new double[0])); + } + + @Override + public void run() + { + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/File.java b/src/main/java/plugins/adufour/blocks/tools/input/File.java new file mode 100644 index 0000000..141604a --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/File.java @@ -0,0 +1,34 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarFile; + +/** + * Input block reading a file + * + * @author Alexandre Dufour + */ +public class File extends Plugin implements InputBlock +{ + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + VarFile vf = new VarFile("file", null); + vf.setDefaultEditorModel(new FileTypeModel("", FileMode.FILES, null, false)); + inputMap.add("file", vf); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Files.java b/src/main/java/plugins/adufour/blocks/tools/input/Files.java new file mode 100644 index 0000000..38592b4 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Files.java @@ -0,0 +1,34 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeListModel; +import plugins.adufour.vars.lang.VarFileArray; + +/** + * Input block reading a list of files + * + * @author Alexandre Dufour + */ +public class Files extends Plugin implements InputBlock +{ + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + VarFileArray vf = new VarFileArray("files", new java.io.File[0]); + vf.setDefaultEditorModel(new FileTypeListModel("", FileMode.FILES, null, false)); + inputMap.add("files", vf); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Folder.java b/src/main/java/plugins/adufour/blocks/tools/input/Folder.java new file mode 100644 index 0000000..af38bad --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Folder.java @@ -0,0 +1,34 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarFile; + +/** + * Input block reading a folder + * + * @author Alexandre Dufour + */ +public class Folder extends Plugin implements InputBlock +{ + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + VarFile vf = new VarFile("folder", null); + vf.setDefaultEditorModel(new FileTypeModel("", FileMode.FOLDERS, null, false)); + inputMap.add("folder", vf); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Folders.java b/src/main/java/plugins/adufour/blocks/tools/input/Folders.java new file mode 100644 index 0000000..038676e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Folders.java @@ -0,0 +1,34 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeListModel; +import plugins.adufour.vars.lang.VarFileArray; + +/** + * Input block reading a list of folders + * + * @author Alexandre Dufour + */ +public class Folders extends Plugin implements InputBlock +{ + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + VarFileArray vf = new VarFileArray("folders", new java.io.File[0]); + vf.setDefaultEditorModel(new FileTypeListModel("", FileMode.FOLDERS, null, false)); + inputMap.add("folders", vf); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/InputBlock.java b/src/main/java/plugins/adufour/blocks/tools/input/InputBlock.java new file mode 100644 index 0000000..a43653e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/InputBlock.java @@ -0,0 +1,13 @@ +package plugins.adufour.blocks.tools.input; + +import plugins.adufour.blocks.lang.Block; + +/** + * Interface used to flag pure input blocks + * + * @author Alexandre Dufour + * + */ +public interface InputBlock extends Block +{ +} \ No newline at end of file diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Integer.java b/src/main/java/plugins/adufour/blocks/tools/input/Integer.java new file mode 100644 index 0000000..514ca1e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Integer.java @@ -0,0 +1,30 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarInteger; + +/** + * Utility block reading a 32-bit integer value + * + * @author Alexandre Dufour + */ +public class Integer extends Plugin implements InputBlock +{ + @Override + public void declareInput(VarList inputMap) + { + VarInteger vi = new VarInteger("integer", 0); + inputMap.add("integer", vi); + } + + @Override + public void run() + { + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Integers.java b/src/main/java/plugins/adufour/blocks/tools/input/Integers.java new file mode 100644 index 0000000..acae7c2 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Integers.java @@ -0,0 +1,29 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarIntegerArrayNative; + +/** + * Utility block reading an array of 32-bit integer values + * + * @author Alexandre Dufour + */ +public class Integers extends Plugin implements InputBlock +{ + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("integers", new VarIntegerArrayNative("integers", new int[0])); + } + + @Override + public void run() + { + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Sequence.java b/src/main/java/plugins/adufour/blocks/tools/input/Sequence.java new file mode 100644 index 0000000..bd2ab20 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Sequence.java @@ -0,0 +1,36 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarSequence; + +/** + * Utility block reading a {@link icy.sequence.Sequence} object + * + * @author Alexandre Dufour + */ +public class Sequence extends Plugin implements InputBlock +{ + private final VarSequence vs = new VarSequence("sequence", null); + + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input sequence", vs); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + + public VarSequence getVariable() + { + return vs; + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Sequences.java b/src/main/java/plugins/adufour/blocks/tools/input/Sequences.java new file mode 100644 index 0000000..bf5d484 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Sequences.java @@ -0,0 +1,30 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarSequenceArray; + +/** + * Utility block reading a list of {@link icy.sequence.Sequence} objects + * + * @author Alexandre Dufour + */ +public class Sequences extends Plugin implements InputBlock +{ + @Override + public void run() + { + } + + @Override + public void declareInput(VarList inputMap) + { + VarSequenceArray vsa = new VarSequenceArray("sequence"); + inputMap.add("input sequence", vsa); + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/input/Text.java b/src/main/java/plugins/adufour/blocks/tools/input/Text.java new file mode 100644 index 0000000..8ced613 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/input/Text.java @@ -0,0 +1,31 @@ +package plugins.adufour.blocks.tools.input; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarString; + +/** + * Text input + * + * @author Alexandre Dufour + */ +public class Text extends Plugin implements InputBlock +{ + @Override + public void run() + { + + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("text", new VarString("text", "")); + } + + @Override + public void declareOutput(VarList outputMap) + { + + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/AppendFilePath.java b/src/main/java/plugins/adufour/blocks/tools/io/AppendFilePath.java new file mode 100644 index 0000000..9cb77e3 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/AppendFilePath.java @@ -0,0 +1,58 @@ +package plugins.adufour.blocks.tools.io; + +import java.io.File; + +import icy.file.FileUtil; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarString; + +public class AppendFilePath extends Plugin implements IOBlock +{ + VarMutable in = new VarMutable("Current file", null); + + VarFile out = new VarFile("New file", null); + + VarBoolean removeExt = new VarBoolean("Remove extension", false); + + VarString suffix = new VarString("Add suffix", ""); + + @Override + public void run() + { + Object input = in.getValue(true); + String newPath = ""; + + if (input instanceof File) + newPath = ((File)input).getAbsolutePath(); + else newPath = input.toString(); + + if (removeExt.getValue()) + { + String fileName = FileUtil.getFileName(newPath, false); + newPath = FileUtil.getDirectory(newPath) + fileName; + } + + newPath += suffix.getValue(); + + out.setValue(new File(newPath)); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input file", in); + inputMap.add("remove ext.", removeExt); + inputMap.add("suffix", suffix); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output file", out); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/CreateFile.java b/src/main/java/plugins/adufour/blocks/tools/io/CreateFile.java new file mode 100644 index 0000000..f327782 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/CreateFile.java @@ -0,0 +1,62 @@ +package plugins.adufour.blocks.tools.io; + +import java.io.File; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarString; +import plugins.adufour.vars.util.VarException; +import icy.file.FileUtil; +import icy.plugin.abstract_.Plugin; + +public class CreateFile extends Plugin implements IOBlock +{ + VarFile parentFolder = new VarFile("Base folder", new File(System.getProperty("user.home"))); + + VarString fileName = new VarString("File name", "newFile"); + + VarFile file = new VarFile("New file", null); + + VarBoolean overwrite = new VarBoolean("Overwrite", false); + + @Override + public void declareInput(VarList inputMap) + { + parentFolder.setDefaultEditorModel(new FileTypeModel(parentFolder.getValue().getPath(), FileMode.FOLDERS, null, true)); + inputMap.add("parent folder", parentFolder); + inputMap.add("file name", fileName); + inputMap.add("overwrite", overwrite); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("new file", file); + } + + @Override + public void run() + { + File parent = parentFolder.getValue(); + + if (parent == null || !parent.exists() || !parent.isDirectory()) throw new VarException(parentFolder, "The parent folder does not exist or is not a folder"); + + if (fileName.getValue().isEmpty()) throw new VarException(fileName, "The name of the file cannot be empty"); + + File newFile = new File(parentFolder + File.separator + fileName.getValue()); + + if (!newFile.exists()) + { + FileUtil.createFile(newFile); + } + else if (!newFile.isDirectory() && !overwrite.getValue()) + { + throw new VarException(fileName, "Cannot create file " + newFile.getPath() + "\n=> A file with this name already exists"); + } + + file.setValue(newFile); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/CreateFolder.java b/src/main/java/plugins/adufour/blocks/tools/io/CreateFolder.java new file mode 100644 index 0000000..e0efdc0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/CreateFolder.java @@ -0,0 +1,57 @@ +package plugins.adufour.blocks.tools.io; + +import icy.file.FileUtil; +import icy.plugin.abstract_.Plugin; + +import java.io.File; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarString; +import plugins.adufour.vars.util.VarException; + +public class CreateFolder extends Plugin implements IOBlock +{ + VarFile parentFolder = new VarFile("Base folder", new File(System.getProperty("user.home"))); + + VarString folderName = new VarString("Folder name", "myFolder"); + + VarFile folder = new VarFile("New folder", null); + + @Override + public void declareInput(VarList inputMap) + { + parentFolder.setDefaultEditorModel(new FileTypeModel(parentFolder.getValue().getPath(), FileMode.FOLDERS, null, true)); + inputMap.add("parent folder", parentFolder); + inputMap.add("folder name", folderName); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("new folder", folder); + } + + @Override + public void run() + { + File parent = parentFolder.getValue(); + + if (parent == null || !parent.exists() || !parent.isDirectory()) throw new VarException(parentFolder, "The parent folder does not exist or is not a folder"); + + if (folderName.getValue().isEmpty()) throw new VarException(folderName, "The name of the folder cannot be empty"); + + File newFolder = new File(parentFolder + File.separator + folderName.getValue()); + + if (newFolder.exists() && !newFolder.isDirectory()) + { + throw new VarException(folderName, "Cannot create folder " + newFolder.getPath() + "\n=> A file with this name already exists"); + } + + FileUtil.createDir(newFolder); + + folder.setValue(newFolder); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/FileToPath.java b/src/main/java/plugins/adufour/blocks/tools/io/FileToPath.java new file mode 100644 index 0000000..e38996e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/FileToPath.java @@ -0,0 +1,31 @@ +package plugins.adufour.blocks.tools.io; + +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarString; + +public class FileToPath extends Plugin implements IOBlock +{ + VarString path = new VarString("path", "", 1); + + VarFile file = new VarFile("file", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input file", file); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output path", path); + } + + @Override + public void run() + { + path.setValue(file.getValue() == null ? null : file.getValue().getPath()); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/FileToSequence.java b/src/main/java/plugins/adufour/blocks/tools/io/FileToSequence.java new file mode 100644 index 0000000..93c386a --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/FileToSequence.java @@ -0,0 +1,56 @@ +package plugins.adufour.blocks.tools.io; + +import icy.file.Loader; +import icy.plugin.abstract_.Plugin; +import icy.sequence.Sequence; + +import java.io.File; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarSequence; +import plugins.adufour.vars.util.VarException; + +/** + * Utility block that reads a file from disk + * + * @author Alexandre Dufour + */ +public class FileToSequence extends Plugin implements IOBlock +{ + VarFile inputFile = new VarFile("input file", null); + VarSequence outputSequence = new VarSequence("sequence", null); + VarInteger series = new VarInteger("Series", 0); + + @Override + public void run() + { + try + { + File file = inputFile.getValue(true); + Sequence sequence = Loader.loadSequence(file.getPath(), series.getValue(), false); + if (sequence == null) throw new VarException(inputFile, "Cannot read " + file.getPath() + " into a sequence"); + outputSequence.setValue(sequence); + } + catch (Exception e) + { + File file = inputFile.getValue(true); + throw new VarException(inputFile, "unable to read file " + file.getAbsolutePath()); + } + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input file", inputFile); + inputMap.add("Series", series); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("sequence", outputSequence); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/FilesToSequence.java b/src/main/java/plugins/adufour/blocks/tools/io/FilesToSequence.java new file mode 100644 index 0000000..3dc196d --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/FilesToSequence.java @@ -0,0 +1,57 @@ +package plugins.adufour.blocks.tools.io; + +import icy.file.Loader; +import icy.plugin.abstract_.Plugin; +import icy.sequence.Sequence; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFileArray; +import plugins.adufour.vars.lang.VarSequence; +import plugins.adufour.vars.util.VarException; + +/** + * Utility block that reads multiple image files from disk into a {@link Sequence} + * + * @author Alexandre Dufour + */ +public class FilesToSequence extends Plugin implements IOBlock +{ + VarFileArray inputFiles = new VarFileArray("input file", new File[] {}); + VarSequence outputSequence = new VarSequence("sequence", null); + + @Override + public void run() + { + try + { + File[] files = inputFiles.getValue(true); + List<String> paths = new ArrayList<String>(files.length); + + for (int i = 0; i < files.length; i++) + if (files[i] != null) paths.add(files[i].getPath()); + + outputSequence.setValue(Loader.loadSequence(paths, false)); + } + catch (Exception e) + { + throw new VarException(inputFiles, "unable to read files"); + } + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input file", inputFiles); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("sequence", outputSequence); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/GetSequenceFolder.java b/src/main/java/plugins/adufour/blocks/tools/io/GetSequenceFolder.java new file mode 100644 index 0000000..5cfbf86 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/GetSequenceFolder.java @@ -0,0 +1,40 @@ +package plugins.adufour.blocks.tools.io; + +import java.io.File; + +import icy.file.FileUtil; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.tools.sequence.SequenceBlock; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarSequence; +import plugins.adufour.vars.util.VarException; + +public class GetSequenceFolder extends Plugin implements SequenceBlock, IOBlock +{ + VarSequence sequence = new VarSequence("Sequence", null); + + VarFile folder = new VarFile("Folder", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("Sequence", sequence); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("Folder", folder); + } + + @Override + public void run() + { + String filePath = sequence.getValue(true).getFilename(); + if (filePath == null) throw new VarException(sequence, "[Get sequence folder]: the selected sequence has not been saved on disk"); + + String folderPath = FileUtil.getDirectory(filePath); + folder.setValue(new File(folderPath)); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/IOBlock.java b/src/main/java/plugins/adufour/blocks/tools/io/IOBlock.java new file mode 100644 index 0000000..2c8eab2 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/IOBlock.java @@ -0,0 +1,14 @@ +package plugins.adufour.blocks.tools.io; + +import plugins.adufour.blocks.lang.Block; + +/** + * Interface used to mark blocks as I/O blocks (and appear in the appropriate menu) + * + * @author Alexandre Dufour + * + */ +public interface IOBlock extends Block +{ + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/PathToFile.java b/src/main/java/plugins/adufour/blocks/tools/io/PathToFile.java new file mode 100644 index 0000000..e1a1155 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/PathToFile.java @@ -0,0 +1,34 @@ +package plugins.adufour.blocks.tools.io; + +import icy.plugin.abstract_.Plugin; + +import java.io.File; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarString; + +public class PathToFile extends Plugin implements IOBlock +{ + VarString path = new VarString("path", "", 1); + + VarFile file = new VarFile("file", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input path", path); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output file", file); + } + + @Override + public void run() + { + file.setValue(new File(path.getValue(true))); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/SendToSwimmingPool.java b/src/main/java/plugins/adufour/blocks/tools/io/SendToSwimmingPool.java new file mode 100644 index 0000000..7ebfcd0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/SendToSwimmingPool.java @@ -0,0 +1,47 @@ +package plugins.adufour.blocks.tools.io; + +import javax.swing.SwingUtilities; + +import icy.main.Icy; +import icy.plugin.abstract_.Plugin; +import icy.swimmingPool.SwimmingObject; +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarMutable; + +/** + * Sends any data to the swimming pool + * + * @author Alexandre Dufour + */ +public class SendToSwimmingPool extends Plugin implements Block +{ + private VarMutable data = new VarMutable("Data", null); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("Data", data); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + + @Override + public void run() + { + if (data.getValue() == null) return; + + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + Icy.getMainInterface().getSwimmingPool().add(new SwimmingObject(data.getValue(), data.getReference().getName())); + } + }); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/io/SequenceToFile.java b/src/main/java/plugins/adufour/blocks/tools/io/SequenceToFile.java new file mode 100644 index 0000000..f3c4a87 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/io/SequenceToFile.java @@ -0,0 +1,47 @@ +package plugins.adufour.blocks.tools.io; + +import icy.file.Saver; +import icy.plugin.abstract_.Plugin; + +import java.io.File; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.FileMode; +import plugins.adufour.vars.gui.model.FileTypeModel; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarSequence; + +/** + * Utility block that saves a sequence to disk + * + * @author Alexandre Dufour + */ +public class SequenceToFile extends Plugin implements IOBlock +{ + private VarFile folder = new VarFile("File or folder", null); + private VarSequence sequence = new VarSequence("sequence", null); + + @Override + public void run() + { + File f = new File(folder.getValue(true).getAbsolutePath()); + if (f.isDirectory()) + { + f = new File(f.getAbsolutePath() + File.separator + sequence.getValue(true) + ".tif"); + } + Saver.save(sequence.getValue(true), f, false, false); + } + + @Override + public void declareInput(VarList inputMap) + { + folder.setDefaultEditorModel(new FileTypeModel("", FileMode.FOLDERS, null, false)); + inputMap.add("folder", folder); + inputMap.add("sequence", sequence); + } + + @Override + public void declareOutput(VarList outputMap) + { + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/AddROIToSequence.java b/src/main/java/plugins/adufour/blocks/tools/roi/AddROIToSequence.java new file mode 100644 index 0000000..231821d --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/AddROIToSequence.java @@ -0,0 +1,56 @@ +package plugins.adufour.blocks.tools.roi; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import icy.sequence.Sequence; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.adufour.vars.lang.VarSequence; + +/** + * (Deprecated: Use plugins.tprovoost.sequenceblocks.add.AddRois instead) + * Block to add one or several {@link ROI} to a Sequence. + * + * class: plugins.adufour.blacks.tools.roi.AddROIToSequence + * + * @author Alexandre Dufour + */ +@Deprecated +public class AddROIToSequence extends Plugin implements ROIBlock +{ + VarROIArray rois = new VarROIArray("ROI to add"); + + VarSequence sequence = new VarSequence("Source", null); + + VarBoolean replace = new VarBoolean("Overwrite", false); + + @Override + public void run() + { + Sequence s = sequence.getValue(true); + + if (replace.getValue()) s.removeAllROI(); + + s.beginUpdate(); + for (ROI roi : rois.getValue(true)) + s.addROI(roi); + s.endUpdate(); + + s.saveXMLData(); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("target sequence", sequence); + inputMap.add("input rois", rois); + inputMap.add("replace existing", replace); + } + + @Override + public void declareOutput(VarList outputMap) + { + + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/CropSequenceToROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/CropSequenceToROI.java new file mode 100644 index 0000000..fa22cb8 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/CropSequenceToROI.java @@ -0,0 +1,114 @@ +package plugins.adufour.blocks.tools.roi; + +import icy.image.IcyBufferedImage; +import icy.image.IcyBufferedImageUtil; +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import icy.sequence.Sequence; +import icy.type.rectangle.Rectangle5D; +import icy.util.OMEUtil; +import icy.util.StringUtil; + +import java.awt.Rectangle; +import java.util.ArrayList; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarGenericArray; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.adufour.vars.lang.VarSequence; + +public class CropSequenceToROI extends Plugin implements ROIBlock +{ + VarSequence input = new VarSequence("Sequence to crop", null); + + VarROIArray rois = new VarROIArray("List of ROI"); + + VarGenericArray<Sequence[]> output = new VarGenericArray<Sequence[]>("List of crops", Sequence[].class, new Sequence[] {}); + + @Override + public void run() + { + Sequence seq = input.getValue(true); + + int nbROIs = rois.getValue(true).length; + + ArrayList<Sequence> crops = new ArrayList<Sequence>(nbROIs); + + int cpt = 1; + int digitSize = 1 + (int) Math.log10(nbROIs); + + for (ROI roi : rois.getValue()) + { + final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(seq.getOMEXMLMetadata())); + + Rectangle5D.Integer region = roi.getBounds5D().toInteger(); + + final Rectangle region2d = region.toRectangle2D().getBounds(); + final int startZ; + final int endZ; + final int startT; + final int endT; + + if (region.isInfiniteZ()) + { + startZ = 0; + endZ = seq.getSizeZ(); + } + else + { + startZ = Math.max(0, region.z); + endZ = Math.min(seq.getSizeZ(), region.z + region.sizeZ); + } + if (region.isInfiniteT()) + { + startT = 0; + endT = seq.getSizeT(); + } + else + { + startT = Math.max(0, region.t); + endT = (int) Math.min(seq.getSizeT(), (long) region.t + (long) region.sizeT); + } + + result.beginUpdate(); + try + { + for (int t = startT; t < endT; t++) + { + for (int z = startZ; z < endZ; z++) + { + IcyBufferedImage img = seq.getImage(t, z); + + if (img != null) img = IcyBufferedImageUtil.getSubImage(img, region2d, region.c, region.sizeC); + + result.setImage(t - startT, z - startZ, img); + } + } + } + finally + { + result.endUpdate(); + } + + result.setName(seq.getName() + "_crop" + StringUtil.toString(cpt, digitSize)); + crops.add(result); + cpt++; + } + + output.setValue(crops.toArray(new Sequence[crops.size()])); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("sequence to crop", input); + inputMap.add("list of ROI", rois); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("list of crops", output); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/DilateROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/DilateROI.java new file mode 100644 index 0000000..1a984cd --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/DilateROI.java @@ -0,0 +1,265 @@ +package plugins.adufour.blocks.tools.roi; + +import java.awt.Point; +import java.util.ArrayList; + +import icy.roi.BooleanMask2D; +import icy.roi.BooleanMask3D; +import icy.roi.ROI; +import icy.roi.ROI2D; +import icy.roi.ROI3D; +import icy.type.point.Point3D; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.adufour.vars.util.VarException; +import plugins.kernel.roi.roi2d.ROI2DArea; +import plugins.kernel.roi.roi3d.ROI3DArea; + +public class DilateROI extends MorphROI +{ + public DilateROI() + { + roiOUT = new VarROIArray("Dilated ROI"); + } + + @Override + public void run() + { + switch (unit.getValue()) + { + case PIXELS: + roiOUT.setValue(dilateROI(roiIN.getValue(), x.getValue(), y.getValue(), z.getValue())); + break; + case PERCENTAGE: + roiOUT.setValue(dilateROIByPercentage(roiIN.getValue(), x.getValue(), y.getValue(), z.getValue())); + break; + default: + throw new VarException(unit, "Unsupported unit"); + } + } + + /** + * Perform a morphological dilation on the specified set of ROI by the given amount in each + * dimension + * + * @param inputRoi + * The ROI to dilate + * @param xRadius + * the radius (in pixels) along X + * @param yRadius + * the radius (in pixels) along X + * @param zRadius + * the radius (in pixels) along Z (not used if <code>roi</code> is 2D) + * @return a new set of dilated ROI of type "area" + */ + public static ROI[] dilateROI(ROI[] inputRoi, int xRadius, int yRadius, int zRadius) + { + ArrayList<ROI> out = new ArrayList<ROI>(inputRoi.length); + + for (ROI roi : inputRoi) + { + if (Thread.currentThread().isInterrupted()) + break; + + ROI dilated = dilateROI(roi, xRadius, yRadius, zRadius); + if (dilated != null) + out.add(dilated); + } + + return out.toArray(new ROI[out.size()]); + } + + /** + * Perform a morphological dilation on the specified set of ROI by the given percentage in each + * dimension + * + * @param inputRoi + * the ROI to dilate + * @param xPct + * the percentage (from 0 to 100) to dilate along X + * @param yPct + * the percentage (from 0 to 100) to dilate along Y + * @param zPct + * the percentage (from 0 to 100) to dilate along Z (not used in 2D) + * @return a new set of dilated ROI of type "area" + */ + public static ROI[] dilateROIByPercentage(ROI[] inputRoi, int xPct, int yPct, int zPct) + { + ArrayList<ROI> out = new ArrayList<ROI>(inputRoi.length); + + for (ROI roi : inputRoi) + { + if (Thread.currentThread().isInterrupted()) + break; + + ROI dilated = dilateROIByPercentage(roi, xPct, yPct, zPct); + if (dilated != null) + out.add(dilated); + } + + return out.toArray(new ROI[out.size()]); + } + + /** + * Perform a morphological dilation on the specified ROI by the given percentage in each + * dimension + * + * @author Joel Rogers, Alexandre Dufour + * @param roi + * the ROI to dilate + * @param xPct + * the percentage (from 0 to 100) to dilate along X + * @param yPct + * the percentage (from 0 to 100) to dilate along Y + * @param zPct + * the percentage (from 0 to 100) to dilate along Z (not used in 2D) + * @return a new, dilated ROI of type "area" + */ + public static ROI dilateROIByPercentage(ROI roi, int xPct, int yPct, int zPct) + { + int xRadius = percentageToRadiusX(roi, xPct); + int yRadius = percentageToRadiusY(roi, yPct); + int zRadius = percentageToRadiusZ(roi, zPct); + + return dilateROI(roi, xRadius, yRadius, zRadius); + } + + /** + * Perform a morphological dilation on the specified ROI by the given radius in each dimension + * + * @param roi + * the ROI to dilate + * @param xRadius + * the radius in pixels along X + * @param yRadius + * the radius in pixels along X + * @param zRadius + * the radius in pixels along Z (not used if <code>roi</code> is 2D) + * @return a new, dilated ROI of type "area" + */ + public static ROI dilateROI(ROI roi, int xRadius, int yRadius, int zRadius) + { + int rx = xRadius, rrx = rx * rx; + int ry = yRadius, rry = ry * ry; + int rz = zRadius, rrz = rz * rz; + + if (roi instanceof ROI2D) + { + BooleanMask2D m2 = ((ROI2D) roi).getBooleanMask(true); + ROI2DArea r2 = new ROI2DArea(m2); + r2.setC(((ROI2D) roi).getC()); + r2.setZ(((ROI2D) roi).getZ()); + r2.setT(((ROI2D) roi).getT()); + r2.setName(roi.getName() + " dilated[" + xRadius + "," + yRadius + "]"); + + r2.beginUpdate(); + + for (Point p : m2.getContourPoints()) + { + // Brute force + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + + if (xr2 + yr2 <= 1.0) + { + if (!m2.contains(p.x + x, p.y + y)) + r2.addPoint(p.x + x, p.y + y); + } + } + + // Bresenham style + // int x = r; + // int y = 0; + // int xChange = 1 - (r << 1); + // int yChange = 0; + // int radiusError = 0; + // + // while (x >= y) + // { + // for (int i = p.x - x; i <= p.x + x; i++) + // { + // if (!m2.contains(i, p.y + y)) r2.addPoint(i, p.y + y); + // if (!m2.contains(i, p.y - y)) r2.addPoint(i, p.y - y); + // } + // for (int i = p.x - y; i <= p.x + y; i++) + // { + // if (!m2.contains(i, p.y + x)) r2.addPoint(i, p.y + x); + // if (!m2.contains(i, p.y - x)) r2.addPoint(i, p.y - x); + // } + // + // y++; + // radiusError += yChange; + // yChange += 2; + // if (((radiusError << 1) + xChange) > 0) + // { + // x--; + // radiusError += xChange; + // xChange += 2; + // } + // } + } + r2.endUpdate(); + + return r2; + } + else if (roi instanceof ROI3D) + { + ROI3D roi3D = (ROI3D) roi; + + BooleanMask3D m3 = roi3D.getBooleanMask(true); + + ROI3DArea r3 = new ROI3DArea(m3); + r3.setC(((ROI3D) roi).getC()); + r3.setT(((ROI3D) roi).getT()); + r3.setName(roi.getName() + " dilated[" + xRadius + "," + yRadius + "," + zRadius + "]"); + + r3.beginUpdate(); + + for (Point3D.Integer p : m3.getContourPoints()) + { // Brute force + + if (rrz == 0) + { + int z = 0; + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + + if (xr2 + yr2 <= 1.0) + { + if (!m3.contains(p.x + x, p.y + y, p.z + z)) + r3.addPoint(p.x + x, p.y + y, p.z + z); + } + } + } + else + { + for (int z = -rz; z <= rz; z++) + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + double zr2 = rrz == 0 ? 0 : z * z / rrz; + + if (xr2 + yr2 + zr2 <= 1.0) + { + if (!m3.contains(p.x + x, p.y + y, p.z + z)) + r3.addPoint(p.x + x, p.y + y, p.z + z); + } + } + } + } + r3.endUpdate(); + r3.optimizeBounds(); + return r3; + } + + System.out.println("[Dilate ROI] Warning: unsupported ROI: " + roi.getName()); + return null; + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/ErodeROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/ErodeROI.java new file mode 100644 index 0000000..aa0fc7d --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/ErodeROI.java @@ -0,0 +1,259 @@ +package plugins.adufour.blocks.tools.roi; + +import java.awt.Point; +import java.util.ArrayList; + +import icy.roi.BooleanMask2D; +import icy.roi.BooleanMask3D; +import icy.roi.ROI; +import icy.roi.ROI2D; +import icy.roi.ROI3D; +import icy.type.point.Point3D; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.adufour.vars.util.VarException; +import plugins.kernel.roi.roi2d.ROI2DArea; +import plugins.kernel.roi.roi3d.ROI3DArea; + +public class ErodeROI extends MorphROI +{ + public ErodeROI() + { + roiOUT = new VarROIArray("Eroded ROI"); + } + + @Override + public void run() + { + switch (unit.getValue()) + { + case PIXELS: + roiOUT.setValue(erodeROI(roiIN.getValue(), x.getValue(), y.getValue(), z.getValue())); + break; + case PERCENTAGE: + roiOUT.setValue(erodeROIByPercentage(roiIN.getValue(), x.getValue(), y.getValue(), z.getValue())); + break; + default: + throw new VarException(unit, "Unsupported unit"); + } + } + + /** + * Perform a morphological erosion on the specified set of ROI by the given radius in each + * dimension + * + * @param inputRoi + * the ROI to erode + * @param xRadius + * the radius (in pixels) along X + * @param yRadius + * the radius (in pixels) along Y + * @param zRadius + * the radius (in pixels) along Z (not used if <code>roi</code> is 2D) + * @return a new set of eroded ROI of type "area" + */ + public static ROI[] erodeROI(ROI[] inputRoi, int xRadius, int yRadius, int zRadius) + { + ArrayList<ROI> out = new ArrayList<ROI>(inputRoi.length); + + for (ROI roi : inputRoi) + { + if (Thread.currentThread().isInterrupted()) + break; + + ROI eroded = erodeROI(roi, xRadius, yRadius, zRadius); + if (eroded != null) + out.add(eroded); + } + + return out.toArray(new ROI[out.size()]); + } + + /** + * Perform a morphological erosion on the specified set of ROI by the given scale factor in each + * dimension + * + * @param inputRoi + * the ROI to erode + * @param xPct + * the percentage (from 0 to 100) to erode along X + * @param yPct + * the percentage (from 0 to 100) to erode along Y + * @param zPct + * the percentage (from 0 to 100) to erode along Z (not used in 2D) + * @return a new set of eroded ROI of type "area" + */ + public static ROI[] erodeROIByPercentage(ROI[] inputRoi, int xPct, int yPct, int zPct) + { + ArrayList<ROI> out = new ArrayList<ROI>(inputRoi.length); + + for (ROI roi : inputRoi) + { + if (Thread.currentThread().isInterrupted()) + break; + + ROI eroded = erodeROIByPercentage(roi, xPct, yPct, zPct); + if (eroded != null) + out.add(eroded); + } + + return out.toArray(new ROI[out.size()]); + } + + /** + * Perform a morphological erosion on the specified ROI by the given scale factor in each + * dimension + * + * @author Joel Rogers, Alexandre Dufour + * @param roi + * the ROI to erode + * @param xPct + * the percentage (from 0 to 100) to dilate along X + * @param yPct + * the percentage (from 0 to 100) to dilate along Y + * @param zPct + * the percentage (from 0 to 100) to dilate along Z (not used in 2D) + * @return a new, dilated ROI of type "area" + */ + public static ROI erodeROIByPercentage(ROI roi, int xPct, int yPct, int zPct) + { + int xRadius = percentageToRadiusX(roi, xPct); + int yRadius = percentageToRadiusY(roi, yPct); + int zRadius = percentageToRadiusZ(roi, zPct); + + return erodeROI(roi, xRadius, yRadius, zRadius); + } + + /** + * Perform a morphological erosion on the specified ROI by the given radius in each dimension + * + * @param roi + * the ROI to erode + * @param xRadius + * the radius in pixels along X + * @param yRadius + * the radius in pixels along X + * @param zRadius + * the radius in pixels along Z (not used if <code>roi</code> is 2D) + * @return a new, eroded ROI of type "area" + */ + public static ROI erodeROI(ROI roi, int xRadius, int yRadius, int zRadius) + { + // The basis of this erosion operator is to remove all pixels within a distance of "radius" + // from the border. Since we have easy access to the contour points of the ROI, we will + // start from there and instead use a radius of "radius - 1" when searching for pixels to + // erase, so as to be consistent with the dual dilation operator, such that openings + // (erosion + dilation) and closings (dilation + erosion) preserve the global ROI size + + int rx = Math.max(0, xRadius - 1), rrx = rx * rx; + int ry = Math.max(0, yRadius - 1), rry = ry * ry; + int rz = Math.max(0, zRadius - 1), rrz = rz * rz; + + if (roi instanceof ROI2D) + { + BooleanMask2D m2 = ((ROI2D) roi).getBooleanMask(true); + ROI2DArea r2 = new ROI2DArea(m2); + r2.setC(((ROI2D) roi).getC()); + r2.setZ(((ROI2D) roi).getZ()); + r2.setT(((ROI2D) roi).getT()); + r2.setName(roi.getName() + " eroded[" + xRadius + "," + yRadius + "]"); + + r2.beginUpdate(); + + for (Point p : m2.getContourPoints()) + { + // Brute force + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + + // correct the sphere equation to include the outer rim for each pixel + if (xr2 + yr2 <= 2.0) + { + if (m2.contains(p.x + x, p.y + y)) + r2.removePoint(p.x + x, p.y + y); + } + } + } + r2.endUpdate(); + + return r2.getNumberOfPoints() > 0 ? r2 : null; + } + else if (roi instanceof ROI3D) + { + ROI3D roi3D = (ROI3D) roi; + + BooleanMask3D m3 = roi3D.getBooleanMask(true); + + ROI3DArea r3 = new ROI3DArea(m3); + r3.setC(((ROI3D) roi).getC()); + r3.setT(((ROI3D) roi).getT()); + r3.setName(roi.getName() + " eroded[" + xRadius + "," + yRadius + "," + zRadius + "]"); + + r3.beginUpdate(); + + for (Point3D.Integer p : m3.getContourPoints()) + { // Brute force + + if (rrz == 0) + { + int z = 0; + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + + // correct the sphere equation to include the outer rim for each pixel + if (xr2 + yr2 <= 2.0) + { + if (m3.contains(p.x + x, p.y + y, p.z + z)) + try + { + r3.removePoint(p.x + x, p.y + y, p.z + z); + } + catch (ArrayIndexOutOfBoundsException aioobe) + { + // FIXME @Stephane, what's wrong here?! + } + } + } + } + else + { + for (int z = -rz; z <= rz; z++) + for (int y = -ry; y <= ry; y++) + for (int x = -rx; x <= rx; x++) + { + double xr2 = rrx == 0 ? 0 : x * x / rrx; + double yr2 = rry == 0 ? 0 : y * y / rry; + double zr2 = rrz == 0 ? 0 : z * z / rrz; + + // correct the sphere equation to include the outer rim for each + // pixel + if (xr2 + yr2 + zr2 <= 2.0) + { + if (m3.contains(p.x + x, p.y + y, p.z + z)) + try + { + r3.removePoint(p.x + x, p.y + y, p.z + z); + } + catch (ArrayIndexOutOfBoundsException aioobe) + { + // FIXME @Stephane, what's wrong here?! + } + } + } + } + } + r3.endUpdate(); + r3.optimizeBounds(); + + return r3.getNumberOfPoints() > 0 ? r3 : null; + } + + System.out.println("[Erode ROI] Warning: unsupported ROI: " + roi.getName()); + return null; + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/GetROIFromSequence.java b/src/main/java/plugins/adufour/blocks/tools/roi/GetROIFromSequence.java new file mode 100644 index 0000000..29024d6 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/GetROIFromSequence.java @@ -0,0 +1,72 @@ +package plugins.adufour.blocks.tools.roi; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import icy.sequence.Sequence; + +import java.util.ArrayList; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarEnum; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.adufour.vars.lang.VarSequence; + +public class GetROIFromSequence extends Plugin implements ROIBlock +{ + public enum ROIFilter + { + SELECTED, NON_SELECTED, ALL + } + + VarEnum<ROIFilter> filter = new VarEnum<ROIFilter>("ROI to get", ROIFilter.ALL); + + VarROIArray rois = new VarROIArray("List of ROI"); + + VarSequence sequence = new VarSequence("Source", null); + + @Override + public void run() + { + Sequence s = sequence.getValue(); + + if (s == null) + { + rois.setValue(new ROI[] {}); + return; + } + + ArrayList<ROI> inputROI = s.getROIs(); + + switch (filter.getValue()) + { + case SELECTED: + for (int i = 0; i < inputROI.size(); i++) + if (!inputROI.get(i).isSelected()) inputROI.remove(i--); + break; + case NON_SELECTED: + for (int i = 0; i < inputROI.size(); i++) + if (inputROI.get(i).isSelected()) inputROI.remove(i--); + break; + case ALL: + break; + + default: + throw new UnsupportedOperationException("Unknown ROI selection option: " + filter.getValueAsString()); + } + + rois.setValue(inputROI.toArray(new ROI[inputROI.size()])); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input sequence", sequence); + inputMap.add("selection state of ROI to extract", filter); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("extracted rois", rois); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/MergeROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/MergeROI.java new file mode 100644 index 0000000..07c51d5 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/MergeROI.java @@ -0,0 +1,47 @@ +package plugins.adufour.blocks.tools.roi; + +import java.util.Arrays; +import java.util.List; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import icy.roi.ROIUtil; +import icy.util.ShapeUtil.BooleanOperator; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarEnum; +import plugins.adufour.vars.lang.VarROIArray; + +public class MergeROI extends Plugin implements ROIBlock +{ + VarEnum<BooleanOperator> operation = new VarEnum<BooleanOperator>("Merge operation", BooleanOperator.AND); + + VarROIArray roiIn = new VarROIArray("List of ROI"); + + VarROIArray roiOut = new VarROIArray("Merged ROI"); + + @Override + public void run() + { + roiOut.setValue(new ROI[0]); + + List<ROI> rois = Arrays.asList(roiIn.getValue()); + + ROI merge = ROIUtil.merge(rois, operation.getValue()); + + if (merge != null) roiOut.add(merge); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("List of ROI", roiIn); + inputMap.add("Merge operation", operation); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("Merged ROI", roiOut); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/MorphROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/MorphROI.java new file mode 100644 index 0000000..8f5ee8d --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/MorphROI.java @@ -0,0 +1,115 @@ +package plugins.adufour.blocks.tools.roi; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.gui.model.IntegerRangeModel; +import plugins.adufour.vars.lang.VarEnum; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarROIArray; + +abstract class MorphROI extends Plugin implements ROIBlock +{ + protected enum MorphUnit + { + PIXELS("Radius (px)"), PERCENTAGE("Scale (%)"); + + private final String displayText; + + private MorphUnit(String displayText) + { + this.displayText = displayText; + } + + @Override + public String toString() + { + return displayText; + } + } + + VarROIArray roiIN = new VarROIArray("List of ROI"); + VarInteger x = new VarInteger("Along X", 1); + VarInteger y = new VarInteger("Along Y", 1); + VarInteger z = new VarInteger("Along Z", 1); + + VarEnum<MorphUnit> unit = new VarEnum<MorphUnit>("Unit", MorphUnit.PIXELS); + + VarROIArray roiOUT; + + @Override + public void declareInput(VarList inputMap) + { + x.setDefaultEditorModel(new IntegerRangeModel(1, 0, 100, 1)); + y.setDefaultEditorModel(new IntegerRangeModel(1, 0, 100, 1)); + z.setDefaultEditorModel(new IntegerRangeModel(1, 0, 100, 1)); + inputMap.add("input ROI", roiIN); + inputMap.add("X radius", x); + inputMap.add("Y radius", y); + inputMap.add("Z radius", z); + inputMap.add("unit", unit); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output ROI", roiOUT); + } + + /** + * Converts a scaling factor to a corresponding radius along the X axis (to the nearest pixel) + * + * @author Joel Rogers, Alexandre Dufour + * @param roi + * the ROI to scale + * @param pct + * the percentage factor + * @return a radius that is directly usable with the + * {@link ErodeROI#erodeROI(ROI, int, int, int)} and + * {@link DilateROI#dilateROI(ROI, int, int, int)} methods + */ + static int percentageToRadiusX(ROI roi, int pct) + { + double size = roi.getBounds5D().getSizeX(); + double diff = (size * pct / 100); + return (int) Math.round(diff * 0.5); + } + + /** + * Converts a scaling factor to a corresponding radius along the Y axis (to the nearest pixel) + * + * @author Joel Rogers, Alexandre Dufour + * @param roi + * the ROI to rescale + * @param pct + * the percentage factor + * @return a radius that is directly usable with the + * {@link ErodeROI#erodeROI(ROI, int, int, int)} and + * {@link DilateROI#dilateROI(ROI, int, int, int)} methods + */ + static int percentageToRadiusY(ROI roi, int pct) + { + double size = roi.getBounds5D().getSizeY(); + double diff = (size * pct / 100); + return (int) Math.round(diff * 0.5); + } + + /** + * Converts a scaling factor to a corresponding radius along the Z axis (to the nearest pixel) + * + * @author Joel Rogers, Alexandre Dufour + * @param roi + * the ROI to rescale + * @param pct + * the percentage factor + * @return a radius that is directly usable with the + * {@link ErodeROI#erodeROI(ROI, int, int, int)} and + * {@link DilateROI#dilateROI(ROI, int, int, int)} methods + */ + static int percentageToRadiusZ(ROI roi, int pct) + { + double size = roi.getBounds5D().getSizeZ(); + double diff = (size * pct / 100); + return (int) Math.round(diff * 0.5); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/ROIBlock.java b/src/main/java/plugins/adufour/blocks/tools/roi/ROIBlock.java new file mode 100644 index 0000000..56b43f2 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/ROIBlock.java @@ -0,0 +1,14 @@ +package plugins.adufour.blocks.tools.roi; + +import plugins.adufour.blocks.lang.Block; + +/** + * Interface used to flag ROI-related blocks + * + * @author Alexandre Dufour + * + */ +public interface ROIBlock extends Block +{ + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/SubtractROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/SubtractROI.java new file mode 100644 index 0000000..3529353 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/SubtractROI.java @@ -0,0 +1,66 @@ +package plugins.adufour.blocks.tools.roi; + +import java.util.ArrayList; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarROIArray; + +public class SubtractROI extends Plugin implements ROIBlock +{ + VarROIArray roiA = new VarROIArray("List of ROI #1"); + + VarROIArray roiB = new VarROIArray("List of ROI #2"); + + VarROIArray roiOut = new VarROIArray("Subtracted ROI"); + + @Override + public void run() + { + roiOut.setValue(subtractROI(roiA.getValue(true), roiB.getValue(true))); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("List of ROI #1", roiA); + inputMap.add("List of ROI #2", roiB); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("subtraction output ROI", roiOut); + } + + /** + * Subtracts a set of ROI from another + * + * @param roiA + * ROIs to be subtracted from. + * @param roiB + * ROIs subtracted from the first group of ROIs. + * @return A - B. The group of ROIs resulting from the subtraction from the each element of the group A and each element of the group B. + */ + public static ROI[] subtractROI(ROI[] roiA, ROI[] roiB) + { + ArrayList<ROI> out = new ArrayList<ROI>(roiA.length); + + for (ROI a : roiA) + { + ROI subtraction = a.getCopy(); + + for (ROI b : roiB) + subtraction = subtraction.getSubtraction(b); + + if (subtraction == null || subtraction.isEmpty()) + continue; + + if (!subtraction.getBounds5D().isEmpty()) + out.add(subtraction); + } + + return out.toArray(new ROI[out.size()]); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/roi/TranslateROI.java b/src/main/java/plugins/adufour/blocks/tools/roi/TranslateROI.java new file mode 100644 index 0000000..8d20cf4 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/roi/TranslateROI.java @@ -0,0 +1,83 @@ +package plugins.adufour.blocks.tools.roi; + +import icy.plugin.abstract_.Plugin; +import icy.roi.ROI; +import icy.type.point.Point5D; + +import java.awt.geom.Point2D; + +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarROIArray; +import plugins.kernel.roi.roi2d.ROI2DArea; +import plugins.kernel.roi.roi3d.ROI3DArea; + +public class TranslateROI extends Plugin implements ROIBlock +{ + VarROIArray inputROI = new VarROIArray("Input ROI"); + + VarInteger xShift = new VarInteger("X shift", 0); + VarInteger yShift = new VarInteger("Y shift", 0); + VarInteger zShift = new VarInteger("Z shift", 0); + + VarROIArray outputROI = new VarROIArray("Translated ROI"); + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input ROI", inputROI); + inputMap.add("X shift", xShift); + inputMap.add("Y shift", yShift); + inputMap.add("Z shift", zShift); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output ROI", outputROI); + } + + @Override + public void run() + { + outputROI.setValue(new ROI[0]); + + for (ROI roi : inputROI) + { + if (Thread.currentThread().isInterrupted()) break; + + if (roi.canSetPosition()) + { + ROI shiftedROI = roi.getCopy(); + Point5D pos = shiftedROI.getPosition5D(); + pos.setX(pos.getX() + xShift.getValue()); + pos.setY(pos.getY() + yShift.getValue()); + pos.setZ(pos.getZ() + zShift.getValue()); + shiftedROI.setPosition5D(pos); + outputROI.add(shiftedROI); + } + else + { + if (roi instanceof ROI3DArea) + { + ROI3DArea shiftedROI = new ROI3DArea(); + + for (ROI2DArea slice : (ROI3DArea) roi) + { + ROI2DArea sliceCopy = (ROI2DArea) slice.getCopy(); + + Point2D p2 = slice.getPosition2D(); + p2.setLocation(p2.getX() + xShift.getValue(), p2.getY() + yShift.getValue()); + sliceCopy.setPosition2D(p2); + + shiftedROI.setSlice(slice.getZ() + zShift.getValue(), sliceCopy); + } + + outputROI.add(shiftedROI); + } + } + + } + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceBlock.java b/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceBlock.java new file mode 100644 index 0000000..bbc1f14 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceBlock.java @@ -0,0 +1,14 @@ +package plugins.adufour.blocks.tools.sequence; + +import plugins.adufour.blocks.lang.Block; + +/** + * Interface used to flag Sequence-related blocks + * + * @author Alexandre Dufour + * + */ +public interface SequenceBlock extends Block +{ + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceScreenshot.java b/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceScreenshot.java new file mode 100644 index 0000000..f5ae733 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/sequence/SequenceScreenshot.java @@ -0,0 +1,67 @@ +package plugins.adufour.blocks.tools.sequence; + +import icy.canvas.Canvas2D; +import icy.gui.viewer.Viewer; +import icy.plugin.abstract_.Plugin; +import icy.sequence.Sequence; +import icy.system.thread.ThreadUtil; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarSequence; + +public class SequenceScreenshot extends Plugin implements SequenceBlock +{ + private VarSequence seq = new VarSequence("Sequence", null); + + private VarSequence seqOut = new VarSequence("Screenshot", null); + + @Override + public void run() + { + final Sequence in = seq.getValue(); + final int time = in.getSizeT(); + final int depth = in.getSizeZ(); + final Sequence out = new Sequence("Screenshot of " + in.getName()); + + final Viewer viewer = new Viewer(in, false); + final Canvas2D[] canvas2DP = new Canvas2D[1]; + + // init + canvas2DP[0] = null; + // force completion of SWING EDT tasks + ThreadUtil.invokeLater(new Runnable() + { + @Override + public void run() + { + canvas2DP[0] = (Canvas2D) viewer.getCanvas(); + } + }); + + final long startTime = System.currentTimeMillis(); + // wait 2 seconds max for initialization + while ((canvas2DP[0] == null) && ((System.currentTimeMillis() - startTime) < 2000L)) + ThreadUtil.sleep(10); + + final Canvas2D canvas2D = canvas2DP[0]; + + for (int t = 0; t < time; t++) + for (int z = 0; z < depth; z++) + out.setImage(t, z, canvas2D.getRenderedImage(t, z, -1, false)); + + viewer.close(); + + seqOut.setValue(out); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input sequence", seq); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("screenshot", seqOut); + } +} diff --git a/src/main/java/plugins/adufour/blocks/tools/sequence/ShowSequence.java b/src/main/java/plugins/adufour/blocks/tools/sequence/ShowSequence.java new file mode 100644 index 0000000..8689351 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/sequence/ShowSequence.java @@ -0,0 +1,35 @@ +package plugins.adufour.blocks.tools.sequence; + +import icy.main.Icy; +import icy.plugin.abstract_.Plugin; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.vars.lang.VarSequence; + +//@BlockSearchAnnotation( +// name="show sequence", +// category="sequence", +// description="", +// author="adufour" +//) +public class ShowSequence extends Plugin implements SequenceBlock +{ + VarSequence sequence = new VarSequence("sequence", null); + + @Override + public void run() + { + if (sequence.getValue() != null) Icy.getMainInterface().addSequence(sequence.getValue()); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("sequence", sequence); + } + + @Override + public void declareOutput(VarList outputMap) + { + } + +} diff --git a/src/main/java/plugins/adufour/blocks/tools/text/AppendText.java b/src/main/java/plugins/adufour/blocks/tools/text/AppendText.java new file mode 100644 index 0000000..dd9ed78 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/tools/text/AppendText.java @@ -0,0 +1,53 @@ +package plugins.adufour.blocks.tools.text; + +import icy.plugin.abstract_.Plugin; +import icy.plugin.interface_.PluginBundled; +import icy.sequence.Sequence; +import plugins.adufour.blocks.tools.ToolsBlock; +import plugins.adufour.blocks.util.VarList; +import plugins.adufour.protocols.Protocols; +import plugins.adufour.vars.lang.VarObject; +import plugins.adufour.vars.lang.VarString; + +public class AppendText extends Plugin implements ToolsBlock, PluginBundled +{ + VarObject in = new VarObject("input", null); + VarString separator = new VarString("Separator", "_"); + VarObject suffix = new VarObject("Suffix", ""); + + VarString out = new VarString("output", ""); + + @Override + public void run() + { + Object i = in.getValue(); + + Object o = suffix.getValue(); + + String prefix = i == null ? "" : i.toString(); + + if (i instanceof Sequence) prefix = ((Sequence) i).getName(); + + out.setValue(prefix + separator.getValue() + (o == null ? "" : o)); + } + + @Override + public void declareInput(VarList inputMap) + { + inputMap.add("input", in); + inputMap.add("Separator", separator); + inputMap.add("Suffix", suffix); + } + + @Override + public void declareOutput(VarList outputMap) + { + outputMap.add("output", out); + } + + @Override + public String getMainPluginClassName() + { + return Protocols.class.getName(); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlockAnnotations.java b/src/main/java/plugins/adufour/blocks/util/BlockAnnotations.java new file mode 100644 index 0000000..05c98c8 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlockAnnotations.java @@ -0,0 +1,232 @@ +package plugins.adufour.blocks.util; + +import icy.plugin.abstract_.Plugin; +import icy.sequence.Sequence; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Iterator; + +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarBoolean; +import plugins.adufour.vars.lang.VarDouble; +import plugins.adufour.vars.lang.VarFile; +import plugins.adufour.vars.lang.VarFloat; +import plugins.adufour.vars.lang.VarInteger; +import plugins.adufour.vars.lang.VarSequence; +import plugins.adufour.vars.lang.VarString; + +/** + * Utility class that parses block annotations and generates blocks from valid methods + * + * @author Alexandre Dufour + * @see BlockInput + * @see BlockMethod + */ +public class BlockAnnotations +{ + /** + * Annotation used to mark a method as block-compatible + * + * @author Alexandre Dufour + * @see BlockAnnotations + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @interface BlockMethod + { + /** + * @return The method's description + */ + String value(); + } + + /** + * Annotation used to mark a method parameter as a block input parameter + * + * @author Alexandre Dufour + * @see BlockAnnotations + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @interface BlockInput + { + String value(); + } + + public static Class<?> getInnerType(Class<? extends Var<?>> variableClass) + { + if (variableClass == VarInteger.class) return Integer.class; + + if (variableClass == VarDouble.class) return Double.class; + + if (variableClass == VarFloat.class) return Float.class; + + if (variableClass == VarBoolean.class) return Boolean.class; + + if (variableClass == VarFile.class) return File.class; + + if (variableClass == VarSequence.class) return Sequence.class; + + if (variableClass == VarString.class) return String.class; + + throw new UnsupportedOperationException("Unsupported type: " + variableClass.getSimpleName()); + } + + public static Class<? extends Var<?>> getVariableType(Class<?> clazz) + { + if (clazz == Integer.class) return VarInteger.class; + + if (clazz == Double.class) return VarDouble.class; + + if (clazz == Float.class) return VarFloat.class; + + if (clazz == Boolean.class) return VarBoolean.class; + + if (clazz == File.class) return VarFile.class; + + if (clazz == Sequence.class) return VarSequence.class; + + if (clazz == String.class) return VarString.class; + + throw new UnsupportedOperationException("Unsupported type: " + clazz.getSimpleName()); + } + + public static ArrayList<BlockAnnotations> findBlockMethods(Class<? extends Plugin> clazz) + { + final ArrayList<BlockAnnotations> blockDescriptors = new ArrayList<BlockAnnotations>(); + + Method[] methods = clazz.getDeclaredMethods(); + + for (final Method method : methods) + { + // make sure the method is static and annotated + + if (!Modifier.isStatic(method.getModifiers())) continue; + + BlockMethod blockFunction = method.getAnnotation(BlockMethod.class); + if (blockFunction == null) continue; + + blockDescriptors.add(new BlockAnnotations(method)); + } + + return blockDescriptors; + } + + private final Method method; + + public BlockAnnotations(Method method) + { + this.method = method; + } + + public String getDescription() + { + return method.getAnnotation(BlockMethod.class).value(); + } + + public Block createBlock() + { + return new Block() + { + @SuppressWarnings("rawtypes") + private Var outputVariable; + + private VarList inputMap; + + @SuppressWarnings("unchecked") + @Override + public void run() + { + try + { + Object[] arguments = new Object[inputMap.size()]; + Iterator<Var<?>> inputIterator = inputMap.iterator(); + + for (int i = 0; i < arguments.length; i++) + arguments[i] = inputIterator.next().getValue(); + + Object result = method.invoke(null, arguments); + + if (outputVariable != null) outputVariable.setValue(result); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Override + public void declareOutput(VarList outputMap) + { + try + { + Class<?> returnType = method.getReturnType(); + if (returnType == Void.TYPE) + { + outputVariable = null; + } + else + { + Class<? extends Var<?>> outputVarClass = getVariableType(returnType); + Constructor<? extends Var<?>> outputConstructor = outputVarClass.getDeclaredConstructor(String.class, returnType); + outputVariable = outputConstructor.newInstance("output (" + returnType.getSimpleName() + ")", null); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + if (outputVariable != null) outputMap.add("output", outputVariable); + } + + @Override + public void declareInput(VarList theInputMap) + { + this.inputMap = theInputMap; + + try + { + Class<?>[] types = method.getParameterTypes(); + Annotation[][] annotations = method.getParameterAnnotations(); + + for (int i = 0; i < types.length; i++) + { + Class<?> parameterClass = types[i]; + String parameterName = parameterClass.getSimpleName(); + + for (Annotation annotation : annotations[i]) + if (annotation.annotationType() == BlockInput.class) + { + parameterName = ((BlockInput) annotation).value(); + } + + Class<? extends Var<?>> inputVarClass = getVariableType(parameterClass); + Constructor<? extends Var<?>> inputConstructor = inputVarClass.getDeclaredConstructor(String.class, parameterClass); + Var<?> instance = inputConstructor.newInstance(parameterName, null); + theInputMap.add(instance.getName(), instance); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }; + } + + public String getName() + { + return method.getName(); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlockInfo.java b/src/main/java/plugins/adufour/blocks/util/BlockInfo.java new file mode 100644 index 0000000..6614cc5 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlockInfo.java @@ -0,0 +1,23 @@ +package plugins.adufour.blocks.util; + +/** + * @deprecated Using this interface is no longer recommended, as the information provided here can + * not be parsed either online or via the plug-in installer. Block information (name and + * description) will soon appear on the Icy web site (and the plug-in loader). + * @author Alexandre Dufour + * + */ +@Deprecated +public interface BlockInfo +{ + /** + * @return The title of the block (displays on the graphical interface) + */ + String getName(); + + /** + * @return A short description of the block (will appear as a tool tip text when hovering on the + * block title) + */ + String getDescription(); +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlockListener.java b/src/main/java/plugins/adufour/blocks/util/BlockListener.java new file mode 100644 index 0000000..5c8085e --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlockListener.java @@ -0,0 +1,20 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.blocks.lang.BlockDescriptor.BlockStatus; +import plugins.adufour.vars.lang.Var; + +public interface BlockListener +{ + void blockCollapsed(BlockDescriptor block, boolean collapsed); + + void blockDimensionChanged(BlockDescriptor block, int newWidth, int newHeight); + + void blockLocationChanged(BlockDescriptor block, int newX, int newY); + + void blockStatusChanged(BlockDescriptor block, BlockStatus status); + + <T> void blockVariableChanged(BlockDescriptor block, Var<T> variable, T newValue); + + void blockVariableAdded(BlockDescriptor block, Var<?> variable); +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlocksException.java b/src/main/java/plugins/adufour/blocks/util/BlocksException.java new file mode 100644 index 0000000..8c45f68 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlocksException.java @@ -0,0 +1,29 @@ +package plugins.adufour.blocks.util; + +/** + * Convenience class for the Blocks framework that allows optimizing bug finding / reporting / + * fixing. + * + * @author Alexandre Dufour + */ +public class BlocksException extends RuntimeException +{ + private static final long serialVersionUID = 666L; + + public final boolean catchException; + + /** + * Creates a new exception with the specified message and catching behavior. + * + * @param message + * the error message bound the current exception + * @param catchException + * true if the exception should be caught within the EzPlug layer, false to let the + * exception pass to the global exception manager + */ + public BlocksException(String message, boolean catchException) + { + super(message); + this.catchException = catchException; + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlocksFinder.java b/src/main/java/plugins/adufour/blocks/util/BlocksFinder.java new file mode 100644 index 0000000..73c13ac --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlocksFinder.java @@ -0,0 +1,752 @@ +package plugins.adufour.blocks.util; + +import icy.gui.menu.PluginMenuItem; +import icy.image.ImageUtil; +import icy.plugin.PluginDescriptor; +import icy.plugin.PluginLoader; +import icy.plugin.abstract_.Plugin; +import icy.resource.ResourceUtil; +import icy.resource.icon.IcyIcon; +import icy.system.SystemUtil; +import icy.util.ClassUtil; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.TransferHandler; + +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.blocks.lang.Loop; +import plugins.adufour.blocks.lang.WorkFlow; +import plugins.adufour.blocks.tools.Display; +import plugins.adufour.blocks.tools.ReLoop; +import plugins.adufour.blocks.tools.ToolsBlock; +import plugins.adufour.blocks.tools.ij.IJBlock; +import plugins.adufour.blocks.tools.input.InputBlock; +import plugins.adufour.blocks.tools.io.IOBlock; +import plugins.adufour.blocks.tools.roi.ROIBlock; +import plugins.adufour.blocks.tools.sequence.SequenceBlock; +import plugins.adufour.protocols.gui.BlockSearchPanel; +import plugins.adufour.protocols.gui.block.WorkFlowContainer; + +public class BlocksFinder +{ + /** + * Indicates whether annotated methods should be parsed and made available as + * blocks + */ + public final boolean parseAnnotations = false; + + /** + * class for handling Drag and Drop on the menu items + * + * @author Ludovic Laborde, Alexandre Dufour + */ + @SuppressWarnings("serial") + public class DND_MenuItem extends PluginMenuItem + implements MouseListener, Transferable, DragSourceListener, DragGestureListener + { + private DragSource source; + private TransferHandler handler; + private PluginDescriptor descriptor; + + private List<MenuItemListener> menuItemListeners; + + public DND_MenuItem(final PluginDescriptor d) + { + super(d); + setOpaque(false); + + for (MouseListener listener : getMouseListeners()) + removeMouseListener(listener); + + descriptor = d; + menuItemListeners = new ArrayList<MenuItemListener>(); + + handler = new TransferHandler() + { + public Transferable createTransferable(JComponent c) + { + return new DND_MenuItem(descriptor); + } + }; + + source = new DragSource(); + source.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY, this); + + setTransferHandler(handler); + addMouseListener(this); + } + + public PluginDescriptor getDescriptor() + { + return descriptor; + } + + @Override + public void dragGestureRecognized(DragGestureEvent dge) + { + source.startDrag(dge, DragSource.DefaultMoveDrop, new DND_MenuItem(descriptor), this); + } + + @Override + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException + { + return this; + } + + @Override + public DataFlavor[] getTransferDataFlavors() + { + try + { + return new DataFlavor[] { + new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=java.util.ArrayList")}; + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + return null; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) + { + return true; + } + + @Override + public void mouseClicked(MouseEvent arg0) + { + setBackground(Color.GRAY); + notifyListeners(); + } + + public void addMenuItemListener(MenuItemListener l) + { + menuItemListeners.add(l); + } + + // updates the search panel + private void notifyListeners() + { + for (MenuItemListener l : menuItemListeners) + l.displayDoc(descriptor); + } + + @Override + public void dragDropEnd(DragSourceDropEvent dsde) + { + setBackground(Color.GRAY); + notifyListeners(); + } + + @Override + public void dragEnter(DragSourceDragEvent dsde) + { + } + + @Override + public void dragExit(DragSourceEvent dse) + { + } + + @Override + public void dragOver(DragSourceDragEvent dsde) + { + } + + @Override + public void dropActionChanged(DragSourceDragEvent dsde) + { + } + + @Override + public void mouseEntered(MouseEvent arg0) + { + } + + @Override + public void mouseExited(MouseEvent arg0) + { + } + + @Override + public void mousePressed(MouseEvent arg0) + { + } + + @Override + public void mouseReleased(MouseEvent arg0) + { + } + } + + /** + * Searches for a block using the specified search text, and populates the + * specified search panel with the search results + * + * @param menuContainer BlockSearchPanel + * @param searchText String + */ + public void createSearchMenu(BlockSearchPanel menuContainer, String searchText) + { + ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(Block.class, true, false, false); + + if (plugins.size() == 0) + { + JMenuItem item = new JMenuItem( + "The plug-in list has been updated.\nPlease close and re-open the Protocols editor."); + item.setFont(item.getFont().deriveFont(Font.ITALIC)); + menuContainer.add(item); + return; + } + + for (PluginDescriptor descriptor : plugins) + { + + Class<? extends Plugin> _class = descriptor.getPluginClass(); + final Class<? extends Block> blockClass = _class.asSubclass(Block.class); + + if (ClassUtil.isAbstract(blockClass)) + continue; + if (ClassUtil.isPrivate(blockClass)) + continue; + + // ALEX (2014-06-17): removing annotations for now + // // handle default annotated blocks defined in protocols + // if (blockClass.isAnnotationPresent(BlockSearchAnnotation.class)) + // { + // Annotation annotation = + // blockClass.getAnnotation(BlockSearchAnnotation.class); + // BlockSearchAnnotation blockSearchAnnotation = (BlockSearchAnnotation) + // annotation; + // + // // search conditions + // if + // (!(blockSearchAnnotation.name().toLowerCase().contains(searchText.toLowerCase()) + // || + // blockSearchAnnotation.category().toLowerCase().contains(searchText.toLowerCase()) + // || blockSearchAnnotation.description().toLowerCase() + // .contains(searchText.toLowerCase()))) continue; + // } + + // handle other external plugins + // search conditions + + String testString = "block"; + testString += descriptor.getName(); + testString += blockClass.getSimpleName(); + testString += descriptor.getDescription(); + testString += descriptor.getAuthor(); + testString = testString.toLowerCase(); + + searchText = searchText.trim().toLowerCase(); + String searchTextNoSpaces = searchText.replace(" ", ""); + + if (testString.contains(searchText) || testString.contains(searchTextNoSpaces)) + { + DND_MenuItem menuItem = new DND_MenuItem(descriptor); + menuItem.addMenuItemListener(menuContainer); + menuItem.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35)); + menuItem.setAlignmentX(Component.LEFT_ALIGNMENT); + + String name = blockClass.getSimpleName(); + name = descriptor.getName().equalsIgnoreCase(name) ? getFlattened(name) : descriptor.getName(); + + // Remove the "block" word at the end of the class name + if (name.toLowerCase().endsWith("block")) + name = name.substring(0, name.length() - 5); + + if (blockClass.isAnnotationPresent(Deprecated.class)) + { + name = "(deprecated) " + name; + } + menuItem.setText(name); + menuContainer.add(menuItem); + } + } + } + + /** + * Create a JMenu component with all existing classes implementing {@link Block} + * + * @param menuContainer Container + * @param workFlowPane WorkFlowContainer + * @param location Point + */ + public final void createJMenu(Container menuContainer, final WorkFlowContainer workFlowPane, final Point location) + { + JMenu mnBlocks = new JMenu("Blocks..."); + mnBlocks.setIcon(new IcyIcon(ResourceUtil.getAlphaIconAsImage("box.png"), 22)); + + JMenu mnInput = new JMenu("Read..."); + mnInput.setIcon(new IcyIcon(ResourceUtil.ICON_REDO, 22)); + + JMenu mnIO = new JMenu("I/O..."); + mnIO.setIcon(new IcyIcon(ResourceUtil.ICON_SAVE, 22)); + + JMenu mnSeq = new JMenu("Sequence..."); + mnSeq.setIcon(new IcyIcon(ResourceUtil.ICON_PHOTO, 22)); + + JMenu mnROI = new JMenu("ROI..."); + mnROI.setIcon(new IcyIcon(ResourceUtil.ICON_ROI_POLYGON, 22)); + + JMenu mnImageJ = new JMenu("ImageJ..."); + mnImageJ.setIcon(new IcyIcon(ResourceUtil.ICON_TOIJ, 22)); + + JMenu mnLoops = new JMenu("Loop / Batch..."); + mnLoops.setIcon(new IcyIcon(ResourceUtil.ICON_RELOAD, 22)); + + JMenu mnTools = new JMenu("Tools..."); + mnTools.setIcon(new IcyIcon(ResourceUtil.ICON_TOOLS, 22)); + + ArrayList<PluginDescriptor> plugins = PluginLoader.getPlugins(Block.class, true, false, false); + + if (plugins.size() == 0) + { + JMenuItem item = new JMenuItem( + "The plug-in list has been updated.\nPlease close and re-open the Protocols editor."); + item.setFont(item.getFont().deriveFont(Font.ITALIC)); + menuContainer.add(item); + return; + } + + for (PluginDescriptor descriptor : plugins) + { + Class<? extends Plugin> clazz = descriptor.getPluginClass(); + try + { + final Class<? extends Block> blockClass = clazz.asSubclass(Block.class); + + if (ClassUtil.isAbstract(blockClass)) + continue; + if (ClassUtil.isPrivate(blockClass)) + continue; + if (blockClass.isAnnotationPresent(Deprecated.class)) + continue; + + // create the menu item + PluginMenuItem menuItem = new PluginMenuItem(descriptor); + + // remove the internal action listener + menuItem.removeActionListener(menuItem); + + String name = blockClass.getSimpleName(); + + name = descriptor.getName().equalsIgnoreCase(name) ? getFlattened(name) : descriptor.getName(); + + // Remove the "block" word at the end of the class name + if (name.toLowerCase().endsWith("block")) + name = name.substring(0, name.length() - 5); + + menuItem.setText(name); + + if (!descriptor.getDescription().isEmpty()) + { + menuItem.setToolTipText( + "<html>" + descriptor.getDescription().replaceAll("\n", "<br/>") + "</html>"); + } + + menuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + try + { + addBlock(workFlowPane, blockClass.newInstance(), location); + } + catch (InstantiationException e1) + { + e1.printStackTrace(); + } + catch (IllegalAccessException e1) + { + e1.printStackTrace(); + } + } + }); + + // place the menu item where appropriate + + if (blockClass == Display.class) + { + // will be dealt with outside this loop + continue; + } + else if (ClassUtil.isSubClass(blockClass, Loop.class) + && !blockClass.isAnnotationPresent(Deprecated.class)) + { + final JMenuItem item = new JMenuItem(getFlattened(blockClass.getSimpleName())); + item.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + try + { + addBlock(workFlowPane, blockClass.newInstance(), location); + } + catch (InstantiationException e1) + { + e1.printStackTrace(); + } + catch (IllegalAccessException e1) + { + e1.printStackTrace(); + } + } + }); + mnLoops.add(item); + } + else if (InputBlock.class.isAssignableFrom(blockClass)) + { + mnInput.add(menuItem); + } + else if (SequenceBlock.class.isAssignableFrom(blockClass)) + { + mnSeq.add(menuItem); + } + else if (IOBlock.class.isAssignableFrom(blockClass)) + { + mnIO.add(menuItem); + } + else if (ROIBlock.class.isAssignableFrom(blockClass)) + { + mnROI.add(menuItem); + } + else if (IJBlock.class.isAssignableFrom(blockClass)) + { + mnImageJ.add(menuItem); + } + else if (ToolsBlock.class.isAssignableFrom(blockClass)) + { + if (blockClass == ReLoop.class) + continue; // TODO + + mnTools.add(menuItem); + } + else + { + // default case: put the rest in the "Blocks" menu + mnBlocks.add(menuItem); + } + + if (parseAnnotations) + { + // find annotated methods + ArrayList<BlockAnnotations> blocks = BlockAnnotations.findBlockMethods(clazz); + + for (final BlockAnnotations annotatedMethod : blocks) + { + JMenuItem item2 = new JMenuItem(clazz.getSimpleName() + "." + annotatedMethod.getName()); + item2.setToolTipText(annotatedMethod.getDescription()); + item2.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + addBlock(workFlowPane, annotatedMethod.createBlock(), location); + } + }); + mnBlocks.add(item2); + } + } + } + catch (ClassCastException e1) + { + } + } + + splitLongMenus(mnBlocks, 15); + splitLongMenus(mnInput, 15); + splitLongMenus(mnSeq, 15); + splitLongMenus(mnROI, 15); + splitLongMenus(mnIO, 15); + splitLongMenus(mnImageJ, 15); + splitLongMenus(mnLoops, 15); + splitLongMenus(mnTools, 15); + + menuContainer.add(mnBlocks); + menuContainer.add(mnInput); + menuContainer.add(mnSeq); + menuContainer.add(mnROI); + menuContainer.add(mnIO); + menuContainer.add(mnImageJ); + menuContainer.add(mnLoops); + menuContainer.add(mnTools); + + // and add the Display block last + + JMenuItem mnDisp = new JMenuItem("Display"); + mnDisp.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + try + { + addBlock(workFlowPane, Display.class.newInstance(), location); + } + catch (InstantiationException e1) + { + e1.printStackTrace(); + } + catch (IllegalAccessException e1) + { + e1.printStackTrace(); + } + } + }); + mnDisp.setIcon(new IcyIcon(ImageUtil.scaleQuality(ResourceUtil.getAlphaIconAsImage("eye_open.png"), 22, 22))); + menuContainer.add(mnDisp); + + // // 3rd-party block providers (very soon...) + // + // JMenu mnOthers = new JMenu("Others..."); + // mnOthers.setIcon(new IcyIcon(ResourceUtil.getAlphaIconAsImage("box.png"), + // 22)); + // + // for (PluginDescriptor providerPlugin : + // PluginLoader.getPlugins(BlockProvider.class, true, + // false, false)) + // { + // try + // { + // BlockProvider provider = + // providerPlugin.getPluginClass().asSubclass(BlockProvider.class).newInstance(); + // + // Map<String, Block> blocks = provider.getBlocks(); + // + // for(String blockName : blocks.keySet()) + // { + // final Block block = blocks.get(blockName); + // final JMenuItem item = new JMenuItem(blockName); + // item.addActionListener(new ActionListener() + // { + // @Override + // public void actionPerformed(ActionEvent arg0) + // { + // try + // { + // addBlock(workFlowPane, block.getClass().newInstance(), location); + // } + // catch (InstantiationException e1) + // { + // e1.printStackTrace(); + // } + // catch (IllegalAccessException e1) + // { + // e1.printStackTrace(); + // } + // } + // }); + // mnOthers.add(item); + // } + // } + // catch (Exception e) + // { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // } + // splitLongMenus(mnOthers, 15); + // menuContainer.add(mnOthers); + } + + /** + * Create a JMenu component with all existing classes implementing {@link Block} + * + * @param menuContainer Container + * @param workFlowPane WorkFlowContainer + */ + public static void createEmbedJMenu(final Container menuContainer, final WorkFlowContainer workFlowPane) + { + // One day, when Java 8 rules... + // PluginLoader.getPlugins(WorkFlow.class).stream() + // // get the plug-in class + // .map(PluginDescriptor::getPluginClass) + // // Create a menu item for each element + // .forEach(enclosure -> { + // String menuName = getFlattened(enclosure.getSimpleName()); + // JMenuItem menuItem = new JMenuItem(menuName); + // menuItem.addActionListener(a -> { + // try + // { + // workFlowPane.embedWorkFlow((Class<? extends WorkFlow>) enclosure); + // } + // catch (Exception e) + // { + // throw new RuntimeException(e); + // } + // }); + // menuContainer.add(menuItem); + // }); + + // In the meantime... + for (PluginDescriptor plugin : PluginLoader.getPlugins(WorkFlow.class)) + { + JMenuItem menuItem = new JMenuItem(getFlattened(plugin.getName())); + final PluginDescriptor pf = plugin; + + menuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent arg0) + { + try + { + workFlowPane.embedWorkFlow(pf.getPluginClass().asSubclass(WorkFlow.class)); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + menuContainer.add(menuItem); + } + } + + private static void addBlock(WorkFlowContainer workFlowPane, Block block, Point location) + { + BlockDescriptor blockDesc = (block instanceof WorkFlow ? ((WorkFlow) block).getBlockDescriptor() + : new BlockDescriptor(-1, block)); + + blockDesc.setLocation(location.x, location.y); + + workFlowPane.getWorkFlow().addBlock(blockDesc); + } + + /** + * Breaks the list of items in the specified menu, by creating sub-menus + * containing the specified number of items, and a "More..." menu to access + * subsequent items. + * + * @param menu + * the menu to break into smaller sub-menus + * @param maxItemsPerMenu + * the maximum number of items to display in each sub-menu + */ + private void splitLongMenus(JMenu menu, int maxItemsPerMenu) + { + ArrayList<Component> components = new ArrayList<Component>(Arrays.asList(menu.getPopupMenu().getComponents())); + + if (components.size() > maxItemsPerMenu) + { + menu.removeAll(); + + JMenu currentMenu = menu; + + while (components.size() > 0) + { + int n = Math.min(components.size(), maxItemsPerMenu - 1); + + for (int i = 0; i < n; i++) + currentMenu.add(components.remove(0)); + + if (components.size() > 0) + currentMenu = (JMenu) currentMenu.add(new JMenu("More...")); + } + + if (components.size() > 0) + throw new RuntimeException("Error while splitting menus: " + components.size() + " are remaining."); + } + + // do this recursively for sub-menus + for (Component component : menu.getPopupMenu().getComponents()) + { + if (component instanceof JMenu) + splitLongMenus((JMenu) component, maxItemsPerMenu); + } + } + + /** + * Creates a flattened version of the provided String. The flattening operation + * splits the string by inserting spaces between words starting with an upper + * case letter, and converts upper case letters to lower case (with the + * exception of the first word). Note that <b>consecutive upper case letters</b> + * <b>will remain grouped</b>, as they are considered to represent an acronym.<br> + * <br> + * <u>NOTE:</u> This method is optimized for class names that follow the Java + * naming convention. <br> + * Examples:<br> + * MyGreatClass -< "My great class"<br> + * MyXYZClass -< "My XYZ class" + * + * @param string + * the string to flatten + * @return a flattened (i.e. pretty-printed) String based on the name of the + * string + */ + public static String getFlattened(String string) + { + String[] words = string.split("(?=[A-Z])"); + + String output = words[0]; + if (words.length > 1) + { + int nextWordIndex = 1; + + final int javaVersion = (int) SystemUtil.getJavaVersionAsNumber(); + if (javaVersion < 8) + { + output = words[1]; + nextWordIndex++; + } + + for (int i = nextWordIndex; i < words.length; i++) + { + String word = words[i]; + if (word.length() == 1) + { + // single letter + if (words[i - 1].length() == 1) + { + // append to the previous letter (acronym) + output += word; + } + else + { + // new isolated letter or acronym + output += " " + word; + } + } + else + output += " " + word.toLowerCase(); + } + } + + return output; + } + +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlocksML.java b/src/main/java/plugins/adufour/blocks/util/BlocksML.java new file mode 100644 index 0000000..c77a0ac --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlocksML.java @@ -0,0 +1,1753 @@ +package plugins.adufour.blocks.util; + +import java.awt.Point; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import javax.swing.filechooser.FileFilter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; + +import icy.gui.frame.progress.AnnounceFrame; +import icy.main.Icy; +import icy.network.NetworkUtil; +import icy.plugin.PluginDescriptor; +import icy.plugin.PluginInstaller; +import icy.plugin.PluginLoader; +import icy.plugin.PluginRepositoryLoader; +import icy.plugin.PluginUpdater; +import icy.plugin.interface_.PluginBundled; +import icy.system.IcyExceptionHandler; +import icy.system.IcyHandledException; +import icy.system.thread.ThreadUtil; +import icy.util.ClassUtil; +import icy.util.XMLUtil; +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.blocks.lang.Link; +import plugins.adufour.blocks.lang.WorkFlow; +import plugins.adufour.blocks.tools.input.InputBlock; +import plugins.adufour.protocols.Protocols; +import plugins.adufour.vars.gui.model.TypeSelectionModel; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; +import plugins.adufour.vars.lang.VarMutableArray; +import plugins.adufour.vars.lang.VarString; +import plugins.adufour.vars.util.MutableType; +import plugins.adufour.vars.util.VarListener; + +public class BlocksML +{ + private static final String RUNTIME = "runtime"; + + private static BlocksML instance = new BlocksML(); + + public static BlocksML getInstance() + { + return instance; + } + + public static final int CURRENT_VERSION = 4; + + public static final FileFilter XML_FILE_FILTER = new FileFilter() + { + @Override + public String getDescription() + { + return "Icy protocols (.xml | .protocol)"; + } + + @Override + public boolean accept(File f) + { + return f.isDirectory() || f.getPath().toLowerCase().endsWith(".xml") + || f.getPath().toLowerCase().endsWith(".protocol"); + } + }; + + private Transformer transformer; + + private VarString status = new VarString("status", ""); + + private BlocksML() + { + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } + catch (Exception e) + { + } + } + + public void addStatusListener(VarListener<String> listener) + { + status.addListener(listener); + } + + public void removeStatusListener(VarListener<String> listener) + { + status.removeListener(listener); + } + + /** + * Generates the XML representation of this work flow as a String object. + * + * @param workFlow + * Target workflow instance. + * @return A XML representation of this work flow as a String object + * @throws TransformerException + * If an unrecoverable error occurs during the course of the transformation. + */ + public synchronized String toString(WorkFlow workFlow) throws TransformerException + { + return toString(toXML(workFlow)); + } + + /** + * Converts an XML document to its string representation. + * + * @param xml + * The target xml document to transform into string. + * @return A XML representation of this work flow as a String object + * @throws TransformerException + * If an unrecoverable error occurs during the course of the transformation. + */ + public synchronized String toString(Document xml) throws TransformerException + { + xml.normalizeDocument(); + + DocumentType doctype = xml.getDoctype(); + DOMSource domSource = new DOMSource(xml); + StringWriter string = new StringWriter(); + StreamResult streamResult = new StreamResult(string); + + if (doctype != null) + { + transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctype.getPublicId()); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctype.getSystemId()); + } + + transformer.transform(domSource, streamResult); + + return string.toString(); + } + + /** + * Creates a XML document representing the specified work flow. + * + * @param workFlow + * The work flow to save. + * @return The XML document representing the target workflow. + */ + public synchronized Document toXML(WorkFlow workFlow) + { + Document xml = XMLUtil.createDocument(false); + Element workSpaceRoot = XMLUtil.createRootElement(xml, "protocol"); + + XMLUtil.setAttributeIntValue(workSpaceRoot, "VERSION", CURRENT_VERSION); + + switch (CURRENT_VERSION) + { + case 1: + saveWorkFlow_V1(workFlow, workSpaceRoot); + break; + case 2: + saveWorkFlow_V2(workFlow, workSpaceRoot); + break; + case 3: + saveWorkFlow_V3(workFlow, workSpaceRoot); + break; + case 4: + saveWorkFlow_V4(workFlow, workSpaceRoot); + break; + default: + throw new UnsupportedOperationException("Cannot save Blocks ML version " + CURRENT_VERSION); + } + + return xml; + } + + /** + * Saves the specified work flow into the specified file in XML format. + * + * @param workFlow + * The work flow to save. + * @param f + * The file to write (or overwrite). + * @throws BlocksException + * If the file could not be saved. + * @throws IOException + * If an error occurs when saving the workflow in the file. + */ + public synchronized void saveWorkFlow(WorkFlow workFlow, File f) throws BlocksException, IOException + { + Document xml = toXML(workFlow); + + if (!XMLUtil.saveDocument(xml, f)) + throw new IOException( + "Unable to save the protocol.\nDo you have write permissions to the destination folder."); + } + + /** + * @deprecated Legacy method, use {@link #saveWorkFlow_V4(WorkFlow, Element)} instead. + * @param workFlow + * The workflow to save. + * @param workFlowRoot + * Root element where elements are to be inserted. + */ + @SuppressWarnings("deprecation") + @Deprecated + public synchronized void saveWorkFlow_V1(WorkFlow workFlow, Element workFlowRoot) + { + Element blocksNode = XMLUtil.addElement(workFlowRoot, "blocks"); + + for (BlockDescriptor blockData : workFlow) + { + Element blockNode; + Block block = blockData.getBlock(); + + if (block instanceof WorkFlow) + { + blockNode = XMLUtil.addElement(blocksNode, "workflow"); + } + else + { + blockNode = XMLUtil.addElement(blocksNode, "block"); + } + + XMLUtil.setAttributeValue(blockNode, "type", block.getClass().getCanonicalName()); + XMLUtil.setAttributeIntValue(blockNode, "ID", workFlow.indexOf(blockData)); + XMLUtil.setAttributeIntValue(blockNode, "xLocation", blockData.getLocation().x); + XMLUtil.setAttributeIntValue(blockNode, "yLocation", blockData.getLocation().y); + + if (block instanceof WorkFlow) + { + WorkFlow innerWorkFlow = (WorkFlow) block; + + saveWorkFlow_V1(innerWorkFlow, blockNode); + + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + BlockDescriptor owner = innerWorkFlow.getInputOwner(var); + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeIntValue(varNode, "ID", blockData.inputVars.indexOf(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", innerWorkFlow.indexOf(owner)); + XMLUtil.setAttributeIntValue(varNode, "varID", owner.inputVars.indexOf(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + BlockDescriptor owner = innerWorkFlow.getOutputOwner(var); + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeIntValue(varNode, "ID", blockData.outputVars.indexOf(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", innerWorkFlow.indexOf(owner)); + XMLUtil.setAttributeIntValue(varNode, "varID", owner.outputVars.indexOf(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + } + } + else + { + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeIntValue(varNode, Var.XML_KEY_ID, blockData.inputVars.indexOf(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeIntValue(varNode, Var.XML_KEY_ID, blockData.outputVars.indexOf(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + } + } + } + + Element linkRoot = XMLUtil.addElement(workFlowRoot, "links"); + + for (Link<?> link : workFlow.getLinksIterator()) + { + Element linkNode = XMLUtil.addElement(linkRoot, "link"); + + XMLUtil.setAttributeIntValue(linkNode, "srcBlockID", workFlow.indexOf(link.srcBlock)); + XMLUtil.setAttributeIntValue(linkNode, "srcVarID", link.srcBlock.outputVars.indexOf(link.srcVar)); + XMLUtil.setAttributeIntValue(linkNode, "dstBlockID", workFlow.indexOf(link.dstBlock)); + XMLUtil.setAttributeIntValue(linkNode, "dstVarID", link.dstBlock.inputVars.indexOf(link.dstVar)); + } + } + + /** + * @deprecated Legacy method, use {@link #saveWorkFlow_V4(WorkFlow, Element)} instead. + * @param workFlow + * The workflow to save. + * @param workFlowRoot + * Root element where elements are to be inserted. + */ + @Deprecated + public synchronized void saveWorkFlow_V2(WorkFlow workFlow, Element workFlowRoot) + { + Element blocksNode = XMLUtil.addElement(workFlowRoot, "blocks"); + + for (BlockDescriptor blockData : workFlow) + { + Element blockNode; + Block block = blockData.getBlock(); + + if (block instanceof WorkFlow) + { + blockNode = XMLUtil.addElement(blocksNode, "workflow"); + XMLUtil.setAttributeBooleanValue(blockNode, "collapsed", blockData.isCollapsed()); + } + else + { + blockNode = XMLUtil.addElement(blocksNode, "block"); + } + + XMLUtil.setAttributeValue(blockNode, "type", block.getClass().getCanonicalName()); + XMLUtil.setAttributeIntValue(blockNode, "ID", workFlow.indexOf(blockData)); + XMLUtil.setAttributeIntValue(blockNode, "xLocation", blockData.getLocation().x); + XMLUtil.setAttributeIntValue(blockNode, "yLocation", blockData.getLocation().y); + + if (blockData.isWorkFlow()) + { + WorkFlow innerWorkFlow = (WorkFlow) block; + + saveWorkFlow_V2(innerWorkFlow, blockNode); + + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + BlockDescriptor owner = innerWorkFlow.getInputOwner(var); + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", blockData.inputVars.getID(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", innerWorkFlow.indexOf(owner)); + XMLUtil.setAttributeValue(varNode, "varID", owner.inputVars.getID(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + BlockDescriptor owner = innerWorkFlow.getOutputOwner(var); + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", blockData.outputVars.getID(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", innerWorkFlow.indexOf(owner)); + XMLUtil.setAttributeValue(varNode, "varID", owner.outputVars.getID(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + } + } + else + { + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.inputVars.getID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getCanonicalName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getCanonicalName()); + } + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.outputVars.getID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getCanonicalName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getCanonicalName()); + } + } + } + } + + Element linkRoot = XMLUtil.addElement(workFlowRoot, "links"); + + for (Link<?> link : workFlow.getLinksIterator()) + { + Element linkNode = XMLUtil.addElement(linkRoot, "link"); + + XMLUtil.setAttributeIntValue(linkNode, "srcBlockID", workFlow.indexOf(link.srcBlock)); + XMLUtil.setAttributeValue(linkNode, "srcVarID", link.srcBlock.getVarID(link.srcVar)); + XMLUtil.setAttributeIntValue(linkNode, "dstBlockID", workFlow.indexOf(link.dstBlock)); + XMLUtil.setAttributeValue(linkNode, "dstVarID", link.dstBlock.getVarID(link.dstVar)); + } + } + + /** + * @deprecated Legacy method, use {@link #saveWorkFlow_V4(WorkFlow, Element)} instead. + * @param workFlow + * The workflow to save. + * @param workFlowRoot + * Root element where elements are to be inserted. + */ + @Deprecated + public synchronized void saveWorkFlow_V3(WorkFlow workFlow, Element workFlowRoot) + { + Element blocksNode = XMLUtil.addElement(workFlowRoot, "blocks"); + + for (BlockDescriptor blockData : workFlow) + { + Element blockNode; + Block block = blockData.getBlock(); + + if (block instanceof WorkFlow) + { + blockNode = XMLUtil.addElement(blocksNode, "workflow"); + XMLUtil.setAttributeBooleanValue(blockNode, "collapsed", blockData.isCollapsed()); + XMLUtil.setAttributeIntValue(blockNode, "width", blockData.getDimension().width); + XMLUtil.setAttributeIntValue(blockNode, "height", blockData.getDimension().height); + } + else + { + blockNode = XMLUtil.addElement(blocksNode, "block"); + } + + String blockClass = block.getClass().getCanonicalName(); + String mainClass = block instanceof PluginBundled ? ((PluginBundled) block).getMainPluginClassName() + : blockClass; + + XMLUtil.setAttributeValue(blockNode, "className", mainClass); + XMLUtil.setAttributeValue(blockNode, "blockType", blockClass); + XMLUtil.setAttributeIntValue(blockNode, "ID", workFlow.indexOf(blockData)); + XMLUtil.setAttributeIntValue(blockNode, "xLocation", blockData.getLocation().x); + XMLUtil.setAttributeIntValue(blockNode, "yLocation", blockData.getLocation().y); + + if (blockData.isWorkFlow()) + { + WorkFlow innerWorkFlow = (WorkFlow) block; + + saveWorkFlow_V3(innerWorkFlow, blockNode); + + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", innerWorkFlow.getInputVarID(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", innerWorkFlow.getOutputVarID(var)); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + } + } + else + { + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.inputVars.getID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getCanonicalName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getCanonicalName()); + } + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.outputVars.getID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getCanonicalName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getCanonicalName()); + } + } + } + } + + Element linkRoot = XMLUtil.addElement(workFlowRoot, "links"); + + for (Link<?> link : workFlow.getLinksIterator()) + { + Element linkNode = XMLUtil.addElement(linkRoot, "link"); + + XMLUtil.setAttributeIntValue(linkNode, "srcBlockID", workFlow.indexOf(link.srcBlock)); + XMLUtil.setAttributeValue(linkNode, "srcVarID", link.srcBlock.getVarID(link.srcVar)); + XMLUtil.setAttributeIntValue(linkNode, "dstBlockID", workFlow.indexOf(link.dstBlock)); + XMLUtil.setAttributeValue(linkNode, "dstVarID", link.dstBlock.getVarID(link.dstVar)); + } + } + + /** + * @param workFlow + * The workflow to save. + * @param workFlowRoot + * Root element where elements are to be inserted. + */ + public synchronized void saveWorkFlow_V4(WorkFlow workFlow, Element workFlowRoot) + { + Element blocksNode = XMLUtil.addElement(workFlowRoot, "blocks"); + + for (BlockDescriptor blockData : workFlow) + { + Element blockNode; + Block block = blockData.getBlock(); + + if (block instanceof WorkFlow) + { + blockNode = XMLUtil.addElement(blocksNode, "workflow"); + XMLUtil.setAttributeBooleanValue(blockNode, "collapsed", blockData.isCollapsed()); + } + else + { + blockNode = XMLUtil.addElement(blocksNode, "block"); + } + + String blockClass = block.getClass().getName(); + String mainClass = block instanceof PluginBundled ? ((PluginBundled) block).getMainPluginClassName() + : blockClass; + + XMLUtil.setAttributeValue(blockNode, "className", mainClass); + XMLUtil.setAttributeValue(blockNode, "blockType", blockClass); + XMLUtil.setAttributeIntValue(blockNode, "ID", blockData.getID()); + XMLUtil.setAttributeIntValue(blockNode, "xLocation", blockData.getLocation().x); + XMLUtil.setAttributeIntValue(blockNode, "yLocation", blockData.getLocation().y); + XMLUtil.setAttributeIntValue(blockNode, "width", blockData.getDimension().width); + XMLUtil.setAttributeIntValue(blockNode, "height", blockData.getDimension().height); + XMLUtil.setAttributeBooleanValue(blockNode, "collapsed", blockData.isCollapsed()); + XMLUtil.setAttributeValue(blockNode, "definedName", blockData.getDefinedName()); + XMLUtil.setAttributeBooleanValue(blockNode, "keepsResults", blockData.keepsResults()); + + if (block instanceof InputBlock) + XMLUtil.setAttributeValue(blockNode, "CommandLineID", blockData.getCommandLineID()); + + if (blockData.isWorkFlow()) + { + WorkFlow innerWorkFlow = (WorkFlow) block; + + saveWorkFlow_V4(innerWorkFlow, blockNode); + + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + BlockDescriptor owner = innerWorkFlow.getInputOwner(var); + if (owner == null) + { + System.err + .println("Warning: could not find owner for input variable: \"" + var + "\", skipping"); + continue; + } + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", blockData.getVarID(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", owner.getID()); + var.saveToXML(varNode); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + BlockDescriptor owner = innerWorkFlow.getOutputOwner(var); + if (owner == null) + { + System.err.println( + "Warning: could not find owner for output variable: \"" + var + "\", skipping"); + continue; + } + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, "ID", blockData.getVarID(var)); + XMLUtil.setAttributeIntValue(varNode, "blockID", owner.getID()); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + } + } + else + { + Element varRoot = XMLUtil.addElement(blockNode, "variables"); + + Element inputVarRoot = XMLUtil.addElement(varRoot, "input"); + + for (Var<?> var : blockData.inputVars) + { + Element varNode = XMLUtil.addElement(inputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.getVarID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + + // only save user values (linked values are stored or generated elsewhere) + if (var.getReference() == null) + var.saveToXML(varNode); + + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.inputVars.isVisible(var)); + if (var instanceof MutableType) + { + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getName()); + } + } + XMLUtil.setAttributeBooleanValue(varNode, RUNTIME, blockData.inputVars.isRuntimeVariable(var)); + } + + Element outputVarRoot = XMLUtil.addElement(varRoot, "output"); + + for (Var<?> var : blockData.outputVars) + { + Element varNode = XMLUtil.addElement(outputVarRoot, "variable"); + XMLUtil.setAttributeValue(varNode, Var.XML_KEY_ID, blockData.getVarID(var)); + XMLUtil.setAttributeValue(varNode, "name", var.getName()); + XMLUtil.setAttributeBooleanValue(varNode, "visible", blockData.outputVars.isVisible(var)); + if (var instanceof MutableType) + { + if (var instanceof VarMutable && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getName()); + } + else if (var instanceof VarMutableArray && var.getType() != null) + { + XMLUtil.setAttributeValue(varNode, "type", var.getType().getComponentType().getName()); + } + } + XMLUtil.setAttributeBooleanValue(varNode, RUNTIME, blockData.outputVars.isRuntimeVariable(var)); + } + } + } + + Element linkRoot = XMLUtil.addElement(workFlowRoot, "links"); + + for (Link<?> link : workFlow.getLinksIterator()) + { + Element linkNode = XMLUtil.addElement(linkRoot, "link"); + + XMLUtil.setAttributeIntValue(linkNode, "srcBlockID", link.srcBlock.getID()); + XMLUtil.setAttributeValue(linkNode, "srcVarID", link.srcBlock.getVarID(link.srcVar)); + if (link.srcVar instanceof MutableType) + { + if (link.srcVar instanceof VarMutable && link.srcVar.getType() != null) + { + XMLUtil.setAttributeValue(linkNode, "srcVarType", link.srcVar.getType().getName()); + } + else if (link.srcVar instanceof VarMutableArray && link.srcVar.getType() != null) + { + XMLUtil.setAttributeValue(linkNode, "srcVarType", + link.srcVar.getType().getComponentType().getName()); + } + } + XMLUtil.setAttributeIntValue(linkNode, "dstBlockID", link.dstBlock.getID()); + XMLUtil.setAttributeValue(linkNode, "dstVarID", link.dstBlock.getVarID(link.dstVar)); + } + } + + /** + * @param xml + * The xml document to load. + * @param targetWorkFlow + * The workflow where elements generated from the xml document are to be inserted. + * @return true if the loaded file is at the latest BlocksML version, false otherwise. + */ + public synchronized boolean loadWorkFlow(Document xml, WorkFlow targetWorkFlow) + { + Element xmlRoot = XMLUtil.getRootElement(xml); + + String rootName = xmlRoot.getNodeName(); + + if (!rootName.equalsIgnoreCase("protocol") && !rootName.equalsIgnoreCase("workspace")) + { + throw new IcyHandledException("The selected file is not an Icy protocol"); + } + + int fileVersion = XMLUtil.getAttributeIntValue(xmlRoot, "VERSION", -1); + + // scan the protocol for missing plugins + Set<String> missingPlugins = getMissingPlugins(xmlRoot, fileVersion); + + if (missingPlugins.size() > 0) + { + // Are we connected to the internet? + if (!NetworkUtil.hasInternetAccess()) + throw new IcyHandledException( + "Some plugins required by this protocol are missing, but no internet connection is available"); + + // Are we using Icy's plug-in loader (and not Eclipse)? + if (getClass().getClassLoader() == ClassLoader.getSystemClassLoader()) + throw new IcyHandledException( + "Cannot install missing blocks while using Icy4Eclipse in \"Debug\" or \"Run\" modes"); + + // we are live! + String message = "[Protocols] Installing required plugins..."; + System.out.println(message); + AnnounceFrame announcement = null; + + if (!Icy.getMainInterface().isHeadLess()) + announcement = new AnnounceFrame(message); + + Set<PluginDescriptor> descriptors = new HashSet<PluginDescriptor>(missingPlugins.size()); + + for (String className : missingPlugins) + { + PluginDescriptor pDesc = PluginRepositoryLoader.getPlugin(className); + if (pDesc == null) + { + // sadly, I believe the plugin loader could be reloaded at anytime... + // double check that for every plugin, just to be safe + if (PluginLoader.getPlugins(Block.class, true, false, false).size() == 0) + throw new BlocksReloadedException(); + + throw new BlocksException( + "Couldn't find plugin " + ClassUtil.getSimpleClassName(className) + " online", true); + } + descriptors.add(pDesc); + } + + for (PluginDescriptor pDesc : descriptors) + PluginInstaller.install(pDesc, false); + + PluginInstaller.waitInstall(); + PluginLoader.waitWhileLoading(); + + System.out.println("[Protocols] Plugins installed successfully."); + if (announcement != null) + announcement.close(); + + // reload the whole mother! + throw new BlocksReloadedException(); + } + + switch (fileVersion) + { + case 1: + loadWorkFlow_V1(xmlRoot, targetWorkFlow); + break; + case 2: + loadWorkFlow_V2(xmlRoot, targetWorkFlow); + break; + case 3: + loadWorkFlow_V3(xmlRoot, targetWorkFlow); + break; + case 4: + loadWorkFlow_V4(xmlRoot, targetWorkFlow); + break; + default: + throw new UnsupportedOperationException("Unknown Protocol version: " + fileVersion); + } + + return fileVersion == CURRENT_VERSION; + } + + private Set<String> getMissingPlugins(Element workFlowRoot, int fileVersion) + { + Set<String> missingPlugins = new HashSet<String>(); + + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + // we are not in a node that has blocks => stop the recursion + if (blocksRoot == null) + return missingPlugins; + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (Element blockNode : blocksNode) + { + // the class name of the block to find + String blockTag = (fileVersion <= 2) ? "type" : "blockType"; + String blockType = XMLUtil.getAttributeValue(blockNode, blockTag, null); + // the class name of the plugin providing this block (may be the same) + String pluginClassName = XMLUtil.getAttributeValue(blockNode, "className", blockType); + + try + { + ClassUtil.findClass(blockType); + } + catch (ClassNotFoundException e) + { + // the block appears to be missing + // => is the corresponding plug-in missing or out-dated? + PluginDescriptor mainPlugin = PluginLoader.getPlugin(pluginClassName); + if (mainPlugin == null || PluginUpdater.getUpdate(mainPlugin) != null) + { + // store the class name of the missing plug-in + missingPlugins.add(pluginClassName); + } + else + { + // The block is definitely missing, stop everything! + IcyExceptionHandler.handleException(mainPlugin, e, false); + } + } + finally + { + // whether the current block is missing or not, + // perhaps it's a work flow and has more blocks within + missingPlugins.addAll(getMissingPlugins(blockNode, fileVersion)); + } + } + + return missingPlugins; + } + + /** + * @deprecated legacy method. Use {@link #loadWorkFlow_V4(Element, WorkFlow)} instead. + * @param workFlowRoot + * XML element of the workflow root to be loaded. + * @param workFlow + * Target workflow to add elements from document. + */ + @Deprecated + @SuppressWarnings({"unchecked", "deprecation"}) + public synchronized void loadWorkFlow_V1(Element workFlowRoot, WorkFlow workFlow) + { + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (Element blockNode : blocksNode) + { + String className = XMLUtil.getAttributeValue(blockNode, "type", null); + + int xPos = XMLUtil.getAttributeIntValue(blockNode, "xLocation", 0); + int yPos = XMLUtil.getAttributeIntValue(blockNode, "yLocation", 0); + + try + { + Class<? extends Block> blockClass = (Class<? extends Block>) ClassUtil.findClass(className); + + Block block = blockClass.newInstance(); + + if (block == null) + throw new BlocksException("Couldn't create block from class " + blockClass.getName(), true); + + if (block instanceof WorkFlow) + { + // load the inner work flow + loadWorkFlow_V1(blockNode, (WorkFlow) block); + + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + // adjust visibility triggers + { + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + int id = XMLUtil.getAttributeIntValue(varNode, "ID", -1); + Var<?> var = blockInfo.inputVars.get(id); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.inputVars.setVisible(var, visible); + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + int id = XMLUtil.getAttributeIntValue(varNode, "ID", -1); + Var<?> var = blockInfo.outputVars.get(id); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.outputVars.setVisible(var, visible); + } + } + } + else + { + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + int id = XMLUtil.getAttributeIntValue(varNode, Var.XML_KEY_ID, -1); + Var<?> var = blockInfo.inputVars.get(id); + var.loadFromXML(varNode); + blockInfo.inputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + int id = XMLUtil.getAttributeIntValue(varNode, Var.XML_KEY_ID, -1); + Var<?> var = blockInfo.outputVars.get(id); + var.loadFromXML(varNode); + blockInfo.outputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + } + } + } + catch (Exception e1) + { + throw new BlocksException("Cannot create block (" + e1.getMessage() + ")", true); + } + } + + Element linkRoot = XMLUtil.getElement(workFlowRoot, "links"); + + for (Element linkNode : XMLUtil.getElements(linkRoot)) + { + int srcBlockID = XMLUtil.getAttributeIntValue(linkNode, "srcBlockID", -1); + int srcVarID = XMLUtil.getAttributeIntValue(linkNode, "srcVarID", -1); + int dstBlockID = XMLUtil.getAttributeIntValue(linkNode, "dstBlockID", -1); + int dstVarID = XMLUtil.getAttributeIntValue(linkNode, "dstVarID", -1); + + BlockDescriptor srcBlock = workFlow.getBlock(srcBlockID); + @SuppressWarnings("rawtypes") + // this is assumed correct + Var srcVar = srcBlock.outputVars.get(srcVarID); + + BlockDescriptor dstBlock = workFlow.getBlock(dstBlockID); + @SuppressWarnings("rawtypes") + // this is assumed correct + Var dstVar = dstBlock.inputVars.get(dstVarID); + + workFlow.addLink(srcBlock, srcVar, dstBlock, dstVar); + } + } + + /** + * @deprecated legacy method. Use {@link #loadWorkFlow_V4(Element, WorkFlow)} instead. + * @param workFlowRoot + * XML element of the workflow root to be loaded. + * @param workFlow + * Target workflow to add elements from document. + */ + @Deprecated + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public synchronized void loadWorkFlow_V2(Element workFlowRoot, WorkFlow workFlow) + { + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (Element blockNode : blocksNode) + { + String className = XMLUtil.getAttributeValue(blockNode, "type", null); + + int xPos = XMLUtil.getAttributeIntValue(blockNode, "xLocation", -1); + int yPos = XMLUtil.getAttributeIntValue(blockNode, "yLocation", -1); + + try + { + Class<? extends Block> blockClass = (Class<? extends Block>) ClassUtil.findClass(className); + + Block block = blockClass.newInstance(); + + if (block == null) + throw new BlocksException("Couldn't create block from class " + blockClass.getName(), true); + + if (block instanceof WorkFlow) + { + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + // load the inner work flow + loadWorkFlow_V2(blockNode, (WorkFlow) block); + + // adjust visibility triggers + { + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.inputVars.get(uid); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.inputVars.setVisible(var, visible); + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.outputVars.get(uid); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.outputVars.setVisible(var, visible); + } + } + } + else + { + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.inputVars.get(uid); + var.loadFromXML(varNode); + blockInfo.inputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + ((MutableType) var).setType(Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.outputVars.get(uid); + var.loadFromXML(varNode); + blockInfo.outputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + ((MutableType) var).setType(Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + } + } + } + catch (Exception e1) + { + throw new BlocksException("Cannot create block (" + e1.getMessage() + ")", true); + } + } + + Element linkRoot = XMLUtil.getElement(workFlowRoot, "links"); + + for (Element linkNode : XMLUtil.getElements(linkRoot)) + { + int srcBlockID = XMLUtil.getAttributeIntValue(linkNode, "srcBlockID", -1); + String srcVarID = XMLUtil.getAttributeValue(linkNode, "srcVarID", null); + int dstBlockID = XMLUtil.getAttributeIntValue(linkNode, "dstBlockID", -1); + String dstVarID = XMLUtil.getAttributeValue(linkNode, "dstVarID", null); + + BlockDescriptor srcBlock = workFlow.getBlock(srcBlockID); + Var srcVar = srcBlock.outputVars.get(srcVarID); + if (srcVar == null) + srcVar = srcBlock.inputVars.get(srcVarID); + + BlockDescriptor dstBlock = workFlow.getBlock(dstBlockID); + Var dstVar = dstBlock.inputVars.get(dstVarID); + + workFlow.addLink(srcBlock, srcVar, dstBlock, dstVar); + } + } + + /** + * @deprecated legacy method. Use {@link #loadWorkFlow_V4(Element, WorkFlow)} instead. + * @param workFlowRoot + * XML element of the workflow root to be loaded. + * @param workFlow + * Target workflow to add elements from document. + */ + @Deprecated + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + public synchronized void loadWorkFlow_V3(Element workFlowRoot, WorkFlow workFlow) + { + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (Element blockNode : blocksNode) + { + String blockType = XMLUtil.getAttributeValue(blockNode, "blockType", null); + + int xPos = XMLUtil.getAttributeIntValue(blockNode, "xLocation", -1); + int yPos = XMLUtil.getAttributeIntValue(blockNode, "yLocation", -1); + + try + { + Class<? extends Block> blockClass = (Class<? extends Block>) ClassUtil.findClass(blockType); + + Block block = blockClass.newInstance(); + + if (block == null) + throw new BlocksException("Couldn't create block from class " + blockClass.getName(), true); + + if (block instanceof WorkFlow) + { + int width = XMLUtil.getAttributeIntValue(blockNode, "width", 500); + int height = XMLUtil.getAttributeIntValue(blockNode, "height", 500); + ((WorkFlow) block).getBlockDescriptor().setDimension(width, height); + + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + // load the inner work flow + loadWorkFlow_V3(blockNode, (WorkFlow) block); + + // adjust visibility triggers + { + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.inputVars.get(uid); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.inputVars.setVisible(var, visible); + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.outputVars.get(uid); + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + blockInfo.outputVars.setVisible(var, visible); + } + } + } + else + { + BlockDescriptor blockInfo = workFlow.addBlock(-1, block, new Point(xPos, yPos)); + + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + for (Element varNode : XMLUtil.getElements(inVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.inputVars.get(uid); + var.loadFromXML(varNode); + blockInfo.inputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + ((MutableType) var).setType(Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + } + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + for (Element varNode : XMLUtil.getElements(outVarRoot)) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockInfo.outputVars.get(uid); + var.loadFromXML(varNode); + blockInfo.outputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + ((MutableType) var).setType(Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + } + } + } + catch (Exception e1) + { + throw new BlocksException("Cannot create block (" + e1.getMessage() + ")", true); + } + } + + Element linkRoot = XMLUtil.getElement(workFlowRoot, "links"); + + for (Element linkNode : XMLUtil.getElements(linkRoot)) + { + int srcBlockID = XMLUtil.getAttributeIntValue(linkNode, "srcBlockID", -1); + String srcVarID = XMLUtil.getAttributeValue(linkNode, "srcVarID", null); + int dstBlockID = XMLUtil.getAttributeIntValue(linkNode, "dstBlockID", -1); + String dstVarID = XMLUtil.getAttributeValue(linkNode, "dstVarID", null); + + BlockDescriptor srcBlock = workFlow.getBlock(srcBlockID); + Var srcVar = srcBlock.outputVars.get(srcVarID); + if (srcVar == null) + srcVar = srcBlock.inputVars.get(srcVarID); + + BlockDescriptor dstBlock = workFlow.getBlock(dstBlockID); + Var dstVar = dstBlock.inputVars.get(dstVarID); + + workFlow.addLink(srcBlock, srcVar, dstBlock, dstVar); + } + } + + /** + * @param workFlowRoot + * XML element of the workflow root to be loaded. + * @param workFlow + * Target workflow to add elements from document. + * @throws BlocksException + * If an error occurs while loading the workflow. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public synchronized void loadWorkFlow_V4(Element workFlowRoot, final WorkFlow workFlow) throws BlocksException + { + boolean hasWarnings = false; + + // Prepare a list of blocks to collapse after loading + ArrayList<BlockDescriptor> blocksToCollapse = new ArrayList<BlockDescriptor>(); + + // 1) Load blocks and inner work flows + + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (final Element blockNode : blocksNode) + { + final String blockType = XMLUtil.getAttributeValue(blockNode, "blockType", null); + + Block block = null; + + try + { + block = ThreadUtil.invokeNow(new Callable<Block>() + { + @Override + public Block call() throws Exception + { + return (Block) ClassUtil.findClass(blockType).newInstance(); + } + }); + } + catch (Exception e) + { + String message = "Couldn't create block from class \"" + blockType + "\".\n"; + message += "Reason: " + e.getClass().getName() + " (" + e.getMessage() + ")"; + + throw new BlocksException(message, true); + } + + int blockID = XMLUtil.getAttributeIntValue(blockNode, "ID", -1); + + final BlockDescriptor blockDescriptor; + + if (block instanceof WorkFlow) + { + blockDescriptor = ((WorkFlow) block).getBlockDescriptor(); + blockDescriptor.setID(blockID); + } + else + { + blockDescriptor = new BlockDescriptor(blockID, block); + } + + int xPos = XMLUtil.getAttributeIntValue(blockNode, "xLocation", -1); + int yPos = XMLUtil.getAttributeIntValue(blockNode, "yLocation", -1); + blockDescriptor.setLocation(xPos, yPos); + + int width = XMLUtil.getAttributeIntValue(blockNode, "width", 0); + int height = XMLUtil.getAttributeIntValue(blockNode, "height", 0); + + // set the block's dimension, but don't collapse just now + // => wait for all the drawing to be done + blockDescriptor.setDimension(width, height); + + String definedName = XMLUtil.getAttributeValue(blockNode, "definedName", null); + blockDescriptor.setDefinedName(definedName); + + blockDescriptor.keepResults(XMLUtil.getAttributeBooleanValue(blockNode, "keepsResults", true)); + + if (block instanceof InputBlock) + blockDescriptor.setCommandLineID(XMLUtil.getAttributeValue(blockNode, "CommandLineID", "")); + + workFlow.addBlock(blockDescriptor); + + if (block instanceof WorkFlow) + { + // load the inner work flow + loadWorkFlow_V4(blockNode, (WorkFlow) block); + + // adjust visibility triggers + + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + final ArrayList<Element> inVarNodes = XMLUtil.getElements(inVarRoot); + + synchronized (blockDescriptor.inputVars) + { + for (Element varNode : inVarNodes) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockDescriptor.inputVars.get(uid); + + if (var == null) + { + System.err.println(new NoSuchVariableException(blockDescriptor, uid).getMessage()); + continue; + } + + if (var.getReference() == null) + { + var.loadFromXML(varNode); + + // adjust visibility + + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + try + { + blockDescriptor.inputVars.setVisible(var, visible); + } + catch (NoSuchVariableException e) + { + if (!hasWarnings) + { + System.err.println("Error(s) while loading protocol:"); + hasWarnings = true; + } + System.err.println(new NoSuchVariableException(blockDescriptor, uid).getMessage()); + } + } + } + } + + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + final ArrayList<Element> outVarNodes = XMLUtil.getElements(outVarRoot); + + synchronized (blockDescriptor.outputVars) + { + for (Element varNode : outVarNodes) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockDescriptor.outputVars.get(uid); + + // adjust visibility + + boolean visible = XMLUtil.getAttributeBooleanValue(varNode, "visible", false); + try + { + blockDescriptor.outputVars.setVisible(var, visible); + } + catch (NoSuchVariableException e) + { + if (!hasWarnings) + { + System.err.println("Error(s) while loading protocol:"); + hasWarnings = true; + } + System.err.println(new NoSuchVariableException(blockDescriptor, uid).getMessage()); + // throw new NoSuchVariableException(blockInfo, uid); + } + } + } + } + else + { + Element varRoot = XMLUtil.getElement(blockNode, "variables"); + Element inVarRoot = XMLUtil.getElement(varRoot, "input"); + final ArrayList<Element> inVarNodes = XMLUtil.getElements(inVarRoot); + + synchronized (blockDescriptor.inputVars) + { + for (Element varNode : inVarNodes) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockDescriptor.inputVars.get(uid); + + boolean isDynamicVariable = XMLUtil.getAttributeBooleanValue(varNode, RUNTIME, false); + + if (var == null) + { + if (isDynamicVariable) + { + var = new VarMutable(XMLUtil.getAttributeValue(varNode, "name", "input"), null); + ((VarMutable) var).setDefaultEditorModel(new TypeSelectionModel()); + blockDescriptor.inputVars.addRuntimeVariable(uid, (VarMutable) var); + } + else + { + if (!hasWarnings) + { + System.err.println("Error(s) while loading protocol:"); + hasWarnings = true; + } + System.err.println(new NoSuchVariableException(blockDescriptor, uid).getMessage()); + continue; + // throw new NoSuchVariableException(blockInfo, uid); + } + } + + try + { + if (var instanceof MutableType) + { + Class<?> mutableType = null; + + String type = XMLUtil.getAttributeValue(varNode, "type", null); + + if (type != null) + { + if (var instanceof VarMutable) + { + mutableType = getPrimitiveType(type); + + if (mutableType == null) + mutableType = Class.forName(type); + } + else if (var instanceof VarMutableArray) + { + mutableType = Class.forName("[L" + type + ";"); + } + + ((MutableType) var).setType(mutableType); + } + } + + var.loadFromXML(varNode); + + // Input blocks can have their values set from command line + if (block instanceof InputBlock) + { + String clID = blockDescriptor.getCommandLineID(); + if (!clID.isEmpty()) + { + Map<String, String> map = Protocols.getCommandLineArguments(); + if (map.containsKey(clID)) + { + // Set the value of the first (and only) block variable + var.setValueAsString(map.get(clID)); + } + } + } + } + catch (Exception e) + { + String message = "Unable to read input variable \"" + var.getName() + "\" in block \"" + + blockDescriptor.getDefinedName() + "\".\n"; + message += "Reason: " + e.getClass().getName() + " (" + e.getMessage() + ")"; + throw new BlocksException(message, true); + } + blockDescriptor.inputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + } + } + + Element outVarRoot = XMLUtil.getElement(varRoot, "output"); + final ArrayList<Element> outVarNodes = XMLUtil.getElements(outVarRoot); + + synchronized (blockDescriptor.outputVars) + { + for (Element varNode : outVarNodes) + { + String uid = XMLUtil.getAttributeValue(varNode, "ID", null); + Var<?> var = blockDescriptor.outputVars.get(uid); + + boolean isDynamicVariable = XMLUtil.getAttributeBooleanValue(varNode, RUNTIME, false); + + if (var == null) + { + if (isDynamicVariable) + { + var = new VarMutable(XMLUtil.getAttributeValue(varNode, "name", "output"), null); + ((VarMutable) var).setDefaultEditorModel(new TypeSelectionModel()); + blockDescriptor.outputVars.addRuntimeVariable(uid, (VarMutable) var); + } + else + { + if (!hasWarnings) + { + System.err.println("Error(s) while loading protocol:"); + hasWarnings = true; + } + System.err.println(new NoSuchVariableException(blockDescriptor, uid).getMessage()); + continue; + // throw new NoSuchVariableException(blockInfo, uid); + } + } + + blockDescriptor.outputVars.setVisible(var, + XMLUtil.getAttributeBooleanValue(varNode, "visible", false)); + + if (var instanceof MutableType) + try + { + String type = XMLUtil.getAttributeValue(varNode, "type", null); + if (type != null) + { + if (var instanceof VarMutable) + { + Class<?> mutableType = getPrimitiveType(type); + ((MutableType) var) + .setType(mutableType != null ? mutableType : Class.forName(type)); + } + else if (var instanceof VarMutableArray) + { + ((MutableType) var).setType(Class.forName("[L" + type + ";")); + } + } + } + catch (ClassNotFoundException e) + { + String message = "Unable to read output variable \"" + var.getName() + "\" in block \"" + + blockDescriptor.getDefinedName() + "\".\n"; + message += "Reason: " + e.getClass().getName() + " (" + e.getMessage() + ")"; + throw new BlocksException(message, true); + } + } + } + } + + // prepare to collapse the block if necessary + if (XMLUtil.getAttributeBooleanValue(blockNode, "collapsed", false)) + blocksToCollapse.add(blockDescriptor); + } + + // 2) Load links + + Element linkRoot = XMLUtil.getElement(workFlowRoot, "links"); + + ArrayList<Element> links = XMLUtil.getElements(linkRoot); + Collections.sort(links, new Comparator<Element>() + { + @Override + public int compare(Element link1, Element link2) + { + int srcBlock1 = XMLUtil.getAttributeIntValue(link1, "srcBlockID", -1); + int srcBlock2 = XMLUtil.getAttributeIntValue(link2, "srcBlockID", -1); + + int i1 = workFlow.indexOf(workFlow.getBlockByID(srcBlock1)); + int i2 = workFlow.indexOf(workFlow.getBlockByID(srcBlock2)); + + return i1 < i2 ? -1 : i1 > i2 ? 1 : 0; + } + }); + + for (Element linkNode : links) + { + int srcBlockID = XMLUtil.getAttributeIntValue(linkNode, "srcBlockID", -1); + String srcVarID = XMLUtil.getAttributeValue(linkNode, "srcVarID", null); + int dstBlockID = XMLUtil.getAttributeIntValue(linkNode, "dstBlockID", -1); + String dstVarID = XMLUtil.getAttributeValue(linkNode, "dstVarID", null); + + // load the source variable + BlockDescriptor srcBlock = workFlow.getBlockByID(srcBlockID); + Var srcVar = srcBlock.outputVars.get(srcVarID); + if (srcVar == null) + srcVar = srcBlock.inputVars.get(srcVarID); + + if (srcVar == null) + { + System.err.println("Cannot create a link from variable " + srcVarID + " (from block " + srcBlock + + "). It may have been removed or renamed."); + continue; + } + + if (srcVar instanceof MutableType) + { + String type = XMLUtil.getAttributeValue(linkNode, "srcVarType", null); + if (type != null) + { + try + { + if (srcVar instanceof VarMutable) + { + Class<?> mutableType = getPrimitiveType(type); + ((MutableType) srcVar).setType(mutableType != null ? mutableType : Class.forName(type)); + } + else if (srcVar instanceof VarMutableArray) + { + ((MutableType) srcVar).setType(Class.forName("[L" + type + ";")); + } + } + catch (ClassNotFoundException e1) + { + throw new BlocksException("Cannot create block (" + e1.getMessage() + ") => class not found", + true); + } + } + } + + // load the destination variable + BlockDescriptor dstBlock = workFlow.getBlockByID(dstBlockID); + + Var dstVar = dstBlock.inputVars.get(dstVarID); + + if (dstVar == null) + { + System.err.println("Cannot link to variable " + dstVarID + " (from block " + dstBlock + + "). It may have been removed or renamed."); + continue; + } + + workFlow.addLink(srcBlock, srcVar, dstBlock, dstVar); + } + + // 3) Collapse necessary blocks + for (BlockDescriptor block : blocksToCollapse) + block.setCollapsed(true); + + if (hasWarnings) + System.err.println("--"); + } + + public synchronized void loadWorkFlow_V5(Element workFlowRoot, WorkFlow workFlow, WorkFlow parentFlow) + throws BlocksException + { + boolean noWarnings = true; + + Element blocksRoot = XMLUtil.getElement(workFlowRoot, "blocks"); + + ArrayList<Element> blocksNode = XMLUtil.getElements(blocksRoot); + + for (Element blockNode : blocksNode) + { + BlockDescriptor blockDesc = new BlockDescriptor(); + noWarnings &= blockDesc.loadFromXML(blockNode); + workFlow.addBlock(blockDesc); + } + + Element linkRoot = XMLUtil.getElement(workFlowRoot, "links"); + + for (Element linkNode : XMLUtil.getElements(linkRoot)) + { + @SuppressWarnings("rawtypes") + Link link = new Link(workFlow); + link.loadFromXML(linkNode); + } + + if (!noWarnings) + System.err.println("--"); + } + + /** + * @param primitiveName + * The string representation of the primitive type. + * @return The Java primitive type represented by the given name, or null if the given name does + * not represent a primitive type + */ + public static Class<?> getPrimitiveType(String primitiveName) + { + if (primitiveName.equals("byte")) + return byte.class; + if (primitiveName.equals("short")) + return short.class; + if (primitiveName.equals("int")) + return int.class; + if (primitiveName.equals("long")) + return long.class; + if (primitiveName.equals("char")) + return char.class; + if (primitiveName.equals("float")) + return float.class; + if (primitiveName.equals("double")) + return double.class; + if (primitiveName.equals("boolean")) + return boolean.class; + if (primitiveName.equals("void")) + return void.class; + + return null; + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/BlocksReloadedException.java b/src/main/java/plugins/adufour/blocks/util/BlocksReloadedException.java new file mode 100644 index 0000000..70a13c0 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/BlocksReloadedException.java @@ -0,0 +1,10 @@ +package plugins.adufour.blocks.util; + +@SuppressWarnings("serial") +public class BlocksReloadedException extends BlocksException +{ + public BlocksReloadedException() + { + super("The plugin list has been updated", true); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/LinkCutException.java b/src/main/java/plugins/adufour/blocks/util/LinkCutException.java new file mode 100644 index 0000000..16dafea --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/LinkCutException.java @@ -0,0 +1,10 @@ +package plugins.adufour.blocks.util; + +@SuppressWarnings("serial") +public class LinkCutException extends BlocksException +{ + public LinkCutException(String s) + { + super(s, true); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/LoopException.java b/src/main/java/plugins/adufour/blocks/util/LoopException.java new file mode 100644 index 0000000..6336d78 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/LoopException.java @@ -0,0 +1,18 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.blocks.lang.WorkFlow; + +/** + * Exception raised whenever a loop is detected in an {@link WorkFlow} + * + * @author Alexandre Dufour + */ +public class LoopException extends BlocksException +{ + private static final long serialVersionUID = 1L; + + public LoopException() + { + super("Cannot create loops inside a workflow", true); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/MenuItemListener.java b/src/main/java/plugins/adufour/blocks/util/MenuItemListener.java new file mode 100644 index 0000000..1a51723 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/MenuItemListener.java @@ -0,0 +1,7 @@ +package plugins.adufour.blocks.util; + +import icy.plugin.PluginDescriptor; + +public interface MenuItemListener { + public void displayDoc(PluginDescriptor d); +} diff --git a/src/main/java/plugins/adufour/blocks/util/NoSuchBlockException.java b/src/main/java/plugins/adufour/blocks/util/NoSuchBlockException.java new file mode 100644 index 0000000..19eab99 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/NoSuchBlockException.java @@ -0,0 +1,25 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.blocks.lang.WorkFlow; + +public class NoSuchBlockException extends BlocksException +{ + private static final long serialVersionUID = 1L; + + public NoSuchBlockException(BlockDescriptor block) + { + super("Cannot find block '" + (block == null ? "null" : block.getName()) + "'", false); + } + + public NoSuchBlockException(int ID) + { + super("Cannot find block with ID '" + ID + "'", false); + } + + public NoSuchBlockException(int ID, WorkFlow container) + { + super("Cannot find block with ID '" + ID + "' in workflow '" + container.getBlockDescriptor().getName() + "'", false); + + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/NoSuchLinkException.java b/src/main/java/plugins/adufour/blocks/util/NoSuchLinkException.java new file mode 100644 index 0000000..68fc762 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/NoSuchLinkException.java @@ -0,0 +1,12 @@ +package plugins.adufour.blocks.util; + +public class NoSuchLinkException extends BlocksException +{ + private static final long serialVersionUID = 1L; + + public NoSuchLinkException(String message) + { + super(message, false); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/util/NoSuchVariableException.java b/src/main/java/plugins/adufour/blocks/util/NoSuchVariableException.java new file mode 100644 index 0000000..5b60c7c --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/NoSuchVariableException.java @@ -0,0 +1,24 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.vars.lang.Var; + +public class NoSuchVariableException extends BlocksException +{ + private static final long serialVersionUID = 1L; + + public NoSuchVariableException(String varName) + { + super("Variable not found: '" + varName + "'", false); + } + + public NoSuchVariableException(BlockDescriptor blockInfo, String varName) + { + super("Variable '" + varName + "' not found in block '" + blockInfo.getName() + "'. It may have been removed or renamed.", false); + } + + public NoSuchVariableException(Var<?> src) + { + this(src == null ? null : src.getName()); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/ScopeException.java b/src/main/java/plugins/adufour/blocks/util/ScopeException.java new file mode 100644 index 0000000..d7488a3 --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/ScopeException.java @@ -0,0 +1,13 @@ +package plugins.adufour.blocks.util; + +public class ScopeException extends BlocksException +{ + private static final long serialVersionUID = 1L; + + public ScopeException() + { + super("Two variables located in different loops or workflows cannot be directly linked.\n" + + "Expose the inner-most variable(s) by right-clicking on them and select \"expose variable\"", true); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/util/StopException.java b/src/main/java/plugins/adufour/blocks/util/StopException.java new file mode 100644 index 0000000..8bcce2b --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/StopException.java @@ -0,0 +1,10 @@ +package plugins.adufour.blocks.util; + +@SuppressWarnings("serial") +public class StopException extends RuntimeException +{ + public StopException() + { + super("This workflow was interrupted prematurely"); + } +} diff --git a/src/main/java/plugins/adufour/blocks/util/VarList.java b/src/main/java/plugins/adufour/blocks/util/VarList.java new file mode 100644 index 0000000..a484a3b --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/VarList.java @@ -0,0 +1,334 @@ +package plugins.adufour.blocks.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; + +import plugins.adufour.blocks.lang.Block; +import plugins.adufour.vars.lang.Var; +import plugins.adufour.vars.lang.VarMutable; + +/** + * Class defining a map of variables using a {@link java.util.HashMap} dictionary + * + * @author Alexandre Dufour + */ +public class VarList implements Iterable<Var<?>> +{ + private final LinkedHashMap<String, Var<?>> varMap = new LinkedHashMap<String, Var<?>>(); + + private final HashMap<Var<?>, Boolean> visibilityMap = new HashMap<Var<?>, Boolean>(); + + private final HashMap<Var<?>, Boolean> runtimeVariableMap = new HashMap<Var<?>, Boolean>(); + + private final ArrayList<VarVisibilityListener> visibilityListeners = new ArrayList<VarVisibilityListener>(); + + private final ArrayList<VarListListener> varListListeners = new ArrayList<VarListListener>(); + + /** + * Adds the specified variable to this variable list. Each variable is given a unique + * identifier, which is here the variable's name. Adding another variable with the same name + * using this method will throw a {@link IllegalArgumentException}. Instead, use the + * {@link #add(String, Var)} method to specify a unique identifier for each variable. + * + * @param variable + * The variable to add + * @throws IllegalArgumentException + * if the variable already exists, or if a variable with same unique ID exists + * @deprecated Changing the name of a variable will also change the default UID, and protocols + * containing an older version of this block (with the old name) will not reload + * properly. Use {@link #add(String, Var)} to specify a unique identifier (and make + * sure this identifier never changes across versions!)<br> + * NB: for the same reason, when migrating to {@link #add(String, Var)}, use the + * current name of the variable as unique identifier + */ + @Deprecated + public void add(Var<?> variable) + { + add(variable.getName(), variable); + } + + /** + * Adds the given variable to this list with the specified unique ID. If a variable with same + * unique ID already exists, an {@link IllegalArgumentException} is thrown. + * + * @param uid + * the unique ID of the variable (within this list) + * @param variable + * The variable to add + * @throws IllegalArgumentException + * if the variable already exists, or if a variable with same unique ID exists + */ + public void add(String uid, Var<?> variable) + { + add(uid, variable, false); + } + + /** + * Add a runtime variable to this list (see {@link #isRuntimeVariable(Var)} for more details). + * Runtime variables are limited to {@link VarMutable} for reloading purposes. <br> + * WARNING: Do *not* add a runtime variable from within {@link Block#declareInput(VarList)} or + * {@link Block#declareOutput(VarList)} with no particular runtime condition. + * + * @param uid + * the unique ID of the variable (within this list) + * @param variable + * The variable to add + */ + public void addRuntimeVariable(String uid, VarMutable variable) + { + add(uid, variable, true); + } + + /** + * Adds the given variable to this list with the specified unique ID. Note that if a variable + * with same unique ID already exists, an {@link IllegalArgumentException} is thrown. <br/> + * <br/> + * WARNING: Do *not* mark a variable as dynamic if it is created and added from within + * {@link Block#declareInput(VarList)} or {@link Block#declareOutput(VarList)} with no + * particular runtime condition. + * + * @param uid + * the unique ID of the variable (within this list) + * @param variable + * The variable to add + * @param isRuntimeVariable + * <code>true</code> is the variable is dynamic (see {@link #isRuntimeVariable(Var)}) + * for more details. + * @throws IllegalArgumentException + * if the variable already exists, or if a variable with same unique ID exists + */ + private void add(String uid, Var<?> variable, boolean isRuntimeVariable) + { + if (varMap.containsKey(uid)) + throw new IllegalArgumentException("A variable with same unique ID (" + uid + ") exists in the map"); + + varMap.put(uid, variable); + + // By default, all block variables are visible + // However, variables from embedded blocks should not be visible + // (and should remain so until they are exposed by the user) + visibilityMap.put(variable, !uid.contains(":")); + + runtimeVariableMap.put(variable, isRuntimeVariable); + + for (VarListListener l : varListListeners) + l.variableAdded(this, variable); + } + + /** + * Registers a new listener to receive events when a variable is added to this list + * + * @param listener + * Variable list listener to be added. + */ + public void addVarListListener(VarListListener listener) + { + varListListeners.add(listener); + } + + /** + * Registers a new listener to receive events when variables in this list change visibility + * + * @param listener + * Variable list listener to be added. + */ + public void addVisibilityListener(VarVisibilityListener listener) + { + visibilityListeners.add(listener); + } + + public void clear() + { + // remove elements one by one to notify listeners properly + ArrayList<Var<?>> vars = new ArrayList<Var<?>>(varMap.values()); + + for (Var<?> var : vars) + remove(var); + + // FIXME this code does not remove links in the enclosing work flow... + } + + public boolean contains(Var<?> variable) + { + return varMap.containsValue(variable); + } + + /** + * @deprecated Legacy method (used to load old XML work flows). + * @param <T> + * Type of the variable value. + * @param varID + * Variable identifier. + * @return The stored variable. + * @throws NoSuchVariableException + * If no variable is not found with the given identifier + */ + @Deprecated + @SuppressWarnings("unchecked") + public <T> Var<T> get(int varID) throws NoSuchVariableException + { + int id = 0; + for (Var<?> var : this) + { + if (id == varID) + return (Var<T>) var; + id++; + } + throw new NoSuchVariableException("No variable with ID " + varID); + } + + /** + * Generic access method to retrieve a variable from the map. This method uses generic types to + * prevent unchecked conversion warning in higher-level code. + * + * @param <T> + * Type of the variable value. + * @param uid + * The unique ID of the variable to retrieve. + * @return The stored variable, or null if this name isn't in the map. + */ + @SuppressWarnings("unchecked") + public <T> Var<T> get(String uid) + { + Var<?> var = varMap.get(uid); + return var == null ? null : (Var<T>) var; + } + + /** + * Returns the unique ID of the specified variable. Although the underlying structure + * + * @param var + * Target variable + * @return Identifier of the given variable. + * @throws NoSuchVariableException + * If given variable is not present in this variable list. + */ + public String getID(Var<?> var) throws NoSuchVariableException + { + for (String uid : varMap.keySet()) + if (varMap.get(uid) == var) + return uid; + + throw new NoSuchVariableException("Variable " + var.getName() + " does not exist in this list"); + } + + /** + * @deprecated Variable index should not be used to refer to a variable + * @param variable + * Target variable. + * @return The index of the list where the given variable is stored. + * @throws NoSuchVariableException + * If the variable is not found in this variable list. + */ + @Deprecated + public int indexOf(Var<?> variable) throws NoSuchVariableException + { + // throw new UnsupportedOperationException("Cannot retrieve the index of a variable"); + // return varMap.indexOf(variable); + int index = 0; + for (Var<?> var : this) + { + if (variable == var) + return index; + index++; + } + throw new NoSuchVariableException(variable.getName()); + } + + /** + * Indicates whether the specified variable is dynamic.<br> + * A variable is considered dynamic if it has been added to the list at "design-time" (e.g. via + * the graphical user interface) rather than at "compile-time" (i.e. via + * {@link Block#declareInput(VarList)} or {@link Block#declareOutput(VarList)}). <br> + * A dynamic variable is marked with an additional attribute when stored in XML, such that it + * can be restored properly (via {@link VarMutable} objects) when the variable is reloaded from + * XML. + * + * @param var + * Variable to be checked. + * @return true if the variable is of type runtime. + */ + public boolean isRuntimeVariable(Var<?> var) + { + return runtimeVariableMap.get(var); + } + + public boolean isVisible(Var<?> var) + { + return visibilityMap.get(var); + } + + @Override + public Iterator<Var<?>> iterator() + { + return varMap.values().iterator(); + } + + public void remove(Var<?> var) + { + if (var.getReference() != null) + { + var.setReference(null); + return; + } + + varMap.remove(getID(var)); + visibilityMap.remove(var); + runtimeVariableMap.remove(var); + + for (VarListListener l : varListListeners) + l.variableRemoved(this, var); + } + + /** + * Registers a new listener to receive events when a variable is removed from this list. + * + * @param listener + * Listener to remove. + */ + public void removeVarListListener(VarListListener listener) + { + varListListeners.remove(listener); + } + + /** + * Registers a new listener to receive events when variables in this list change visibility. + * + * @param listener + * Listener to remove. + */ + public void removeVisibilityListener(VarVisibilityListener listener) + { + visibilityListeners.remove(listener); + } + + /** + * Adjusts the visibility of the given variable outside the enclosing work flow. + * + * @param var + * Variable to set visibility. + * @param visible + * true if the variable is to be visible. false otherwise. + */ + public void setVisible(Var<?> var, boolean visible) + { + if (!visibilityMap.containsKey(var)) + return;// throw new NoSuchVariableException(var); + + if (visibilityMap.get(var) != visible) + { + visibilityMap.put(var, visible); + + for (VarVisibilityListener l : visibilityListeners) + l.visibilityChanged(var, visible); + } + } + + public int size() + { + return varMap.size(); + } + +} diff --git a/src/main/java/plugins/adufour/blocks/util/VarListListener.java b/src/main/java/plugins/adufour/blocks/util/VarListListener.java new file mode 100644 index 0000000..d9f1cdd --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/VarListListener.java @@ -0,0 +1,10 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.vars.lang.Var; + +public interface VarListListener +{ + void variableAdded(VarList list, Var<?> variable); + + void variableRemoved(VarList list, Var<?> variable); +} diff --git a/src/main/java/plugins/adufour/blocks/util/VarVisibilityListener.java b/src/main/java/plugins/adufour/blocks/util/VarVisibilityListener.java new file mode 100644 index 0000000..2df7f3a --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/VarVisibilityListener.java @@ -0,0 +1,8 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.vars.lang.Var; + +public interface VarVisibilityListener +{ + void visibilityChanged(Var<?> source, boolean isVisible); +} diff --git a/src/main/java/plugins/adufour/blocks/util/WorkFlowListener.java b/src/main/java/plugins/adufour/blocks/util/WorkFlowListener.java new file mode 100644 index 0000000..29f431f --- /dev/null +++ b/src/main/java/plugins/adufour/blocks/util/WorkFlowListener.java @@ -0,0 +1,85 @@ +package plugins.adufour.blocks.util; + +import plugins.adufour.blocks.lang.BlockDescriptor; +import plugins.adufour.blocks.lang.Link; +import plugins.adufour.blocks.lang.WorkFlow; +import plugins.adufour.blocks.lang.BlockDescriptor.BlockStatus; +import plugins.adufour.vars.lang.Var; + +public interface WorkFlowListener +{ + static final String WORKFLOW_MODIFIED = "WORKFLOW_MODIFIED"; + + static final String WORKFLOW_REPLACED = "WORKFLOW_REPLACED"; + + /** + * Called when a block has been added to a work flow + * + * @param source + * The workflow on which the block has been added. + * @param addedBlock + * The block that has been added. + */ + void blockAdded(WorkFlow source, BlockDescriptor addedBlock); + + void blockCollapsed(WorkFlow source, BlockDescriptor block, boolean collapsed); + + void blockDimensionChanged(WorkFlow source, BlockDescriptor block, int newWidth, int newHeight); + + void blockLocationChanged(WorkFlow source, BlockDescriptor block, int newX, int newY); + + void blockStatusChanged(WorkFlow source, BlockDescriptor block, BlockStatus status); + + void blockVariableAdded(WorkFlow source, BlockDescriptor block, Var<?> variable); + + <T> void blockVariableChanged(WorkFlow source, BlockDescriptor block, Var<T> variable, T newValue); + + /** + * Called when a block has been removed from a work flow + * + * @param source + * The workflow on which the block has been removed. + * @param removedBlock + * The block that has been removed. + */ + void blockRemoved(WorkFlow source, BlockDescriptor removedBlock); + + /** + * Called when a link has been added to a work flow + * + * @param source + * The workflow to which the link has been added. + * @param addedLink + * The link that has been added to the workflow. + */ + void linkAdded(WorkFlow source, Link<?> addedLink); + + /** + * Called when a link has been removed from a work flow + * + * @param source + * The workflow to which the link has been removed. + * @param removedLink + * The link that has been added to the workflow. + */ + void linkRemoved(WorkFlow source, Link<?> removedLink); + + /** + * Called when the order of the blocks in the work flow has changed + * + * @param source + * The workflow that has been reordered. + */ + void workFlowReordered(WorkFlow source); + + /** + * Called when the work flow changes status (useful to display messages to the user via a status + * bar) + * + * @param source + * The workflow which its status has been changed. + * @param message + * The message describing the change. + */ + void statusChanged(WorkFlow source, String message); +} diff --git a/src/main/java/plugins/adufour/protocols/Protocols.java b/src/main/java/plugins/adufour/protocols/Protocols.java index 202cd32..056e59a 100644 --- a/src/main/java/plugins/adufour/protocols/Protocols.java +++ b/src/main/java/plugins/adufour/protocols/Protocols.java @@ -20,6 +20,7 @@ import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import icy.common.Version; import icy.file.FileUtil; import icy.main.Icy; import icy.plugin.PluginLoader; @@ -93,14 +94,14 @@ public class Protocols extends PluginActionable } /** - * Saves the current state of the Protocols interface and restarts it. - * <p> - * This method is useful when plug-ins have been modified (via the plug-in - * loader) and requires Protocols to restart to take into account the new - * changes - * - * @throws TransformerFactoryConfigurationError - */ + * Saves the current state of the Protocols interface and restarts it. + * <p> + * This method is useful when plug-ins have been modified (via the plug-in + * loader) and requires Protocols to restart to take into account the new + * changes + * + * @throws TransformerFactoryConfigurationError + */ public void reload(final Document reloadingXML, final String reloadingPath) { // 0) avoid silly situations... @@ -154,7 +155,7 @@ public class Protocols extends PluginActionable ThreadUtil.invokeLater(new Runnable() { @Override - public void run() + public void run() { // 3) launch a new instance of the Protocols plug-in try @@ -388,4 +389,11 @@ public class Protocols extends PluginActionable if (mainFrame != null) mainFrame.getContentPane().dispatchEvent(key); } + + public String getFriendlyVersion() + { + Version v = getDescriptor().getVersion(); + return "Blocks engine v." + v.getMajor() + "." + v.getMinor(); + } + } diff --git a/src/main/java/plugins/adufour/protocols/gui/MainFrame.java b/src/main/java/plugins/adufour/protocols/gui/MainFrame.java index ffff065..2049c11 100644 --- a/src/main/java/plugins/adufour/protocols/gui/MainFrame.java +++ b/src/main/java/plugins/adufour/protocols/gui/MainFrame.java @@ -53,7 +53,6 @@ import icy.system.IcyExceptionHandler; import icy.system.IcyHandledException; import icy.system.thread.ThreadUtil; import icy.util.XMLUtil; -import plugins.adufour.blocks.Blocks; import plugins.adufour.blocks.lang.Block; import plugins.adufour.blocks.lang.BlockDescriptor; import plugins.adufour.blocks.lang.BlockDescriptor.BlockStatus; @@ -107,7 +106,7 @@ public class MainFrame extends IcyFrame implements IcyFrameListener, ActionListe public MainFrame(Protocols pluginInstance) { - super("Protocols editor (" + new Blocks().getFriendlyVersion() + ")", true, true, true, true, true); + super("Protocols editor (" + pluginInstance.getFriendlyVersion() + ")", true, true, true, true, true); this.pluginInstance = pluginInstance; diff --git a/src/main/resources/logo/icysoftware_icon-protocols-sdk.png b/src/main/resources/logo/icysoftware_icon-protocols-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..21eb08f98fbf3e02dd68e1c96e5f2e435a1ecaaa GIT binary patch literal 6683 zcmV+$8sz1PP)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000@{Nkl<Zc-rlo zYpf;NRo{PW?W#KG+(-BA+mCs_JO(>9g98qR5aSq7h$02TBN9e&A{;3p5lZqU1PPQ6 zpd=6>J|raKLzE~=A_ZAWu!BH|VEEv%jbr1n=jE|yJk!&j>F4dfuXE2iRkio}=R?)) z=^5L2@C+e@9H~@wx9V2y_2299Uu*5MZ~Ko|c|Cq-&zxdwn%HQJwIn{jZ>-g@S;J-p z&&^>i!CD1<fPnQ18V5I+_s|xYBxnP=0{3phK?est<bW>V-VHeFVV+^NzyCGQ_X9t4 z5&-8<`S1S&@TVPs>W}HY2Al=%2krs(foFir!0P{10FH&ecYDg$ttH;HUh{^N4QJZI z;;`q7^PZ>oT0RFn3tR-+Kgj@`0PY6f2)y~BZ5~{&xN}l*sxoQ-gqlQPoT?M|Y)^UT z-5caSSS&JE<~`2=PXnI^&I4D0{C^_=2HXpL-FQ6s_OrTYR&ly^W)3EhM2R3;=rAlW zt3X{q2?d61cv!PhbKi-Y2kuzoyNl@C!rmft`LMTVM?k(Xn$7$*0Z^gW1AuS%_S4<J z*qo%bRk+V8lORgSN>A5=NphK)4UX3UvY?PGa*!o_9Jc|iFo@(t<CICVwN2;2lOA8c zo4fDn4_92eKL1VNUw&;<9(X`CwbuoJ&E2DJ`uQE}R^ir6DnBtXKRrpd)dcI7&B&I+ zd=+MWm<cSxte2B>kOc{;F=<vy>os1PMN93MmmbgUWiITmw6nkJ+Eo`;x{8vwUb6|T z`to`Jpi74Z+^sgqIqXVFc5!KDY2uS=l1|p)+o{;5_dcUFkFcX0HmMSA7o4ocscBMA zt=*XTQwL3a;mGWTBgMhIO|05dt5=FlO_(DKMZ9JJB3>5&P=qBjH%H0B>tu!(_hbo6 zekf0etz%Yu<7+-M*$HdyuRmxUW%!L3YCd+|b+tn`dejxnAnKesQ)PT6N)~ECND<+R zu-EPc6|avQj4<3>VZscHaGAM9@svoeWGUg5;mNV4w)EuQRPTQQd+Rp*`U}Vns}p4_ zK~7jiP)c9~5d|wnQb<uy6jLd$9RP)1pD3U(g@Kt7=5FQ;AXtQ9<}SBnVCxCrS(45l zz=Lb}|2#ysqgjO%&^kfjmIIGWM3Ev)5w77{DzB3U{Oh0k<}Vri2mkC7e|Qs+92ANW zgV4#$EJ9{hoJj7;Y*JBO@8OAEtV#H!!gh|(bPz?wu?S2-QRJHfN4^PyDHo(LUI+92 zvbn&oeE6U8AAa)BLd2^*Oc9_kR0aVQgPV_V1#a}VX7arr9^D}U-WYC9N=B05Av{D1 z;fm}k#Z<zjlBBTV?_xKv$M=3M$EzYj|M};?llT6U54}na2vZz~`eHJ(uw*{k#hNEO z>LWdR-OMVtX`;#*4`Cq)hPa<aMBwB+sCpDr9?x+sbRoV*A8_XnzGu9;@anR`U->uR zi$bsNC}BZ33L6xn7|7NvY}p5oT**WPwLu1DVFsRs7m@M^#<)ty0#kA^8?TSYC_zM6 zMDS{lH~qx>zBVfS-{1eYiN_w}hkoYce_#|)GEq#VkjVnf8nftn^oi?{f|S>0HZiG6 zvN{`8215`c<k$dY&XG$WIbyK=TuKhe1?7-(IgSLcdgb5t6aR?*wdMjJ{P}nDeLwl3 zuVeyJv5?UlW|c~+t28M^v(N1GGg+FRn55c0Qc|W+6Tqnc6!2uYD{9D-!F@(6#ZrnK zCFdx)%cUzeILBJ$s{^q8<KM?aYwuwH^-6|*<ClMczw%@M?aRm?Cifb%nI|^ZCux=v zlh0huE@3B|gfV~=$xJ0oa=`#V7*H^kkQb6El3CPst&NZ?lyYajbfu%5b1TV-KRAZp z_2chl`A3lr{?>nbFMs(*|Ld(oYfa_%OeYC*Yns||;PI;~9kmv-SuI6In3)+e11pL! zlN&`K4MEf*6OmCwL=MP-sgO+7tn$qbt2X<ja&%N|m3{jw&GC(Y_s2N;qfCc?``7;= zlTUq;cmM4F0+=C9mR)pJP45Ywyt1%)hV^MPghLEjff6?pXv36L83`n0(L>HCawsyX zWCa;3MSafEwH<xmE4RHzpHnENUv6%1|FNIq@c&dj;G;kHE*|>nkFQoGlak?0T|RMn zW%~=+<}7(zf)tRFVG2^!;AU7|GxdsPK}QA5Ao)-^Q0^&xOV?$xg03w@pP^($pQVze zC`5U=37q>IKg?TBy!}<J{OeT|t$}<0{-@vn(6n1}`sjLk@v|4I<+VLuv({9KI-I_) zmDe_5<1}n+!DL2ph;GGv(NPvFyjvllOg7NQHf3^(QcdW(f>ujxwLonPT^Yip0;1eJ z?)j1T{gGFMe5~o+H&%V#)PMJ1dZ1pLp8eLf!`mNU9ddPV;c0iC-n=<O-4-#~QmLEh znu^|xF0xA7qhdCchcczFP~WpyK$Y4-J1kL8E2`-^W?S3zs|DTOF5UhftE0K1@9-4F zSRDJt_x)q`zs8)d`YI)pyMYIRH`nie_Zxg`>n>)qt!SDiK$3#o_4{7Or`t`Y>nCk{ z%dttLDgh;|EDzIUzDFh~mgudfPgV4mSWvUtoO0BOuFE7B$AL{N10^g~kk+VAZ>l<T zZ_N7Ey2I<%?_6ZLyAyx$@BA!#{p(v3d?)Y&8}E6~?KYjBR$b3ZDPD3Gr34V<+yQ3D zPHGVx?ZxB!OYS<eO|B9feZjko_q}98A1n++VD!{(=7JMpwy`(S=I)ZYZ>XQFV;b%v zM6s;I!jlzCjZsagQ|ioON9r$d=Uq>}8Tfl+ZSYy(5`5Lj_i_OIzWZJ|_HLaz_4fAZ zr<YZ7()M=8sZ-vaJegKa<9*H^i-lEP?^WLcb$Y9I`K4>Ws*fF<<>TF!J-d0ArS)x2 zPN&+aYbGjyunJNZ(RI*f^R~lFW%bShwsAY%Nee5jCwh_>YNv*vv^@tm_L*P)BAUO* zx$Q&U`=-$S5BZ5Zns5E#(u6v17YjSwIbip~2*@)d;lBcWMIEWSy}ehC<%8e<WKQ$B zsp~vF@r13J*~#^Fv+ZqP-F~|t-gaBsKXr-&uYI@g<%?zdwzA-R{keQ)Ip<T;2l(Ck zE&hq=9rigp=}b0sx@|diW1rd40oM1H3mg&btgUmSG5XlIdO1(tLy@uS!<L7KwDVo+ z{t{<T@93d#S(f|XRPsIdo#pE4aMCZsO751jlxl10Q-1c&)9JRmsq`1J=dQEm;UXPg zowI*_tRg=LJaudHrD~79Bxu+Kna^gX>D2P(rgc*CoYQn?$IpK8i|Jg1Gnu+>-(df) z+nX0QPggH~-8*dlZSS(?@sH~J7tixIU;Ou>Cm&<?a;p#C_8s=Y2fvHY-1AmHI=8Kj zy^f>B4XU+<cig$jGf!S87qH!mye#B?kEXfA{b#SnJr5n|?t8je+b&ToS>M%m<633= zOQ%l7r;}2GRGVZG3KO}qIwn3VYql}n(8+se?dE;9tlo9u@LAj0zxvC-Z`=f+*q3CK zRxo$7R6uT4W$-p7r)iSb*Ha^TUF3GKN?Yw`^VWu?H}5`){oprSdgf{PrGEwcLo;Bz zL+^Y11M$8OeUJ;M?~D(<<(>Xp_kBCh{P}OO_rCLOyrnXJ<>BAq%=UuWZ5KFo=jZjd zGgG}GP0CDDExN{z<}C;F*4w%zth4oLM`?pp%hPhLTDKEvvpJz@ReN7$cI=X#Sv`@S zJNj(7IKQlU+jh#RDNJ0fz03r@Ogdo&%!jl4n5Z~dwz1R&?oLW}qT<XQ#^I$Z|Mq7{ z|K;B*Km41X4~UqZz}tr8&irI;T@+hy=e+0P59mif{DJtHwYBuvH~uwy>cv0DcYedC zm_D>ayfsSYd-g)<b-vU#S#H_((OGRAoVJa_Q%vWZQVC9MorLxZ=jzjJZ5-9tCfBM* z>kr2><<U65xMVllLRnJkf;E9^;ZqR^lwaNm`Dy@|gBP%BxYtK8Qcj+1<D~h{wTeeR zS8>iLSFRc7_ht3Oysfag>p;$7GFG!0tF%GP65H3Dl`gV-<JUNI_^W*VR?F4qo3wlV zjTY?`d571Tc{68!`x<dqM-@=z#@@JkoBr0u&)M&`zsCFh7yY@}6V>iAk`{Q~Q<|RG z%1|YST^IpYkTR}<lX;oSS6{Bt7K8R$0ZZe#8|b?iNbX+c%}q-Ohsl5cGbST>Jq1;! z1^CM3SsVs-REJs*Iz)?+-PyMZ%XAv~X@bJxYRU3&n>=kuc{!k;jcH!P+s14eN>fK` z9si^LcXsB)343h+5k9*AxNT?E+Z`#jgb$N9i(#de5j|2j5JxhmR|4=?uMS&eK5*{b z&A~DXZMSGc8bG1AQ*CY%i$&@_`SB`&n@CkJV(I7*d>I^8;%HLHRAdt?Nu9#8j1HsA zC`uy6Tv*{|r4W6DSHcix1p$rMw$ZKg>D9v=CF$9CN{{S6Z09n*mT>P$3QT#J{3tx5 z@I%*e7)!6zfSb9%_g}q=My(m2^Cd^`POPtc?mDeL{&DNUdWHh0cLiTTTEIfa%8NRR zI*FCcvSgi9diA9zM+s92EJ_fPsYHnq#Y%`iq>^PtY9g_2YkY3`2rnF6=2USaI&$fl zl!W&*%A`@aq>L-$^td`u;2e*Q8le0YnBeL0e8eDC8U;_F;;w3KjVPrYedHr)B4r-{ zpn;ms%~$vl<b`7^Gx{*CD$$|n#(OhPi=?r3R3aJ0QOQ^dg@Q@}#Yk?p;p=+l@KK)W zzF@N%)Va1s6-|JOn_e#5pz1HJybr}kG8xprT=-i7u%TT)c*rtnK^Z%_eKJc`)2Nu) z;lmGGBQ*g{m{kGZNoq>69L5oN2i7^!Ib=f_tVhvH$||&2v{8&wEQ(}BrW`@n2a#PB z)G2YI+SGH0PuS<$&$BUeJOY&hMW`T)a;(-UV+x#(P&Zx=;Aqs0P>&N|3V_|>{RaS~ zC=n$^NGYvpI-OZv&*mTd*fdG0O3AaCB{OA7mNThpqK(9IKrC7_I(Ito7PDTWS15<c z$)yx36%n~e1y>jH+{prgv)-)Ra|fU2vqz6>eXX`iMv2a>jCNWiN-;97p*;~c0O8~H z;P8&szQ-sK{g(o8+^@Iqx_UK!=iIrOSJkPyuIt*ERCSYlGO_*N`#n?JvhJ>uQZ5oj zvR=Z#S~qWHu>guETA8{Cb#BTk^fL7di!gOik>!+RD#aB>DMdM}OoY~(HGgsMSv@&_ z#MfpObzgxO6nIowMzK=BdkANumY$3vBlQ%9s#>v3D26C-?8sJ&uMB_#N-3SOsybP6 zE>TY+CX?9x<R`5h9I#f`RwVC-$Up+6aUM-{S}C&&s6cTk%UA(jNG(~tRncKFA)=J1 zEDIF{S;@0h70R@l+133Qcx?aoeKM=5Be@kB{n#@Y)Ei~+fU2Z*!XuzqM4v?~#WIQ% zJ-s@zg5HNnfC5%MZ2$9t0v`iVjXtjm$@|qRH}1V5ZhYh;7W28Uj}fDHS2^Yv8j89T zU=pD-(W?c!94mG;;ee^S2(y%wCG}m72!#`+w5I4GvW#$9G1hI}Zya3kM|M8$lW9#| zYEu_cRLoS2VAx%h4Ro_<9AwF}<ErXpUW09@vMi!6<YmcPMjK0o5+T3E@2MKgTJ_+} zQABtE)HPQhd4xQlGX)lA>T<RQI1#~CN^AmCGYn|u)Yq=reW3dd7V31RZ*@K_p069b z<}<C_NS4?jM_VclcI3D@YrbjM_FmMZmp)1}sY+8Cv%WWTnWZ9=<blzdkqz$%3PvU@ zBb7ofRUsEyN#WrnRo3`qO_j}xvf60PtlJy`NI)E~d@~-VAwkl`r=H^Q;DB|Ysw!<s zb{pt4_|!};QNY>=dcX;6G_@v*>JEEA!~;OiIbUU=r`-9ht+9j0zST@hG7(X;;Wy^H z`pnTMY23=p&1w}%#V91}%m|rkWNvtX4HP%jkB%)YycqO|K)DpDWR;>^a@Ob&8;M54 z@~2}HsBQ(IM5KfB=jGS0>(2mpRTZZsn~sjRhuAZ-v6~}osP9=6n%t`c76OB~Zjgj( zuoIx~h|s&3mw;|>sd!R}sn^;&yw0chp3KczwM|7uWY5_X1+!TalW?m{DPfYdd$da| zhph-M6EZ~(qVy^yP%k2b3ZViP$TFfJ3h5TDziIo&TA<a98`W)K-(==9cXbiAG~-~T z$Qq9^7#TxP1Jl4FmY|{JPoUu*gV-Ax?^MKHpfg3>ZVlDmW&6Sxp2TiLX_GK>k2NzU zfo~vY#i$7h>oc%&j+nzCgo|tiC5R+UL$`k9{dhk(WQN{KkO@JBGaF^sD{F#&CZ+SA zOzBNu=(>&55%rxzbySsEDPsfYIBa5um%$Yp!?v&GcuyL8en9vD3O*!N=?2u7Vp?7J z;;x>4^hvi(qS^AOPFb=wh&AD6qF}K=WD!FLS#E`5X60Z2G}#b`eRzSpQPd1C3x?RQ zelQgi;8Zx%TYW%`t0^G8aWZ-Oo|7jvU#(7_Z`-@iuU7Z2N;$DE)u$vj%&5(VElj28 zSOdK8DY@5TK=oMQFi;2VL3#nwc_Wr$^++5ppG%KT*S>@K^t3A`Mh-lR#4ePKncz5m zcZd*H%*a(y-iMT4qH&`Tag){!6wg@0Y>HWn=@3+?si_eO=&O<Wb|WHcp)Y1fchvRa z{dIkj+3eAI-=BV=>+XE9@9()%$`(?pC7X;HcR(~UQA9gbAVa_b)R#e5AUar{aV~!| zeBtXWp1D@FZ+~iO@2Ih<2ce`Arh<&)lEo;CY)sHXqdiT-^BgRVp7*#xJN!$$Z10lc zWGd#MCDJB^85J71&oKb~$OlS>gWha!4yl!z2DF*d#cx<&zw}VV<9)>DwS$8@E|qfE z9-Li3ooBP^&iDL?mX|J;3(r00N05%7G_Zd+eDPb2=kIb}OcnEkM7aU+R-1h4#(aJM z(v_oouB*SdOf+RiQ_W;lR%D1W>@*r`6CwwZD)T(E!XX}M6p_K(NW<nTzzE!WP*0I_ zsCCYB2j`jaBpuB_iv~qGX2$|jc0cH<YBxoskm@jN!mO#FuB)m~dwa_}OSyVCJYHbq z2)38!&fPUVbNY^lKmDmD!{I}4{*7?{G+a%_qKAB7*p3nVPB{dv8jE|jr=QwBTYu_$ zRW>h0f7`*)>cRe~e?x>e4Qe-IWXU(%=nIQzeM!tI0&@WcVN~wpGA6Pn>}iV&I>EE7 z^8yuz9)uPwUaA!hJ|J_8SMSXByOgG>uAPXAQW{dK0-AnzxAL~FYp5$oH5{IV7q^~& z{?qXbzcBfpwYA#eFu^iIv_ooLRz_YxUO`zJapdTzvdMnMWC>nsc4sFhJG!raB1hTi z=J~FC(7&-9^$%!SwyaP`3yXbp@44+bV|Vi8#pjvLaGPSb2CS29%3fLHyk=a`l*<?k zM6p6)#gL5;R@#jVpQ~GHAOTqGzF4KS?~=(ZDW%Gw>UVZ(Uo28HatA>bgCFu5<SD6{ z&2qa~Xf>Ep>7idjZXqv>SU{{G7KRqdCT(R+#!~NIM~KLl950j2>D;!`)61@%(Q(dK zJfJym_{PZ_&i?qf|3vfQ$9~uS?8nsH&Wa6o%9Qib@IqAF09p@%pxD6Cdc?}0W!S(o zAHPG$K&ftV8UpCt7ptnOeB9w}kh-f^lOG<Y<|WQhkILo-o*7!(Ii(ndj~;RdrG>JD z+y=37)C%>1db3Gbs>5*1DokD>6FzCx*Ym|eEmLPThcV^Za*_`}&xO;!`0UTzUnY5b z@=fe?6)(y-u)+4_Jpy_gR=5K?6Fs_lT@Z7I98Jd>a8r1I-fXo>$rHnNj_&g1#NM7y zUlKS0NiSuPH?t~BNqJo5)gXLiQ5J?45UU^-8a|8b!`k90NpfVBBTLDxQ(GT&`&B5K zWK;?oM5>IskPk0(m(Q=&Q!tVhDvL1+ir(y|ns>&y`U4<6A`c{)#W)_v8o({Aj^3>6 z+^tE;x{DWyogJSI(Z@AXaKQl3aAvF=%e#)vAv=tEAY?j%m<#O>s~=r~m0`UsNh!Hx zPFQlcHhSM*-AJOO<Wv%s85K|qDu-%fZjIGd*6Nsr21E6#jKbzoz%pLvapkiYWMVn0 zzmA#UvGBnlU@ld~;l+z|SFX^EB(ENN1FVCu4S(Y=Rgf9!uws1J3^ak*0v!nLWOO;f zfv`5CKAVT!rDRSrN@j0<jjWu!!Vw9(B~=#{hMSRmC`Sz2YzDerFp3?s{Xyukan(%( zg|LB~w`1tH<0$a5-Co74+r50bR2MID8tnAAQD9>f+Q9Mvq1{4OzzlPz+)`USt~1U+ z_W-vQxL<J5;JJbw^^Un)-?etFy-YXh@CsAG5oRLEF*!bhkpK@ByUAS#4saX{ha$Vi z>p|e*9B6D(A6EwL)?DD`gyY7Ia!1Z_$FPb+&N#Zw)u`_FO1l}x<YqTFI2`9Bx`k^8 zgB{%$a7PFG#jWjIZY(a+PTPT&4b;Xoh-{WH*#L-yk?~v#DmSs0+4y*kuna3MgiK== zIINoFXlw@P#!u^}3EccGb1qkat>bLr%X}SmOiH$fQu?3?Idd&%9heQ7;VlWqt$P8F zjju08)hAxuy}|)4t8I|qWOhS>(T$nskm!vM3(NGwyl!0qMsmwYrsJk|9N(wOOVi@U zPwOl05c$J?K=8VJaPZ3K_3fad#w(aTP*iMOb-U&Ldf7}2D*x(p`{Vj@^fdu%w<JKX lbus?rd^>Ef(><^F{cnAWjO@mvQ~&?~002ovPDHLkV1hP(8@T`g literal 0 HcmV?d00001 -- GitLab