Did you spot something wrong or missing on this page? If you have an account on this TWiki, you can fix it yourself by editing the page. If you don't have an account, you can leave a message at the bottom of the page to tell us about it. Thank you in advance!
Common utilities in HIPE
This section explains additional useful information for HIPE developers, both for contributors with views, editors, etc. and for maintainers of the HIPE framework.
This section is aimed as a complement for developers contributing to HIPE by writing their own views, editors, tasks, etc.
If you are a maintainer of the HIPE framework, you might find useful the section below as well.
The following advice includes common development in a Java application and some HIPE specific matters.
Avoid SEVERE level when the problem is not so relevant, for example that your view miss some input, or a parameter is not totally correct. This would only affect your view, but it would still work (maybe in a degraded way) and, moreover, HIPE as an application will still work. Use WARNING or INFO for these cases.
The other way around, if the information is relevant for the user, don't hide it with FINE or lower levels, since they are almost always filtered out.
Note that the upper log levels (including INFO) should never be used for developer related data, see Log use (internal resource).
Make HIPE responsive
Let the Stop button work
One of the hot topics when working with HIPE is the Stop button, which is meant for cancelling the current job. Letting it work properly is a cooperative issue.
When talking about cancellation in HIPE, we may divide three main areas:
Jython command/s (a whole script or pipeline lies in this category). This can be interrupted with the Stop button, unless the currently executing code ignores the interruption request. Normally, such ignorance of an interruption request would only happen in...
Tasks. Long tasks should have check points for interruption requests. Since tasks are run by the interpreter, pressing the Stop button in this case would cancel the task in such intermediate check point, if existing.
GUI actions. Code executed in the EDT (Wikipedia article) should provide fast response. If some long job is needed, javax.swing.SwingWorker can be used, which also provides means for cancelling the work (see its cancel method). A worker can be used in combination of BusyJob, which also supports cancellation. Busy jobs can be stopped with the Stop button with no extra code.
In summary:
Plain Jython commands do not need to be aware of interruption; the interpreter takes care of it.
Long tasks should follow the guidelines for being cancellable in the middle of their execution.
Code executed in the EDT should be fast enough that it shouldn't block the GUI.
Avoid freezes
Apart of low responsiveness due to previous issues (long tasks that don't respond to interruption requests, heavy computations done in the EDT...), in all Java GUI tools there is a risk of hanging the entire application, so the only corrective action is to kill and restart it.
The most dangerous GUI freezes are the deadlocks: the EDT is waiting for some job to be done in other thread, while this thread is waiting for the EDT doing something.
Some hints to avoid deadlocks:
Execute your GUI code in the EDT. Take into account that some callbacks to listeners are run in other threads (like CommandExecutedEvent). When in doubt, execute any code related to your GUI in a call to SwingUtilities.invokeLater or SiteUtil.execLater. Try to avoid calls to SwingUtilities.invokeAndWait, since they are potential cause for deadlock.
Beware that PlotXY class forces to be executed in the EDT, by calling SwingUtilities.invokeAndWait or SwingUtilities.invokeLater. You can safely call any plot method from any thread, only when the thread does not hold an EDT lock.
Avoid calling SiteUtil.execAndWait(String command) and its variants from EDT even when the command does not perform any GUI stuff, since it could lead to a deadlock. If you want to execute a Jython command from EDT that involves any GUI operation, please call SiteUtil.execLater(String command) or use a SwingWorker.
The Java Development Toolkit provides lots of libraries that developers coming from other languages like C/C++ find wonderful, because almost all basic funcionality in these languages need to be re-invented, copied or buyed.
The Herschel Java code base has plenty of common utilities as well. Knowing them could help you in developing your tools with much less effort.
Here is a list of some of these common packages:
Holds a VariableSelection that can be represented in a tree, triggering SelectionEvent when a node is clicked, providing a popup menu for opening children nodes, etc.
Resize columns and rows of a JTable according to its contents, allow sorting the table by clicking on a column, by also letting to unsort the table afterwards.
Converts objects into byte array back and forth, through serialization, taking into account also objects handled by resolvers (like Product, Dataset, etc.).
Test of object equality taking nulls into account, straightforward implementation of hashCode(), create instances from class names with nice handling of exceptions, non wordy casts...
Specialization of Set holding WeakReference internally, which means that they can be removed in the background if no pointed by any other reference. Useful for listener collections.
This list can be expanded; feel free to enrich it with more useful classes (please respect the alphabetical order when doing so).
More information about this topic can be found in DpHipeCommonUtilities.
How to ...
This section explains how to code some common actions in HIPE.
Trigger events
The standard way of triggering events is to get a reference to the SiteEventHandler interface, which contains all related methods to the HIPE event system. This is typically done via the ViewPart interface that is passed on through the init method of the Viewable.
However there are cases where you do not have access to a Viewable and you still want to trigger events for informing the rest of the system about something. In order to achieve this you can use a utility method available in a dedicated utility class.
If no view part is accessible from your code, then you can choose one of the SiteUtil.trigger methods.
Execute Jython statements
Sending execution events
HIPE has a dedicated Event for asking the execution of Jython statement: CommandExecutionRequestEvent.
The event is served by the ConsoleView and the result of its execution is visible both in the screen of the ConsoleView and in the list of the HistoryView (but you don't need to know about these views and their api).
Every time a CommandExecutionRequestEvent event is served, HIPE generates an equivalent CommandExecutedEvent tracing the original CommandExecutionRequestEvent, so you can easily synchronize on the completion of the execution. However, this is not needed either, since SiteUtil class provides a convenient method for doing it: execAndWait.
private void someMethod() {
:
// this is the reference to the modifier
Command c = SiteUtil.execAndWait(this, "print 'something'");
:
}
The execAndWait method returns a Command tracing all the information about its execution including the statement result (isSuccesful()).
Last but not least, SiteUtil contains other utility methods that allow for more options including a un-synchronized execution method. See SiteUtil for the full detail.
A Jython Example
This example provides a short script for testing the SiteUtil execution functionality directly from the console.
Please note:
the reference used is _jconsole, which is a name available in jython and pointing to the jconsole (indeed visible).
the call is synchronous and therefore it stops the gui thread for the time of the execution (see Lesson: Concurrency in Swing for a general discussion about GUI and threads).
During the processing of a particular view, it may be wanted to create a variable, so that it is shown in the Variables view and the user can do a further work with it.
Creating a variable can be done either with or without echo to the console.
With echo to the console
Just use one of the methods provided by SiteUtil:
Synchronous call: Command result = SiteUtil.execAndWait("yourVariable", "Something()"); // result will inform about the executed command
Asynchronous call: SiteUtil.execLater("yourVariable", "Something()"); // request execution to the interpreter but return immediately
Prefer this way when you want the variable creation be recorded in the history, so it can be reproduced later.
Example:
For the PolygonHistogramPanel we need to construct a Double1d with the corners of the polygon (which are listed in Double1d edges) and add it to the variables map of the task panel:
Command c = SiteUtil.execAndWait("Double1d(" + edges.toString() + ")", "pyedges");
String name = c.getOutputName();
Object value = c.getOutputValue();
VariableSelection edgesPixel = new VariableSelection(name, value);
getMap().put(getTask().getSignature().getTaskParameter("edgesPixel"), edgesPixel);
Without echo to the console
In this case, you can create the variable with any Java code (not to be executed by the interpreter), and then publish it like this:
Prefer this way when the creation of the variable is so complex that doing it with Jython commands would be too difficult.
Show file choosers
Selecting a file is a common user task. Java Swing comes with JFileChooser, which can be configured in many ways. It even allows you to provide file filters so the user can select among a defined set of them.
Now, leaving each code to show and create its own file chooser can lead to inconsistencies. For example, in one place a FITS file can be considered any file ending with .fits, while other code can be more wide and accept also extensions like .fts or .fits.gz.
In order to provide a unified way of showing file choosers and define file filters, you should use the FileChooser class provided by HIPE. The following lines explain more about this.
Define file types
A file type is normally determined by its extension. HIPE allows to define a file type either by a list of possible extensions (for example, jpg, png, gif for image files) or by a regular expression.
In order to define a file type, you need to:
1. Create a class extending java.io.File.
2. Register that class as a file type in the extension registry. This can be achieved in a __init__.py file with lines like the following:
from herschel.ia.gui.kernel import ExtensionRegistry, Extension
from herschel.ia.gui.kernel.util import FileType
FILE_TYPE = FileType.FILE_TYPE
REGISTRY = ExtensionRegistry.getInstance()
# Register the image file type
REGISTRY.register(FILE_TYPE, Extension(
"site.view.navigator.image", # file id; navigator stands for the Navigator view (which shows the file trees)
"herschel.some.package.ImageFile", # class extending File
"jpg,png,gif", # comma-separated list of extensions
"herschel/some/package/Image.gif")); # icon associated to this file type
# Cleanup
del(ExtensionRegistry, Extension, FileType, REGISTRY, FILE_TYPE)
REGISTRY.register(FILE_TYPE, Extension(
"site.view.navigator.obsDescriptor", # file id; navigator stands for the Navigator view (which shows the file trees)
"herschel.some.package.ObsDescriptorFile", # class extending File
"regex:obs-[\\d]+[.]xml", # regular expression
"herschel/some/package/Observation.gif")); # icon associated to this file type
Use file choosers
herschel.ia.gui.kernel.util.component.FileChooser extends javax.swing.JFileChooser with additional functionality:
File filters for defined file types as explained above can be added with the setFileFilters method.
If the dialog is opened with the save mode and an existing file is chosen, it warns the user and ask for overwriting. Being this the default behaviour, it can be disabled.
File choosers can be shared so that the user sees them opened in the previously chosen directory. The global file chooser can be retrieved with FileChooser.getChooser(). Custom file choosers can be obtained with FileChooser.getChooser(owner), which keeps also their state among calls.
If you want to avoid the boilerplate code for creating, configuring, showing and getting the result of a file chooser, you can use one of the static methods FileChooser.getFile.
Configuration of file choosers is made easier by means of the auxiliary FileChooser.Configurer class.
Consult the Javadoc for additional details.
Select files in task GUIs
Some task parameters can expect a file. In these cases, the parameter type is usually String rather than File, to make its call easier from the console. For example, compare
someTask(file="/path/to/file.txt")
with the wordier
someTask(file=java.io.File("/path/to/file.txt"))
However, the default modifier (GUI component) for a string parameter is a plain text field.
In order to show the text field along with the more handy button with a folder icon that shows a file chooser and updates the field with the chosen file, you can use JFilePathModifier.
For more information on modifiers, refer to the tasks and tools documentation.
Show popup dialogs
The PopupDialog class
The Java Swing library provides a class for showing popup dialogs: JOptionPane (Java 6) for HIPE 11 or older or JOptionPane== (Java 7) for HIPE 12 or newer.
This class is used in different modules along the project.
Now, in order to unify the look and feel of dialogs, and to simplify the API as well (the calls to !JOptionPane are normally too long), there is a wrapper class which should be used for showing popup dialogs in HIPE: PopupDialog.
Using this class we ensure that we use HIPE-like icons instead of Java-like icons:
Dialogs with HIPE-like iconsDialogs with Java-like icons You can use PopupDialog as well if your component is used by both HIPE and JIDE. It internally checks whether to show the HIPE style icons or the Java standard ones.
Sample Java code
The API is easy and self-explanatory, just consult PopupDialog.
For example, the first dialog above could be generated with the following code:
int option = PopupDialog.YES_NO_CANCEL_OPTION;
String title = "Unsaved changes";
String message = fileName + " has unsaved changes.\nSave before closing?";
int res = PopupDialog.showConfirm(this, message, option, title);
if (res == PopupDialog.YES) {
saveFile();
}
if (res != PopupDialog.CANCEL && res != PopupDialog.CLOSED) {
closeEditor();
}
Load icons
Icons are widely used in HIPE.
Instead of needing to work with the Java API directly, when you want to load icons, HIPE provides some utilities for making the job easier:
herschel.ia.gui.kernel.util.IconLibrary provides some static icons already loaded. If the icon you want to use is there, you are done.
herschel.ia.toolbox.spectrum.explorer.actions.IconLoader provides static methods for loading an icon.
If you want to use one of the icons in ia_gui_kernel, call getKernelIcon, if you want to load an icon located in your own module, use any of the variants of IconLoader.getIcon.
The IconLoader caches already loaded icons, so further accesses to them is faster: access to disk is only performed the first time. Therefore, it is not needed that you create your own icon constants.
Debug GUI problems
As part of giving better support to developers and to help diagnose GUI problems, a Debug preferences category has been added to HIPE.
The preference Enable debug in that new category shall be switched on to let debug services be run individually. When this preference is set, debug services will also be controllable through the Tools -> Debug menu, which will be invisible while that master preference is unset. Current options for debug:
Check the painting of graphical components: it checks that all painting is done in the EDT. If it is not the case then you have to move code to the EDT.
Check that the application is responsive: checks that no event in the EDT takes more than a second to process and that threads are not (inter)blocked. If it is the first case then you need to move code out of the EDT. If it is the second case then you need to redesign shared resource access.
Paint special border on the component pointed at: this helps to diagnose GUI layout problems.
Provide an image preview of a FITS file
When the user selects a FITS file in the Navigator view, the Outline view shows few of the most relevant meta data.
It tries also to make an image preview of the FITS file. For this, it looks for a PreviewMaker that can cope with the product type that is returned by FitsReaderTask.
If you develop SomeProduct (which could be an image, a spectrum, a cube...) and want to provide an image for it in the outline, look at how to do it.
Save files in the HIPE directory
If your module needs to save hidden files in the user disk, consider doing it under the HIPE directory, so the user home is not flooded with many different directories.
The HIPE directory is ~/.hcss/apps/hipe, but it used to be ~/.hipe.
In order to avoid depending on such changes, use SiteFileUtil.getSiteDirectory() or SiteFileUtil.getSiteDirectory(String area), which handle the actual file path properly.
By using these methods of herschel.ia.gui.kernel.util.SiteFileUtil, your code would be protected against changes in the HIPE directory. You wouldn't even need to worry about moving data from the old location to the new one; it would be done transparently.
If your module generates many files that need periodic clean up, consider also registering in the herschel.ia.gui.kernel.util.FileCleaner.
Persist data among sessions
HIPE remembers some user customizations that are not expicitly saved in preferences: perpective layouts, last opened perspective, recent files, opened editors, etc.
These are not saved as preferences because they have no corresponding preferences panel nor preferences category and key. They are persisted through the session persistor framework.
If you want to store data at application exit like these, for being recovered at next HIPE start up, you can do it yourself by writing files within a site area (see previous tip).
However, using the session persistor framework instead may save you some coding. Moreover, your session items would be exported and imported automatically with the File -> Session -> Export / Import options for free.
The following example shows how to use it:
import herschel.ia.gui.kernel.session.SessionItem;
import herschel.ia.gui.kernel.session.SessionPersistor;
// This viewer has a split pane; we want to persist the separator location
// so the user doesn't need to relocate it each time the viewer is opened
public class SplitViewer {
private SessionItem<Double> splitLocation =
new SessionItem<Double>("site.split.viewer.location", Double.class, 0.25); // initialize with default value
// Constructor
public SplitViewer() {
SessionPersistor.getInstance().register(splitLocation); // this reads the previous value, if existing
double location = splitLocation.getValue();
// use location for setting the split division
}
// Listen to changes in the divider for updating the location
public void splitLocationChanged(double location) {
splitLocation.setValue(location); // this will be saved automatically at normal exit
}
}
SessionItem may hold a value of any of the following types: Integer, Long, Float, Double, Boolean, String or byte[].
If you want to persist an item of other type, you can do it by means of SerializableItem instead, provided that the object can be serialized, that is, it implements java.io.Serializable or is managed by one of the resolvers of herschel.share.io.
For more information and options of these classes, see their Javadoc.
Register removed names
Obsolete tasks, functions, etc. that are removed in a given version of HIPE but are still present in users' scripts may confuse them, because they worked in the previous release.
The error message
<type 'exceptions.NameError'>: name 'someRemovedName' is not defined
does not help much on how to proceed.
To overcome this problem, the removed name can be registered so that useful information can be given to the user upon name errors on it.
The registration should be done in the __init__.py file of the package where it was defined, like this:
from herschel.share.interpreter import ObsoleteNames
ObsoleteNames.register(removedName, version, newName, reason) # the new name and the reason are optional
For example, if a function oldSlowAlgorithm is removed in HIPE 9.0, and a newFastAlgorithm is created in its stead, it should be recorded like this:
from herschel.share.interpreter import ObsoleteNames
ObsoleteNames.register("oldSlowAlgorithm", "9.0", "newFastAlgorithm", "The algorithm was intrinsically slow")
which would produce the following output when oldSlowAlgorithm is called:
HIPE> oldSlowAlgorithm(data)
Error: The obsolete name 'oldSlowAlgorithm' has been removed in HIPE 9.0
Reason: The algorithm was intrinsically slow
Please use 'newFastAlgorithm' instead
HIPE framework
This section is meant for helping developers of the HIPE framework.
Got lost in the Java code? Below some information that you might find useful.
Design
In the Architecture Desing Document (ADD, included in HIPE Developer's Reference Manual) there is an introduction to the terminology and how classes are related to the functionality.
Modules and classes
Besides, the following table can help you further as a quick guide.