2023-02-04: build 2732
*) Handle line breaks and tabs in cells when copying and pasting TSV. Cells
containing any of \r \n \t " are enclosed in double quotes, and any double
quotes are doubled. This way of escaping is compatible with Microsoft Office
and LibreOffice, and hopefully with other spreadsheets as well.

2023-01-19: build 2731
*) Fixed Reload for PartialTables created by FK chasing.
*) Prevent PartialTable in query with functions.
*) Added Select All option to QueryResultFrame.
*) Added logic to identify 'identity' columns for MS SQL
*) Fixed conflict between exit confirmation dialog and About box, that would
cause the app to get stuck if the former appeared while the latter happened to
be visible.

2022-12-10: build 2730
*) Added shortcuts: Ctrl-L for Reload and Ctrl-Shift-R for Reconnect.
*) Foreign key following: now offers choice between showing the full referenced
table with the referenced rows highlighted (the already existing behavior) and
showing a partial table with only the referenced rows loaded. The new option
helps when the referenced table is very large.
*) Support for generated columns: editing them is now prevented, and on insert
and update, they are now skipped.
*) When editing cells with rearranged columns, the cell editor would be
initialized with the column type corresponding to the un-rearranged column,
leading to editing and commit failures. Fixed.

2022-11-03: build 2727
*) FK option selector would not populate if the PK column names contained
reserved words. Fixed.
*) No longer closing table and query result windows when Reload fails. Treating
this as a fatal error is no longer appropriate now that the browser frames can
reconnect, making that failure mode non-fatal.

2022-10-26: build 2726
*) Date & time handling fixes for MS SQL.

2022-10-24: build 2724
*) Added support for Microsoft SQL Server (MS SQL). This is very new; please
report any issues you may run into. I'm using this at work so it is at least
somewhat usable.
*) Fixed some bugs in selection handling in table views, related to earlier
changes to support copying rectangular selections of cells.
*) Added Reconnect option, so you can continue working with a database browser
window after the JDBC connection to the server has timed out.

2019-05-06: build 2717
*) When getting table metadata, changed the order in which the column metadata
is fetched to match the order in which the elements are listed in the JDBC
spec. Apparently, fetching them in a different order can make certain JDBC
drivers unhappy -- specifically, Oracle doesn't like fetching COLUMN_DEF after
IS_NULLABLE, at least if COLUMN_DEF is not null.

2019-02-23: build 2716
*) Changed the window cycling shortcut key from 'W' to 'R' to fix collision
with Ctrl-W for Close.
*) Fixed Yes/No, Yes/No/Cancel, and OK/Cancel dialogs, so that closing the
dialogs by typing Esc performs the safe action (No or Cancel) instead of the
unsafe one (Yes or OK).

2017-11-25: build 2714
*) Fixed the menu shortcuts so that on MacOS, they work with the Command key
rather than Ctrl. Also, wherever alternative shortcuts are needed, standardized
on Command-Shift / Ctrl-Shift instead of Alt or Alt-Shift.
*) Implemented spreadsheet-compatible cell copying (tab-separated values) in

2017-04-16: build 2713
*) Updated email address in About box.

2017-04-09: build 2711
*) Added default values to Table Details.

2014-08-24: build 2709
*) Apparently, some obscure JDBC driver doesn't support DriverManager.
getConnection(String url, Properties props). Modified the connection code so
that it only uses that API when connecting to Oracle (that's the only case when
we specifically need it), and use getConnection(url, user, pass) for all

2014-05-17: build 2708
*) Fixed more connection config deletion breakage.

2014-05-17: build 2707
*) Bug workaround for SQLite: DatabaseMetaData.getPrimaryKeys() returns an
element with KEY_SEQ = 0, which should never happen accoring to the JDBC API
documentation; KEY_SEQ is a 1-based index. Simply ignoring this element fixes
the problem.
Note, however, that the SQLite JDBC driver has other bugs in its metadata
handling as well (as of May 17, 2014) -- specifically, it throws exceptions
from DBMD.getIndexInfo() in many cases -- so use JDBC Navigator with SQLite at
your own risk.
*) Deleting a connection configuration was broken: it would be removed from the
menu, but not from the stored configs, so it would reappear the next time the
Open JDBC Connection dialog was presented.

2012-12-02: build 2706
*) Generating escaped HTML strings would choke on character codes greater than
255. This could cause saving File databases and presenting table details
windows to fail. Fixed.
*) When importing CSV files into tables, empty strings would be treated as
nulls. Fixed.
*) Upgraded Rhino to 1.7R4, and added generics wherever the new Rhino and the
latest Java APIs support them (e.g. JList).

2012-11-30: build 2705
*) With Java 1.7, the Preferences, MessageBox, and Binary Editor windows would
throw a ClassCastException in their constructors. Fixed.

2011-11-23: build 2703
*) The JDBCDatabase constructor was protected, which caused opening
unrecognized database types to fail. Changed it to public, fixing the problem.
*) Improved JDBCDatabase.makeTypeSpec(): it now recognizes basic string,
numeric, and date/time types, so that some basic table editing is now possible
even with unsupported databases.

2010-06-14: build 2700
*) In JDBCDatabase.getJavaTypes(), added a "where 1 = 2" clause to the
statement used to find a table's column types. This prevents stupid JDBC
drivers (specifically, MySQL) from actually loading the whole table when all
we want is the metadata.

2009-07-10: build 2697
*) Changed the homepage URL to http://thomasokken.com/jdbcnav/.

2009-07-02: build 2696
*) Sorting on byte[] columns works now.
*) When moving columns, the column formatting would not move. Fixed.

2008-02-16: build 2688
*) Running a query that selects columns from only one table, including all of
that table's PK columns, now returns an updatable query result *even* if the
JDBC driver does not provide catalog/schema/table information in ResultSet.
Formerly, with such drivers you would only get an updatable query result if you
performed a "select *".

2007-12-16: build 2684
*) The initial Database.getRootNode() call, which loads all the table metadata
when a JDBC connection is opened, is now performed in the background, so it
does not freeze the UI.

2007-12-04: build 2681
*) The MySQL driver did not recognize varchar columns, making it impossible to
edit them. Fixed.

2007-08-13: build 2680
*) Saving the .jdbcnav file did not work unless one already existed. When JDBC
Navigator exits, it actually writes a file called .jdbcnavrc-new, and once
that is done, it deletes the old .jdbcnavrc file, and renames .jdbcnavrc-new to
.jdbcnavrc. All well and good so far, except that it considered it an error if
it couldn't delete the old .jdbcnavrc. Note to self: getting an error while
trying to delete a file is not *really* an error if that file did not exist in
the first place!

2007-04-07: build 2679
*) Now showing a "Please Wait" dialog while the tree in the BrowserFrame is
initially populated upon opening a JDBC connection. It's a totally lame dialog
so far; it does not yet have a progress indicator nor a Cancel button, but at
least now there is *some* feedback -- you can now tell the difference between
waiting for the Connection to be established, and waiting for the table
information to load.

2007-04-06: build 2672
*) Now showing an hourglass cursor while opening a JDBC connection, opening a
table editing window, opening a table details window, and while running a

2007-03-14: build 2657
*) Now recognizes MySQL unsigned types, so they can be edited. They are still
not handled correctly when generating scripts, though.

2007-03-04: build 2654
*) The previous release still didn't fix all of the problems with loading large
tables; it turned out that there were still a couple of places where background
threads were making Swing calls. I fixed those, and so far, it looks like it's
working properly now. (Loading tables that won't fit in memory is still a
problem, but that's a separate issue, and has much different symptoms. This
type of problem can be identified by running jdbcnav from the command line and
looking for OutOfMemoryError messages.)

2007-03-03: build 2651
*) Added a new option for Import CSV, called "Replace conflicting rows". What
it does is completely replace rows whose PK matches that of imported rows, as
opposed to the "Overwrite conflicting rows" option, which only replaces those
columns that are actually present in the CSV.
*) When importing a CSV file into a table that has no primary key, or when
importing a CSV file that does not contain the full set of columns matching the
table's primary key, none of the imported rows are considered to conflict with
(i.e., match) any of the table's rows, so they are all imported and all of the
table's existing rows are retained. Previously, jdbcnav would refuse to import
CSV files that did not specify the full PK, and it would attempt to match rows
even in tables with no PK. I think this new behavior is a bit more useful, and
hopefully also a bit more intuitive.
*) Pasting rows from the Clipboard now uses the same logic as Import CSV in
"Overwrite/Replace conflicting rows" mode.
*) Writing the .jdbcnavrc file is now done in two stages: first, write a new
file called .jdbcnavrc-new, and only when that is successful, delete the old
.jdbcnavrc and rename .jdbcnavrc-new to .jdbcnavrc. This prevents the
.jdbcnavrc file from becoming corrupted if jdbcnav crashes on exit. (Not that
that ever happens, of course. ;-) )
*) Sometimes, modifying a table using JavaScript would cause the application to
freeze. Also, loading large tables sometimes failed in weird ways. I believe
both of these types of problems were caused by ResultSetTableModel firing table
events from background threads -- some of the event handlers that get invoked
perform Swing calls that should only be made from the AWT event thread.
I modified ResultSetTableModel so that it will now always check if it is on the
AWT event thread before firing events; if it is not, it queues them up using
SwingUtilities.invokeLater() instead.

2007-03-02: build 2638
*) In the Preferences dialog, clicking OK just after editing the Class Path
would cause the most recent edits to be lost (if one of the Class Path items
still had a blinking cursor in it). Fixed.

2007-02-27: build 2637
*) SELECT * queries from more than one table would fail, because jdbcnav would
treat them like SELECT * from one table, and then choke when trying to match
the columns from the ResultSet to the smaller number of columns from the first
table from the FROM clause. The end result was that no result window would
appear, and the Exception would just kill the background table loading thread
and never be reported to the user. Fixed now.

2007-02-26: build 2636
*) Copy Cell could copy the wrong cell if the columns in the table view had
been rearranged.

2007-02-10: build 2635
*) Updatable query results (that is, windows resulting from a "select * from
foo" type query) now support the same foreign-key chasing as regular table

2006-12-25: build 2628
*) With Oracle 10g (and possibly earlier versions), DBMD.getTables() can
return a lot of useless junk along with the actual table/view/synonym info.
This is confusing, annoying, and it slows things down.
I changed the jdbcnav's Oracle driver so that it now queries SYS.ALL_TABLES,

2006-11-11: build 2626
*) In some situations, JDBC Navigator would get an "ORA-01866: the datetime
class is invalid" while running the initial DBMD.getTables() right after
opening a connection to an Oracle database. The problem appears to be caused by
an earlier (unreported!) error while trying to set the session time zone
("ORA-01882: timezone region  not found").
The Exception thrown by setSessionTimeZone() is now reported to the user, and
jdbcnav now recognizes a System property named jdbcnav.tz which can be used to
override the default time zone ID. The default, which is used if jdbcnav.tz is
not set, is whatever is returned by java.util.TimeZone.getDefault().getID().

2006-09-19: build 2625
*) Removed a couple of JDK 1.5 dependencies that I had inadvertently introduced
in earlier builds. The offending code used the Boolean.parseBoolean(String)
method and the IllegalArgumentException(Throwable) constructor.

2006-08-26: build 2619
*) Added support for Oracle SYNONYMs.
*) When pasting illegal values into a column, the table editing window now
catches the exceptions thrown by the object-to-string conversion, and displays
the word "illegal" instead. Before, it used to just let the exceptions fall
through to the AWT repaint thread, killing it and messing up the UI! Oops.

2006-08-25: build 2610
*) Using MyTextArea/MyTextField instead of JTextArea/JTextField again; these
take care of automatically refreshing the Clipboard window when a cut or copy
operation is performed. I had removed this functionality in the previous
release, as part of the changes for eliminating the JDBC Navigator private
clipboard, but I later realized that the clipboard window auto-refresh can
still be supported. It's still not perfect, but that is impossible as long as
the java Clipboard class does not support content change notifications. So,
meanwhile, whenever the clipboard is changed by a plain JTextArea/JTextField,
or anything else outside of jdbcnav, the clipboard window will not refresh

2006-08-24: build 2609
*) Copying rows in QueryResultFrame has been broken since build 2239; it didn't
handle the TypeSpec column class introduced in that build. As a result, the
copy operation itself would still succeed, but displaying the Clipboard would
not. Fixed now.
*) All copy and paste operations now use the system clipboard. The JDBC
Navigator clipboard is gone completely, as is the messy and broken code to
synchronize it with the system clipboard, that I introduced in build 2531. Copy
and paste of table rows should work properly again.
*) Copying n table rows would always copy the top n rows from the table,
instead of the n rows that were actually selected. Oops! Fixed now.

2006-07-22: build 2600
*) When using the 'Select FK Value' command on a column that allows nulls, the
ForeignKeySelector window did not show the last of the available PK values.
This was caused by an error in the code that adds 'null' to the list of options
for this case. Fixed.

2006-07-16: build 2596
*) The DateTime and Interval classes now implement the Comparable interface, so
that DATE, TIMESTAMP, and INTERVAL columns can now be used for sorting.

2006-05-09: build 2593
*) In addition to oracle.jdbc.driver.OracleDriver, JDBC Navigator now also
recognizes oracle.jdbc.OracleDriver as being an Oracle driver class.
Previously, if you opened a connection using the oracle.jdbc.OracleDriver class
name, the generic internal driver would get used, and that leads to problems.
*) MyTable.getDefaultRenderer() and MyTable.getDefaultEditor() did not handle
interfaces properly. Fortunately, Table Editing windows rely exclusively on the
TypeSpec class and its associated DatabaseObjectRenderer and DatabaseObject-
Editor, so they were unaffected, but it did cause the wrong renderer to be
picked for the "#", "Size", and "Scale" columns in Table Details windows.

2006-02-05: build 2590
*) Fixed a serious bug that broke table cell editing (!).
*) Added support for SmallSQL 0.10. Note, however, that creating a new database
from JDBC Navigator does not work at the moment, since JDBC Navigator calls
DBMD.getTables() on all newly openend Connections, and the SmallSQL 0.10 driver
throws a NullPointerException when this call is performed on an uninitialized
database. If the DB is initialized outside jdbcnav, you *can* work with it.
This problem is fixed in SmallSQL 0.11.

2006-02-02: build 2586
*) Oracle BFILEs can now be viewed using the Edit Cell command.
*) The PostgreSQL driver now looks at the 'scale' reported by DBMD and RSMD for
time, timetz, timestamp, timestamptz, and interval types. Note that you need a
recent PostgreSQL driver with the relevant DBMD and RSMD patches, or you'll
still get bogus precision/scale values for those types.
*) When generating Drop/Create scripts from a Derby DB, with a set of related
tables, a script containing ALTER TABLE commands would be generated, instead
of the DROP and CREATE statements being ordered properly so as to avoid ALTER
TABLE statements as much as possible. Fixed.

2006-02-01: build 2576
*) Some code cleanup to get rid of warnings in Eclipse.

2006-01-17: build 2567
*) Derby driver: fixed TypeSpec generation for NUMERIC columns.

2006-01-16: build 2565
*) Added Derby support.

2006-01-16: build 2561
*) Pushed LOB loading into JDBCDatabase class; added support for DBs where Blob
and Clob objects become invalid when the ResultSet that produced them is
closed. Currently, this support is only activated in the Derby driver. (Note:
this does not mean that Derby is fully supported yet; that will happen in
another release in the near future.)

2005-12-21: build 2549
*) More logging: now logs all system properties on startup, and jdbcnav's
version string. Also, now logs all messages displayed by MessageBox.show().

2005-12-20: build 2545
*) Added option in Preferences to disable the "splash" screen that is usually
displayed when the application starts up.
*) Added logging support. Should usually be disabled because it can slow things
down quite a bit (particularly with Oracle), but can be useful in generating
the kind of information Yours Truly needs when trying to debug problems he
can't reproduce.

2005-12-12: build 2531
*) QueryResultFrame: Cut, Copy, and Paste now use the usual accelerators
(Ctrl-X, Ctrl-C, Ctrl-V) instread of Alt-X etc.; jdbcnav now attempts to keep
its own clipboard and the system's clipboard in sync, and as far as the user is
concerned, there is only one clipboard now.
*) Added code to work around the bug that affects
ResultSetMetaData.getPrecision() in some recent versions of Oracle; this bug
can cause a NumberFormatException to be thrown. This seems to happen when the
precision of a BLOB or CLOB is reported to be 4294967295 (0xFFFFFFFF), which is
too large for a Java "int".
*) Fixed the code that calls setSessionTimeZone() on an OracleConnection; this
now works using the reflection API (again) so that it is once again not
necessary to have the Oracle JDBC driver in the CLASSPATH when compiling the
JDBC Navigator source code.
*) FileDatabase fixes: now loads the "native_representation" TypeSpec property
when opening a File Data Source, so generating a SQL script with matching
source and destination DB types now generates the proper column type -- and not
"null". Also, fixed a bug where for some database types binary columns were
written to the XML file as a hex string, instead of being Base64 encoded, which
would cause an exception when trying to read the file back in.

2005-12-07: build 2450
*) Added DB2 support.

2005-12-06: build 2428
*) Added Transbase support. Not complete yet: inserting/updating BITS and
TIMESPAN does not work yet. I probably need to contact the Transbase developers
since I can't find any documentation on the TBBits and TBTimespan classes;
also, there seem to be some nasty bugs in Transbase itself (e.g. setting a
TIMESPAN to null using a plain "set ts=null where..." causes the entire app to
*) MySQL: BINARY and VARBINARY now recognized correctly.
*) Released the source code under the GNU General Public License.

2005-12-05: build 2395
*) Added MySQL support.
*) Improved the code for handling INTERVAL types in SQL script generation. If
the target DB does not support INTERVAL, it now generates DECIMAL columns and
populates them using the interval's number of months or nanoseconds. For
PostgreSQL's INTERVAL, which has incompatible "YEAR TO SECOND" type semantics,
the months value is multiplied by 2,629,746,000,000,000 and added to the
nanoseconds value; the resulting INTERVAL_DS-compatible value is then used
whenever the target DB is not PostgreSQL.
*) In tables with a primary key, the wrong columns were highlighted if the
primary key's columns were not the first columns in the table. Fixed.
*) SQL script generation would quote number literals. Fixed.

2005-12-02: build 2357
ZONE support completed.
*) Completed DATE, TIME, and TIMESTAMP support in CSV import/export, snapshots,
and SQL script generation. PostgreSQL and SmallSQL time support added.
*) Oracle and PostgreSQL INTERVAL support.
*) Fixed the limitLineLength() function in the SQL script generator; it will
now not only wrap long string literals properly, but also wrap SQL code, by
inserting line feeds when needed but never inside a word, double-quoted word,
or number literal.
*) Fixed behavior of "select * from <table>" in SQL Window.

2005-11-13: build 2254
*) SQL script generation: the code that checks if a column is part of a key or
index is back, and this means that when the Script Generator generates CREATE
TABLE statements for Oracle, it will not use BLOB or CLOB types for such
columns, but fall back on RAW(4000) or VARCHAR2(4000) instead.
*) SQL script generation: Oracle DATE and TIMESTAMP values now handled. Note
that TIMESTAMP WITH LOCAL TIME ZONE probably does not work correctly; it is
misbehaving in the table editor too (the timestampValue() method and the
Timestamp-consuming constructor appear not to be each other's inverses; I must
be missing something there).
*) SQL script generation: now handles Oracle LOB, LONG, and RAW types -- but
note that due to string length restrictions in SQL*Plus, you may still not be
able to use jdbcnav-generated SQL scripts for backing up tables containing
large objects. A big rotten tomato for Oracle for having a script processor
with such silly restrictions!
*) SQL script generation: now handles Oracle BFILE.
*) SQL script generation: the "Same As Source" driver was removed; its special
behavior, which is to use the source database's native data type representation
in CREATE TABLE statements, is now the standard behavior whenever the source
and destination databases have the same internal driver name. It made no sense
to re-encode the type representations in this situation anyway, and the Same
As Source driver also had the problem that it had no specialized knowledge of
how to render data *values*, which made it pretty much useless for generating
Populate and Update scripts.
*) Table editing windows now prevent the user from editing cells containing
Blob, Clob, and byte[] objects inside the JTable; they must use the Edit Cell
command to bring up a separate Binary/Text editor frame. This is to avoid the
confusion over what happens when you start editing a string like
"Clob (size = 10000)" or the hex representation of a byte array.

2005-11-12: build 2239
*) Fixed a few bugs having to do with moving columns in table editing windows.
*) Clob and Blob handling changed: table editing windows now load them lazily,
and they are written back to the database when the table is committed, and no
longer immediately.
*) Snapshots and CSV files now convert objects using the same database-
dependent object->string conversions that are used in the table editing window.
Snapshots and CSV files now support LOB and RAW types properly.
*) Oracle BINARY_FLOAT and BINARY_DOUBLE can now be written back to the DB,
rather than merely being displayed in table editing windows.
*) Added 'clipboard' property to the global scope in JavaScript; this can be
used to get and set the JDBC Navigator clipboard.

2005-11-10: build 2203
*) Oracle DATE columns are once again treated as java.sql.Timestamp values; I
added code to enable the Oracle 8i compatibility mode in the Oracle >= 9
*) Column widths are once again set properly; this got broken in build 2187
because the custom column renderers did not get set until after the table was
starting to be populated.

2005-11-09: build 2193
*) The latest Oracle JDBC drivers apparently finally implement
java.sql.Blob.setBytes(long, byte[]), but it does not work. So, I had to
disable the code that uses this method, and keep using the old Oracle
work-around code, which fortunately is still OK.
The whole thing is rather weird, because according to Oracle's own Javadocs,
oracle.sql.BLOB does not implement setBytes(long, byte[]) at all.

2005-11-09: build 2189
*) Fixed a few bugs (related to BLOB and RAW types, and Oracle 8 & 9) that I
introduced with the code changes for the previous two releases. Sorry about
that, folks -- I hope that kind of thing will become rarer in the future as I
clean up the code further.

2005-11-09: build 2187
*) Abstacted conversion of database datatypes to/from user-editable strings;
this is now done through methods in the Database object. Note, however, that at
the moment this new conversion code is only used in table editing windows and
query result windows; it is not yet used for creating snapshots nor for
generating scripts. This will be added in the near future.

2005-11-08: build 2172
*) Fixed handling of Oracle NCHAR and NVARCHAR2 types.
*) Added support for Oracle BINARY_FLOAT, BINARY_DOUBLE, TIMESTAMP, and
*) Improved type conversion algorithm for SQL script generation, so that
generating a script for a different RDBMS product than the originating database
now generates CREATE TABLE statements with more accurate column types. The new
code is also easier to maintain, so that adding support for additional products
is now much easier than before.

2005-10-25: build 2134
*) Added Transbase driver; this is needed because the Transbase JDBC driver
does not support ResultSetMetaData.getCatalogName() etc. (no surprises there,
I guess -- I have not seen *any* JDBC drivers yet that support this). The
new driver fixes the situation that simple queries in the SQL window do not

2005-09-08: build 2130
*) Fixed handling of NULL values in CLOB, BLOB, LONG, and LONG RAW columns.
*) Fixed NullPointerException that occurred in CLOB columns when the Java type
returned by ResultSetMetaData.getColumnClassName() was an interface rather than
a class (affects DB2).

2005-06-13: build 2108
*) Added custom layout manager to the Windows menu, so it stays usable even
when very many windows are open.
*) Added code to window placement code to ensure that new windows are placed
entirely on-screen, if possible. This means that opening a large table returns
a window that you don't have to manually reposition before you can use it.

2005-06-06: build 2103
*) In the column-jumping menu, columns with "not null" constraints are now
highlighted by showing their names in boldface.
*) Executing "Reload Tree", after a table had been added to a node that had not
been expanded previously (so that the 'kids' array in the MyTreeNode object was
still null), would throw an IndexOutOfBoundsException, and subsequent attempts
to view that node would all fail. Fixed.
*) Fixed a couple of orphanage-related bugs: "Reload Tree" would throw a
ConcurrentModificationException when the orphanage was first created; also,
makeOrphan() generated names that findTableNode() couldn't resolve in some
cases, having to do with quoting.

2005-06-04: build 2075
*) MyFrame.dispose(): added work-around for JInternalFrame bug, where the
behavior of doDefaultCloseAction() (the method invoked when the user clicks
the close box) does extra work that dispose() does not; the upshot is that
without this work-around, the DesktopManager does not highlight the next lower
window when the topmost window is disposed. This is bad, especially if the
Windows L&F is used, because it relies on window activation to handle
maximized internal frames properly.

2005-05-30: build 2066
*) TableFrame now highlights PK and FK columns using special background colors.
*) TableFrame now supports populating FK columns by selecting available values
from the referenced table's corresponding PK columns.
*) Added window cycling (Alt-W, Alt-Shift-W).
*) Added website URL to splash screen.
*) ResultSetTableModel and ArrayTableModel now sort String values using case-
insensitive comparisons, instead of case-sensitive.
*) In BrowserFrame, added a multi-table rollback command.

2005-05-22: build 1977
*) Added SmallSQL support (JDBCDatabase and ScriptGenerator subclasses).

*) Changed the REMARKS getting code so that it first tries REMARKS, then
TABLE_REMARKS. Simply picking TABLE_REMARKS whenever we're talking to Oracle
does not work any more; apparently, Oracle have fixed this JDBC incompatibility
in 9.x.

*) Added showAbout() method to Main.AboutGlassPane. Showing the "about" box on
setVisible(true) turned out not to be such a great idea, since some part of
Swing (JDesktop, I suppose) shows the glass pane when you resize an internal
frame (but not when you drag one -- go figure). So, I had to add a state
variable to AboutGlassPane to decide whether to paint the "about" box or not;
setVisible(true) does not set this flag, and showAbout() does.

*) Added classpath configuration area to preferences dialog. It only shows up
if the jdbcnavboot.Boot class exists. In addition to directories and jar/zip
files, you can use the word "CLASSPATH" in the dialog to indicate that the
value of the CLASSPATH environment variable should be used at that point in the
search order (but this only works with JDK 1.5 or later, since it needs
System.getenv() to get that variable, and older JDKs don't actually implement
that method).
*) Changed JButton.requestFocus() call in MessageBox to requestFocusInWindow().
This was necessary because the former caused the top-level window to be raised
when running under Windows 98SE. In fact, the javadoc for requestFocus() warns
about just such potentially undesired behavior.
*) Removed hard-coded references to oracle.sql.BFILE class from MyTable (this
had to do with the BfileRenderer). Using reflection now, so MyTable will load
properly even if the Oracle driver is not in the classpath.
*) Fixed handling of missing <classpath> element; this is detected in the
Preferences() constructor now, after the call to read(), so that the fall-back
version of the classpath gets set even if there is no .jdbcnavrc file at all.
*) Changed the "system properties" area in PreferencesFrame from JTextArea to
NonTabJTextArea so that tabbing behavior is a bit nicer (though you still need
Ctrl-Tab to escape from the JTable).
*) Fixed the classpath searching code in SneakyClassLoader, and fixed the URL
generation so that valid URLs are generated when running under Windows. This
turns out to be pretty simple: use File.getAbsolutePath() to get an absolute
path; translate all file separators to slashes; if the path does not start with
a slash, prepend one (this is needed for Windows paths that start with a drive
letter); for directory URLs, prepend "file:"; for Jar URLs, prepend "jar:file:"
and append "!". Finally, when generating the final URL, take the resource name
and tack it onto what we just generated (note that resource names start with
slashes and have their components separated by slashes;
SneakyClassLoader.findResource() and findResources() enforce the former, and
Class.getResourceAsStream() takes care of the latter).
This works like a charm on both Windows 98SE and Linux. Of course, I do wonder
if this works on MacOS, too...

*) I found a reasonably elegant way to create a runnable jar file after all
(see yesterday's item 3). It uses a two-stage startup process, stage 1 being a
new class named jdbcnavboot.Boot; this class creates a class loader that looks
for classes and resources in two places: in the default class loader, with
"/foo" prepended to the class or resource name, and in a list of jar files and
directories, which can be configured using the method
The build process moves the entire jdbcnav package to a directory called "foo",
which has the effect of throwing the default class loader off the scent; the
class loader installed by jdbcnavboot.Boot looks for names starting with
"jdbcnav", prepends "foo", and looks them up in the default class loader.
Voila, all the application code is now loaded through my custom class loader,
and I'm still only using one jar file. Hee hee hee. And of course when it comes
to loading additional stuff, say, a JDBC driver, I can make my own custom class
loader look wherever I want. Mission accomplished. (Except for the part of
adding a classpath configuration panel to PreferencesFrame; that's next on the
list. Meanwhile you can add stuff like


to the .jdbcnavrc file.)
*) Some code changes to support builds both with and without the Boot class.
Also added code to allow the use of $CLASSPATH by SneakyClassLoader if running
under JDK 1.5 or later -- with this functionality, a user who already had his
JDBC drivers listed on the CLASSPATH won't have to perform *any* configuration
when using the stand-alone version.

*) Added a splash screen. This is also displayed when the "About JDBC
Navigator" command is selected, unless the splash.gif image is missing, in
which case the old plain message box is used.
*) Cleaned up the build.xml file a little bit.
*) A wasted afternoon: tried to add a custom class loader, so I could load JDBC
drivers without them being in the classpath, so I would be able to wrap the
application in a self-contained double-clickable jar file (the contortions
would be needed because when you run a jar file with "java -jar", CLASSPATH is
ignored). Alas, 'twas all in vain; java.sql.DriverManager refuses to use
drivers that were loaded by a different class loader than the application
itself. Loading the app itself through a custom class loader would require a
two-stage startup process that can't be done with a single jar file. Ergo,
double-clickable jar files can't use JDBC drivers, so JDBC Navigator can't be
a double-clickable jar file. :-(

*) Added option to encrypt JDBC connection configuration settings when saving
them to the .jdbcnavrc file. It is possible to run JDBC Navigator if you don't
know the password for the encrypted .jdbcnavrc stuff; the non-encrypted parts
of that file are processed normally, and the encrypted stuff is kept in memory
in encrypted state, until such time as you enter the right password. It is
allowed to enter a different password at that time; any new configs will be
written to .jdbcnavrc encrypted with the new password, while the old stuff is
written unchanged (that is, still encrypted with the old password). In this
manner, it is possible to accumulate any number of encrypted chunks in
.jdbcnavrc, all encrypted with different passwords. Nothing is lost; whenever
you enter a password that matches one of the chunks, that chunk is decrypted;
enter all the passwords one by one and everything is available once more.
Neat, huh? :-)
*) Added code to track a brief history of observed 'window drift' (that's when
you call setLocation() on a JFrame, then show it, then call
getLocationOnScreen(), and find that the location that's returned is not the
location you just set). Race conditions involving the window manager make my
drift detection approach a bit unreliable (no way to do it better without
having access to X11 APIs, though), but the occasions where I get it wrong seem
to be few and far between. So, I maintain a list of the 5 most recent drift
values, and go with the majority. The downside is that you may get drift a few
times when you switch window managers or reconfigure your window decorations;
the upside is that you won't get drift on any other occasion.
*) Clicking "connect" in the JDBC login dialog now automatically saves the
connection configuration parameters; if the "name" field is blank, a name of
the form "Unnamed #n" is automagically provided. Saving now always moves a
config to the top of the list (even if you only do "save" without "connect").
*) Strengthened config encryption: switched from using plain DESede (that is,
DESede in the default mode, which is (gasp) ECB) to DESede/CBC; to make things
even more cryptic, I start the encryption with 8 bytes of randomness. Of course
the Achilles heel of JDBC Navigator's security is that I keep all kinds of
stuff in plaintext in memory... Here's hoping that security-conscious admins
manage to keep prying eyes away from /dev/mem and such, and that they and
jdbcnav users know about the the perils of core files. It's hard to do a better
job, although I *could* do it by using a third-party DES implementation (e.g. a
Java port, in source form, of libdes) where I can ensure that keys are *never*
stored in Strings, and that all byte or char arrays used to store keys are
scrubbed after use, and (this is the really unpleasant part) by making the user
re-enter the password whenever we need it, so we never have to keep it around.
Yeah, I know, that doesn't appeal to me either.

*) In QueryResultFrame.nuke(), handle updatable query results specially: don't
tell the user that "No" is a safe option, as it is for plain Tables, but warn
them that "No" means their edits will be lost. (This is still not really
satisfactory; updatable query results should be accessible from JavaScript,
but since I don't feel like dealing with that can of worms right now, I just
want to make sure that at least the user isn't lied to.)
*) The configuration name combobox in JDBCDatabase now displays config names in
the order they were read from the .jdbcnavrc file; when the user tries to open
a connection, that config is moved to the front of the list. The .jdbcnavrc
file no longer has a line with the most recently used config name at the
beginning; it is no longer needed since the first config is guaranteed to be
the most recently used one. (There is no need for special code to deal with old
.jdbcnavrc files; the most-recently-selected line is just another line with no
pipe character, and as such is silently ignored by the config reading code.)
Cleaned up the config list code -- created a ConfigList class that encapsulates
all the ugliness and which implements ComboBoxModel to keep things simple.
*) Changed .jdbcnavrc to a new XML-based format; in addition to JDBC connection
configurations, it is now also used to store the Swing Look-and-Feel name,
the size and location of the top-level window, and an arbitrary set of system
properties (name/value pairs that are fed to System.setProperty() on startup;
I needed this so I could set swing.metalTheme=steel (in case I never get used
to that new "Ocean" theme in Metal)).

*) CHANGELOG file created. What a concept, 3 1/2 years and at least 1790 builds
after I started work on JDBC Navigator. :-)
*) Fixed PartialTable so that reloading it (i.e., calling getData()) causes the
query to be re-run. Also added a distinguishing " (query)" to the title of
QueryResultFrame and TableDetailsFrame when they're displaying an updatable
query result; rigged PartialTable.remarks so it returns the query that was
used to create the instance; added code to TableDetailsFrame to display the
Table.remarks property.

Go back.