Coverage Report - org.crosswire.jsword.book.sword.TreeKeyIndex
 
Classes in this File Line Coverage Branch Coverage Complexity
TreeKeyIndex
0%
0/73
0%
0/20
2.727
 
 1  
 /**
 2  
  * Distribution License:
 3  
  * JSword is free software; you can redistribute it and/or modify it under
 4  
  * the terms of the GNU Lesser General Public License, version 2.1 or later
 5  
  * as published by the Free Software Foundation. This program is distributed
 6  
  * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 7  
  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 8  
  * See the GNU Lesser General Public License for more details.
 9  
  *
 10  
  * The License is available on the internet at:
 11  
  *      http://www.gnu.org/copyleft/lgpl.html
 12  
  * or by writing to:
 13  
  *      Free Software Foundation, Inc.
 14  
  *      59 Temple Place - Suite 330
 15  
  *      Boston, MA 02111-1307, USA
 16  
  *
 17  
  * © CrossWire Bible Society, 2005 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.book.sword;
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.IOException;
 24  
 import java.io.RandomAccessFile;
 25  
 import java.net.URI;
 26  
 
 27  
 import org.crosswire.common.activate.Activatable;
 28  
 import org.crosswire.common.activate.Activator;
 29  
 import org.crosswire.common.activate.Lock;
 30  
 import org.crosswire.common.util.FileUtil;
 31  
 import org.crosswire.common.util.NetUtil;
 32  
 import org.crosswire.common.util.Reporter;
 33  
 import org.crosswire.jsword.JSMsg;
 34  
 import org.crosswire.jsword.JSOtherMsg;
 35  
 import org.crosswire.jsword.book.BookException;
 36  
 import org.crosswire.jsword.passage.DefaultKeyList;
 37  
 import org.crosswire.jsword.passage.Key;
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 /**
 42  
  * TreeKeyIndex reads Sword index files that are path based. Paths are of the
 43  
  * form /a/b/c, and can be of any depth. The ultimate output of a TreeKeyIndex
 44  
  * is the offset and length of a chunk of data in another file that can be read.
 45  
  * 
 46  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 47  
  * @author DM Smith
 48  
  */
 49  
 public class TreeKeyIndex implements Activatable {
 50  
     /**
 51  
      * Simple ctor
 52  
      * 
 53  
      * @param sbmd 
 54  
      */
 55  0
     public TreeKeyIndex(SwordBookMetaData sbmd) {
 56  0
         bmd = sbmd;
 57  0
     }
 58  
 
 59  
     /**
 60  
      * @return the root TreeNode for the module.
 61  
      * @throws IOException
 62  
      */
 63  
     public TreeNode getRoot() throws IOException {
 64  0
         return getTreeNode(getOffset(0));
 65  
     }
 66  
 
 67  
     /**
 68  
      * Get the parent of the TreeNode.
 69  
      * 
 70  
      * @param node
 71  
      *            the node being worked upon
 72  
      * @return the parent node
 73  
      * @throws IOException
 74  
      */
 75  
     public TreeNode getParent(TreeNode node) throws IOException {
 76  0
         return getTreeNode(getOffset(node.getParent()));
 77  
     }
 78  
 
 79  
     /**
 80  
      * Get the first child of the TreeNode.
 81  
      * 
 82  
      * @param node
 83  
      *            the node being worked upon
 84  
      * @return the first child node
 85  
      * @throws IOException
 86  
      */
 87  
     public TreeNode getFirstChild(TreeNode node) throws IOException {
 88  0
         return getTreeNode(getOffset(node.getFirstChild()));
 89  
     }
 90  
 
 91  
     /**
 92  
      * Get the next sibling of the TreeNode.
 93  
      * 
 94  
      * @param node
 95  
      *            the node being worked upon
 96  
      * @return the next sibling node
 97  
      * @throws IOException
 98  
      */
 99  
     public TreeNode getNextSibling(TreeNode node) throws IOException {
 100  0
         return getTreeNode(getOffset(node.getNextSibling()));
 101  
     }
 102  
 
 103  
     /**
 104  
      * The idx file contains offsets into the dat file.
 105  
      * 
 106  
      * @param index
 107  
      *            the record id
 108  
      * @return an offset into the dat file
 109  
      * @throws IOException
 110  
      */
 111  
     private int getOffset(int index) throws IOException {
 112  0
         if (index == -1) {
 113  0
             return -1;
 114  
         }
 115  
 
 116  0
         checkActive();
 117  0
         byte[] buffer = SwordUtil.readRAF(idxRaf, index, 4);
 118  0
         return SwordUtil.decodeLittleEndian32(buffer, 0);
 119  
     }
 120  
 
 121  
     /**
 122  
      * Given an offset get the TreeNode from the dat file.
 123  
      * 
 124  
      * @param offset
 125  
      *            start of a TreeNode record in the dat file.
 126  
      * @return the TreeNode
 127  
      * @throws IOException
 128  
      */
 129  
     private TreeNode getTreeNode(int offset) throws IOException {
 130  0
         TreeNode node = new TreeNode(offset);
 131  
 
 132  0
         if (offset == -1) {
 133  0
             return node;
 134  
         }
 135  
 
 136  0
         checkActive();
 137  0
         byte[] buffer = SwordUtil.readRAF(datRaf, offset, 12);
 138  0
         node.setParent(SwordUtil.decodeLittleEndian32(buffer, 0));
 139  0
         node.setNextSibling(SwordUtil.decodeLittleEndian32(buffer, 4));
 140  0
         node.setFirstChild(SwordUtil.decodeLittleEndian32(buffer, 8));
 141  
 
 142  0
         buffer = SwordUtil.readUntilRAF(datRaf, (byte) 0);
 143  0
         int size = buffer.length;
 144  0
         if (buffer[size - 1] == 0) {
 145  0
             size--;
 146  
         }
 147  
 
 148  0
         Key key = new DefaultKeyList(null, bmd.getName());
 149  
         // Some of the keys have extraneous whitespace, so remove it.
 150  0
         node.setName(SwordUtil.decode(key.getName(), buffer, size, bmd.getBookCharset()).trim());
 151  
 
 152  0
         buffer = SwordUtil.readNextRAF(datRaf, 2);
 153  0
         int userDataSize = SwordUtil.decodeLittleEndian16(buffer, 0);
 154  0
         if (userDataSize > 0) {
 155  0
             node.setUserData(SwordUtil.readNextRAF(datRaf, userDataSize));
 156  
         }
 157  
 
 158  0
         return node;
 159  
     }
 160  
 
 161  
     /* (non-Javadoc)
 162  
      * @see org.crosswire.common.activate.Activatable#activate(org.crosswire.common.activate.Lock)
 163  
      */
 164  
     public final void activate(Lock lock) {
 165  0
         String path = null;
 166  
         try {
 167  0
             path = getExpandedDataPath();
 168  0
         } catch (BookException e) {
 169  0
             Reporter.informUser(this, e);
 170  0
             return;
 171  0
         }
 172  
 
 173  0
         idxFile = new File(path + EXTENSION_INDEX);
 174  0
         datFile = new File(path + EXTENSION_DATA);
 175  
 
 176  0
         if (!idxFile.canRead()) {
 177  
             // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
 178  
             // {0} is a placeholder for the file.
 179  0
             Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", idxFile.getAbsolutePath())));
 180  0
             return;
 181  
         }
 182  
 
 183  0
         if (!datFile.canRead()) {
 184  
             // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
 185  
             // {0} is a placeholder for the file.
 186  0
             Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", datFile.getAbsolutePath())));
 187  0
             return;
 188  
         }
 189  
 
 190  
         try {
 191  0
             idxRaf = new RandomAccessFile(idxFile, FileUtil.MODE_READ);
 192  0
             datRaf = new RandomAccessFile(datFile, FileUtil.MODE_READ);
 193  0
         } catch (IOException ex) {
 194  0
             log.error("failed to open files", ex);
 195  0
             idxRaf = null;
 196  0
             datRaf = null;
 197  0
         }
 198  0
         active = true;
 199  0
     }
 200  
 
 201  
     /* (non-Javadoc)
 202  
      * @see org.crosswire.common.activate.Activatable#deactivate(org.crosswire.common.activate.Lock)
 203  
      */
 204  
     public final void deactivate(Lock lock) {
 205  
         try {
 206  0
             if (idxRaf != null) {
 207  0
                 idxRaf.close();
 208  
             }
 209  0
             if (datRaf != null) {
 210  0
                 datRaf.close();
 211  
             }
 212  0
         } catch (IOException ex) {
 213  0
             log.error("failed to close nt files", ex);
 214  
         } finally {
 215  0
             idxRaf = null;
 216  0
             datRaf = null;
 217  0
         }
 218  0
         active = false;
 219  0
     }
 220  
 
 221  
     /**
 222  
      * Helper method so we can quickly activate ourselves on access
 223  
      */
 224  
     protected final void checkActive() {
 225  0
         if (!active) {
 226  0
             Activator.activate(this);
 227  
         }
 228  0
     }
 229  
 
 230  
     private String getExpandedDataPath() throws BookException {
 231  0
         URI loc = NetUtil.lengthenURI(bmd.getLibrary(), bmd.getProperty(SwordBookMetaData.KEY_DATA_PATH));
 232  
 
 233  0
         if (loc == null) {
 234  
             // FIXME(DMS): missing parameter
 235  0
             throw new BookException(JSOtherMsg.lookupText("Missing data files for old and new testaments in {0}."));
 236  
         }
 237  
 
 238  0
         return new File(loc.getPath()).getAbsolutePath();
 239  
     }
 240  
 
 241  
     private static final String EXTENSION_INDEX = ".idx";
 242  
     private static final String EXTENSION_DATA = ".dat";
 243  
 
 244  
     private SwordBookMetaData bmd;
 245  
     private File idxFile;
 246  
     private File datFile;
 247  
     private RandomAccessFile idxRaf;
 248  
     private RandomAccessFile datRaf;
 249  
     private boolean active;
 250  
 
 251  
     /**
 252  
      * The log stream
 253  
      */
 254  0
     private static final Logger log = LoggerFactory.getLogger(TreeKeyIndex.class);
 255  
 }