Developer Notes
Tools
- JBuilder 3 is the IDE.
- Visual SourceSafe is used for source code control.
- gnu ident.exe (located in tools directory under VSS) to
display VSS Header strings in source, class, or jar files.
Getting Started
Image200 maintains two separate VSS projects:
"Image2000" is the base product and "I2kCorePlugin" has
the core plugins.
To configure your PC for Image2000 development:
- "Get latest" on the entire Image2000 VSS project into
your own directory tree.
- Change the file attributes on i2k.jpr to be writable.
(Note: currently, we do not checkin/checkout the i2k.jpr
project file.)
- In JBuilder, open the i2k.jpr project file at the top
of your tree.
- Under "project parameters", configure the paths for
the XML, Rhino, and JAI jar files.
- Select gov.nasa.gsfc.i2k.main.Image2000 as the class
to "run".
- Make sure that all of the Image2000 packages in your
VSS "get latest" tree have been added to the project.
- Under "project parameters", set the command line
parameter -Di2k.root=your-root, where "your-root" is the
absolute path to the top of your directory tree.
(Operationally, the i2k.root property will be set
automatically--for now, you must hard-code the absolute path.)
- Build and run using JBuilder.
- If Image2000 does not come up, use JBuilder to display
the "execution log".
To configure your PC for Image2000 Core plugin development,
do "get latest" on the I2kCorePlugin project and follow the
steps above.
Plugin Development
See Plugins for a description of the
plugin architecture and a cookbook for developing and packaging a plugin.
Launcher
The launcher is a host-native executable that
starts Image2000 running within the Java VM. You should
run Image2000 using the launcher rather than running
the VM directly. Under Win32, the file path for the launcher is
bin/Image2000w.exe. There is also the
console version bin/Image2000.exe that performs
the same function but assumes the existence of a DOS
console for standard output; the console form is typically
used by programmers.
The bin/Image2000w.exe executable is suitable for use as
a Windows shortcut icon. Dragging image, script, or DAG
files to such a shortcut produces the standard result.
Properties Set by Launcher
Before running the Java VM, the launcher sets the values of
the following Java properties:
- i2k.root is set to the root
installation directory for Image2000.
- i2k.user.home is set to the
user's Image2000 directory as follows:
- UNIX systems: the i2k sub-directory of
the user's home directory (as defined by the HOME
environment variable set by login).
- Windows 9x and NT: The i2k sub-directory of the user's
home directory, i.e.,
WindowsDirectory/Profiles/username/i2k.
The WindowsDirectory is typically C:\WINDOWS
for Windows 9x and C:\WINNT for Windows NT.
- Single-user Windows 9x: if users have not been defined, then
then i2k.user.home is
WindowsDirectory/i2k.
- java.class.path is set to the following paths:
- Jars needed by the Java VM (the "bootstrap classes").
- i2k.root/classes. During development,
this setting allows us to run without jarring class files after
every compilation. Operationally, the directory is not used.
- Jars needed by Image2000. The launcher places each
jar file found in the i2k.root/lib directory in CLASSPATH.
- Jars and directories needed by plugins. The launcher examines each directory
under i2k.root/plugins, placing each jar file and classes
sub-directory in CLASSPATH. See also Plugins.
- Jars and directories needed by user plugins. The
launcher examines each directory under i2k.user.home/plugins,
placing each jar file and classes sub-directory in CLASSPATH.
- java.library.path is set to a list of JRE
and plugin directories that have shared libraries.
Launcher Command Line Options
The Image2000 command line has the following form:
Image2000[w] [javaVmOptions] [files]
The javaVmOptions are the same as for running the JDK Java VM; see
The Java Application Launcher. Image2000, however, supplies the
class name automatically.
In addition to the standard Java VM options, the following are available:
- -hotspot: specifies that the HotSpot Java VM be used.
- -classic: specifies that the classic Java VM be used. This
is the default option.
- -jre=jre-path: specifies the path of the JRE root directory.
By default, this path is {i2k.root}/jre.
- -help: produces usage summary.
- -Xmain:class-spec: sets the main class
to be executed. The default is
gov/nasa/gsfc/i2k/main/Image2000. Use slashes rather
than periods as a separator.
- --: indicates the end of launcher command options and
the beginning of command options for the Java program. This is
only needed when the Java program has options that start with
"-". When "--" is not present, then the first command line
argument that does not begin with "-" becomes the first
argument to the Java program.
The file list may include image, DAG, and/or JavaScript files.
If a file ends with ".dag", it is opened as
a DAG; if the file ends with ".js", it is
executed as a script; otherwise, the file is opened as an image.
Files listed on the command line are subjected to macro
substitution, i.e., {propertyName} is replaced with
the value of the property.
Note: I2K properties defined on the command line using
the -D option override the corresponding property value
from a property file; this rule applies only to property names
that start with i2k..
Image2000 Properties
See Tailorability for a description of
how Image2000 reads property files. Image2000 honors properties from those files
as follows:
- The i2k.root property sets the root of the
Image2000 product installation tree.
- The user.home property sets the root of the
user's directory.
- The i2k.<frame>.undo.limit family of
properties configures "dag" and "image" undo stacks. See "Undo/Redo
Support" below.
- The i2k.url property is the URL for the I2k home page.
This property defaults to http://invision.gsfc.nasa.gov.
- The i2k.url.command property is the OS shell command
to execute in order to view the I2k home page URL. This property
defaults to start, which is good for Win32 only.
- The i2k.label.alignment property determines the
style of "Bean style" property dialogs: "LEFT" means that
the property labels are left aligned and "RIGHT" means
right aligned. Note that the value column is always left aligned.
- There are several properties related to logging: see "Logging"
below.
- Properties are used to configure file and directory
handling: see "Document Handling" below.
- See Scripting for a description of
properties that control scripting.
- The property i2k.icon is the file name for the
icon associated with the main Image2000 frame. The default
value is icons/i2k.gif.
- The property i2k.double.buffer is "false" to disable
Swing double buffering. By default, Image2000 enables double buffering.
- The property i2k.display.precision is the precision
for displaying floating point values. The default precision is "2".
- Properties determine the location of on-line help text. See On-line
Help.
There is a JavaScript script, show-properties.js, that
displays the list of current property values.
Cookbook for Adding an Action
To add an action written in Java:
- For each action, there is a corresponding definition in
some plugin directory. To add an action to a plugin,
edit an existing spec.xml file or start a new
plugin directory. Some spec.xml files use XML "external entities"
to include action definitions, in which case you will edit
some file referenced by the spec.xml file.
- Place the action on some menu by editing the corresponding file
in the menubars directory.
- Optional: place the action on a toolbar by editing
the corresponding file in the toolbars directory.
- Optional: put an icon for the action in the images
directory and reference the icon from the XML file.
- Write a Java subclass of AbstractAction to
implement the action. Use one of the gov.nasa.gsfc.i2k.main
Action classes as a guide.
- When applicable, your Action class should make provisions for
enabling/disabling the Action based on context.
MDI Child Windows
Each child window (JInternalFrame) should be a subclass
of gov.nasa.gsfc.i2k.main.Child. The following considerations
apply:
- The Child constructor sets a default size for
the new window. The subclass may override the size.
- The Child constructor determines the position of
the new window (by cascading).
- A Child window does not become visible until the display
method is called. Thus, after construction, the client may
configure the window before display.
- The Child class automatically handles menubar and toolbar
responsibilities: (a) when a Child frame becomes active, the
Child class replaces the main menubar with the subclass-specific
menubar, and (b) when constructing the Child, the Child class places
the subclass-specific toolbar in the Child frame.
- There is a Child static method, getCurrent(), that returns the
currently active JInternalFrame window (if any). This method
is typically used by Actions to find the currently active Child
and invoke some method on that Child object.
- If the subclass needs notification when the frame is made active,
the the subclass should override the setSelected method.
The subclass logic must invoke the Child's implementation
of setSelected in order to get proper menu handling.
- See below for "Document Handling".
- See below for "Undo/Redo Support".
- See below for "Cut/Copy/Paste Support".
Document Handling
The Child class assumes that each subclass is manipulating
a "document" that may be saved and restored from disk. The
type of the document is determined by the type of the Child
subclass as determined by the second argument to the Child's
constructor. Example: the type of the "DAG Editor" window
is "dag".
If the document has a text representation on disk, then the subclass
should implement the writeTextDoc (PrintWriter) method
to write the document. If the document has a binary form,
then the subclass should implement the writeDocfile(File)
method.
The Child framework handles the Save and SaveAs menu entries, presenting
the appropriate file dialog and calling
the subclass's writeTextDoc or writeDocFile as needed.
The Child framework also handles the prompt for "do you want to save
changes?" when the window is closed or the application is exited.
To assist the framework in this regard,
the subclass must maintain the state of the "dirty" flag by calling
setDocDirty(true) whenever edits are made to the document.
If the subclass uses the framework's undo/redo mechanism, then setDocDirty
is called automatically when the subclass posts a document change via
postEdit.
For each Child frame type ("dag", etc),
there are the following properties that relate to document handling:
- i2k.type.file.types: A comma-separated list
of file extensions for the document files. For most windows,
there is only one file type. "image" windows do not
use this property--image windows use the list of registered
codecs in order to determine file extensions.
- i2k.type.file.title: The title for "Open" and
"SaveAs" file dialogs.
- i2k.type.file.dir: The initial directory for
the first "Open" or "SaveAs" file dialog. If this property has
a relative file path, Image2000 roots the path at i2k.root.
Undo/Redo Support
The Child framework supports undo/redo according to the javax.swing.undo
package. The framework maintains an UndoManager for each Child window and
manages the enable/disable state of the Undo/Redo actions.
Each Child subclass has the following undo/redo repsonsibility:
after performing an action that changes the state of the Child's
document, the Child must call postEdit(UndoableEdit).
The UndoableEdit argument is an object--constructed
by the subclass--that knows how to undo and redo the action.
In this regard, a Child has several options:
- Full undo support: The Child calls postEdit for
every document change.
- Partial support: Some changes may be difficult to undo. To obtain partial
undo support, the Child calls postEdit(UndoableEdit) after document
changes that have a working UndoableEdit object and postEdit(null)
following those "renegade" changes that are too difficult to be undone (or undo support
for which is being deferred). Thus, following undoable document changes,
the Undo/Redo actions are operable and, following renegade changes,
the Undo/Redo actions are disabled. If the subclass makes a document change
and forgets to call postEdit, then the state of the document may
become corrupted.
- No undo support: If the Child subclass never calls
postEdit, then the Undo/Redo actions never become
enabled.
Composing UndoableEdit objects is non-trivial. The
javax.swing.undo.StateEdit classes provide a general-purpose approach
for constructing UndoableEdit objects and, for many applications,
StateEdit provides considerable simplication. The standard paradigm
for a StateEdit is:
// capture state of the document, make the change, annnouce change:
StateEdit stateEdit = new StateEdit (document, "TypeOfChangeHere");
changeTheDocument();
stateEdit.end();
postEdit (stateEdit);
There is a test application (gov.nasa.gsfc.i2k.main.testapp.Frame) that
demonstrates undo/redo working within the I2k framework. The application
presents a list of items within a Child window, with buttons for adding,
deleting, or modifying an entry in the list. All three actions are undoable.
If you set the i2k.<frame>.undo.limit property to some
number greater than one, then the actions are undoable/redoable through that
number of levels. To run testapp, run the undo-redo-demo.js script.
Run several copies of testapp to demonstrate that an UndoManager exists for each
window.
testapp demonstrates both the UndoableEdit
and the StateEdit approaches. By default, testapp
uses custom UndoableEdit objects; if the
i2k.testapp.stateEdit property is set to "true",
then testapp uses the StateEdit approach.
The following notes apply to the undo/redo framework:
- The i2k.<frame>.undo.limit property determines the
number of levels of undo that the framework supports, where
<frame> is the child frame type ("dag", "image", etc.). The
default value is "1". Image processing applications should consider a small
limit because the UndoableEdit objects often contain references to
large image objects.
- You may set i2k.<frame>.undo.limit to zero
in order to disable undo/redo entirely. This option exists to simplify
the investigation of memory leaks (because UndoableEdit objects
tend to hold object references).
- The die method of UndoableEdit classes
is important for releasing large referenced resources. die
is called by the UndoManager when the UndoableEdit
gets pushed off the undo list, i.e., when the edit is no longer
a candidate for undo/redo.
- UndoableEdit classes maintain sufficient document state
so that the edit may be re-done and un-done. The classes should
be careful to hold deep copies of the document state rather than
simple references to existing objects. The same considerations
apply when use the StateEdit approach.
- In addition to the Geary Swing book, there is a
good Javaworld article on undo/redo. Note that the article's
coding of the AbstractUndoableEdit classes appears to be wrong
(or at least obsolete): the undo() and redo() methods should
start with super.undo() and super.redo() and
the canRedo() and canUndo() are not needed. The
testapp has the AbstractUndoableEdit classes coded correctly.
Cut/Copy/Paste Support
The Child class provides methods for the popular edit
operations: cut, paste, copy, etc. The Child implementations
of these methods do nothing.
Wait Cursor Paradigm
The gov.nasa.gsfc.i2k.util.ShowWaitCursor class does
the work of showing the wait cursor on the Image2000 frame.
The paradigm for using ShowWaitCursor is as follows:
ShowWaitCursor waitCursor = new ShowWaitCursor();
try {
do_stuff_with_returns_or_exceptions();
}
finally {
waitCursor.finish();
}
Note that the finally clause gets control
no matter how the body of the try clause
exits, i.e., whether by return, exception,
or normal termination.
Using VSS Header Strings
Every Java source file should have the following statement:
private static String ident = "$Header: $";
When fetched from VSS, the Header string gets substituted with the VSS version
string for the source file, containing file name, date/time of the most recent
checkin, and version number. The Header string may be queried in source,
class, uncompressd jar file with the gnu ident utility. Examples;
ident.exe *.java
ident.exe *.class
ident.exe *.jar
For a compressed jar file, the info-zip unzip utility provides
a convenient way to list Header strings:
unzip -p *.jar >jar.tmp
ident jar.tmp
Note: For some unknown reason, piping directly from "unzip -p"
to ident does not work.
The gnu ident utility is carried under VSS in the "tools" project.
There is a perl utility that will attempt to add the Header strings
automatically:
perl \i2k\tools\plant-header.pl *.java
Logging
General
The gov.nasa.gsfc.i2k.util.Log class manages the PrintStream
Log.out that I2k uses for logging. If logging is disabled,
Log.out contains an inert PrintStream so that you may
code "Log.out.println();" without a null check. By default, Image2000
directs logging to System.out.
(Note that the public static variable, Log.out, violates several coding
conventions (e.g., accessors, naming conventions). The thinking is
that "Log.out.println(...)" is a useful, readable, terse paradigm.)
Generally, you should not write directly to System.out. Use Log.out
instead.
When logging is disabled, your println logic still incurs overhead,
especially the formatting of the string argument to println. In
those special cases where the overhead is significant,
you could conditionalize the Log.out method calls on Log.out.isLoggingEnabled().
Categories
Categories provide for conditional logging. If you want conditional logging,
use the category argument to println or print. A category is an arbitrary
string that names a type of logging, e.g., "dag.xml.parsing". To use
conditional logging, pass a category string to println or print and the output
is ignored except when the category is enabled.
Programmers create categories simply by placing a category string in a log
method call. By convention, the first component of the category should be the
i2k package name, e.g., "dag" in the "dag.xml.parsing" category.
Categories may be enabled via the i2k.log.categories
property (below). You may also enable a category programmatically
by calling Log.out.enableCategory(String name, boolean state).
When logging is disabled for a category, your println logic still incurs
overhead, especially the formatting of the string argument to println. In
those special cases where the overhead is significant, you could
conditionalize the Log.out method calls on Log.out.isCategoryEnabled().
General convention: Severe errors should be logged unconditionally.
Properties
i2k.log.path specifies the path for the log file. If the
path is relative, I2K roots the path to the i2k.root directory.
If the path is "none", then no logging output is produced.
If i2k.log.path is not present, then Image2000 logs to
stdout.
Warning: When specifying paths in Windows, note that
backslashes must be doubled in property files. However, the Windows
implementation of Image2000 accepts forward slashes as
the file separator and such characters do not have
to be doubled.
i2k.log.append should be "true"
to append to the log file upon Image2000 activation. By default, Image2000
overwrites the previous log file.
i2k.log.tee should be "true"
to write to the log file and to standard out. This
provides "tee" output as in UNIX. This property
only applies when i2k.log.path specifies
that logging should be written to a file.
i2k.log.categories may be used to enable categories. The
value of this property is
a list of categories to be enabled. Separate element in the list
with comma, semi-colon, or colon.
Scripting
In a script, the log is known as i2k.log. Scripts may
use the standard println method, e.g.,
i2k.log.println("hello"),
or the category method, e.g., i2k.log.println("myCat", "hello").
Miscellaneous
- Product version string: the product version string is maintained
in the text file version located at the top of the classes
directory. Image2000 reads this string as a resource so that the
the file is accessible during development as a normal file in the
classes directory, and operationally as a member of the
jar file.