[jsword-svn] r1792 - in trunk: common common/src/main/java/org/crosswire/common/util jsword/src/main/java/org/crosswire/jsword/book/sword

dmsmith at www.crosswire.org dmsmith at www.crosswire.org
Thu Apr 10 14:16:24 MST 2008


Author: dmsmith
Date: 2008-04-10 14:16:23 -0700 (Thu, 10 Apr 2008)
New Revision: 1792

Modified:
   trunk/common/JSwordDictionary.txt
   trunk/common/src/main/java/org/crosswire/common/util/Reporter.java
   trunk/common/src/main/java/org/crosswire/common/util/StringUtil.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/AbstractBackend.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/GenBookBackend.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/IndexKey.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawBackend.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawLDBackend.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/SwordUtil.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/TreeKeyIndex.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZLDBackend.java
   trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZVerseBackend.java
Log:
Incremental improvements in backend, toward ability to write and toward fast dictionaries.

Modified: trunk/common/JSwordDictionary.txt
===================================================================
--- trunk/common/JSwordDictionary.txt	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/common/JSwordDictionary.txt	2008-04-10 21:16:23 UTC (rev 1792)
@@ -46,3 +46,15 @@
 internet
 editable
 throwable
+endian
+refactor
+backend
+unascribed
+backends
+calendarized
+devotionals
+versification
+uninstall
+proxying
+markup
+unicode

Modified: trunk/common/src/main/java/org/crosswire/common/util/Reporter.java
===================================================================
--- trunk/common/src/main/java/org/crosswire/common/util/Reporter.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/common/src/main/java/org/crosswire/common/util/Reporter.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -36,7 +36,7 @@
  *     in response to a single error.</li>
  * <li>The class being implemented may implement an interface that disallows
  *     nested exceptions and yet does not want to loose the root cause error
- *     information. (This is the weakest of the above arguements, but probably
+ *     information. (This is the weakest of the above arguments, but probably
  *     still valid.)</li>
  * However in many of the times this class is used, this is the reason:
  * <li>Within UI specific code - to throw up a dialog box (or whatever). Now

Modified: trunk/common/src/main/java/org/crosswire/common/util/StringUtil.java
===================================================================
--- trunk/common/src/main/java/org/crosswire/common/util/StringUtil.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/common/src/main/java/org/crosswire/common/util/StringUtil.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -258,7 +258,7 @@
      * StringUtils.split(null, *)         = null
      * StringUtils.split("", *)           = []
      * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
-     * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
+     * StringUtils.split("a..b.c", '.')   = ["a", "", "b", "c"]
      * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
      * StringUtils.split("a\tb\nc", null) = ["a", "b", "c"]
      * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
@@ -307,6 +307,72 @@
     }
 
     /**
+     * <p>Splits the provided text into an array, separator specified.
+     * This is an alternative to using StringTokenizer.</p>
+     *
+     * <p>The separator is not included in the returned String array.
+     * Adjacent separators are treated individually.</p>
+     *
+     * <p>A <code>null</code> input String returns <code>null</code>.</p>
+     *
+     * <pre>
+     * StringUtils.split(null, *)         = null
+     * StringUtils.split("", *)           = []
+     * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
+     * StringUtils.split("a..b.c", '.')   = ["a", "", "b", "c"]
+     * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
+     * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
+     * </pre>
+     *
+     * @param str  the String to parse, may be null
+     * @param separatorChar  the character used as the delimiter
+     * @param max  the maximum number of elements to include in the
+     *  array. A zero or negative value implies no limit
+     * @return an array of parsed Strings
+     * @since 2.0
+     */
+    public static String[] splitAll(String str, char separatorChar, int max)
+    {
+        // Performance tuned for 2.0 (JDK1.4)
+
+        if (str == null)
+        {
+            return (String[]) EMPTY_STRING_ARRAY.clone();
+        }
+        int len = str.length();
+        if (len == 0)
+        {
+            return (String[]) EMPTY_STRING_ARRAY.clone();
+        }
+        List list = new ArrayList();
+        int sizePlus1 = 1;
+        int i = 0;
+        int start = 0;
+        boolean match = false;
+        while (i < len)
+        {
+            if (str.charAt(i) == separatorChar)
+            {
+                if (sizePlus1++ == max)
+                {
+                    i = len;
+                }
+                list.add(str.substring(start, i));
+                start = ++i;
+                match = false;
+                continue;
+            }
+            match = true;
+            i++;
+        }
+        if (match)
+        {
+            list.add(str.substring(start, i));
+        }
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+    /**
      * <p>Splits the provided text into an array, separators specified.
      * This is an alternative to using StringTokenizer.</p>
      *
@@ -351,7 +417,7 @@
      * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
      * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
      * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
-     * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cdef"]
+     * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
      * </pre>
      *
      * @param str  the String to parse, may be null
@@ -406,7 +472,7 @@
         }
         else if (separatorChars.length() == 1)
         {
-            // Optimise 1 character case
+            // Optimize 1 character case
             char sep = separatorChars.charAt(0);
             while (i < len)
             {

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/AbstractBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/AbstractBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/AbstractBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -21,7 +21,6 @@
  */
 package org.crosswire.jsword.book.sword;
 
-import java.io.File;
 import java.net.URI;
 
 import org.crosswire.common.activate.Activatable;
@@ -36,6 +35,7 @@
  * @see gnu.lgpl.License for license details.
  *      The copyright to this program is held by it's authors.
  * @author Joe Walker [joe at eireneh dot com]
+ * @author DM Smith [dmsmith555 at yahoo dot com]
  */
 public abstract class AbstractBackend implements Activatable
 {
@@ -71,11 +71,24 @@
             {
                 data[i] = cipherEngine.cipher(data[i]);
             }
+            // destroy any evidence!
+            cipherEngine.burn();
         }
+        cipherKeyString = null;
     }
 
-    public String getExpandedDataPath() throws BookException
+    /**
+     * Encipher the data in place, if there is a key to unlock it.
+     * @param data
+     */
+    public void encipher(byte[] data)
     {
+        // Enciphering and deciphering are the same!
+        decipher(data);
+    }
+
+    public URI getExpandedDataPath() throws BookException
+    {
         URI loc = NetUtil.lengthenURI(bmd.getLibrary(), (String) bmd.getProperty(ConfigEntryType.DATA_PATH));
 
         if (loc == null)
@@ -83,17 +96,17 @@
             throw new BookException(Msg.MISSING_FILE);
         }
 
-        return new File(loc.getPath()).getAbsolutePath();
+        return loc;
     }
 
     /**
-     * Initialise a AbstractBackend before use. This method needs to call addKey() a
+     * Initialize a AbstractBackend before use. This method needs to call addKey() a
      * number of times on SwordDictionary
      */
     public abstract Key readIndex();
 
     /**
-     * Get the bytes alotted for the given verse
+     * Get the text allotted for the given entry
      * @param key The key to fetch
      * @return String The data for the verse in question
      * @throws BookException If the data can not be read.
@@ -101,6 +114,16 @@
     public abstract String getRawText(Key key) throws BookException;
 
     /**
+     * Set the text allotted for the given verse
+     * @param key The key to fetch
+     * @throws BookException If the data can not be set.
+     */
+    public void setRawText(Key key, String text) /* throws BookException */
+    {
+        throw new UnsupportedOperationException("Could not set text (" + text + ") for " + key); //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
+    /**
      * Returns whether this AbstractBackend is implemented.
      * @return true if this AbstractBackend is implemented.
      */

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/GenBookBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/GenBookBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/GenBookBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -24,6 +24,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -54,8 +55,8 @@
 
         index = new TreeKeyIndex(sbmd);
 
-        String path = getExpandedDataPath();
-        bdtFile = new File(path + EXTENSION_BDT);
+        URI path = getExpandedDataPath();
+        bdtFile = new File(path.getPath() + EXTENSION_BDT);
 
         if (!bdtFile.canRead())
         {
@@ -127,7 +128,7 @@
                 int size = SwordUtil.decodeLittleEndian32(userData, 4);
                 byte[] data = SwordUtil.readRAF(bdtRaf, start, size);
                 decipher(data);
-                return SwordUtil.decode(key, data, getBookMetaData().getBookCharset());
+                return SwordUtil.decode(key.getName(), data, getBookMetaData().getBookCharset());
             }
 
             return ""; //$NON-NLS-1$

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/IndexKey.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/IndexKey.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/IndexKey.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -1,4 +1,3 @@
-package org.crosswire.jsword.book.sword;
 /**
  * Distribution License:
  * JSword is free software; you can redistribute it and/or modify it under
@@ -20,6 +19,8 @@
  *
  * ID: $Id$
  */
+package org.crosswire.jsword.book.sword;
+
 import org.crosswire.jsword.passage.DefaultLeafKeyList;
 import org.crosswire.jsword.passage.Key;
 

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -25,11 +25,13 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.URI;
 
 import org.crosswire.common.activate.Activator;
 import org.crosswire.common.activate.Lock;
 import org.crosswire.common.util.FileUtil;
 import org.crosswire.common.util.Logger;
+import org.crosswire.common.util.NetUtil;
 import org.crosswire.jsword.book.BookException;
 import org.crosswire.jsword.passage.Key;
 import org.crosswire.jsword.passage.KeyUtil;
@@ -55,13 +57,15 @@
 
         assert (datasize == 2 || datasize == 4);
 
-        String path = getExpandedDataPath();
+        URI path = getExpandedDataPath();
 
-        idxFile[SwordConstants.TESTAMENT_OLD] = new File(path + File.separator + SwordConstants.FILE_OT + SwordConstants.EXTENSION_VSS);
-        txtFile[SwordConstants.TESTAMENT_OLD] = new File(path + File.separator + SwordConstants.FILE_OT);
+        URI otPath = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_OT);
+        txtFile[SwordConstants.TESTAMENT_OLD] = new File(otPath.getPath());
+        idxFile[SwordConstants.TESTAMENT_OLD] = new File(otPath.getPath() + SwordConstants.EXTENSION_VSS);
 
-        idxFile[SwordConstants.TESTAMENT_NEW] = new File(path + File.separator + SwordConstants.FILE_NT + SwordConstants.EXTENSION_VSS);
-        txtFile[SwordConstants.TESTAMENT_NEW] = new File(path + File.separator + SwordConstants.FILE_NT);
+        URI ntPath = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_NT);
+        txtFile[SwordConstants.TESTAMENT_NEW] = new File(ntPath.getPath());
+        idxFile[SwordConstants.TESTAMENT_NEW] = new File(ntPath.getPath() + SwordConstants.EXTENSION_VSS);
 
         // It is an error to be neither OT nor NT
         if (!txtFile[SwordConstants.TESTAMENT_OLD].canRead() && !txtFile[SwordConstants.TESTAMENT_NEW].canRead())
@@ -75,12 +79,14 @@
      */
     public final void activate(Lock lock)
     {
+        String fileMode = isWritable() ? FileUtil.MODE_WRITE : FileUtil.MODE_READ;
+ 
         if (idxFile[SwordConstants.TESTAMENT_OLD].canRead())
         {
             try
             {
-                idxRaf[SwordConstants.TESTAMENT_OLD] = new RandomAccessFile(idxFile[SwordConstants.TESTAMENT_OLD], FileUtil.MODE_READ);
-                txtRaf[SwordConstants.TESTAMENT_OLD] = new RandomAccessFile(txtFile[SwordConstants.TESTAMENT_OLD], FileUtil.MODE_READ);
+                idxRaf[SwordConstants.TESTAMENT_OLD] = new RandomAccessFile(idxFile[SwordConstants.TESTAMENT_OLD], fileMode);
+                txtRaf[SwordConstants.TESTAMENT_OLD] = new RandomAccessFile(txtFile[SwordConstants.TESTAMENT_OLD], fileMode);
             }
             catch (FileNotFoundException ex)
             {
@@ -95,8 +101,8 @@
         {
             try
             {
-                idxRaf[SwordConstants.TESTAMENT_NEW] = new RandomAccessFile(idxFile[SwordConstants.TESTAMENT_NEW], FileUtil.MODE_READ);
-                txtRaf[SwordConstants.TESTAMENT_NEW] = new RandomAccessFile(txtFile[SwordConstants.TESTAMENT_NEW], FileUtil.MODE_READ);
+                idxRaf[SwordConstants.TESTAMENT_NEW] = new RandomAccessFile(idxFile[SwordConstants.TESTAMENT_NEW], fileMode);
+                txtRaf[SwordConstants.TESTAMENT_NEW] = new RandomAccessFile(txtFile[SwordConstants.TESTAMENT_NEW], fileMode);
             }
             catch (FileNotFoundException ex)
             {
@@ -163,7 +169,7 @@
 
             int entrysize = datasize + OFFSETSIZE;
 
-            // Read the next entrysize byes.
+            // Read the next entry size bytes.
             byte[] read = SwordUtil.readRAF(idxRaf[testament], index * entrysize, entrysize);
             if (read == null || read.length == 0)
             {
@@ -197,7 +203,7 @@
 
             decipher(data);
 
-            return SwordUtil.decode(key, data, charset);
+            return SwordUtil.decode(key.getName(), data, charset);
         }
         catch (IOException ex)
         {
@@ -226,6 +232,35 @@
         }
     }
 
+    /* (non-Javadoc)
+     * @see org.crosswire.jsword.book.sword.AbstractBackend#isWritable()
+     */
+    public boolean isWritable()
+    {
+        // For the module to be writable either the old testament or the new testament needs to be present
+        // (i.e. readable) and both the index and the data files need to be readable
+        if (idxFile[SwordConstants.TESTAMENT_OLD].canRead()
+            && (idxFile[SwordConstants.TESTAMENT_OLD].canWrite() || !txtFile[SwordConstants.TESTAMENT_OLD].canWrite()))
+        {
+            return false;
+        }
+        if (idxFile[SwordConstants.TESTAMENT_NEW].canRead()
+            && (idxFile[SwordConstants.TESTAMENT_NEW].canWrite() || !txtFile[SwordConstants.TESTAMENT_NEW].canWrite()))
+        {
+            return false;
+        }
+        return idxFile[SwordConstants.TESTAMENT_OLD].canRead() || idxFile[SwordConstants.TESTAMENT_NEW].canRead();
+    }
+
+    public void create(String path)
+    {
+        idxFile[SwordConstants.TESTAMENT_OLD] = new File(path + File.separator + SwordConstants.FILE_OT + SwordConstants.EXTENSION_VSS);
+        txtFile[SwordConstants.TESTAMENT_OLD] = new File(path + File.separator + SwordConstants.FILE_OT);
+
+        idxFile[SwordConstants.TESTAMENT_NEW] = new File(path + File.separator + SwordConstants.FILE_NT + SwordConstants.EXTENSION_VSS);
+        txtFile[SwordConstants.TESTAMENT_NEW] = new File(path + File.separator + SwordConstants.FILE_NT);
+    }
+
     /**
      * Are we active
      */

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawLDBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawLDBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/RawLDBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -24,20 +24,20 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.URI;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.Locale;
 
 import org.crosswire.common.activate.Activator;
 import org.crosswire.common.activate.Lock;
 import org.crosswire.common.icu.DateFormatter;
-import org.crosswire.common.util.ClassUtil;
 import org.crosswire.common.util.FileUtil;
 import org.crosswire.common.util.Logger;
 import org.crosswire.common.util.Reporter;
 import org.crosswire.common.util.StringUtil;
 import org.crosswire.jsword.book.BookCategory;
 import org.crosswire.jsword.book.BookException;
-import org.crosswire.jsword.book.DataPolice;
 import org.crosswire.jsword.passage.DefaultKeyList;
 import org.crosswire.jsword.passage.Key;
 
@@ -54,28 +54,15 @@
      * Simple ctor
      * @param datasize We need to know how many bytes in the size portion of the index
      */
-    public RawLDBackend(SwordBookMetaData sbmd, int datasize) throws BookException
+    public RawLDBackend(SwordBookMetaData sbmd, int datasize)
     {
         super(sbmd);
         this.datasize = datasize;
         this.entrysize = OFFSETSIZE + datasize;
+        this.size = -1;
 
         assert (datasize == 2 || datasize == 4);
 
-        String path = getExpandedDataPath();
-
-        idxFile = new File(path + SwordConstants.EXTENSION_INDEX);
-        datFile = new File(path + SwordConstants.EXTENSION_DATA);
-
-        if (!idxFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { idxFile.getAbsolutePath() });
-        }
-
-        if (!datFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { datFile.getAbsolutePath() });
-        }
     }
 
     /* (non-Javadoc)
@@ -85,6 +72,32 @@
     {
         try
         {
+            URI path = null;
+            try
+            {
+                path = getExpandedDataPath();
+            }
+            catch (BookException e)
+            {
+                Reporter.informUser(this, e);
+                return;
+            }
+
+            idxFile = new File(path.getPath() + SwordConstants.EXTENSION_INDEX);
+            datFile = new File(path.getPath() + SwordConstants.EXTENSION_DATA);
+
+            if (!idxFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { idxFile.getAbsolutePath() }));
+                return;
+            }
+
+            if (!datFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { datFile.getAbsolutePath() }));
+                return;
+            }
+
             // Open the files
             idxRaf = new RandomAccessFile(idxFile, FileUtil.MODE_READ);
             datRaf = new RandomAccessFile(datFile, FileUtil.MODE_READ);
@@ -117,6 +130,7 @@
 
         idxRaf = null;
         datRaf = null;
+        size   = -1;
 
         active = false;
     }
@@ -140,7 +154,7 @@
         long entries;
         try
         {
-            entries = getEntryCount();
+            entries = getSize();
         }
         catch (IOException ex)
         {
@@ -153,35 +167,18 @@
             try
             {
                 // Read the offset and size for this key from the index
-                DataIndex index = getIndex(entry);
-                String rawData = getEntry(reply, index);
+                DataEntry dataEntry = getEntry(reply.getName(), entry, true);
+                String keytitle = dataEntry.getKey();
 
-                int keyend = rawData.indexOf(SEPARATOR);
-                if (keyend == -1)
-                {
-                    DataPolice.report("Failed to find keyname. offset=" + index.getOffset() + " data='" + rawData + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-                    continue;
-                }
-
-                String keytitle = rawData.substring(0, keyend).trim();
-
-                // for some weird reason plain text (i.e. SourceType=0) dicts
-                // all get \ added to the ends of the index entries.
-                if (keytitle.endsWith("\\")) //$NON-NLS-1$
-                {
-                    keytitle = keytitle.substring(0, keytitle.length() - 1);
-                }
-
-                // Massage keytitle if can be.
                 if (isDailyDevotional && keytitle.length() >= 3)
                 {
-                    String[] parts = StringUtil.splitAll(keytitle, '.');
-                    greg.set(Calendar.MONTH, Integer.parseInt(parts[0]) - 1);
-                    greg.set(Calendar.DATE, Integer.parseInt(parts[1]));
+                    String[] spec = StringUtil.splitAll(keytitle, '.');
+                    greg.set(Calendar.MONTH, Integer.parseInt(spec[0]) - 1);
+                    greg.set(Calendar.DATE, Integer.parseInt(spec[1]));
                     keytitle = nameDF.format(greg.getTime());
                 }
 
-                Key key = new IndexKey(keytitle, index, reply);
+                Key key = new IndexKey(keytitle);
 
                 // remove duplicates, keeping later one.
                 // This occurs under some conditions:
@@ -212,10 +209,14 @@
      * @return the number of entries in the Book
      * @throws IOException 
      */
-    public long getEntryCount() throws IOException
+    public long getSize() throws IOException
     {
         checkActive();
-        return idxRaf.length() / entrysize;
+        if (size == -1)
+        {
+            size = idxRaf.length() / entrysize;
+        }
+        return size;
     }
 
     /**
@@ -228,20 +229,20 @@
     {
         // Read the offset and size for this key from the index
         byte[] buffer = SwordUtil.readRAF(idxRaf, entry * entrysize, entrysize);
-        int offset = SwordUtil.decodeLittleEndian32(buffer, 0);
-        int size = -1;
+        int entryOffset = SwordUtil.decodeLittleEndian32(buffer, 0);
+        int entrySize = -1;
         switch (datasize)
         {
         case 2:
-            size = SwordUtil.decodeLittleEndian16(buffer, 4);
+            entrySize = SwordUtil.decodeLittleEndian16(buffer, 4);
             break;
         case 4:
-            size = SwordUtil.decodeLittleEndian32(buffer, 4);
+            entrySize = SwordUtil.decodeLittleEndian32(buffer, 4);
             break;
         default:
             assert false : datasize;
         }
-        return new DataIndex(offset, size);
+        return new DataIndex(entryOffset, entrySize);
     }
 
     /**
@@ -251,15 +252,20 @@
      * @return the text for the entry.
      * @throws IOException 
      */
-    public String getEntry(Key reply, DataIndex index) throws IOException
+    public DataEntry getEntry(String reply, long index, boolean decipher) throws IOException
     {
+        DataIndex dataIndex = getIndex(index);
         // Now read the data file for this key using the offset and size
-        byte[] data = SwordUtil.readRAF(datRaf, index.getOffset(), index.getSize());
+        byte[] data = SwordUtil.readRAF(datRaf, dataIndex.getOffset(), dataIndex.getSize());
 
-        decipher(data);
+        if (decipher)
+        {
+            decipher(data);
+        }
 
-        return SwordUtil.decode(reply, data, getBookMetaData().getBookCharset()).trim();
+        return new DataEntry(reply, data, getBookMetaData().getBookCharset());
     }
+
     /*
      * (non-Javadoc)
      * @see org.crosswire.jsword.book.sword.AbstractBackend#getRawText(org.crosswire.jsword.passage.Key, java.lang.String)
@@ -269,24 +275,43 @@
     {
         checkActive();
 
-        if (!(key instanceof IndexKey))
+        try
         {
-            throw new BookException(Msg.BAD_KEY, new Object[] { ClassUtil.getShortClassName(key.getClass()), key.getName() });
+            long pos = search(key.getName());
+            if (pos >= 0)
+            {
+                DataEntry entry = getEntry(key.getName(), pos, true);
+                if (entry.isLinkEntry())
+                {
+                    return getRawText(entry.getLinkTarget());
+                }
+                return entry.getRawText();
+            }
+            throw new BookException(UserMsg.READ_FAIL);
         }
+        catch (IOException ex)
+        {
+            throw new BookException(UserMsg.READ_FAIL, ex);
+        }
+    }
 
-        IndexKey ikey = (IndexKey) key;
+    public String getRawText(String key) throws BookException
+    {
+        checkActive();
 
         try
         {
-            String data = getEntry(ikey, ikey.getDataIndex());
-
-            int keyend = data.indexOf(SEPARATOR);
-            if (keyend == -1)
+            long pos = search(key);
+            if (pos >= 0)
             {
-                throw new BookException(UserMsg.READ_FAIL);
+                DataEntry entry = getEntry(key, pos, true);
+                if (entry.isLinkEntry())
+                {
+                    return getRawText(entry.getLinkTarget());
+                }
+                return entry.getRawText();
             }
-
-            return data.substring(keyend + 1);
+            throw new BookException(UserMsg.READ_FAIL);
         }
         catch (IOException ex)
         {
@@ -295,6 +320,83 @@
     }
 
     /**
+     * Find a matching entry, returning it's index. Otherwise return < 0, such that
+     * (-pos - 1) gives the insertion index.
+     * @param key
+     * @return
+     * @throws IOException
+     */
+    private long search(String key) throws IOException
+    {
+        SwordBookMetaData bmd = getBookMetaData();
+
+        boolean isDailyDevotional = bmd.getBookCategory().equals(BookCategory.DAILY_DEVOTIONS);
+
+        Calendar greg = new GregorianCalendar();
+        DateFormatter nameDF = DateFormatter.getDateInstance();
+
+        String target = key.toUpperCase(Locale.US);
+
+        long low = 1;
+        long high = getSize() - 1;
+
+        while (low <= high)
+        {
+            long mid = (low + high) >> 1;
+
+            // Get the key for the item at "mid"
+            DataEntry entry = getEntry(key, mid, true);
+            String midVal = entry.getKey();
+
+            // Massage midVal if can be.
+            if (isDailyDevotional && midVal.length() >= 3)
+            {
+                String[] spec = StringUtil.splitAll(midVal, '.');
+                greg.set(Calendar.MONTH, Integer.parseInt(spec[0]) - 1);
+                greg.set(Calendar.DATE, Integer.parseInt(spec[1]));
+                midVal = nameDF.format(greg.getTime());
+            }
+
+            int cmp = midVal.toUpperCase(Locale.US).compareTo(target);
+
+            if (cmp < 0)
+            {
+                low = mid + 1;
+            }
+            else if (cmp > 0)
+            {
+                high = mid - 1;
+            }
+            else
+            {
+                return mid; // key found
+            }
+        }
+
+        // Strong's Greek And Hebrew dictionaries have an introductory entry, so check it for a match.
+        // Get the key for the item at "mid"
+        DataEntry entry = getEntry(key, 0, true);
+        String midVal = entry.getKey();
+
+        // Massage midVal if can be.
+        if (isDailyDevotional && midVal.length() >= 3)
+        {
+            String[] spec = StringUtil.splitAll(midVal, '.');
+            greg.set(Calendar.MONTH, Integer.parseInt(spec[0]) - 1);
+            greg.set(Calendar.DATE, Integer.parseInt(spec[1]));
+            midVal = nameDF.format(greg.getTime());
+        }
+
+        int cmp = midVal.toUpperCase(Locale.US).compareTo(target);
+        if (cmp == 0)
+        {
+            return 0;
+        }
+
+        return -(low + 1); // key not found
+    }
+
+    /**
      * Helper method so we can quickly activate ourselves on access
      */
     protected final void checkActive()
@@ -306,34 +408,34 @@
     }
 
     /**
-     * Are we active
+     * How many bytes in the offset pointers in the index
      */
-    private boolean active;
+    private static final int OFFSETSIZE = 4;
 
     /**
-     * Used to separate the key name from the key value
+     * Flags whether there are open files or not
      */
-    private static final byte SEPARATOR = 10; // ^M=CR=13=0x0d=\r ^J=LF=10=0x0a=\n
+    private boolean active;
 
     /**
-     * How many bytes in the offset pointers in the index
+     * The number of bytes in the size count in the index
      */
-    private static final int OFFSETSIZE = 4;
+    private int datasize;
 
     /**
-     * How many bytes in the size count in the index
+     * The number of bytes for each entry in the index: either 6 or 8
      */
-    private int datasize;
+    private int entrysize;
 
     /**
-     * How many bytes for each entry in the index: either 6 or 8
+     * The number of entries in the book.
      */
-    private int entrysize;
+    private long size;
 
     /**
-     * The data random access file
+     * The index file
      */
-    private RandomAccessFile datRaf;
+    private File idxFile;
 
     /**
      * The index random access file
@@ -346,9 +448,9 @@
     private File datFile;
 
     /**
-     * The index file
+     * The data random access file
      */
-    private File idxFile;
+    private RandomAccessFile datRaf;
 
     /**
      * The log stream

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/SwordUtil.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/SwordUtil.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/SwordUtil.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -27,7 +27,6 @@
 
 import org.crosswire.common.util.Logger;
 import org.crosswire.jsword.book.DataPolice;
-import org.crosswire.jsword.passage.Key;
 
 /**
  * Various utilities used by different Sword classes.
@@ -153,8 +152,27 @@
     }
 
     /**
+     * Encode little endian data from a byte array.
+     * This assumes that the number fits in a Java integer.
+     * That is, the range of an unsigned C integer is greater than a signed Java integer.
+     * For a practical limit, 2**31 is way bigger than any document that we can have.
+     * If this ever doesn't work, use a long for the number.
+     * 
+     * @param val the number to encode into little endian
+     * @param data the byte[] from which to write 4 bytes
+     * @param offset the offset into the array
+     */
+    protected static void encodeLittleEndian32(int val, byte[] data, int offset)
+    {
+        data[0 + offset] = (byte)(val & 0xFF);
+        data[1 + offset] = (byte)((val >> 8) & 0xFF);
+        data[2 + offset] = (byte)((val >> 16) & 0xFF);
+        data[3 + offset] = (byte)((val >> 24) & 0xFF);
+    }
+
+    /**
      * Decode little endian data from a byte array
-     * @param data the byte[] from which to read 4 bytes
+     * @param data the byte[] from which to read 2 bytes
      * @param offset the offset into the array
      * @return The decoded data
      */
@@ -169,6 +187,18 @@
     }
 
     /**
+     * Encode a 16-bit little endian from an integer.
+     * It is assumed that the integer's lower 16 bits are the only that are set.
+     * @param data the byte[] from which to write 2 bytes
+     * @param offset the offset into the array
+     */
+    protected static void encodeLittleEndian16(int val, byte[] data, int offset)
+    {
+        data[0 + offset] = (byte)(val & 0xFF);
+        data[1 + offset] = (byte)((val >> 8) & 0xFF);
+    }
+
+    /**
      * Find a byte of data in an array
      * @param data The array to search
      * @param sought The data to search for
@@ -176,7 +206,19 @@
      */
     protected static int findByte(byte[] data, byte sought)
     {
-        for (int i = 0; i < data.length; i++)
+        return findByte(data, 0, sought);
+    }
+
+    /**
+     * Find a byte of data in an array
+     * @param data The array to search
+     * @param offset The position in the array to begin looking
+     * @param sought The data to search for
+     * @return The index of the found position or -1 if not found
+     */
+    protected static int findByte(byte[] data, int offset, byte sought)
+    {
+        for (int i = offset; i < data.length; i++)
         {
             if (data[i] == sought)
             {
@@ -190,24 +232,42 @@
     /**
      * Transform a byte array into a string given the encoding.
      * If the encoding is bad then it just does it as a string.
+     * 
      * @param data The byte array to be converted
      * @param charset The encoding of the byte array
      * @return a string that is UTF-8 internally
      */
-    public static String decode(Key key, byte[] data, String charset)
+    public static String decode(String key, byte[] data, String charset)
     {
-        return decode(key, data, data.length, charset);
+        return decode(key, data, 0, data.length, charset);
     }
 
     /**
-     * Transform a byte array into a string given the encoding.
+     * Transform a portion of a byte array into a string given the encoding.
      * If the encoding is bad then it just does it as a string.
+     * 
      * @param data The byte array to be converted
+     * @param length The number of bytes to use.
      * @param charset The encoding of the byte array
      * @return a string that is UTF-8 internally
      */
-    public static String decode(Key key, byte[] data, int length, String charset)
+    public static String decode(String key, byte[] data, int length, String charset)
     {
+        return decode(key, data, 0, length, charset);
+    }
+
+    /**
+     * Transform a portion of a byte array starting at an offset into a string given the encoding.
+     * If the encoding is bad then it just does it as a string.
+     * 
+     * @param data The byte array to be converted
+     * @param offset The starting position in the byte array
+     * @param length The number of bytes to use.
+     * @param charset The encoding of the byte array
+     * @return a string that is UTF-8 internally
+     */
+    public static String decode(String key, byte[] data, int offset, int length, String charset)
+    {
         if ("WINDOWS-1252".equals(charset)) //$NON-NLS-1$
         {
             clean1252(key, data, length);
@@ -215,13 +275,13 @@
         String txt = ""; //$NON-NLS-1$
         try
         {
-            txt = new String(data, 0, length, charset);
+            txt = new String(data, offset, length, charset);
         }
         catch (UnsupportedEncodingException ex)
         {
             // It is impossible! In case, use system default...
             log.error(key + ": Encoding: " + charset + " not supported", ex); //$NON-NLS-1$ //$NON-NLS-2$
-            txt = new String(data, 0, length);
+            txt = new String(data, offset, length);
         }
 
         return txt;
@@ -233,7 +293,7 @@
      * and in UTF-8 or are non-printing control characters in the range
      * of 0-32.
      */
-    public static void clean1252(Key key, byte[] data)
+    public static void clean1252(String key, byte[] data)
     {
         clean1252(key, data, data.length);
     }
@@ -244,7 +304,7 @@
      * and in UTF-8 or are non-printing control characters in the range
      * of 0-32.
      */
-    public static void clean1252(Key key, byte[] data, int length)
+    public static void clean1252(String key, byte[] data, int length)
     {
         for (int i = 0; i < length; i++)
         {
@@ -255,7 +315,7 @@
                 || (c == 0x81 || c == 0x8D || c == 0x8F || c == 0x90 || c == 0x9D))
             {
                 data[i] = 0x20;
-                DataPolice.report(key.getName() + " has bad character 0x" + Integer.toString(c, 16) + " at position " + i + " in input."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+                DataPolice.report(key + " has bad character 0x" + Integer.toString(c, 16) + " at position " + i + " in input."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
             }
         }
     }

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/TreeKeyIndex.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/TreeKeyIndex.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/TreeKeyIndex.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -163,7 +163,7 @@
 
         Key key = new DefaultKeyList(null, bmd.getName());
         // Some of the keys have extraneous whitespace, so remove it.
-        node.setName(SwordUtil.decode(key, buffer, size, bmd.getBookCharset()).trim());
+        node.setName(SwordUtil.decode(key.getName(), buffer, size, bmd.getBookCharset()).trim());
 
         buffer = SwordUtil.readNextRAF(datRaf, 2);
         int userDataSize = SwordUtil.decodeLittleEndian16(buffer, 0);

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZLDBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZLDBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZLDBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -103,32 +103,6 @@
     {
         super(sbmd);
 
-        String path = getExpandedDataPath();
-
-        idxFile = new File(path + EXTENSION_INDEX);
-        datFile = new File(path + EXTENSION_DATA);
-        zdxFile = new File(path + EXTENSION_Z_INDEX);
-        zdtFile = new File(path + EXTENSION_Z_DATA);
-
-        if (!idxFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { idxFile.getAbsolutePath() });
-        }
-
-        if (!datFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { datFile.getAbsolutePath() });
-        }
-
-        if (!zdxFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { zdxFile.getAbsolutePath() });
-        }
-
-        if (!zdtFile.canRead())
-        {
-            throw new BookException(UserMsg.READ_FAIL, new Object[] { zdtFile.getAbsolutePath() });
-        }
     }
 
     /* (non-Javadoc)
@@ -138,6 +112,46 @@
     {
         try
         {
+            String path = null;
+            try
+            {
+                path = getExpandedDataPath().getPath();
+            }
+            catch (BookException e)
+            {
+                Reporter.informUser(this, e);
+                return;
+            }
+
+            idxFile = new File(path + EXTENSION_INDEX);
+            datFile = new File(path + EXTENSION_DATA);
+            zdxFile = new File(path + EXTENSION_Z_INDEX);
+            zdtFile = new File(path + EXTENSION_Z_DATA);
+
+            if (!idxFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { idxFile.getAbsolutePath() }));
+                return;
+            }
+
+            if (!datFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { datFile.getAbsolutePath() }));
+                return;
+            }
+
+            if (!zdxFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { zdxFile.getAbsolutePath() }));
+                return;
+            }
+
+            if (!zdtFile.canRead())
+            {
+                Reporter.informUser(this, new BookException(UserMsg.READ_FAIL, new Object[] { zdtFile.getAbsolutePath() }));
+                return;
+            }
+
             idxRaf = new RandomAccessFile(idxFile, FileUtil.MODE_READ);
             datRaf = new RandomAccessFile(datFile, FileUtil.MODE_READ);
             zdxRaf = new RandomAccessFile(zdxFile, FileUtil.MODE_READ);
@@ -242,7 +256,7 @@
                 byte[] keydata = new byte[keyend];
                 System.arraycopy(data, 0, keydata, 0, keyend);
 
-                String keytitle = SwordUtil.decode(keys, keydata, charset).trim();
+                String keytitle = SwordUtil.decode(keys.getName(), keydata, charset).trim();
 
                 // for some weird reason plain text (i.e. SourceType=0) dicts
                 // all get \ added to the ends of the index entries.
@@ -371,7 +385,7 @@
             byte[] entryBytes = new byte[entrySize];
             System.arraycopy(uncompressed, entryStart, entryBytes, 0, entrySize);
 
-            return SwordUtil.decode(key, entryBytes, charset).trim();
+            return SwordUtil.decode(key.getName(), entryBytes, charset).trim();
         }
         catch (IOException e)
         {

Modified: trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZVerseBackend.java
===================================================================
--- trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZVerseBackend.java	2008-04-10 21:04:38 UTC (rev 1791)
+++ trunk/jsword/src/main/java/org/crosswire/jsword/book/sword/ZVerseBackend.java	2008-04-10 21:16:23 UTC (rev 1792)
@@ -25,12 +25,14 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.net.URI;
 
 import org.crosswire.common.activate.Activator;
 import org.crosswire.common.activate.Lock;
 import org.crosswire.common.compress.CompressorType;
 import org.crosswire.common.util.FileUtil;
 import org.crosswire.common.util.Logger;
+import org.crosswire.common.util.NetUtil;
 import org.crosswire.jsword.book.BookException;
 import org.crosswire.jsword.passage.Key;
 import org.crosswire.jsword.passage.KeyUtil;
@@ -102,28 +104,10 @@
     /**
      * Simple ctor
      */
-    public ZVerseBackend(SwordBookMetaData sbmd, BlockType blockType) throws BookException
+    public ZVerseBackend(SwordBookMetaData sbmd, BlockType blockType)
     {
         super(sbmd);
-
-        String path = getExpandedDataPath();
-        String otAllButLast = path + File.separator + SwordConstants.FILE_OT + '.' + blockType.getIndicator() + SUFFIX_PART1;
-        idxFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_INDEX);
-        textFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_TEXT);
-        compFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_COMP);
-
-        String ntAllButLast = path + File.separator + SwordConstants.FILE_NT + '.' + blockType.getIndicator() + SUFFIX_PART1;
-        idxFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_INDEX);
-        textFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_TEXT);
-        compFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_COMP);
-
-        // It is an error to be neither OT nor NT
-        if (!textFile[SwordConstants.TESTAMENT_OLD].canRead()
-            && !textFile[SwordConstants.TESTAMENT_NEW].canRead())
-        {
-            log.error("Failed to find OT or NT files: '" + otAllButLast + SUFFIX_TEXT + "' and '" + ntAllButLast + SUFFIX_TEXT + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-            throw new BookException(Msg.MISSING_FILE, new Object[] { path });
-        }
+        this.blockType = blockType;
     }
 
     /* (non-Javadoc)
@@ -131,6 +115,35 @@
      */
     public final void activate(Lock lock)
     {
+        try
+        {
+            if (idxFile[SwordConstants.TESTAMENT_OLD] == null)
+            {
+                URI path = getExpandedDataPath();
+                String otAllButLast = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_OT + '.' + blockType.getIndicator() + SUFFIX_PART1).getPath();
+                idxFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_INDEX);
+                textFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_TEXT);
+                compFile[SwordConstants.TESTAMENT_OLD] = new File(otAllButLast + SUFFIX_COMP);
+    
+                String ntAllButLast = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_NT + '.' + blockType.getIndicator() + SUFFIX_PART1).getPath();
+                idxFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_INDEX);
+                textFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_TEXT);
+                compFile[SwordConstants.TESTAMENT_NEW] = new File(ntAllButLast + SUFFIX_COMP);
+            }
+        }
+        catch (BookException e)
+        {
+            idxFile[SwordConstants.TESTAMENT_OLD] = null;
+            textFile[SwordConstants.TESTAMENT_OLD] = null;
+            compFile[SwordConstants.TESTAMENT_OLD] = null;
+
+            idxFile[SwordConstants.TESTAMENT_NEW] = null;
+            textFile[SwordConstants.TESTAMENT_NEW] = null;
+            compFile[SwordConstants.TESTAMENT_NEW] = null;
+
+            return;
+        }
+
         if (idxFile[SwordConstants.TESTAMENT_OLD].canRead())
         {
             try
@@ -244,7 +257,7 @@
                 return ""; //$NON-NLS-1$
             }
 
-            // 10 because we the index is 10 bytes long for each verse
+            // 10 because the index is 10 bytes long for each verse
             byte[] temp = SwordUtil.readRAF(compRaf[testament], index * COMP_ENTRY_SIZE, COMP_ENTRY_SIZE);
 
             // If the Bible does not contain the desired verse, return nothing.
@@ -296,7 +309,7 @@
             byte[] chopped = new byte[verseSize];
             System.arraycopy(uncompressed, verseStart, chopped, 0, verseSize);
 
-            return SwordUtil.decode(key, chopped, charset);
+            return SwordUtil.decode(key.getName(), chopped, charset);
         }
         catch (IOException e)
         {
@@ -326,6 +339,11 @@
     }
 
     /**
+     * Whether the book is blocked by Book, Chapter or Verse.
+     */
+    private BlockType blockType;
+
+    /**
      *
      */
     private int lastTestament = -1;




More information about the jsword-svn mailing list