    ==============================================================================
    ===  TO DO  ==================================================================
    ==============================================================================
        Create driftwood.signal for publish/subscribe message passing

    DATA: {{{
        Implement CheapMap like CheapSet, but intersperse keys and values in one array.
            entrySet() may be hard w/out pre-existing Map.Entry objects
    }}}
    GUI: {{{
        Create an enabler that traverses a Container syncing the enable state
        WeakButtonModel would hold only weak references to GUI listeners
        Make a DialogButtonBar from Box:  Btn1--Btn2--Btn3------------BtnLast
        Make a list with checkboxes for selecting a set of things?
        TreeFoldSlider controls tree expansion like TOC in LyX
        Add addStrut() to TablePane2 as combo of addCell(strut(...))
    }}}
    ISOSURFACE: {{{
    }}}
    MOLDB2: {{
        Write PdbUpdater that reads new coords/state into an old model
        Add a class that can WRITE moldb2 data into a StarFile DOM (CifWriter)
        We need an Model.extractAtomStates() function based on AS [identity? name?]
      * CifReader should use _atom_site.pdbx_PDB_residue_no instead of CIF numbers?
      ? Why does AminoAcid.translate() return lower case three-letter codes?
        Alignment needs to accept gap start vs. gap extend penalties (16/2/2 for BLOSUM 62)
        Consider supporting "WPDB" (wide PDB) format standard
    }}}
    PARSER: {{
        TokenWindow.syntaxError doesn't skip whitespace -- add TokenMatcher.start()?
        Tokenizer is *too* insensitive to whitespace: "123abc" becomes "123", "abc".
            So there's no difference between "123abc" and "123 abc" !
            This is bad for most languages, but I don't know how to fix it...
    }}}
    R3: {{{
    }}}
    STAR: {{{
        Add StarWriter
        Make StarReader more robust to errors (unnamed data_ and save_, etc)
            See email on 7/24/06 from John Westbrook
    }}}
    UTIL: {{{
    }}}

    ==============================================================================
    ===  CHANGE LOG  =============================================================
    ==============================================================================
    ### 0.92 ###
    VBC:
    -Fixed bugs in PdbDisulfides and PdbReader to account for two character chain IDs
     to fix a bug where Dangle wasn't outputting disulfide data.

    ### 0.91 ###
    DAK:
    - Added Triple.likeProjection(); reflects base-P perp bug fix in Dangle
    - Added "alpha" & "beta" keywords to Selection for e.g. SubImpose
    - Minor tweak to Residue.nickname()

    ### 0.90 ###
    DAK:
    - Added proper ANISOU card support for PdbReader & PdbWriter
    - Added chemical element output to PdbWriter
    - Fixed bug related to ANISOU output in PdbWriter

    ### 0.89 ###
    VBC:
    - Changed CifReader to use author defined sequence and chain identifiers; using
      label identifiers means kins don't match residue numbers given on webpage, articles, etc.
    - Changed CoordinateFile to contain the common PDB file suffixes,
      including regex pattern for pdb#.gz, like PDB uses for biological units.
    - Changed SuffixFileFilter to take in regex expression strings also.
    - Fixed mysql access in databasemanager
    - Changed build system to use java 1.5.
    - Added ability for dials to show boundaries.
    - All caps added to aminoacid
    DAK:
    - Added Disulfides & PdbDisulfides in spirit of SecondaryStructure &
      PdbSecondaryStructure for reading "SSBOND" cards with PdbReader and storing
      in CoordinateFile
    - Minor update to PdbDisulfides to handle shortened SSBOND records
    - Let CoordinateFile "deploy" its Disulfides object to all its Models.  Not
      ideal b/c SSBONDs defined at file level, not model level, but lets lots of
      outside stuff use the information, and remains optional!
    - Renamed some methods in Disulfides & its subclass PdbDisulfides
    - Added hopefully convenient Disulfide.otherEnd(Model,Residue) method
    - Added AminoAcid.isCisPeptide method
    - Added Residue.nickname method
    - Added moldb2.Neighborhood to encode notion of local structural region
    - Prettified ModelState.debugStates
    ARK:
    - To allow for calculating ribbon sidedness, added several fields to
      SecondaryStructure.range and added method SecondaryStructure.consolidateSheets()

    ### 0.88 ###
    - added WrapMap.  Like UberMap, but wraps from start to end and vise versa
    - Added int[]->String and double[]->String methods to util.Strings (DAK)
    - Added kinPoint() method to Strings

    ### 0.87 ###
    - change in modelstate to give more useful error messages when identical atoms are added.
    - added gzip reading support to cifreader and pdbreader
    - CifReader now adds pdb entry id to resulting coordinatefile.
    - Changed PdbReader and CifReader to read as much of input files as memory allows (mainly for applet).
    - Fixed bug in CifReader that was looping forever when identical (except coords) atoms in cif file.

    ### 0.86 ###
    - Uses new Hybrid-36 system for dealing with big files (specifically, the seqence
      numbers).

    ### 0.85 ### (DAK)
    - Added moldb2.selection.ResRangeTerm as Ian intended to implement at some pt
    - Modified moldb2.selection.SelectionParser to accommodate the above

    ### 0.84 ###
    - extended PdbReader to deal with new PDBv3 format hydrogens. Added code for dealing
      with hydrogen atom names getting identified as heavy atoms (e.g. Hg, Hf).
    - PdbReader now counts how many PDBv2.3 atoms an input pdb contains.
    - added code to CoordinateFile to store how many PDBv2.3 atoms it contains.

    ### 0.83 ###
    - added setName to Model
    - extended PdbReader to deal with degenerate atom names gracefully.
      This is important for making Molikin robust to weird PDBs (like Rosetta's).
    - removed debugging message from parser.TokenWindow
    - added initial working framework for Probe-like selection algebra
    - moved extractOrderedStatesByName() from molikin.Util to moldb2.Model,
      because it's a generally useful utility function.
    - added support for "within DIST of (SELECTION)" to Selection
    - added support for "within DIST of X, Y, Z" to Selection
    - added support for "all", "none", and "het"
    - changed how Selection.toString() outputs parentheses (so we get fewer)
    - added support for atom____, res__, and chain_ selectors
    - added grammar-level support for residue ranges, but no implementation yet
      Getting the grammar right was very tricky; ambiguity with "-" symbol and ints.
    - added Selection.selectResidues()
    - added "fromres" keyword; not in Probe but important to me for research

    ### 0.82 ###
    - added mysql/DatabaseManager.  Any code that uses this will need the mysql
      connector j jar in the classpath when compiling and running.

    ### 0.81 ###
    - fixed bug in Model where getChain wouldn't return null if called with a chain
      that wasn't present.

    ### 0.80 ###
    - added r3.LsqPlane.getRMSD()

    ### 0.79 ###
    - changed star.DataBlock to return save frames in same order as file
    - added gui.FullTextAsTipsJTable
    - added util.CharWindow, the first step toward a better tokenizer for Dangle
    - moved CharWindow to driftwood.parser
    - added more classes to driftwood.parser; now have very flexible tokenizer!
    - added lots of predefined token patterns to RegexTokenMatcher
    - added a better main() to TokenWindow -- can now tokenize Java source
    - changed parser.* to not consume whitespace prematurely
    - added RegexTokenMatcher.recursivePattern()
    - giving up on parser.* for now -- can't figure out how to make it work
    - heavily reworked CharWindow to make it simpler, and offer both look-ahead
      and look-behind (for generating context of error msgs)
    - improved TokenWindow.syntaxError(), though leading whitespace still a problem
    - fixed error context by making TokenMatcher.end() meaningful for failed match
    - in parser: minor name changes, larger default size for CharWindow
    - typo bug fix in CharWindow.contextAt()
    - null pointer (null token) fix in TokenWindow.accept()
    - more work on TokenWindow.syntaxError() ... why is this so tricky?
    - added TokenWindow.require() -- like accept(), but error on failure

    ### 0.78 ###
    - added util.ReflectiveRunnable
    - added util.StreamTank.toByteArray()
    - added r3.Quaternion, based on Warren Robinett's adapted version of Ken
      Shoemake's code, as seen in Shoemake's 1985 SIGGRAPH paper.
      C code stolen from VRPN project at UNC (public domain code).
    - added r3.Transform.likeQuaternion() based on same code.
    - added Quaternion.likeSlerp() for interpolating views
    - added gui.ProgressDialog to simplify long-running jobs
    - added special case element recognition for VMD hydrogens (e.g. _1HB)
    - added AminoAcid.isExtendedAminoAcid to look for residue names with alt confs.
    - added AminoAcid.getAAName to search a string for a match to a normal amino acid.
    - added static distance() methods to Triple [already had angle() and dihedral()]
    - added r3.UltrafastShapeRecognition -- possible algor. for RNA fitting?
    - added moldb2.Alignment for gapped alignments -- superpositions or RNA motifs?
    - moved UltrafastShapeRecognition to old/; showed little promise for conformers

    ### 0.77 ###
    - added Builder.makeBoundingBox() for spheres, not just points
    - added Strings.usDecimalFormat() for writing text formats that require
      numbers to be formatted with US English conventions (123,456.789)
    - added PDB read/store/write support for data stored beyond column 80

    ### 0.76 ###
    - fixed AminoAcid.getPhi/Psi() to check distance to next/prev residue.
      This prevents spurious Rama outliers at chain breaks.
    - added data.TinyMap, which will be used for rare kinemage point properties.
    - fixed a bug in TablePane2 where we restored the default GridBag constraints
      before using the modified ones to mark occupied cells. This leads to overlap.
    - fixed typo bug in star.StarTokenizer re: ending save frames
    - fixed bug in StarReader (didn't skip end-of-save-frame tokens)
    - made it possible to repeat a save frame (new data add or overwrite)
    - added DataCell.getName() -- using toString() was unsafe for future
    - reformatted code in moldb2.AminoAcid to match Ian's code style
    - added lower-level spline interface to r3.NRUBS
    - elaborated on moldb2.SecondaryStructure so you can tell apart e.g. two
      strands that run into each other (this happens in RNA, for instance).

    ### 0.75 ###
    - we now allow redefinition of identically named residues in a PDB file,
      as long as they're separated by 1+ TER cards. This helps in dealing with
      symmetry-expaned PDBs from crystallography. Don't know yet if CIF needs this.
    - fixed PdbWriter to not need a Map of states in order to write a Model
    - PdbWriter will no longer write alt_ for an atom that has altA, altB, etc.
    - added default weights to r3.SuperPoser, and made helper methods private
    - removed public access to point weights period, b/c they may not work
    - added Triple.addMult() and r3.NRUBS (splines)
    - added Residue.getSequenceInteger()
    - added moldb2.(Pdb)SecondaryStructure; create one in PdbReader
    - added CifSecondaryStructure and moved some functions to SecondaryStructure
    - read struct_sheet_range in addition to struct_conf
    - fixed PdbSecondaryStructure to catch IndexOutOfBoundsException for short lines
    - fixed PdbSecondaryStructure to allow short TURN records

    ### 0.74 ###
    - fillInStates code now has the option of creating new ATOMs or not.
    - PdbReader and CifReader now default to NOT creating new atoms during fill-in.
    - added a field to Residue so compareTo will work with trimmed residue numbers.
      Because CIF residues "numbers" can be any length, any string, space padding
      them the way we did for PDB numbers isn't practical.
      (PDB numbers are currently still space padded, just to be obnoxious --
      if code is robust, it shouldn't care which convention is used!)
    - added getElement() to moldb2.Atom and AtomState
    - PdbReader now sets element when file is read in
    - CifReader now sets element when file is read in
    - removed Model.getChains() -- no one uses it, and it should be a Map
    - removed Model.getStateIDs(); changed getStates() to return a Map
    - added Model.setStates(); with clone() this should make modification easier
    - moved Model.makeState() logic into PdbReader and CifReader, where it belongs.
      It makes the Reader code uglier, but it makes Model cleaner.

    ### 0.73 ###
    - fixed code formatting in AminoAcid
    - added sectionID field to Residue
    - added TER-counting code to PdbReader; TER count goes in Residue.sectionID
    - added KeyComparator and ValueComparator for working with Map.Entry objects
    - added data.Maps with sort() and increment() functions
    - BUG: data.CheapSet.rehash() failed to use the requested hash function!
    - BUG: data.UberMap.rehash() also failed to use the requested hash function!
    - changed semantics of UberMap.put() to be like LinkedHashMap and PHP
    - changed moldb2.Model to keep chains/segs in insertion order, not alphabetical
    - worked on util.ProcessTank to speed processing; made modest gains

    ### 0.72 ###
    - added the get() function to CheapSet
    - implemented string interning for PdbReader, for ~10% memory savings --
      this isn't as good as I had hoped, but better than nothing I guess.
    - added string interning to CifReader also, for a ~10% speedup
    - added support for cmd-W to close gui.LogViewer windows
    - though about using ModelStates for NMR models, but then PdbWriter wouldn't
      know how to output them, and there would be problems with the root model " ".
    - renamed ModelGroup to CoordinateFile in driftwood.moldb2

    ### 0.71 ###
    - modified CheapSet to apply secondary hash function only once. This keeps the
      memory addresses of probe positions actually spaced at the triangular numbers,
      which should improve cache locality substantially in large sets.
    - added data.ReverseComparator, which isn't provided in java.util.Collections
      until Java 1.5.
    - modified ProcessTank to check for process termination less often, because
      the check can generate an expensive-to-process exception.
    - fixed ReclickListSelectionModel bug that caused multiple events to the
      listeners for every mouse click.

    ### 0.70 ###
    - added moldb2.ModelState.fillInForModel() to deal with alt conf weirdnesses.
      Still, this can be weird if other state is modified but old alt is saved.
    - added moldb2.Model.fillInStates(), which solves the weirdnesses by making
      alt conf IDs consistent. It's not perfect, but may be the best I can do!
    - fixed up PdbReader and CifReader to use Model.fillInStates()
    - repaired a few bugs in alt fill-in and recompiled everybody

    ### 0.69 ###
    - defined data.HashFunction
    - extended data.NullNaturalComparator to implement HashFunction
    - changed UberMap to accept a HashFunction
    - changed UberSet to accept a HashFunction
    - added r3.Tuple3HashFunction
    - added data.IdentityHashFunction
    - added data.CheapSet, a quadratic-probed, open-addressed hashtable Set impl.
      It's been tested only very lightly...
    - fixed remove() methods in CheapSet to decrement set size

    ### 0.68 ###
    - added isosurface.LowResolutionVertexSource to make coarser isosurfaces

    ### 0.67 ###
    - edited Jama files to remove Javadoc errors
    - added -source and -target flags to build.xml

    ### 0.66 ###
    - wrote moldb2.CifReader; it works for most atoms but not e.g. waters
    - inserted fakeResNumber hack to invent residue numbers for waters
    - fixed TER cards in PdbWriter
    - added Strings.forceLeft/Right() to make PdbWriter safer
    - added makeDotSphere to r3.Builder
    - changed SpatialBin to use get() rather than iterator() for searching bins
    - added Builder.makeBoundingBox()
    - added Triple.div() and .likeQuot()

    ### 0.65 ###
    - made moldb2.ResidueException a checked exception
    - made moldb2.AtomException a checked exception
    - changed AtomState's serial and altconf to Strings (for mmCIF compatibility)
    - changed Residue chain and insCode to Strings (CIF)
    - converted Residue sequence number to a String (CIF)
    - changed PdbWriter to not depend on order of residues in *chain*, just order
    - fixed Javadoc bug for Residue.getNext/Prev()

    ### 0.64 ###
    - added gui.ReclickListSelectionListener

    ### 0.63 ###
    - added URLEncodedOutputStream to driftwood.util

    ### 0.62 ###
    - fixed PdbWriter so it can't output two atoms with the same name.
    - added [+] and [-] buttons to AngleDial for fine control w/out right drag
    - modified PdbWriter so it hopefully won't create duplicated ATOMs when
      working with refit models containing alternate conformations.

    ### 0.61 ###
    - started CifReader; realized changes to moldb2 are required
    - modified gui.SuffixFileFilter to also implement java.io.FilenameFilter
    - moved NullNaturalComparator to driftwood.data b/c it makes more sense there

    ### 0.60 ###
    - added CrystalVertexSource.getValueForPoint()
    - finished r3.PrincipleAxes so data is available from the API
    - fixed prototype on getValueForPoint()
    - getValueForPoint (evaluateAtPoint) should call evaluateVertex(), not getValue()
    - fixed a typo in the interpolation of evaluateAtPoint()
    - fixed a bug in SuffixFileFilter that was unconditionally accepting filenames without extensions

    ### 0.59 ###
    - StarReader now allows stop_ tokens to end loop_ constructs
    - modified Transform.transform to use setXYZ instead of individual set funcs

    ### 0.58 ###
    - added checkTriangle() and signedArea2() to r3.Builder (for lack of a better place)
      Thanks to Andrew Ban for the code!
    - fixed handling of literal quotes within quoted strings in StarTokenizer
      Thanks to John Westbrook for clarifying this issue!

    ### 0.57 ###
    - fixed a potential bug in data.UberMap from calling putBefore(key, key, value)
    - added UberMap.replace() as a complement to put()
    - moved unused classes in data into old/
    - added data.FinalArrayList to wrap arrays in an unmodifiable way
    - finished writing StarFile class
    - added StarReader

    ### 0.56 ###
    - added Props.minimizeDifferences()
    - added gui.TablePane2, which uses memorize() instead of save()/restore()
      It hasn't been tested much yet and so may still be quite buggy!
    - made TablePane2.startSubtable() automatically do insets(0), hfill/vfill
    - moved PDFs I shouldn't be distributing into doc/nodist
    - added package driftwood.star
    - added star.StarTokenizer

    ### 0.55 ###
    - added r3.SpatialBin for doing rapid spatial lookups of point neighbors
    - added moldb2.AminoAcid.isAminoAcid()
    - added Triple.format(DecimalFormat df, String sep)
    - made Builder.construct4() take Tuple3s instead of Triples
    - commentted out PdbReader.extractSheetDefinitions()
    + added the Jama classes to the driftwood package (~20kb compiled)
      These will now be used by the r3 subpackage
    - for ATOM numbers greater than 99,999 the PDB uses multiple files
    - added LsqPlane to driftwood.r3
    - added data.Order for permutations, sorting, etc.
      However, I don't think I should really do it this way -- the better answer
      is to use standard sort functions on aggregate objects...
    - added r3.PrincipleAxes to compute viewpoints for point clouds
    - migrated to using version.props AND buildnum.props to simplify version control

    ### 0.54 ###
    - parsing mmCIF files is way too involved to be practical for moldb2
      Besides, they have a different data model than the PDB's chain/segment system.
    - moldb2 correctly handles the case-sensitive chain IDs found in 1S5L
    - added moldb2.Residue.getCNIT()
    - changed Model.getResidue() to use the CNIT code
    - removed the restriction that all residues in the Model have unique names
    - added PdbReader.extractSheetDefinitions()
    - added filename and ID code fields to moldb2.ModelGroup

    ### 0.53 ###
    - Model.clone() was not deep enough b/c contents of chainMap and segmentMap
      were not deep copied. This broke the mutate/unmutate/mutate cycle in KiNG.

    ### 0.52 ###
    - rewrite of MeshMarchingCubes as MarchingCubes
    - it can now make *either* wireframe meshes *or* solid triangle strip surfaces.
    - added getMode() to MarchingCubes
    - triangle strip mode of MarchingCubes does sometimes leave holes in the surface;
      this is a known issue arising from the simplified approach to surface topology.
      All the alternatives are prohibitively difficult to implement, however.
    - changed default build target to "dist"

    ### 0.51 ###
    - started on data.UberMap, which will eventually replace the gnutil package
    - UberMap is complete and passes its unit tests.
    - added UberSet, a simple wrapper around UberMap
    - removed package driftwood.gnutil; rewrote dependents to use UberMap/UberSet
    - created a LICENSE file for the Driftwood distribution
    - added ModelState.hasState()
    - added AminoAcid.isBackbone()
    - created AttentiveComboBox, which is like AttentiveTextField
    - added a form of Strings.expandVariables that works with text labels instead of ints

    ### 0.50 ###
    - created AttentiveTextField, which sends ActionEvents when it loses the keyboard focus.
    - ModelState can now eliminate AtomStates that are not relevant to a particular model
    - removed unmodRes from moldb2.Model because it gave a stale cache after mutations (actually not)
    - extended Model.restoreOrder() to also sort chains and segments
    - created a special header section USER_MOD for ModelGroup, to place headers at the top of the file
    - added FatJList to gui; stolen/refactored from king.EDMapPlugin

    ### 0.49 ###
    - moldb2.Model can now be cloned(). This makes non-destructive mutations much easier.
    - added contains(Residue) to Model.

    ### 0.48 ###
    - THIS RELEASE FEATURES MAJOR CHANGES TO THE INTRA-HIERARCHY CONTRACTS OF MOLDB2!!!
    - Residues no longer track a parent model; they can belong to any number of models.
    - Thus, getNext() and getPrev() now have to be fed a model, as do some methods in AminoAcid.
      This actually triggered some substantial changes in other packages.
      It's a really unfortunate cost to pay, but I think it's probably worth it in the end.
    - Model now has to query all residues for a mod count.
    - Atoms now have UIDs that are used to test equality. Thus, they can be cloned.
    - Residues can also be "cloned", but the clone is not equal to the original.
    - A serious issue: AtomStates know which atoms they belong to!
      This causes havoc when you clone Atoms but use the same States.
      Thus, Atoms return to strict equality only, and AtomStates have to be cloned.
    - AtomState.cloneFor() and Residue.cloneStates() should make this fairly painless.

    - Eliminated these goals because missing atoms would be filled in in a fairly arbitrary way:
        Stack alt confs: C on B on A on ' ' etc. ???
    - Eliminated these goals because PDBCNS is too complex to emulate without a lot of effort:
        Auto repair CD1 ILE issue as files are read in
        Auto repair HD12 type atoms? (but what to do about HG = mercury?)
            set fixCnsNames flag on encountering _HA# (Gly), _HB#, HN, etc.
            Note: _HB# becomes #HB_, but HB12 becomes 2HB1
        Auto rename HN, OT1, OT2; HN# or HT# to #H

    ### 0.47 ###
    - added compareVersions to util.Strings
    - in moldb2.PdbReader.readAtom(), added protection against PDB files that
      duplicate an entry (i.e., same atom AND same state re-defined).

    ### 0.46 ###
    - copied html pages on ED map formats from web to make sure they're safe

    ### 0.45 ###
    - added bindWindow() to gui.ReflectiveAction
    - also set up a default CommandKey based on the given name
    - removed the original moldb package!

    ### 0.44 ###
    - did some work to moldb2.PdbReader to make it handle segIDs a little better.
    - added moldb2.ModelGroup.replace()

    ### 0.43 ###
    - added EPSGraphics, based on org.jibble.epsgraphics but with support for
      Paints and FontMetrics.

    ### 0.42 ###
    - added likeOrthogonal() to r3.Triple
    - modified r3.Triple.angle() and dihedral() to distinguish between
      very near to 0 and very near to 180.
    - fixed bug in likeOrthogonal and added test case to Triple
    - added test for colinearity to Triple.likeNormal()
    - replaced normal calc in Builder.construct4() with likeNormal() for robustness
    - undid change: a/b/c colinear leaves dihedral undefined anyway.
    - made gui.SuffixFileFilter more robust to null parameters

    ### 0.41 ###
    - for xmaps and omaps in isosurface: if mean, sigma are ~= (0,1) then
      assume they are exactly 0 and 1 respectively. For Brix maps, use the
      sigma from the file in this case.

    ### 0.40 ###
    - added util.Strings.expandVariables()
    - added "Select All" to TextCCPMenu
    - implemented LogViewer.onSave()
    - added gui.ExpSlider.setLabels()

    ### 0.39 ###
    - created new classes in util to support SoftLog (wanted by KiNG).
    - added SoftLog.clear()
    - added gui.LogViewer to visualize SoftLogs quickly
    - added util.OdHash: a hashtable mapping Objects to doubles
    - modified r3.Triple.hashCode() to reduce collisions

    ### 0.38 ###
    - added get(i,j) and set(i,j,val) to r3.Transform
    - added mag2() to r3.Triple
    - created r3.SuperPoser to do general least-squares fitting of point clouds in 3-D.
    - merged driftwood.string into driftwood.util

    ### 0.37 ###
    - added string.Strings.jarUrlToFile()

    ### 0.36 ###
    - modified moldb2 to handle seg IDs more robustly (and are now never null)
    - PdbReader now does better if lines are of different lengths,
      even if seg ID field is cut short to less than 4 characters.
      This was necessary because of a bug in Reduce (v2.15?).

    ### 0.35 ###
    - imported MagnifiedTheme from king

    ### 0.34 ###
    - marked gui.SuffixFileFilter as implementing java.io.FileFilter
    - removed r3.Triple.like(double, double, double) b/c it was a duplicate
    - improved some JavaDocs

    ### 0.33 ###
    - added support for CCP4 maps in isosurface

    ### 0.32 ###
    - made most methods in Triple take a Tuple3 instead of a Triple
      This may slow things down; I don't know.
    - added r3.Builder.dock3on3() to support Mage-like docking and
      sidechain mutations.
    - fixed Model to use 'A' as the default conformation when present
    - ModelStates can now be collapsed() to single (new) objects.
    - added toString() and isNaN() to r3.Transform
    - added isNaN() to r3.Triple
    - fixed an irritation where (0,0,0).unit() --> (NaN,NaN,NaN)
    - finally got r3.Builder.dock3on3() to work correctly!!

    ### 0.31 ###
    - added '_debug' property to gui.TablePane
    - fixed an oversight in r3.Triple where angle() and dihedral() could,
      under certain conditions, return NaN for an angle very close to 0.
    - added gui.TextCutCopyPasteMenu
    - made AngleDial pay attention to setEnabled()

    ### 0.30 ###
    - create gui.ExpSlider to support future development of a tau minimizer in KiNG
    - allowed PdbWriter to recreate a whole PDB file from a ModelGroup
    - wrote PdbMunger to test how badly my reader and writer mess things up.
      It looks like they do pretty good, actually, except for things that have
      anisotropic B's, etc. which I'm not prepared to handle.
    - fixed a logical bug in gui.TablePane.skip() which affected subtables only.

    ### 0.29 ###
    - continued work on moldb2
    - added modification counting to the model
    - wrote first draft of PdbReader
    - added util.NullNaturalComparator to overcome a deficiency in TreeMap:
      null is not an allowed key when using the natural order of objects.
    - PdbReader seems to perform perfectly well for several of my usual test PDBs.
    - created ModelTreeViewer as a toy for exploring PDB naming structure
    - renamed Model.xxxConformation() to xxxState() in accord w/ original plan
    - added PdbWriter with writeAtoms() and writeResidues()
    - moldb (original) refit to use GnuLinkedHashMap/Set for 1.3.1 compat.
    - removed mouse wheel functionality from gui.AngleDial for 1.3.1 compat.
    - removed resolution info from moldb.PDBFile b/c it used java.util.regexp from 1.4.x
    - made AlignBox the parent of SwapBox

    ### 0.28 ###
    - started package gnutil to import java.util.LinkedHashMap/Set and ancestors
      from GNU Classpath and extend them to support some additional operations.
    - finished adding getBefore/After and moveBefore/After to GnuLinkedHashMap/Set.
    - started work on moldb2 which uses gnutil. This separates stateful and stateless
      model information (i.e. naming hierarchy vs. coordinates)

    ### 0.27 ###
    - moved AngleDial from king to driftwood.gui
    - enlarged AngleDial, added optional read-out of original value
    - improved AngleDial graphics and coloring
    - gave AngleDial *much* friendlier mouse interface
    - modified MenuList to not require an ordered Set (use Collection instead)

    ### 0.26 ###
    - fixed a major pair of bugs in TablePane:
    - wasn't marking cells as occupied
    - gridx and gridy were saved and restored

    ### 0.25 ###
    - added gui.MenuList
    - added accept(String) to gui.SuffixFileFilter. Useful for checking e.g. URLs.
    - squashed an inheritance bug in gui.FoldingBox where an overridden function
      assumed that its constructor had initialized a variable when it was called
      by the *superclass* constructor. Manifest in recessiveon kinemage groups.

    ### 0.24 ###
    - created OrderedSet (really just to prove it can be done)
    - created StreamTank and ProcessTank, for decreasing latency in I/O thru
      stdout and stderr with external processes!
    - added process killer timeout to ProcessTank

    ### 0.23 ###
    - added IndentBox and FoldingBox to gui
    - added moldb.Residue.getAtomMap()
    - added moldb.AminoAcid.atomMap; modified constructor and clone()
    - added gui.SwapBox as the parent to IndentBox

    ### 0.22 ###
    - added explode() to string.Strings
    - added bind() to gui.ReflectiveAction for binding accelerator to components

    ### 0.21 ###
    - duplicated moldb.AABackbone as AminoAcid; added a field for the side chain
    - added tokenizeCommandLine() to string.Strings

    ### 0.20 ###
    - added gui.ReflectiveAction

    ### 0.19 ###
    - updated gui.TablePane so more methods returned this
    - added addCell() methods since we can't change add()'s return type

    ### 0.18 ###
    - introduced r3.MutableTuple3 and updated Triple, Rotation to use it
    - made Transform a full 4x4 matrix to allow calculation of perspective too
    - moldb.AABackbone relaxed distance constraint for getPhi(), getPsi()

    ### 0.17 ###
    - added likeMatrix and likeScale to r3.Transform

    ### 0.16 ###
    - rewrote Triple.dihedral and Triple.angle is response to profiling data,
      but the desired speed-up was not really observed...
    - changed Residue.getAtom() to take advantage of ArrayList.get()
    - started work on PDBFile.writeAtoms()
    - added a Map of Atoms to Residue
    - fixed bug in PDBFile.read(): B and Q were mixed up

    ### 0.15 ###
    - added string.Strings with some functions from the old util.Str class
    - created r3.Builder to do Construct4-like operations
    - added toString() to Triple
    - *** MAJOR BUG FOUND AND FIXED IN Triple.cross() !!! ***
      This was causing all sorts of geometrical calculations to fail,
      and was the result of a simple typo.
    - fixed a bug in PDBFile that was not supporting multiple models

    ### 0.14 ###
    - started moldb, a molecular database library for dealing with PDB files
    - REQUIRES JAVA 1.4 or later because of LinkedHashMap
    - created library with Atom, Residue, Segment, Model, PDBFile
    - added AABackbone for doing Chiropraxis-like things
    - made use of Seg ID optional for PDBFile
    - fixed logical bug in Transform.likeRotation(Triple, Triple, double)
    - consolidated build-*.xml files to simplify dependencies, etc.

    ### 0.13 ###
    - started R3, which supports vector math with Triple and Transform
    - imported Triple and refined the interface
    - defined Tuple3
    - started building Transform
    - Transform is now mostly complete
    - found and squashed a fencepost bug in CrystalVertexSource.evaluateVertex()
      that caused ArrayIndexOutOfBoundsExceptions in CVS.getValue()
    - added get/setDefaults() to Props

    ### 0.12 ###
    - migrated to building with Apache Ant (ant.apache.org)
    - merged subpackages of driftwood under common control
    - imported Props and added hasProperty() method

    ### 0.11 ###
    - moved from util.isosurface to driftwood.isosurface
    - made most fields in CrystalVertexSource public
    - watch for OutOfMemoryError in OMap b/c no way to recognize DSN6 format

    ### 0.10 ###
    - renamed DSN6VertexSource to OMapVertexSource
    - resolved how cell edges are handled in O maps (padded to 8x8x8)
    - added support for Brix maps from O
    - added another BufferedInputStream to constructor to fix the broken
      mark() method in GZIPInputStream. (submitted to Sun)

    ### 0.09 ###
    - added findVertexForPoint() to VertexLocator
    - modified MMC.march() to deal with NaN from VertexEvaluators
    - implemented findVertexForPoint() for CrystalVertexSource
    - added auto-detect/auto-gunzip to XplorVertexSource for gzipped map files
    - fixed problem reading O maps -- every byte pair is transposed
    - added auto-gzip to DSN6VertexSource

    ### 0.08 ###
    - O maps work but are of low quality with many artifacts
    - Try supports color, map format switches
    - I've tried many variations on the O format, but it's still not right
      I don't know what to do at this point...
    - Using MapMan to convert DSN6 to XPLOR and then rendering gives
      perfect results, which are different from the source O-map.
      I must still not understand the format completely.

    ### 0.07 ###
    - it looks like non-orthogonal unit cell axes are now supported properly.
      YAY!!!
    - reorganized code somewhat to combine (hopefully) common functions in
      CrystalVertexSource
    - began work on DSN6VertexSource to support O maps

    ### 0.06 ###
    - support for non-orthogonal axes (in progress)

    ### 0.05 ###
    - fixed bugs in XplorVertexSource.nextValue()
    - added bin wrapping to XplorVertexSource.{evaluate,locate}Vertex()
    - realized locateVertex() shouldn't do wrapping after all. (D'oh!)

    ### 0.04 ###
    - wrote KinfileEdgePlotter
    - started XplorVertexSource
    - fixed fencepost bug in MMC.march()
    - fixed cube cases #23 and #108; regenerated kin files

    ### 0.03 ###
    - wrote MeshMarchingCubes
    - added SuffixFileFilter

    ### 0.02 ###
    - TablePane: replaced awkward earlier method of tracking which cells were
      occupied with a sparse array based on HashMap and java.awt.Point.
    - TablePane: removed the replace() method until we can code it properly.

    ### 0.01 ###
    - added TablePane