TWiki> Public Web>DpHipe>DpHipeTools (revision 57)EditAttach

Adding Tools to HIPE

Task, tools and variables

Tools are processing units that operate on specific data elements.
From the Java point of view, a tool is an implementation of the Tool interface.
The well-known Tasks are examples of tools within HIPE. In this case, TaskTool is used under the hood.

If a data element is selected, a list of tools that can operate on that data should appear. Double clicking on the tool will open an associated view (for non-task tools) or a dialog for settings parameters (for task tools).

This section explains:

  • how you can create a Tool and register it for being available for dedicated data
  • how you can make HIPE aware of an existing Task,
  • how your task can react better on an active data element,
  • the default task dialog and how you implement and contribute a dedicated input dialog for your task,
  • how you can implement and contribute a specific parameter editor


Adding a Tool as a Task

Task Registry

Up to now you have made you task globally available to the system by specifying an instance of that task within the __init__.py file of your sub-system, e.g.:
    # __init__.py file
    compute = ComputeTask()

To make your task appear in the "Tasks" view, you need to add the following lines:

    from herschel.ia.task.views import TaskToolRegistry
    toolRegistry = TaskToolRegistry.getInstance()
    toolRegistry.register(compute)

For PACS users, this __init__.py file is located at $install_dir/data/toolbox/your_sub_system.

You can also specify that your task belongs to one or more Category :

    from herschel.ia.gui.kernel.Tool import Category
    toolRegistry.register(compute, [Category.IMAGE, Category.PACS]))
Your task will now be enabled whenever a session variable is selected which matches the type of the first input parameter within your task!

Within your task, you can control which parameter signs-up to be the prime parameter (the one which reacts on a selected data variable) by the Task API:

    class ComputeTask extends Task {
       ComputeTask() {
          super("compute");
          prime = new TaskParameter("spectrum", SpecificProduct.class)
          :
          getSignature().setPrimeInput(prime)
       }
    }

Naming conventions for task when to be registered in HIPE should follow this example assuming that the task will perform the functionality named "reduce" :

     Name of the Class              : ReduceTask
     Name of the Task (getName())   : reduce
     Name of the variable in Jython : reduce
 

Warning, important For naming tasks we follow the Java conventions, we use camel-case , not underscores : "reduceLight" is valid, "reduce_light" is not. Info log messages are produced for invalid task names. Note that the task names are used as variable names (of type task) automatically created when starting HIPE.

Prime input validation

The mechanism above makes you task to become a tool within the system and it appears whenever a variable of type SpecificProduct (i.e. the type of the value of the Parameter) is selected.

Sometimes this may not be enough, e.g. in certain situations your task will only run on a SpecificProduct if it contains certain contents. A typical situation would be when a SPIRE reduction operates on an ObservationContext: such a task should not be listed whenever a HIFI observation is selected...

You can write a ParameterValidator to do just that:

    prime = new TaskParameter("spectrum", SpecificProduct.class)
    prime.setParameterValidator(new ParameterValidatorAdapter() {
        public void validate(Object value) throws ParameterValidationException {
            SpecificProduct s = (SpecificProduct)value;
            if (! (logic that would validate the contents of the value...)) {
                throw new ParameterValidationException(reason);
            }
        }
    });

In other words, rather than writing this logic within the pre-amble or execution block of your task, we recommend you to move that logic into the parameter validation. This way we achieve two things:

  • make the logic appear where it should be and therefore keeping the execution block of your task concentrated to the algorithm, and
  • make your task appear as a tool within HIPE that can be ran against specific data.

Task Dialogs

Default Task Dialog

The system generates a default input dialog for all registered tasks within the software. As the system does not know the intent of your task, it can only provide a dry-listing of all requested parameters; such a dialog may not be suitable for your purposes.

The default dialog for the crop task:
crop_closed.png

As for instance you may want to have more control over how the input parameters are presented to the user:

  • you may only want to provide a sub-set of parameters (and leave the full-set to expert users on the command-line)
  • you may want to organize your parameters by grouping them in tabs, or putting a border around a group of parameters
  • you may want to have more informative tooltips, labels or even input fields that are more suitable for your task.

Warning, important A new default dialog layout has been implemented following the request of the DPUG: It puts two parameters per line: lines are filled left to right, and then top to bottom. If you only have 1 input (or output) it will fill the whole line. Modifiers implementors should take care that the preferred size of their modifier is smaller (about 20 chars max). Provided modifiers have been updated but some modifiers are too wide for this layout (for instance, AngleModifier can have three texts and one combo in a line, see rotate later).
The default dialog for the crop task, with all sections opened:
crop_new.png

To adapt to these scenarios and more, the system provides three ways for customizing you Task dialogs:

  • Parameter Modifiers
  • Signature Components
  • Task Panels.

Parameter Modifiers

The system provides a default dialog displaying an input area for setting the values of the parameter based on a composition of Modifier objects.

The input area for the crop task:
crop_input_new.png

The composition of Modifier objects is created based on the types of the values of the Task Parameters of the Task Signature.

The Modifier for the row1 Parameter of the crop task:
crop_modifier_new.png

Currently the system contains basic implementation for the simple types Boolean, Integer, Float, Long, Double, String and few more, so there's still a lot of space for improvements and contribution. You can find the general available modifiers in package herschel.ia.gui.apps.modifier; please consult the Javadoc of your HIPE installation.

If the default parameter doesn't fit for your Task Parameter, you can:

  • Implement a Modifier,
  • Register it to the system.

Alternatively, you could want to write your specific Modifier for one of the already available types. In that case, you could create your modifier in a custom Signature Component.

Warning, important NOTE:
The following behaviours and limitations are present in the provided modifiers:

  • While you can always write SomeTask(param = null) in Console, using a task dialog you will get SomeTask(): for GUIs "null is not allowed". The task machinery will take nulls as if the parameter has been ignored by the user.
  • Modifiers have no notion of the optionality of parameters: if they have a valid value, they will return it. The task machinery will not generate a parameter assignment if the value equals the default.
  • Specialized modifiers:
    • Will mark as errorenous variables incompatible with the type (dynamically), but will accept any variable.

Implement a Modifier
The ia.gui.apps.Modifier interface consists of two explicit contracts:
  • Support the drag and drop features (the set/getVariableSelection)
  • Support the inspection for Object (the set/getObject)
and two implicit contracts:

Register a Modifier
The registration of the Modifier is done again in the __init__.py via the Extension Registry with the usual syntax (please note the name of the factory: factory.modifier).

Be aware that the registration is system wise so the registration overrides any other registered modifier for that type.

 REGISTRY.register(COMPONENT,Extension(
        "MyModifier",
        "herschel.ia.mymodifier.MyModifier",
        "factory.modifier",
        "herschel.ia.MyClass")

In case the Modifier you have created is only applicable to a specific task or even to a specific parameter of a specific task, you can simply assign it to the applicable Task Parameter:

    // YourTask constructor
    public YourTask() {
        addTaskParameter(new TaskParameter("someInput", MyClass.class));
        ...
    }

    // Customize your modifiers
    @Override
    public Map<String, Modifier> getCustomModifiers() {
	Map<String, Modifier> map = new LinkedHashMap<String, Modifier>();
	map.put("someInput", new MyModifier());
	return map;
    }

Signature Components

In case the default input area based on Modifiers doesn't fit your needs you can just replace it by your own implementation.

Rotate Alternative Signature (old):
rotate.jpg

Rotate Alternative Signature (new):
rotate_new.png

If this is the case you need to:

  • Implement a Task Signature Component
  • Register it to the system.

Implement a Task Signature Component

The ia.task.gui.dialog.TaskSignatureComponent interface consists of four explicit contracts:
  • Support the setVariableSelection for initial assignment from the Tool Window
  • Assign the Signature to display (setSignature)
  • Return a map of parameters and assigned values (in Map<TaskParameter, VariableSelection> getParameters)
  • Clear and check user inputs implementations (used by the default buttons)
and the two implicit contracts inherited by the Extension Registry
  • Be JComponent
  • Have an empty constructor

Conventions for labels for input parameters: to construct the labels of your parameters you can use the static function of class JTaskSignatureComponent

 public static JLabel getDecoratedLabel(TaskParameter tp, boolean isPrimeInput, String altName) 
it provides a decorated label (including tooltip) that follows the standard style. For the function to work properly your task parameters should be fully configured (for example, the parameter description will be the tooltip of the label) if present.

An easy way of implementing TaskSignatureComponent is by extending ia.task.gui.dialog.JTaskSignatureComponent and providing your own implementation for the makeModifierMap() method.

For example, if you want to use a custom Signature Component that just wants to use ia.gui.apps.modifier.JFilePathModifier for a parameter aimed for a file name, you could do it like this:

public class MySignatureComponent extends JTaskSignatureComponent {

    private static final long serialVersionUID = 1L;

    protected Map<TaskParameter, Modifier> makeModifierMap() {

	SignatureApi signature = getSignature();
	Map<TaskParameter, Modifier> m = new LinkedHashMap<TaskParameter, Modifier>();

	m.put(signature.getTaskParameter("file"), new JFilePathModifier(SAVE));
	m.put(signature.getTaskParameter("number"), new JIntegerModifier());

	return m;
    }
}

Warning, important NOTE:
You no longer need a signature component to choose your own modifiers for your task (and link them with events ...): Task has a new function

public Map<String, Modifier> getCustomModifiers()
where you can do just that, see above "Register a Modifier".

Register a Task Signature Component

The registration of the Task Signature Component is done again in the __init__.py via the Extension Registry with the usual syntax (please note the name of the factory: factory.editor.tool.task.signature).

REGISTRY.register(COMPONENT,Extension(
        "Rotate Signature",
        "herschel.ia.task.example.RotateSignatureComponent",
        "factory.editor.tool.task.signature",
        "herschel.ia.image.RotateTask"))

See also the Extension Registry documentation for more details.

Custom Task Dialogs

Eventually, if the above options still do not accommodate you needs you can replace the the default Task Panel with your own implementation

If this is the case you need to:

  • Implement a Task Panel
  • Register it to the system.

Implement a Task Panel

The ia.task.gui.dialog.TaskPanel interface consists of three explicit contracts:
  • Support the setVariableSelection for initial assignment from the Tool Window
  • Assign the Task to display ( the setTask)
  • Notify request of executions to the framework by
    • Allow for setting the Site Event handler for notifying request of execution (the setSiteEventHandler method)
    • Notify the execution requests calling the trigger method of ia.gui.kernel.SiteEventHandler passing a ia.gui.kernel.event.CommandExecutionRequestEvent.
      You can create this event through the ia.task.gui.dialog.TaskCommandExecutionEventFactory.
  • Return the javax.swing.Actions for running the task and resetting (clear) the signature (the actions that are invoked when pressing "Accept" and "Clear" buttons, if present), to allow to execute them from the toolbar of HIPE. The simplest implementation would be to create and assign those actions to your buttons in your setTask(TaskApi) method and then to return them from the buttons when asked:
    public Action getRunAction() {
        return runbutton.getAction();
    }

    public Action getResetAction() {
        return resetButton.getAction();
    }
and the two implicit contracts inherited by the Extension Registry
  • Be JComponent
  • Have an empty constructor

The Rotate Panel example (herschel.ia.task.example.RotatePanel):
rotate_panel.jpg

Register a Task Panel

The registration of the Task Panel Component is done again in the __init__.py via the Extension Registry with the usual syntax (please note the name of the factory: factory.editor.tool.task.signature).

REGISTRY.register(COMPONENT,Extension(
        "Rotate Task Panel",
        "herschel.ia.task.example.RotatePanel",
        "factory.editor.tool.task",
        "herschel.ia.image.RotateTask"));

See also the Extension Registry documentation for more details.

Task compliance

  • Write user documentation (jtags)! That will be automatically picked up whenever a user asks the system for help on your task.
  • The name of the task should be a legal variable name in the global name-space. For example your instance of DoXTask should report itself as e.g.: "doX" and not as "This is my task" or "DoXTask".
  • If your prime parameter is not the first parameter in your task, specify the prime parameter using the setPrimeInput method in the signature
  • Your main output parameter will be the first (input)output parameter in your task, this will be the parameter value that is returned automatically upon execution of your task (ret value comes from first output in ret = t(x,y,z)).
  • Write a parameter validator for your prime parameter if your task should be listed not only on prime data type but on prime data contents as well.

Recommendations for simple tasks and limitations

  • Use a function that fully initializes TaskParameters: you avoid working with partially initialized data or data with non obvious defaults.
  • Please take care to register your task properly: otherwise it may half-work, which is much worse than not working at all!
  • Capture exceptions and check your params before trying to properly execute, if you want to provide better error reporting than the one provided by default.
  • You should not do user interaction after you have started execute() (not on EDT).
  • Validators: you cannot compare multiple parameters, order of execution is not specified, will be executed several times. So, if you need to validate dependent parameters, it should be done later.
  • Once you are in execute() console has already been updated with the command : Although you can change the value of TaskParameters, this will not be properly reflected on the UI, so don't do it. So once you are on execute() you can check , possibly abort, and properly execute (nothing else).

Adding a Tool that is not a Task

If you have an existing task and want to make it available in HIPE, you just need to follow the steps described in the above section.

Now, a task has its limitations. It is somewhat an atomic operation for which you provide some inputs and expect some result.
Therefore, it is not expected for acting interactively with a user, and it is not meant for holding internal status either, that a user can modify during its execution.

If you need more flexibility, you can write your own implementation of the Tool interface.
Besides, you would most probably need a viewer associated to your tool, for letting the user interact with it.

This follows in some way the MVC pattern: your target data is the Model, your associated viewer is the View, and your tool is the Controller.

Tool Implementation

In order to write a tool, Tool interface needs to be implemented. Instead of doing it directly, it is encouraged to extend AbstractTool.

The information to be provided is passed to one of its constructors in a super call from the derived class:

    /** Constructor for a tool with a single parameter and general category. */
    protected AbstractTool(String name, Parameter primeInput)

    /** Constructor for a tool with a single input parameter. */
    protected AbstractTool(String name, Parameter primeInput, Category... categories)

    /** Constructor for a tool with multiple input parameters and general category. */
    protected AbstractTool(String name, Parameter primeInput, List<? extends Parameter> inputs)

    /** Constructor with all arguments. */
    protected AbstractTool(String name,
                           Parameter primeInput,
                           List<? extends Parameter> inputs,
                           Category... categories)
You provide the variable types you are interested in within the prime input: just return a ia.gui.kernel.ToolParameter initiated with the proper class of data you want to handle:
new ToolParameter("data", MyTargetDataType.class)

More conditions for checking whether the tool can react on a particular variable can be added by providing a ParameterValidator to the prime input.

The actual job to be done can be delegated to a third object (the "tool object"), or just be executed by the tool class itself.
This latter case is the default, otherwise, you need to call setToolObject(Object toolObject) in your constructor.

Moreover, you may return the categories you think the tool is meaningful for, by providing the proper ones in the super call.

Naming conventions

Conventions for names of a tool class and its variable in the Tasks view are similar than those for tasks. For example, a tool for spectrum filtering could be called:

     Name of the Class              : SpectrumFilterTool
     Name of the Tool (getName())   : spectrumFilter
     Name of the variable in Jython : spectrumFilter
 

Tool Viewer

Every tool has an associated viewer, which must implement ia.gui.kernel.parts.EditorComponent (by extending ia.gui.kernel.parts.AbstractEditorComponent or one of its subclasses).

Tool Registry

Once you have your tool and the corresponding viewer, you need to register them like this:
# Associate the tool with the viewer
REGISTRY.register(COMPONENT,Extension(
                 "Spectrum Filter Tool",
                 "herschel.path.to.SpectrumFilterToolComponent",
                 "factory.editor.tool",
                 "herschel.path.to.SpectrumFilterTool"))

# Register the tool so it is automatically available for the proper variables in HIPE
from herschel.ia.gui.kernel import ToolRegistry
from herschel.path.to import SpectrumFilterTool
spectrumFilter = SpectrumFilterTool()
ToolRegistry.getInstance().register(spectrumFilter)

__all__ = [ "spectrumFilter", "SpectrumFilterTool", ... ]

Communicating Tool & Viewer

In the viewer, you can access the tool and the selected data within the makeEditorContent method provided by AbstractEditorComponent.
At this point, you can let the tool know about the viewer as well, if you want:
protected boolean makeEditorContent() {

    // Get the tool and the selected data
    ToolSelection selection = getSelection();
    Tool   tool = selection.getTool();
    Object data = selection.getSelection().getValue();

    // Optional - you would need to provide a setViewer method
    ((MyTool)tool).setViewer(this);

    // Build the editor contents ...
}

Simple sample

This simple reproducible example wraps up the just explained steps altogether.
It is just a button whose label is changed by the tool when the user clicks on it:

    1. The tool class

public class SimpleButtonTool extends AbstractTool {

    private ArrayData _data;
    private boolean _flag = true;

    public SimpleButtonTool() {
	super("simpleButton", new ToolParameter("data", ArrayData.class));
    }

    void setData(ArrayData data) {
	_data = data;
    }

    void updateLabel(JButton button) {
	int size = _data.getSize();
	int rank = _data.getRank();
	button.setText("Data has " + (_flag? "size " + size : "rank " + rank));
	_flag = !_flag;
    }
}

    2. The viewer class

public class SimpleButtonToolComponent extends AbstractEditorComponent<ToolSelection> {

    private static final long serialVersionUID = 1L;
    private static int _counter = 1;
    private SimpleButtonTool _tool;

    public SimpleButtonToolComponent() {
	super(new BorderLayout());
    }

    protected Class getSelectionType() {
	return ToolSelection.class;
    }

    protected boolean makeEditorContent() {
	final JButton button = new JButton();
	setName("Button Tool " + _counter++);
	_tool = (SimpleButtonTool)getSelection().getTool();
	_tool.setData((ArrayData)getSelection().getSelection().getValue());
	_tool.updateLabel(button);
	button.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
	        _tool.updateLabel(button);
            }
	});
	add(button);
	return true;
    }

    public Icon getComponentIcon() {
	return IconLibrary.VARIABLE;
    }
}

    3. The registration

COMPONENT = ExtensionRegistry.COMPONENT
REGISTRY  = ExtensionRegistry.getInstance()

REGISTRY.register(COMPONENT,Extension(
                 "Button Tool",
                 "herschel.path.to.SimpleButtonToolComponent",
                 "factory.editor.tool",
                 "herschel.path.to.SimpleButtonTool"))

from herschel.ia.gui.kernel import ToolRegistry
from herschel.path.to import SimpleButtonTool
simpleButton = SimpleButtonTool()
ToolRegistry.getInstance().register(simpleButton)

# cleanup
del(ExtensionRegistry, Extension, REGISTRY, COMPONENT)

__all__ = [ "simpleButton", "SimpleButtonTool" ]

    4. Executing the example

For executing this simple tool, just include it in a package owned by you, open the workbench in HIPE, and execute the following in the console:
x = Int1d.range(12)
y = Double2d([[1,2,3],[4,5,6]])
Then open the x and y variables with the Button Tool and click the button: its label is updated by the tool.

Triggering Events

For a full detailed section about triggering events have a look at DpHipeCommonUtilities.

Task Compliance Checklist

Developing a task that looks "native" takes time and involves quite a few steps. To help you comply with all requirements, we offer a (high level) list of common deficiencies and mistakes to be taken into account. This is not a MUST comply list (yet).

  • GUI
  1. Use a two column layout for the Inputs
  2. Use foldable sections for the task
  3. Use decorated labels (bold, asterisk ...)
  4. Use tooltips on the labels describing the parameters
  5. Use specialized modifiers
  6. Prefer enumerations (Combo boxes of String lists) to open ended texts
  7. Prefer no scrolling, then vertical to horizontal.

Points 1 to 4 are given for free if you just use the default implementation. So if the default implementation suits you, you will benefit from all the upgrades that the task framework gets.


  • Usage
  1. Update the progress while the task is executing (if it takes more than a second)
  2. Allow the task to be started and cleaned from the toolbar of HIPE
  3. Allow your task to be interrupted
  4. Provide informative error messages (in the constructor of your exceptions)
  5. Log important information for users to check
  6. Follow the naming conventions
  7. Register your task properly
  8. Prefer strings to enums (harder to write without autocomplete) for textual invocation
  9. Use validators to restrict applicability


  • Documentation
  1. Put descriptions in your task parameters
  2. Use _ doc _ to show the signature of your task
  3. Check the generated information in the URM
  4. Provide reproductible (even if not complete) examples
  5. Try to provide alternative documentation to jtags in Javadoc


All of the tasks in ia_toolbox_util try to follow this recomendations. A sample task illustrating some of the previous points:

/**
 * The Decompress task.
 * <p>Decompresses a (relative path) archive file in a  user-chosen directory. 
 * Supports arbitrary nesting (but not per entry) of the following algorithms: TAR, ZIP, GZIP.
 * So tar,gz or zip.gz will be completely expanded but zip entries in tar archive 
 * will only be expanded up to the zip entries.
 *
 *<p>Parameters:
 *<ul>
 *<li><b>archive</b> (INPUT, mandatory) = String with the archive file path
 *<li><b>dirout</b> (INPUT, mandatory) = String with the output directory path (may not exist).
 *<li><b>compression</b> (INPUT) = String with the type of compression in the file.
 *  <p>Valid options:
 *  <ul>
 *  <li> <b>"Guess"<b>: (default) automatically detect the type of compression used </li>
 *  <li> <b> "ZIP" </b>: use unzip (or equivalent) to decompress</li>
 *  <li> <b>"TAR"</b>: use untar (or equivalent) to decompress</li>
 *  <li> <b> "GZ"</b>: use gunzip (or equivalent) to decompress</li>
 *  <li> <b> "TGZ"</b>: use gunzip then untar (or equivalent) to decompress</li>
 *  </ul>
 *<ul>
 *<p>
 *usage:
 *<pre>
 * #Decompress a tar archive in a temporal directory
 * decompress("./mytar.tar", "/tmp") 
 * </pre>

Above, Documentation point 5 (Parts: title, description, parameters (with options), sample usage. Check HTML usage)

 *
 * @author jadiaz
 *
 * @jhelp Decompresses a (relative path) compressed file in a  user-chosen directory
 * Two first arguments are mandatory. Supports arbitrary nesting (but not per entry) of the following algorithms: TAR, ZIP, GZIP.
 * So tar,gz or zip.gz will be completely expanded but zip entries in tar archive will only be expanded up to the zip entries.
 *
 * @jalias decompress
 *
 * @jcategory task
 *
 * @jsynopsis
 * decompress(<archive>, <dirout> [, <compression>])
 *
 * @jexample-preamble
 *   from java.io import File, FileOutputStream
 *   from com.ice.tar import TarOutputStream, TarEntry
 *   file = File("./mytar.tar");
 *   testFile = File("deleteMe");
 *   if testFile.exists(): testFile.createNewFile()
 *   stream = TarOutputStream(FileOutputStream(file))
 *   entry = TarEntry(testFile)
 *   stream.putNextEntry(entry)
 * @jexample Untarring in temp
 *   decompress("./mytar.tar", "/tmp")  
 * @jexample-postamble
 *   file.delete()
 *   testFile.delete()
 *   del(file, testFile, stream, entry) 
 *   del(File, FileOutputStream, TarOutputStream, TarEntry)
 *   
 * @jparameter tar, INPUT, String, MANDATORY, null
 *  Path of the file to be untared
 *
 * @jparameter dirout, INPUT, String, MANDATORY, null
 *  Directory (that may not exist yet) where we want to untar the tar file
 *  
 * @jparameter compression, INPUT, String, OPTIONAL, "Guess"
 * Type of compression of the input file, values: GUESS, ZIP, TAR, GZ, TGZ
 * 
 * @jlimitation
 * Gzipped files will expand to the filename minus the ".gz" or ".gzip" if present, else they will add ".new" to 
 * avoid overwritting the original (if you are expanding in the same directory where the archive is). 
 *
 * @jhistory
 * 2010-05-25 JDS first release
 * 2010-06-09 JDS expanded, renamed to decompress
 *
 */

Above, Documentation point 3 and 4 (Check jtags definitions)


public class DecompressTask extends Task {
    
    private static final long serialVersionUID = 1L;
    //@SuppressWarnings("unused")
    private static final Logger _Log = Logger.getLogger(DecompressTask.class.getName());

Above, Usage point 5 (provide a logger)

    private static final String TASKNAME = "decompress", 
        ARCHIVE = "archive", DIROUT = "dirout", COMPRESS= "compression";

Above, Usage point 6 (we minimize string literals and follow naming conventions)

 
    
    public static PyString __doc__ = new PyString(
            "decompress(<archive>, <dirout> [, <compression>])\n" +
            "\nWhere all parameters are strings, archive is the source file and dirout a (possibly not existing) directory"     
            );

Above, Documentation point 2 (Line 1: syntax (@jsynopsis) , rest of lines: description (@jhelp))

    
    
    public enum CompressType {
        UNKNOWN("Guess"),
        ZIP("ZIP"),
        TAR("TAR"),
        GZIP("GZ"),
        TGZ ("TGZ");
        @Override
        public String toString() {
            return _text;
        }
        
        public static CompressType get(String text) {
            for (CompressType e: CompressType.values()) { //vs valueOf(arg0) -> CONSTANT name
                if (text.equals(e._text)) return e;
            }
            return null;
        }
        
        public static String [] toStringArray() {
            CompressType [] data = CompressType.values();
            String [] out = new String [data.length];
            for (int i = 0; i < data.length; i++) {
                out[i] = data[i].toString();
            }
            return out;
        }

        private CompressType(String text) {
            _text = text;
        }

        private String _text;
    };
    

    public DecompressTask() {
   super(TASKNAME);

   TaskUtil.addParameter(this, 
      ARCHIVE, String.class, INPUT, MANDATORY, null, NULL_NOT_ALLOWED, 
      "The tar file to expand");

Above, Documentation point 1 (each task parameter has a string describing it , note syntax similar to @jparameter)

 

        TaskUtil.addParameter(this, 
                DIROUT, String.class, INPUT, MANDATORY, null, NULL_NOT_ALLOWED, 
                "The directory where the tar will be expanded");

Above, Usage point 8 (this is an enum , but we define it as a string, and we show it like a Combobox)

 
        
        TaskUtil.addParameter(this, 
                COMPRESS, String.class, INPUT, OPTIONAL, CompressType.UNKNOWN.toString(), NULL_NOT_ALLOWED,  
                "The directory where the tar will be expanded");


    }
    

    
    @Override
    public Map<String, Modifier> getCustomModifiers() {
        Map<String, Modifier> m = new LinkedHashMap<String, Modifier>();
        //TODO: also for gzipped files
        m.put(ARCHIVE, new JFilePathModifier(FileSelectionMode.OPEN, 
                new FileNameExtensionFilter("tar files", "tar"),
                new FileNameExtensionFilter("gzip files", "gz", "tgz", "gzip"),
                new FileNameExtensionFilter("zip files", "zip")                
        ));
        m.put(DIROUT, new JFilePathModifier(FileSelectionMode.DIRECTORY));
        
        m.put(COMPRESS, new JOptionModifier((Object []) CompressType.toStringArray()));

Above, GUI point 6 (we use a JCombobox), GUI point 5 (we provide FIlePaths instead of just text fields)

 
        
        return m;       
    }
    

    @Override
    public void execute() throws InterruptedException {         
   String tarpath =  (String) getValue(ARCHIVE);
   String dirpath = (String) getValue(DIROUT);
   String compress = (String) getValue(COMPRESS);

   File tarfile = new File(tarpath);
   File dirfile = new File(dirpath);

   advance(5,"Validating parameters ...");      
   if (isEmpty(tarpath) || ! tarfile.exists() || tarfile.isDirectory()) {
       throw new IllegalArgumentException(ARCHIVE + " must point to an archive file.");
   }
   
        if (isEmpty(dirpath) || (dirfile.exists() && ! dirfile.isDirectory())) {
            throw new IllegalArgumentException(DIROUT + " must point to a directory.");
        }
        CompressType type = null;
        if (isEmpty(compress)) {
            compress = null;
        } else {
            type = CompressType.get(compress);
            if (type == null) { //not found
                throw new IllegalArgumentException(COMPRESS + " invalid value, allowed values are " + Arrays.toString(CompressType.values()));
            }
        }

Above, Usage point 4 (we check validity of inputs, with taylored error messages)

 
        
        advance(10,"Prepare environment ..."); 
        if (! dirfile.exists() &&  ! dirfile.mkdirs()) {
            throw new RuntimeException(getExceptionMessage("Could not create " + DIROUT + "."));               
        }
        
        advance(20,"Analyzing archive ...");
        InputStream i = null;
        try {
            if (type == null || CompressType.UNKNOWN.equals(type)) {
                i = getStream(tarfile);
            } else {    
                i = new BufferedInputStream(new FileInputStream(tarpath));
                switch (type) {
                    case TAR : i = new TarInputStream(i); break;
                    case GZIP : i = new GZIPInputStream(i); break;
                    case ZIP : i = new ZipInputStream(i); break;
                    case TGZ : i = new TarInputStream(new GZIPInputStream(i)); break;
                //default :
                }    
            }
            advance(30,"Decompressing archive ...");  

Above, Usage point 1 (every time we do something significant to the user (or time consuming), we update the progress)

 
          
            //fugly
            if (i instanceof TarInputStream) {
                process((TarInputStream) i, dirfile);
            } else if (i instanceof GZIPInputStream) { //need a file name
                process((GZIPInputStream) i, new File(dirfile, getGZIPName(tarfile.getName())));   
            } else if (i instanceof ZipInputStream) {
                process((ZipInputStream)i, dirfile);
            } else { //unknown compression
                throw new IllegalArgumentException("Unknown compression algorithm in file " + tarpath);
            }

        } catch (IOException e) {
            throw new RuntimeException(getExceptionMessage("Could not expand " + ARCHIVE, e),e);

Above, Usage point 4 (even if we do not check each error, we provide a default "personalized" error message : while expanding something went wrong)

 

        } finally {
            if (i != null) { 
                try {
                i.close();
                } catch (IOException e) { } 
            }
        }

    }

Topic attachments
I Attachment History Action Size Date Who Comment
PNGpng crop.png r1 manage 13.7 K 2009-09-29 - 15:55 JaimeSaiz  
PNGpng crop_closed.png r1 manage 8.1 K 2010-06-02 - 11:55 JavierDiaz crop with closed sections updated to the new look of tasks
PNGpng crop_input.png r1 manage 14.5 K 2009-09-29 - 16:09 JaimeSaiz crop task with input highlighted
PNGpng crop_input_new.png r1 manage 13.8 K 2010-06-02 - 11:54 JavierDiaz crop input updated to the new look of tasks
PNGpng crop_modifier.png r1 manage 14.4 K 2009-09-29 - 16:09 JaimeSaiz crop task with modifier highlighted
PNGpng crop_modifier_new.png r1 manage 13.7 K 2010-06-02 - 11:53 JavierDiaz crop modifier updated to the new look of tasks
PNGpng crop_new.png r1 manage 13.9 K 2010-06-02 - 11:55 JavierDiaz crop updated to the new look of tasks
PNGpng rotate_new.png r1 manage 10.6 K 2010-06-02 - 11:52 JavierDiaz rotate panel updated to the new look of tasks
PNGpng tasks.png r1 manage 13.0 K 2009-09-29 - 16:11 JaimeSaiz Tasks and variables
Edit | Attach | Watch | Print version | History: r113 | r59 < r58 < r57 < r56 | Backlinks | Raw View | Raw edit | More topic actions...
Topic revision: r57 - 2010-06-22 - JavierDiaz
 
This site is powered by the TWiki collaboration platform Powered by Perl