[sword-cvs] r46 - in trunk/app/src/org/crosswire/common: . swing util

Apache apache at crosswire.org
Sun Sep 19 05:32:10 MST 2004


Author: 
Date: 2004-09-19 05:32:10 -0700 (Sun, 19 Sep 2004)
New Revision: 46

Added:
   trunk/app/src/org/crosswire/common/swing/RowColumns.java
   trunk/app/src/org/crosswire/common/swing/RowTable.java
   trunk/app/src/org/crosswire/common/swing/RowTableModel.java
   trunk/app/src/org/crosswire/common/swing/SortRenderer.java
   trunk/app/src/org/crosswire/common/util/
   trunk/app/src/org/crosswire/common/util/CWClassLoader.java
   trunk/app/src/org/crosswire/common/util/CallContext.java
   trunk/app/src/org/crosswire/common/util/CallContext.jbx
   trunk/app/src/org/crosswire/common/util/ResourceUtil.java
   trunk/app/src/org/crosswire/common/util/RowProcessor.java
   trunk/app/src/org/crosswire/common/util/TabbedFileReader.java
Removed:
   trunk/app/src/org/crosswire/common/CWClassLoader.java
   trunk/app/src/org/crosswire/common/CallContext.java
   trunk/app/src/org/crosswire/common/CallContext.jbx
   trunk/app/src/org/crosswire/common/ResourceUtil.java
Log:
added more utility classes

Deleted: trunk/app/src/org/crosswire/common/CWClassLoader.java
===================================================================
--- trunk/app/src/org/crosswire/common/CWClassLoader.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/CWClassLoader.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -1,377 +0,0 @@
-package org.crosswire.common;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-/**
- * CWClassLoader extends the regular class loader by using looking
- * in more places. This is needed so that ResourceBundle can find
- * resources that are not held in the same package as the class.
- *
- * <p><table border='1' cellPadding='3' cellSpacing='0'>
- * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
- *
- * Distribution Licence:<br />
- * JSword is free software; you can redistribute it
- * and/or modify it under the terms of the GNU General Public License,
- * version 2 as published by the Free Software Foundation.<br />
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.<br />
- * The License is available on the internet
- * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA<br />
- * The copyright to this program is held by it's authors.
- * </font></td></tr></table>
- * @see gnu.gpl.Licence
- * @author DM Smith [dmsmith555 at yahoo dot com]
- * @version $Id: CWClassLoader.java,v 1.4 2004/08/16 22:07:35 joe Exp $
- */
-public class CWClassLoader extends ClassLoader
-{
-    /**
-     * Creates a class loader that finds resources
-     * for the supplied class that may not be in the class' package.
-     * You can use this within base classes by passing getClass()
-     * to load resources for a derived class.
-     * @param resourceOwner is the owner of the resource
-     */
-    public CWClassLoader(Class resourceOwner)
-    {
-        owner = resourceOwner;
-    }
-
-    /**
-     * Creates a class loader that finds resources
-     * for the calling class that may not be in the class' package.
-     * Use this only within classes that are directly looking up their resources.
-     */
-    public CWClassLoader()
-    {
-        owner = CallContext.instance().getCallingClass();
-    }
-
-    /* (non-Javadoc)
-     * @see java.lang.ClassLoader#findResource(java.lang.String)
-     */
-    public URL findResource(String search)
-    {
-        URL resource = null;
-        if (search == null || search.length() == 0)
-        {
-            return resource;
-        }
-
-        // First look for it with an absolute path
-        // This allows developer overrides
-        if (search.charAt(0) != '/')
-        {
-            resource = findResource('/' + search);
-        }
-
-        if (resource == null)
-        {
-            // Look for it in the class's package.
-            String newsearch = adjustPackageSearch(search);
-            if (!search.equals(newsearch))
-            {
-                resource = findResource(newsearch);
-            }
-        }
-
-        // Sometimes it comes in with '/' inside of it.
-        // So look for it as a file with '.' in the name
-        // This is the form that will find files in the resource.jar
-        if (resource == null)
-        {
-            // Look for it in the class's package.
-            String newsearch = adjustPathSearch(search);
-            if (!search.equals(newsearch))
-            {
-                resource = findResource(newsearch);
-            }
-        }
-
-        // See if it can be found in the home directory
-        if (resource == null)
-        {
-            resource = CWClassLoader.findHomeResource(search);
-        }
-
-        // See if it can be found by its own class.
-        if (resource == null)
-        {
-            resource = owner.getResource(search);
-        }
-
-        // Try the appropriate class loader
-        if (resource == null)
-        {
-            resource = getClassLoader().getResource(search);
-        }
-
-        // Try the bootstrap and the system loader
-        if (resource == null)
-        {
-            resource = ClassLoader.getSystemResource(search);
-        }
-
-        // For good measure let the super class try to find it.
-        if (resource == null)
-        {
-            resource = super.findResource(search);
-        }
-
-        return resource;
-    }
-
-    /**
-     * Prefix the search with a package prefix, if not already.
-     * Skip a leading '/' if present.
-     */
-    private String adjustPackageSearch(String search)
-    {
-        // If it has embedded '/' there is nothing to do.
-        if (search.indexOf('/', 1) == -1)
-        {
-            String className = owner.getName();
-            String pkgPrefix = className.substring(0, className.lastIndexOf('.') + 1);
-
-            if (search.charAt(0) == '/')
-            {
-                String part = search.substring(1);
-                if (!part.startsWith(pkgPrefix))
-                {
-                    search = '/' + pkgPrefix + part;
-                }
-            }
-            else
-            {
-                if (!search.startsWith(pkgPrefix))
-                {
-                    search = pkgPrefix + search;
-                }
-            }
-        }
-
-        return search;
-    }
-
-    /**
-     * Change all but a leading '/' to '.'
-     */
-    private String adjustPathSearch(String search)
-    {
-        if (search.indexOf('/', 1) != -1)
-        {
-            // Change all but a leading '/' to '.'
-            if (search.charAt(0) == '/')
-            {
-                search = '/' + search.substring(1).replace('/', '.');
-            }
-            else
-            {
-                search = search.replace('/', '.');
-            }
-        }
-        return search;
-    }
-
-    /**
-     * 
-     */
-    public ClassLoader getClassLoader()
-    {
-        // Choose the child loader as it will use the parent if need be
-        // If they are not related then choose the context loader
-        ClassLoader loader = pickLoader(Thread.currentThread().getContextClassLoader(), owner.getClassLoader());
-        return pickLoader(loader, ClassLoader.getSystemClassLoader());
-    }
-
-    /**
-     * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
-     * 'loader1'=='loader2']. Of course, this works only for classloaders that
-     * set their parent pointers correctly. 'null' is interpreted as the
-     * primordial loader [i.e., everybody's parent].
-     */
-    private static ClassLoader pickLoader(final ClassLoader loader1, final ClassLoader loader2)
-    {
-        ClassLoader loader = loader2;
-        if (loader1 != loader2)
-        {
-            loader = loader1;
-            if (loader1 == null)
-            {
-                loader = loader2;
-            }
-            else
-            {
-                // Is loader2 a descendant of loader1?
-                // It is if we can walk up to the top and find it.
-                for (ClassLoader curloader = loader2; curloader != null; curloader = curloader.getParent())
-                {
-                    if (curloader == loader1)
-                    {
-                        loader = loader2;
-                        break;
-                    }
-                }
-            }
-        }
-        return loader;
-    }
-
-    /**
-     * If the application has set the home, it will return
-     * the application's home directory, otherwise it returns null.
-     * @return Returns the home.
-     */
-    public static synchronized URL getHome()
-    {
-        try
-        {
-            if (home != null)
-            {
-
-                return new URL(home.getProtocol(), home.getHost(), home.getPort(), home.getFile());
-            }
-        }
-        catch (MalformedURLException e)
-        {
-            assert false;
-        }
-        return home;
-    }
-
-    /**
-     * Establish the applications home directory from where
-     * additional resources can be found. URL is expected to
-     * end with the directory name, not '/'.
-     * @param newhome The home to set.
-     */
-    public static synchronized void setHome(URL newhome)
-    {
-        home = newhome;
-    }
-
-    /**
-     * Look for the resource in the home directory
-     * @param search must be non-null, non-empty
-     */
-    public static URL findHomeResource(String search)
-    {
-        URL reply = null;
-        
-        URL override = getHomeResource(search);
-
-        // Look at the application's home first to allow overrides
-        if (override != null)
-        {
-            // Make sure the file exists and can be read
-            File f = new File(override.getFile());
-            if (f.canRead())
-            {
-                reply = override;
-            }
-        }
-
-        return reply;
-    }
-
-    /**
-     * Compute an URL for the resource in the home directory
-     * @param search must be non-null, non-empty
-     */
-    public static URL getHomeResource(String search)
-    {
-        URL reply = null;
-        
-        URL homeURL = getHome();
-
-        // Look at the application's home first to allow overrides
-        if (homeURL != null)
-        {
-            reply = lengthenURL(homeURL, search);
-        }
-
-        return reply;
-    }
-
-    /**
-     * Utility to add a string to the end of a URL.
-     * @param orig The URL to strip
-     * @param extra The text to add to the end of the URL
-     * @return The stripped URL
-     */
-    public static URL lengthenURL(URL orig, String extra)
-    {
-        try
-        {
-            char firstChar = extra.charAt(extra.length() - 1);
-            if (isSeparator(firstChar))
-            {
-                extra = extra.substring(1);
-            }
-
-            if (orig.getProtocol().equals(PROTOCOL_FILE))
-            {
-                String file = orig.toExternalForm();
-                char lastChar = file.charAt(file.length() - 1);
-                if (isSeparator(lastChar))
-                {
-                    return new URL(orig.getProtocol(),
-                                   orig.getHost(),
-                                   orig.getPort(),
-                                   orig.getFile() + extra);
-                }
-                else
-                {
-                    return new URL(orig.getProtocol(),
-                                   orig.getHost(),
-                                   orig.getPort(),
-                                   orig.getFile() + File.separator + extra);
-                }
-            }
-            else
-            {
-                return new URL(orig.getProtocol(),
-                               orig.getHost(),
-                               orig.getPort(),
-                               orig.getFile() + SEPARATOR + extra);
-            }
-        }
-        catch (MalformedURLException ex)
-        {
-            assert false : ex;
-            return null;
-        }
-    }
-
-    private static boolean isSeparator(char c)
-    {
-        return c == '/' || c == '\\';
-    }
-
-    /**
-     * Constant for the file: protocol
-     */
-    public static final String PROTOCOL_FILE = "file"; //$NON-NLS-1$
-
-    /**
-     * URL separator
-     */
-    public static final String SEPARATOR = "/"; //$NON-NLS-1$
-
-    /**
-     * The class to which the resources belong
-     */
-    private Class owner;
-
-    /**
-     * Notion of a project's home from where additional resources can be found.
-     */
-    private static URL home = null;
-}

Deleted: trunk/app/src/org/crosswire/common/CallContext.java
===================================================================
--- trunk/app/src/org/crosswire/common/CallContext.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/CallContext.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -1,91 +0,0 @@
-package org.crosswire.common;
-
-/**
- * This singleton class provides a way for a method to determine
- * which class called it.
- * <p>
- * It has been tested to work in command line and WebStart environments.
- * 
- * <p><table border='1' cellPadding='3' cellSpacing='0'>
- * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
- *
- * Distribution Licence:<br />
- * JSword is free software; you can redistribute it
- * and/or modify it under the terms of the GNU General Public License,
- * version 2 as published by the Free Software Foundation.<br />
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.<br />
- * The License is available on the internet
- * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA<br />
- * The copyright to this program is held by it's authors.
- * </font></td></tr></table>
- * @see gnu.gpl.Licence
- * @author DM Smith [ dmsmith555 at yahoo dot com]
- * @version $Id: CallContext.java,v 1.1 2004/04/20 21:16:06 joe Exp $
- */
-public class CallContext extends SecurityManager
-{
-    /**
-     * Prevent instansiation
-     */
-    private CallContext()
-    {
-    }
-
-    /**
-     * Singleton accessor
-     */
-    public static CallContext instance()
-    {
-        if (resolver == null)
-        {
-            resolver = new CallContext();
-        }
-        
-        return resolver;
-    }
-
-    /*
-     * (non-Javadoc)
-     * @see java.lang.SecurityManager#getClassContext()
-     */
-    protected Class[] getClassContext()
-    {
-        return super.getClassContext();
-    }
-
-    /**
-     * When called from a method it will return the class
-     * calling that method.
-      */
-    public Class getCallingClass()
-    {
-        return getCallingClass(1); // add 1 for this method
-    }
-
-    /**
-     * When called from a method it will return the i-th class
-     * calling that method, up the call chain.
-     * If used with a -1 it will return the class making the call
-     * -2 and -3 will return this class
-     * @throws ArrayIndexOutOfBoundsException if the index is not valid
-     */
-    public Class getCallingClass(int i)
-    {
-        return resolver.getClassContext()[CALL_CONTEXT_OFFSET + i];
-    }
-
-    // may need to change if this class is redesigned
-    /**
-     * Offset needed to represent the caller of the method
-     * that called this method.
-     * 
-     */
-    private static final int CALL_CONTEXT_OFFSET = 3;
-
-    private static CallContext resolver;
-}

Deleted: trunk/app/src/org/crosswire/common/CallContext.jbx
===================================================================
--- trunk/app/src/org/crosswire/common/CallContext.jbx	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/CallContext.jbx	2004-09-19 12:32:10 UTC (rev 46)
@@ -1,8 +0,0 @@
-[PropertyInfo]
-callingClass,Class,false,false, , ,true,<default>
-resolver,CallContext,false,false, , ,false,<default>
-[IconNames]
-
-
-
-

Deleted: trunk/app/src/org/crosswire/common/ResourceUtil.java
===================================================================
--- trunk/app/src/org/crosswire/common/ResourceUtil.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/ResourceUtil.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -1,71 +0,0 @@
-package org.crosswire.common;
-
-import java.net.URL;
-import java.util.MissingResourceException;
-
-/**
- * Better implemenetations of the getResource methods with less ambiguity and
- * that are less dependent on the specific classloader situation.
- *
- * <p><table border='1' cellPadding='3' cellSpacing='0'>
- * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
- *
- * Distribution Licence:<br />
- * JSword is free software; you can redistribute it
- * and/or modify it under the terms of the GNU General Public License,
- * version 2 as published by the Free Software Foundation.<br />
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.<br />
- * The License is available on the internet
- * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA<br />
- * The copyright to this program is held by it's authors.
- * </font></td></tr></table>
- * @see gnu.gpl.Licence
- * @author Joe Walker [joe at eireneh dot com]
- * @author DM Smith [ dmsmith555 at yahoo dot com ]
- * @version $Id: ResourceUtil.java,v 1.5 2004/08/16 22:07:35 joe Exp $
- */
-public class ResourceUtil
-{
-    /**
-     * Prevent Instansiation
-     */
-    private ResourceUtil()
-    {
-    }
-
-    /**
-     * Generic resource URL fetcher. One way or the other we'll find it!
-     * Either as a relative or an absolute reference.
-     * @param search The name of the resource (without a leading /) to find
-     * @return The requested resource
-     * @throws MissingResourceException if the resource can not be found
-     */
-    public static URL getResource(String search) throws MissingResourceException
-    {
-        return getResource(CallContext.instance().getCallingClass(), search);
-    }
-
-    /**
-     * Generic resource URL fetcher. One way or the other we'll find it!
-     * Either as a relative or an absolute reference.
-     * @param clazz The resource to find
-     * @return The requested resource
-     * @throws MissingResourceException if the resource can not be found
-     */
-    public static URL getResource(Class clazz, String resourceName) throws MissingResourceException
-    {
-        URL resource = new CWClassLoader(clazz).findResource(resourceName);
-
-        if (resource == null)
-        {
-            throw new MissingResourceException("Can't find resource", clazz.getName(), resourceName);
-        }
-
-        return resource;
-    }
-}

Added: trunk/app/src/org/crosswire/common/swing/RowColumns.java
===================================================================
--- trunk/app/src/org/crosswire/common/swing/RowColumns.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/swing/RowColumns.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,175 @@
+/*
+ * Distribution Licence:
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * The License is available on the internet at:
+ *     http://www.gnu.org/copyleft/gpl.html,
+ * or by writing to:
+ *     Free Software Foundation, Inc.
+ *     59 Temple Place - Suite 330
+ *     Boston, MA 02111-1307, USA
+ * 
+ * The copyright to this program is held by it's authors
+ * Copyright: 2004
+ */
+package org.crosswire.common.swing;
+
+import javax.swing.table.DefaultTableColumnModel;
+
+/**
+ * Defines the prototypes needed to display a RowTable.
+ * Also defines some column indexed concrete methods to access
+ * the prototypes.
+ * 
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ */
+public abstract class RowColumns extends DefaultTableColumnModel
+{
+
+    /**
+     * Method getHeaders gets the headers for all the columns
+     * @return String[] of table headers.
+     */
+    public abstract String[] getHeaders();
+
+    /**
+     * Method getHeaderToolTips gets the tooltips
+     * for the headers for all the columns
+     * @return String[] of table header's tooltips.
+     */
+    public abstract String[] getHeaderToolTips();
+
+    /**
+     * Method getCharacterWidths gets the widths of all the columns,
+     * expressed in Standard Width Characters.
+     * @return int[] of widths in standard characters
+     */
+    public abstract int[] getCharacterWidths();
+
+    /**
+     * Method getFixedWidths gives whether a column is not
+     * resizable (true) or resizable (false)
+     * @return boolean[] of whether a column is fixed
+     */
+    public abstract boolean[] getFixedWidths();
+
+    /**
+     * Method getClasses indicates the type of the data in a column
+     * @return Class[] of data types of the columns
+     */
+    public abstract Class[] getClasses();
+
+    /**
+     * Method getSortKeys returns the primary (array of size 1) or
+     * composite key (size > 1) used for default sorting and
+     * for secondary sorting.
+     * @return int[] of the order of columns participating in sort.
+     */
+    public abstract int[] getSortKeys();
+
+    /**
+     * Method getValueAt gets the contents of a cell from a row.
+     * @param row the row
+     * @param columnIndex int
+     * @return Object The content of a cell from a row
+     */
+    public abstract Object getValueAt(Object row, int columnIndex);
+
+    /**
+     * Method getTableName provides the string for a Titled Border.
+     * @return String the table name
+     */
+    public abstract String getTableName();
+
+    /**
+     * Method getCount is the number of columns in the table.
+     * @return int the number of columns in the table.
+     */
+    public int getCount()
+    {
+        return getHeaders().length;
+    }
+
+    /**
+     * Method getClass gets the class of a given column
+     * @param columnIndex int
+     * @return Class of the given column
+     */
+    public Class getClass(int columnIndex)
+    {
+        final Class[] classes = getClasses();
+        if (classes != null && columnIndex < classes.length)
+        {
+            return classes[columnIndex];
+        }
+        return null;
+    }
+
+    /**
+     * Method getName gets the header for the given column
+     * @param columnIndex int
+     * @return String the header name of the given column
+     */
+    public String getName(int columnIndex)
+    {
+        final String[] headers = getHeaders();
+        if (headers != null && columnIndex < headers.length)
+        {
+            return headers[columnIndex];
+        }
+        return null;
+    }
+
+    /**
+     * Method getClass gets the class of a given column
+     * @param columnIndex int
+     * @return Class of the given column
+     */
+    public String getHeaderToolTip(int columnIndex)
+    {
+        final String[] tooltips = getHeaderToolTips();
+        if (tooltips != null && columnIndex < tooltips.length)
+        {
+            return tooltips[columnIndex];
+        }
+        return null;
+    }
+
+    /**
+     * Method isFixedWidth indicates whether a column is fixed
+     * @param columnIndex int
+     * @return boolean, true if the column cannot be resized
+     */
+    public boolean isFixedWidth(int columnIndex)
+    {
+        final boolean[] fixedWidths = getFixedWidths();
+        if (fixedWidths != null && columnIndex < fixedWidths.length)
+        {
+            return fixedWidths[columnIndex];
+        }
+        return false;
+    }
+
+    /**
+     * Method getCharacterWidth gets the width of the column,
+     * expressed in Standard Characters
+     * @param columnIndex int
+     * @return int the number of characters wide the column is to be.
+     */
+    public int getCharacterWidth(int columnIndex)
+    {
+        final int[] characterWidths = getCharacterWidths();
+        if (characterWidths != null && columnIndex < characterWidths.length)
+        {
+            return characterWidths[columnIndex];
+        }
+        return 0;
+    }
+
+}
\ No newline at end of file

Added: trunk/app/src/org/crosswire/common/swing/RowTable.java
===================================================================
--- trunk/app/src/org/crosswire/common/swing/RowTable.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/swing/RowTable.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,316 @@
+/*
+ * Distribution Licence:
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * The License is available on the internet at:
+ *     http://www.gnu.org/copyleft/gpl.html,
+ * or by writing to:
+ *     Free Software Foundation, Inc.
+ *     59 Temple Place - Suite 330
+ *     Boston, MA 02111-1307, USA
+ * 
+ * The copyright to this program is held by it's authors
+ * Copyright: 2004
+ */
+package org.crosswire.common.swing;
+import java.awt.Component;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * Presents a table of items to a user in a table.
+ * 
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ */
+public class RowTable extends JTable
+{
+    /**
+     * Field ONE_STANDARD_CHARACTER
+     */
+    private static final String ONE_STANDARD_CHARACTER = "M"; //$NON-NLS-1$
+    /**
+     * Field TWO_STANDARD_CHARACTERS
+     */
+    private static final String TWO_STANDARD_CHARACTERS = "MM"; //$NON-NLS-1$
+    /**
+     * Field PADDING
+     */
+    private static final int PADDING = 3;
+
+    /**
+     * Constructor for RowTable
+     * @param aList
+     * @param columns
+     */
+    public RowTable(List aList, RowColumns columns)
+    {
+        super(new RowTableModel(aList, columns));
+        setSortRenderer();
+
+        // Don't display vertical lines in table
+        //		getColumnModel().setColumnMargin(0);
+
+        setColumnWidths(columns.getCharacterWidths(), columns.getFixedWidths());
+
+        getTableHeader().addMouseListener(new MouseAdapter()
+        {
+            public void mouseClicked(MouseEvent e)
+            {
+                sort(getColumnModel().getColumnIndexAtX(e.getX()));
+            }
+        });
+    }
+
+    /**
+     * Save the selection so it can be restored after sorting.
+     * @param aTable
+     * @return List
+     */
+    private List saveSelection(JTable aTable)
+    {
+        final ListSelectionModel lsm = aTable.getSelectionModel();
+        final RowTableModel tm = (RowTableModel) aTable.getModel();
+        final int first = lsm.getMinSelectionIndex();
+        final int last = lsm.getMaxSelectionIndex();
+        final List objs = new ArrayList();
+        if (first != -1)
+        {
+            for (int i = first; i <= last; i++)
+            {
+                if (lsm.isSelectedIndex(i))
+                {
+                    objs.add(tm.getRow(i));
+                }
+            }
+        }
+        return objs;
+    }
+
+    /**
+     * load the selections
+     * @param aTable JTable
+     * @param objs List
+     */
+    private void loadSelection(JTable aTable, List objs)
+    {
+        final ListSelectionModel lsm = aTable.getSelectionModel();
+        final RowTableModel tm = (RowTableModel) aTable.getModel();
+        // reset the selection
+        Object obj = null;
+        int where = -1;
+        for (int i = 0; i < objs.size(); i++)
+        {
+            obj = objs.get(i);
+            where = tm.getRow(obj);
+            if (where != -1)
+            {
+                lsm.addSelectionInterval(where, where);
+            }
+        }
+        scrollToVisible(aTable);
+    }
+
+    /**
+     * Method scrollToVisible
+     * @param aTable JTable
+     */
+    private void scrollToVisible(JTable aTable)
+    {
+        final ListSelectionModel lsm = aTable.getSelectionModel();
+        final int first = lsm.getMinSelectionIndex();
+        final int last = lsm.getMaxSelectionIndex();
+        if (first != -1)
+        {
+            final Rectangle bounds = getRowBounds(aTable, first, last);
+            if (isVerticallyVisible(aTable, bounds) == false)
+            {
+                // Is SwingUtilities.invokeLater needed ???
+                aTable.scrollRectToVisible(bounds);
+            }
+        }
+    }
+
+    /**
+     * Method selectRow
+     * @param row int
+     */
+    public void selectRow(int row)
+    {
+        final ListSelectionModel lsm = getSelectionModel();
+        lsm.clearSelection();
+        lsm.setSelectionInterval(row, row);
+        scrollToVisible(this);
+    }
+
+    /**
+     * Method getRowBounds
+     * @param table JTable
+     * @param first int
+     * @param last int
+     * @return Rectangle
+     */
+    private Rectangle getRowBounds(JTable table, int first, int last)
+    {
+        Rectangle result = table.getCellRect(first, -1, true);
+        result = result.union(table.getCellRect(last, -1, true));
+        final Insets insets = table.getInsets();
+        result.x = insets.left;
+        result.width = table.getWidth() - insets.left - insets.right;
+        return result;
+    }
+
+    /**
+     * Method isVerticallyVisible
+     * @param aTable JTable
+     * @param r Rectangle
+     * @return boolean
+     */
+    private boolean isVerticallyVisible(JTable aTable, Rectangle r)
+    {
+        final Rectangle visible = aTable.getVisibleRect();
+        return visible.y <= r.y && visible.y + visible.height >= r.y + r.height;
+    }
+
+    /**
+     * Method setColumnWidths
+     * @param widths int[]
+     * @param fixed boolean[]
+     */
+    private void setColumnWidths(int[] widths, boolean[] fixed)
+    {
+        final int mWidth = getStandardCharacterWidth();
+        final TableColumnModel tcm = getColumnModel();
+        // The << 1 accounts for two margins
+        // The + PADDING accounts for an extra pixel on either side
+        // and an extra pixel for between the columns
+        //  that the text needs to not display ...
+        final int margins = (tcm.getColumnMargin() << 1) + PADDING;
+        TableColumn tc = null;
+        int width = -1;
+        for (int i = 0; i < widths.length; i++)
+        {
+            tc = tcm.getColumn(i);
+            width = widths[i] * mWidth + margins;
+            if (fixed[i])
+            {
+                tc.setMinWidth(width);
+                tc.setMaxWidth(width);
+            }
+            else
+            {
+                tc.setPreferredWidth(width);
+            }
+        }
+    }
+
+    /**
+     * Method setSortRenderer
+     */
+    private void setSortRenderer()
+    {
+        final TableCellRenderer sortRenderer = new SortRenderer((RowTableModel) getModel());
+        //		TableCellRenderer rowRenderer = new RowRenderer();
+        final TableColumnModel model = getColumnModel();
+        final int colCount = model.getColumnCount();
+        TableColumn tc = null;
+        for (int i = 0; i < colCount; i++)
+        {
+            tc = model.getColumn(i);
+            tc.setHeaderRenderer(sortRenderer);
+        }
+    }
+
+    /**
+     * Size each column to something reasonable
+     * We do this by getting the width of the letter 'M"
+     * from the default Table Header Renderer
+     * and set the preferred width of the column
+     * as the width of some number of 'M's.
+     * @return int
+     */
+    private int getStandardCharacterWidth()
+    {
+        // The preferredSize of the component is more than just the character
+        // So we remove the extra determining the delta
+        // between one and two chars
+        final JTableHeader th = getTableHeader();
+        final TableCellRenderer renderer = th.getDefaultRenderer();
+        Component comp = renderer.getTableCellRendererComponent(this, ONE_STANDARD_CHARACTER, false, false, 0, 0);
+        final int oneStandardCharacterWidth = comp.getPreferredSize().width;
+        comp = renderer.getTableCellRendererComponent(this, TWO_STANDARD_CHARACTERS, false, false, 0, 0);
+        final int twoStandardCharactersWidth = comp.getPreferredSize().width;
+        return twoStandardCharactersWidth - oneStandardCharacterWidth;
+    }
+
+    /**
+     * Method addListSelectionListener
+     * @param listener ListSelectionListener
+     */
+    public void addListSelectionListener(ListSelectionListener listener)
+    {
+        getSelectionModel().addListSelectionListener(listener);
+    }
+
+    /**
+     * Method getPreferredHeight
+     * @param numRows int
+     * @return int
+     */
+    public int getPreferredHeight(int numRows)
+    {
+        int newHeight = getRowHeight() * numRows;
+        // The following may be needed for Java 1.4
+        // newHeight += table.getIntercellSpacing().height * (numRows + 1);
+        newHeight += getTableHeader().getPreferredSize().height;
+        final Insets insets = getInsets();
+        newHeight += insets.top + insets.bottom;
+        return newHeight;
+    }
+
+    /**
+     * Method sort
+     * @param col int
+     */
+    public void sort(int col)
+    {
+        if (col != -1)
+        {
+            final TableColumnModel tcm = getColumnModel();
+            final TableColumn tc = tcm.getColumn(col);
+            final SortRenderer renderer = (SortRenderer) tc.getHeaderRenderer();
+            renderer.setPressedColumn(tc);
+        }
+        final List objs = saveSelection(this);
+        getSelectionModel().clearSelection();
+        ((RowTableModel) getModel()).sort(convertColumnIndexToModel(col));
+        loadSelection(this, objs);
+    }
+
+    public void reset()
+    {
+        final RowTableModel stm = (RowTableModel) getModel();
+        final ListSelectionModel lsm = getSelectionModel();
+        getSelectionModel().clearSelection();
+        lsm.clearSelection();
+        stm.reset();
+    }
+
+}
\ No newline at end of file

Added: trunk/app/src/org/crosswire/common/swing/RowTableModel.java
===================================================================
--- trunk/app/src/org/crosswire/common/swing/RowTableModel.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/swing/RowTableModel.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,345 @@
+/*
+ * Distribution Licence:
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * The License is available on the internet at:
+ *     http://www.gnu.org/copyleft/gpl.html,
+ * or by writing to:
+ *     Free Software Foundation, Inc.
+ *     59 Temple Place - Suite 330
+ *     Boston, MA 02111-1307, USA
+ * 
+ * The copyright to this program is held by it's authors
+ * Copyright: 2004
+ */
+package org.crosswire.common.swing;
+
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * The RowTableModel defines the "model" behaviour for
+ * a RowTable.
+ * 
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ */
+public class RowTableModel extends AbstractTableModel
+{
+    /**
+     * Field list contains the objects that can be worked upon
+     */
+    private List list;
+
+    /**
+     * Field columnModel provides the definition of the structure
+     * of the table
+     */
+    private RowColumns rowColumnModel;
+
+    /**
+     * Field indexes provides a look-aside for the sorted view of the
+     * table to the row list.
+     */
+    private int[] indexes;
+
+    /**
+     * Field keys provides the primary or composite key of the table.
+     * It is a local optimization of columnModel.getSortKeys().
+     */
+    private int[] keys;
+    /**
+     * Field sortColumn indicates the column that was last sorted upon.
+     * It is initialized the first value in keys, if present otherwise -1
+     */
+    private int sortColumn;
+
+    /**
+     * Builds a RowTable model for the provided (non-null) row list,
+     * using the provided row column definition.
+     * @param aList List
+     * @param aColumnModel RowColumns
+     */
+    public RowTableModel(List aList, RowColumns aRowColumnModel)
+    {
+        super();
+        list = aList;
+        rowColumnModel = aRowColumnModel;
+        keys = rowColumnModel.getSortKeys();
+        sortColumn = keys[0];
+        allocate();
+    }
+
+    /**
+     * Method getRowCount returns the number of rows in the list.
+     * @return int
+     * @see javax.swing.table.TableModel#getRowCount()
+     */
+    public int getRowCount()
+    {
+        return (list == null) ? 0 : list.size();
+    }
+
+    /**
+     * Method getColumnCount returns the number of columns in the table
+     * @return int
+     * @see javax.swing.table.TableModel#getColumnCount()
+     */
+    public int getColumnCount()
+    {
+        return rowColumnModel.getCount();
+    }
+
+    /**
+     * Method getValueAt returns the contents of a cell. 
+     * @param row int
+     * @param column int
+     * @return Object
+     * @see javax.swing.table.TableModel#getValueAt(int, int)
+     */
+    public Object getValueAt(int row, int column)
+    {
+        return getCellValue(indexes[row], column);
+    }
+
+    /**
+     * Method getCellValue Translates from a row index to a row object
+     * and asks it for the appropriate cell value
+     * @param rowIndex int
+     * @param columnIndex int
+     * @return Object
+     */
+    private Object getCellValue(int rowIndex, int columnIndex)
+    {
+        final Object obj = list.get(rowIndex);
+        return rowColumnModel.getValueAt(obj, columnIndex);
+    }
+
+    /**
+     * Method getColumnClass returns the class of the column
+     * @param columnIndex int
+     * @return Class
+     * @see javax.swing.table.TableModel#getColumnClass(int)
+     */
+    public Class getColumnClass(int columnIndex)
+    {
+        return rowColumnModel.getClass(columnIndex);
+    }
+
+    /**
+     * Method getHeaderToolTip returns the tooltip for the header of the column
+     * @param columnIndex int
+     * @return String
+     */
+    public String getHeaderToolTip(int columnIndex)
+    {
+        return rowColumnModel.getHeaderToolTip(columnIndex);
+    }
+
+    /**
+     * Method getColumnName returns the header name for the column
+     * @param columnIndex int
+     * @return String
+     * @see javax.swing.table.TableModel#getColumnName(int)
+     */
+    public String getColumnName(int columnIndex)
+    {
+        return rowColumnModel.getName(columnIndex);
+    }
+
+    /**
+     * Method addRow adds a row to the table.
+     * @param obj the row to add
+     */
+    public void addRow(Object obj)
+    {
+        list.add(obj);
+        allocate();
+        final int visibleRow = getRow(obj);
+        fireTableRowsInserted(visibleRow, visibleRow);
+    }
+
+    /**
+     * Method getRow retrieves a row from the table
+     * @param rowIndex int
+     * @return the row
+     */
+    public Object getRow(int rowIndex)
+    {
+        return list.get(indexes[rowIndex]);
+    }
+
+    /**
+     * Method getRow finds the visible row index for a given row
+     * @param obj the row
+     * @return int
+     */
+    public int getRow(Object obj)
+    {
+        for (int i = 0; i < indexes.length; i++)
+        {
+            if (getRow(i).equals(obj))
+            {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Method removeRow removes a row from the model
+     * and causes the display to update itself appropriately
+     * @param obj the row to remove
+     */
+    public void removeRow(Object obj)
+    {
+        final int dataIndex = list.indexOf(obj);
+        final int visibleIndex = getRow(obj);
+        list.remove(dataIndex);
+        fireTableRowsDeleted(visibleIndex, visibleIndex);
+        allocate();
+    }
+
+    /**
+     * Method updateRow causes the display to update itself appropriately.
+     * Methods on rows are actually used to update the row
+     * @param obj the row
+     */
+    public void updateRow(Object obj)
+    {
+        final int visibleIndex = getRow(obj);
+        fireTableRowsUpdated(visibleIndex, visibleIndex);
+    }
+
+    public void reset()
+    {
+        allocate();
+        fireTableDataChanged();
+    }
+
+    public void clear()
+    {
+        list.clear();
+        allocate();
+        fireTableDataChanged();
+    }
+
+    // Bubble Sort!!! Replace if performance is an issue.
+    /**
+     * Method sort
+     * @param column int
+     */
+    public void sort(int modelIndex)
+    {
+        if (modelIndex != -1)
+        {
+            sortColumn = modelIndex;
+        }
+        final int rowCount = getRowCount();
+        boolean changed = false;
+        for (int i = 0; i < rowCount; i++)
+        {
+            for (int j = i + 1; j < rowCount; j++)
+            {
+                if (compareKeys(indexes[i], indexes[j], sortColumn) < 0)
+                {
+                    swap(i, j);
+                    changed = true;
+                }
+            }
+        }
+        if (changed)
+        {
+            fireTableRowsUpdated(0, getRowCount());
+        }
+    }
+
+    /**
+     * Method swap
+     * @param i int
+     * @param j int
+     */
+    private void swap(int i, int j)
+    {
+        final int tmp = indexes[i];
+        indexes[i] = indexes[j];
+        indexes[j] = tmp;
+    }
+
+    /**
+     * Method compareKeys
+     * @param i int
+     * @param j int
+     * @param column int
+     * @return int
+     */
+    private int compareKeys(int i, int j, int column)
+    {
+        int cmp = compare(i, j, column);
+        if (keys != null)
+        {
+            for (int k = 0; cmp == 0 && k < keys.length; k++)
+            {
+                if (k != column)
+                {
+                    cmp = compare(i, j, keys[k]);
+                }
+            }
+        }
+        return cmp;
+    }
+
+    /**
+     * Method compare
+     * @param i int
+     * @param j int
+     * @param column int
+     * @return int
+     */
+    public int compare(int i, int j, int column)
+    {
+        final Object io = getCellValue(i, column);
+        final Object jo = getCellValue(j, column);
+        int cmp = 0;
+        if (io.getClass().equals(jo.getClass()) && io instanceof Comparable)
+        {
+            cmp = ((Comparable) jo).compareTo(io);
+        }
+        else if (io instanceof Boolean)
+        {
+            cmp = io.toString().compareTo(jo.toString());
+        }
+        else
+        {
+            cmp = jo.toString().compareTo(io.toString());
+        }
+
+        return (cmp < 0) ? -1 : ((cmp > 0) ? 1 : 0);
+    }
+
+    /**
+     * Method allocate
+     */
+    private void allocate()
+    {
+        final int rowCount = getRowCount();
+        if (indexes == null || indexes.length != rowCount)
+        {
+            final int[] newData = new int[rowCount];
+            for (int i = 0; i < rowCount; i++)
+            {
+                newData[i] = i;
+            }
+            indexes = newData;
+            // Do the default or last sort
+            sort(-1);
+        }
+    }
+
+}
\ No newline at end of file

Added: trunk/app/src/org/crosswire/common/swing/SortRenderer.java
===================================================================
--- trunk/app/src/org/crosswire/common/swing/SortRenderer.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/swing/SortRenderer.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,113 @@
+/*
+ * Distribution Licence:
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * The License is available on the internet at:
+ *     http://www.gnu.org/copyleft/gpl.html,
+ * or by writing to:
+ *     Free Software Foundation, Inc.
+ *     59 Temple Place - Suite 330
+ *     Boston, MA 02111-1307, USA
+ * 
+ * The copyright to this program is held by it's authors
+ * Copyright: 2004
+ */
+package org.crosswire.common.swing;
+
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumn;
+
+/**
+ * A SortRenderer indicates the column that is sorted by italizing it.
+ * 
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ */
+public class SortRenderer extends DefaultTableCellRenderer {
+
+	/**
+	 * Field pressedColumn
+	 */
+	private TableColumn pressedColumn;
+	/**
+	 * Field model
+	 */
+	private RowTableModel model;
+
+	/**
+	 * Constructor for SortRenderer
+	 * @param stm SegmentTableModel
+	 */
+	public SortRenderer(RowTableModel stm) {
+		model = stm;
+		pressedColumn = null;
+		setHorizontalAlignment(SwingConstants.CENTER);
+	}
+
+	/**
+	 * Method getTableCellRendererComponent
+	 * @param table JTable
+	 * @param value Object
+	 * @param isSelected boolean
+	 * @param hasFocus boolean
+	 * @param row int
+	 * @param column int
+	 * @return Component
+	 */
+	public Component getTableCellRendererComponent(
+		JTable table,
+		Object value,
+		boolean isSelected,
+		boolean hasFocus,
+		int row,
+		int column) {
+		if (table != null) {
+			setToolTipText(model.getHeaderToolTip(column));
+			final JTableHeader header = table.getTableHeader();
+			final TableColumn tableColumn = table.getColumnModel().getColumn(column);
+			if (header != null) {
+				setForeground(header.getForeground());
+				setBackground(header.getBackground());
+				final Font headerFont = header.getFont();
+				if (tableColumn == pressedColumn) {
+					setFont(headerFont.deriveFont(Font.ITALIC));
+				} else {
+					setFont(headerFont);
+				}
+			}
+		}
+
+		setText((value == null) ? "" : value.toString()); //$NON-NLS-1$
+		setBorder(UIManager.getBorder("TableHeader.cellBorder")); //$NON-NLS-1$
+		return this;
+	}
+
+	/**
+	 * Method getPressedColumn
+	 * @return the table column
+	 */
+	public TableColumn getPressedColumn() {
+		return pressedColumn;
+	}
+
+	/**
+	 * Method setPressedColumn
+	 * @param tc the table column
+	 */
+	public void setPressedColumn(TableColumn tc) {
+		pressedColumn = tc;
+	}
+
+}
\ No newline at end of file

Copied: trunk/app/src/org/crosswire/common/util/CWClassLoader.java (from rev 43, trunk/app/src/org/crosswire/common/CWClassLoader.java)
===================================================================
--- trunk/app/src/org/crosswire/common/CWClassLoader.java	2004-09-17 06:21:12 UTC (rev 43)
+++ trunk/app/src/org/crosswire/common/util/CWClassLoader.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,377 @@
+package org.crosswire.common.util;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * CWClassLoader extends the regular class loader by using looking
+ * in more places. This is needed so that ResourceBundle can find
+ * resources that are not held in the same package as the class.
+ *
+ * <p><table border='1' cellPadding='3' cellSpacing='0'>
+ * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
+ *
+ * Distribution Licence:<br />
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.<br />
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.<br />
+ * The License is available on the internet
+ * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA<br />
+ * The copyright to this program is held by it's authors.
+ * </font></td></tr></table>
+ * @see gnu.gpl.Licence
+ * @author DM Smith [dmsmith555 at yahoo dot com]
+ * @version $Id: CWClassLoader.java,v 1.4 2004/08/16 22:07:35 joe Exp $
+ */
+public class CWClassLoader extends ClassLoader
+{
+    /**
+     * Creates a class loader that finds resources
+     * for the supplied class that may not be in the class' package.
+     * You can use this within base classes by passing getClass()
+     * to load resources for a derived class.
+     * @param resourceOwner is the owner of the resource
+     */
+    public CWClassLoader(Class resourceOwner)
+    {
+        owner = resourceOwner;
+    }
+
+    /**
+     * Creates a class loader that finds resources
+     * for the calling class that may not be in the class' package.
+     * Use this only within classes that are directly looking up their resources.
+     */
+    public CWClassLoader()
+    {
+        owner = CallContext.instance().getCallingClass();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.ClassLoader#findResource(java.lang.String)
+     */
+    public URL findResource(String search)
+    {
+        URL resource = null;
+        if (search == null || search.length() == 0)
+        {
+            return resource;
+        }
+
+        // First look for it with an absolute path
+        // This allows developer overrides
+        if (search.charAt(0) != '/')
+        {
+            resource = findResource('/' + search);
+        }
+
+        if (resource == null)
+        {
+            // Look for it in the class's package.
+            String newsearch = adjustPackageSearch(search);
+            if (!search.equals(newsearch))
+            {
+                resource = findResource(newsearch);
+            }
+        }
+
+        // Sometimes it comes in with '/' inside of it.
+        // So look for it as a file with '.' in the name
+        // This is the form that will find files in the resource.jar
+        if (resource == null)
+        {
+            // Look for it in the class's package.
+            String newsearch = adjustPathSearch(search);
+            if (!search.equals(newsearch))
+            {
+                resource = findResource(newsearch);
+            }
+        }
+
+        // See if it can be found in the home directory
+        if (resource == null)
+        {
+            resource = CWClassLoader.findHomeResource(search);
+        }
+
+        // See if it can be found by its own class.
+        if (resource == null)
+        {
+            resource = owner.getResource(search);
+        }
+
+        // Try the appropriate class loader
+        if (resource == null)
+        {
+            resource = getClassLoader().getResource(search);
+        }
+
+        // Try the bootstrap and the system loader
+        if (resource == null)
+        {
+            resource = ClassLoader.getSystemResource(search);
+        }
+
+        // For good measure let the super class try to find it.
+        if (resource == null)
+        {
+            resource = super.findResource(search);
+        }
+
+        return resource;
+    }
+
+    /**
+     * Prefix the search with a package prefix, if not already.
+     * Skip a leading '/' if present.
+     */
+    private String adjustPackageSearch(String search)
+    {
+        // If it has embedded '/' there is nothing to do.
+        if (search.indexOf('/', 1) == -1)
+        {
+            String className = owner.getName();
+            String pkgPrefix = className.substring(0, className.lastIndexOf('.') + 1);
+
+            if (search.charAt(0) == '/')
+            {
+                String part = search.substring(1);
+                if (!part.startsWith(pkgPrefix))
+                {
+                    search = '/' + pkgPrefix + part;
+                }
+            }
+            else
+            {
+                if (!search.startsWith(pkgPrefix))
+                {
+                    search = pkgPrefix + search;
+                }
+            }
+        }
+
+        return search;
+    }
+
+    /**
+     * Change all but a leading '/' to '.'
+     */
+    private String adjustPathSearch(String search)
+    {
+        if (search.indexOf('/', 1) != -1)
+        {
+            // Change all but a leading '/' to '.'
+            if (search.charAt(0) == '/')
+            {
+                search = '/' + search.substring(1).replace('/', '.');
+            }
+            else
+            {
+                search = search.replace('/', '.');
+            }
+        }
+        return search;
+    }
+
+    /**
+     * 
+     */
+    public ClassLoader getClassLoader()
+    {
+        // Choose the child loader as it will use the parent if need be
+        // If they are not related then choose the context loader
+        ClassLoader loader = pickLoader(Thread.currentThread().getContextClassLoader(), owner.getClassLoader());
+        return pickLoader(loader, ClassLoader.getSystemClassLoader());
+    }
+
+    /**
+     * Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
+     * 'loader1'=='loader2']. Of course, this works only for classloaders that
+     * set their parent pointers correctly. 'null' is interpreted as the
+     * primordial loader [i.e., everybody's parent].
+     */
+    private static ClassLoader pickLoader(final ClassLoader loader1, final ClassLoader loader2)
+    {
+        ClassLoader loader = loader2;
+        if (loader1 != loader2)
+        {
+            loader = loader1;
+            if (loader1 == null)
+            {
+                loader = loader2;
+            }
+            else
+            {
+                // Is loader2 a descendant of loader1?
+                // It is if we can walk up to the top and find it.
+                for (ClassLoader curloader = loader2; curloader != null; curloader = curloader.getParent())
+                {
+                    if (curloader == loader1)
+                    {
+                        loader = loader2;
+                        break;
+                    }
+                }
+            }
+        }
+        return loader;
+    }
+
+    /**
+     * If the application has set the home, it will return
+     * the application's home directory, otherwise it returns null.
+     * @return Returns the home.
+     */
+    public static synchronized URL getHome()
+    {
+        try
+        {
+            if (home != null)
+            {
+
+                return new URL(home.getProtocol(), home.getHost(), home.getPort(), home.getFile());
+            }
+        }
+        catch (MalformedURLException e)
+        {
+            assert false;
+        }
+        return home;
+    }
+
+    /**
+     * Establish the applications home directory from where
+     * additional resources can be found. URL is expected to
+     * end with the directory name, not '/'.
+     * @param newhome The home to set.
+     */
+    public static synchronized void setHome(URL newhome)
+    {
+        home = newhome;
+    }
+
+    /**
+     * Look for the resource in the home directory
+     * @param search must be non-null, non-empty
+     */
+    public static URL findHomeResource(String search)
+    {
+        URL reply = null;
+        
+        URL override = getHomeResource(search);
+
+        // Look at the application's home first to allow overrides
+        if (override != null)
+        {
+            // Make sure the file exists and can be read
+            File f = new File(override.getFile());
+            if (f.canRead())
+            {
+                reply = override;
+            }
+        }
+
+        return reply;
+    }
+
+    /**
+     * Compute an URL for the resource in the home directory
+     * @param search must be non-null, non-empty
+     */
+    public static URL getHomeResource(String search)
+    {
+        URL reply = null;
+        
+        URL homeURL = getHome();
+
+        // Look at the application's home first to allow overrides
+        if (homeURL != null)
+        {
+            reply = lengthenURL(homeURL, search);
+        }
+
+        return reply;
+    }
+
+    /**
+     * Utility to add a string to the end of a URL.
+     * @param orig The URL to strip
+     * @param extra The text to add to the end of the URL
+     * @return The stripped URL
+     */
+    public static URL lengthenURL(URL orig, String extra)
+    {
+        try
+        {
+            char firstChar = extra.charAt(extra.length() - 1);
+            if (isSeparator(firstChar))
+            {
+                extra = extra.substring(1);
+            }
+
+            if (orig.getProtocol().equals(PROTOCOL_FILE))
+            {
+                String file = orig.toExternalForm();
+                char lastChar = file.charAt(file.length() - 1);
+                if (isSeparator(lastChar))
+                {
+                    return new URL(orig.getProtocol(),
+                                   orig.getHost(),
+                                   orig.getPort(),
+                                   orig.getFile() + extra);
+                }
+                else
+                {
+                    return new URL(orig.getProtocol(),
+                                   orig.getHost(),
+                                   orig.getPort(),
+                                   orig.getFile() + File.separator + extra);
+                }
+            }
+            else
+            {
+                return new URL(orig.getProtocol(),
+                               orig.getHost(),
+                               orig.getPort(),
+                               orig.getFile() + SEPARATOR + extra);
+            }
+        }
+        catch (MalformedURLException ex)
+        {
+            assert false : ex;
+            return null;
+        }
+    }
+
+    private static boolean isSeparator(char c)
+    {
+        return c == '/' || c == '\\';
+    }
+
+    /**
+     * Constant for the file: protocol
+     */
+    public static final String PROTOCOL_FILE = "file"; //$NON-NLS-1$
+
+    /**
+     * URL separator
+     */
+    public static final String SEPARATOR = "/"; //$NON-NLS-1$
+
+    /**
+     * The class to which the resources belong
+     */
+    private Class owner;
+
+    /**
+     * Notion of a project's home from where additional resources can be found.
+     */
+    private static URL home = null;
+}

Copied: trunk/app/src/org/crosswire/common/util/CallContext.java (from rev 43, trunk/app/src/org/crosswire/common/CallContext.java)
===================================================================
--- trunk/app/src/org/crosswire/common/CallContext.java	2004-09-17 06:21:12 UTC (rev 43)
+++ trunk/app/src/org/crosswire/common/util/CallContext.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,91 @@
+package org.crosswire.common.util;
+
+/**
+ * This singleton class provides a way for a method to determine
+ * which class called it.
+ * <p>
+ * It has been tested to work in command line and WebStart environments.
+ * 
+ * <p><table border='1' cellPadding='3' cellSpacing='0'>
+ * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
+ *
+ * Distribution Licence:<br />
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.<br />
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.<br />
+ * The License is available on the internet
+ * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA<br />
+ * The copyright to this program is held by it's authors.
+ * </font></td></tr></table>
+ * @see gnu.gpl.Licence
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ * @version $Id: CallContext.java,v 1.1 2004/04/20 21:16:06 joe Exp $
+ */
+public class CallContext extends SecurityManager
+{
+    /**
+     * Prevent instansiation
+     */
+    private CallContext()
+    {
+    }
+
+    /**
+     * Singleton accessor
+     */
+    public static CallContext instance()
+    {
+        if (resolver == null)
+        {
+            resolver = new CallContext();
+        }
+        
+        return resolver;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.SecurityManager#getClassContext()
+     */
+    protected Class[] getClassContext()
+    {
+        return super.getClassContext();
+    }
+
+    /**
+     * When called from a method it will return the class
+     * calling that method.
+      */
+    public Class getCallingClass()
+    {
+        return getCallingClass(1); // add 1 for this method
+    }
+
+    /**
+     * When called from a method it will return the i-th class
+     * calling that method, up the call chain.
+     * If used with a -1 it will return the class making the call
+     * -2 and -3 will return this class
+     * @throws ArrayIndexOutOfBoundsException if the index is not valid
+     */
+    public Class getCallingClass(int i)
+    {
+        return resolver.getClassContext()[CALL_CONTEXT_OFFSET + i];
+    }
+
+    // may need to change if this class is redesigned
+    /**
+     * Offset needed to represent the caller of the method
+     * that called this method.
+     * 
+     */
+    private static final int CALL_CONTEXT_OFFSET = 3;
+
+    private static CallContext resolver;
+}

Copied: trunk/app/src/org/crosswire/common/util/CallContext.jbx (from rev 43, trunk/app/src/org/crosswire/common/CallContext.jbx)

Copied: trunk/app/src/org/crosswire/common/util/ResourceUtil.java (from rev 43, trunk/app/src/org/crosswire/common/ResourceUtil.java)
===================================================================
--- trunk/app/src/org/crosswire/common/ResourceUtil.java	2004-09-17 06:21:12 UTC (rev 43)
+++ trunk/app/src/org/crosswire/common/util/ResourceUtil.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,71 @@
+package org.crosswire.common.util;
+
+import java.net.URL;
+import java.util.MissingResourceException;
+
+/**
+ * Better implemenetations of the getResource methods with less ambiguity and
+ * that are less dependent on the specific classloader situation.
+ *
+ * <p><table border='1' cellPadding='3' cellSpacing='0'>
+ * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
+ *
+ * Distribution Licence:<br />
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.<br />
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.<br />
+ * The License is available on the internet
+ * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA<br />
+ * The copyright to this program is held by it's authors.
+ * </font></td></tr></table>
+ * @see gnu.gpl.Licence
+ * @author Joe Walker [joe at eireneh dot com]
+ * @author DM Smith [ dmsmith555 at yahoo dot com ]
+ * @version $Id: ResourceUtil.java,v 1.5 2004/08/16 22:07:35 joe Exp $
+ */
+public class ResourceUtil
+{
+    /**
+     * Prevent Instansiation
+     */
+    private ResourceUtil()
+    {
+    }
+
+    /**
+     * Generic resource URL fetcher. One way or the other we'll find it!
+     * Either as a relative or an absolute reference.
+     * @param search The name of the resource (without a leading /) to find
+     * @return The requested resource
+     * @throws MissingResourceException if the resource can not be found
+     */
+    public static URL getResource(String search) throws MissingResourceException
+    {
+        return getResource(CallContext.instance().getCallingClass(), search);
+    }
+
+    /**
+     * Generic resource URL fetcher. One way or the other we'll find it!
+     * Either as a relative or an absolute reference.
+     * @param clazz The resource to find
+     * @return The requested resource
+     * @throws MissingResourceException if the resource can not be found
+     */
+    public static URL getResource(Class clazz, String resourceName) throws MissingResourceException
+    {
+        URL resource = new CWClassLoader(clazz).findResource(resourceName);
+
+        if (resource == null)
+        {
+            throw new MissingResourceException("Can't find resource", clazz.getName(), resourceName);
+        }
+
+        return resource;
+    }
+}

Added: trunk/app/src/org/crosswire/common/util/RowProcessor.java
===================================================================
--- trunk/app/src/org/crosswire/common/util/RowProcessor.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/util/RowProcessor.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,36 @@
+package org.crosswire.common.util;
+
+/**
+ * A RowProcessor processes a single row consisting of an array of objects.
+ * 
+ * <p><table border='1' cellPadding='3' cellSpacing='0'>
+ * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
+ *
+ * Distribution Licence:<br />
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.<br />
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.<br />
+ * The License is available on the internet
+ * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA<br />
+ * The copyright to this program is held by it's authors.
+ * </font></td></tr></table>
+ * @see gnu.gpl.Licence
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ * @version $Id$
+ */
+public interface RowProcessor
+{
+
+    /**
+     * Process a row of Objects making up a line.
+     * @param row the row to handle
+     */
+    public void process(Object[] row);
+
+}
\ No newline at end of file

Added: trunk/app/src/org/crosswire/common/util/TabbedFileReader.java
===================================================================
--- trunk/app/src/org/crosswire/common/util/TabbedFileReader.java	2004-09-19 12:30:52 UTC (rev 45)
+++ trunk/app/src/org/crosswire/common/util/TabbedFileReader.java	2004-09-19 12:32:10 UTC (rev 46)
@@ -0,0 +1,89 @@
+package org.crosswire.common.util;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+import org.crosswire.common.ResourceUtil;
+
+/**
+ * A TabbedFileReader reads a file consisting of lines with
+ * tab separated columns.
+ * 
+ * <p><table border='1' cellPadding='3' cellSpacing='0'>
+ * <tr><td bgColor='white' class='TableRowColor'><font size='-7'>
+ *
+ * Distribution Licence:<br />
+ * JSword is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License,
+ * version 2 as published by the Free Software Foundation.<br />
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.<br />
+ * The License is available on the internet
+ * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, or by writing to:
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA<br />
+ * The copyright to this program is held by it's authors.
+ * </font></td></tr></table>
+ * @see gnu.gpl.Licence
+ * @author DM Smith [ dmsmith555 at yahoo dot com]
+ * @version $Id$
+ */
+public class TabbedFileReader
+{
+    /**
+     * Process all the lines in the file.
+     * @param fileName java.lang.String
+     * @param columns int
+     * @param lp lineProcessor
+     * @throws IOException
+     */
+    static public void read(String fileName, int columns, RowProcessor lp) throws IOException
+    {
+        InputStream inputStream = null;
+        try
+        {
+            URL fileURL = ResourceUtil.getResource(fileName);
+            inputStream = new FileInputStream(fileURL.getFile());
+        }
+        catch (Exception ex1)
+        {
+            inputStream = new FileInputStream(fileName);
+        }
+ 
+        // open the file
+        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+
+        Object row[] = new Object[columns];
+
+        // read the file a line at a time and send it to the
+        // processor for processing
+        String line = null;
+        while ((line = in.readLine()) != null)
+        {
+            // Split it on tabs
+            int previousLoc = 0;
+            int lastColumn = columns - 1;
+            for (int col = 0; col < lastColumn; col++)
+            {
+                int loc = line.indexOf('\t', previousLoc);
+                if (loc == -1)
+                {
+                    throw new ArrayIndexOutOfBoundsException();
+                }
+                 row[col] = line.substring(previousLoc, loc);
+                previousLoc = (loc + 1);
+            }
+            row[lastColumn] = line.substring(previousLoc);
+            lp.process(row);
+        }
+        // close the file
+        in.close();
+    }
+
+}
\ No newline at end of file



More information about the sword-cvs mailing list