Coverage Report - org.crosswire.jsword.book.sword.RawFileBackend
 
Classes in this File Line Coverage Branch Coverage Complexity
RawFileBackend
0%
0/178
0%
0/48
2.826
 
 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, 2009 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.book.sword;
 21  
 
 22  
 import java.io.BufferedInputStream;
 23  
 import java.io.BufferedOutputStream;
 24  
 import java.io.File;
 25  
 import java.io.FileInputStream;
 26  
 import java.io.FileNotFoundException;
 27  
 import java.io.FileOutputStream;
 28  
 import java.io.IOException;
 29  
 import java.io.RandomAccessFile;
 30  
 
 31  
 import org.crosswire.jsword.book.BookException;
 32  
 import org.crosswire.jsword.book.BookMetaData;
 33  
 import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
 34  
 import org.crosswire.jsword.book.sword.state.RawBackendState;
 35  
 import org.crosswire.jsword.book.sword.state.RawFileBackendState;
 36  
 import org.crosswire.jsword.passage.Key;
 37  
 import org.crosswire.jsword.passage.KeyUtil;
 38  
 import org.crosswire.jsword.passage.Verse;
 39  
 import org.crosswire.jsword.versification.Testament;
 40  
 import org.crosswire.jsword.versification.Versification;
 41  
 import org.crosswire.jsword.versification.system.Versifications;
 42  
 import org.slf4j.Logger;
 43  
 import org.slf4j.LoggerFactory;
 44  
 
 45  
 /**
 46  
  * A Raw File format that allows for each verse to have it's own storage. The
 47  
  * basic structure of the index is as follows:
 48  
  * <ul>
 49  
  * <li><strong>incfile</strong> -- Is initialized with 1 and is incremented once
 50  
  * for each non-linked verse that is actually stored in the Book.</li>
 51  
  * <li><strong>idx</strong> -- There is one index file for each testament having
 52  
  * verses, named nt and ot. These index files contain offsets into the
 53  
  * corresponding data file. The idx files are indexed by the ordinal value of
 54  
  * the verse within the Testament for the Book's versification.</li>
 55  
  * <li><strong>dat</strong> -- There is a data file for each testament having
 56  
  * verses, named nt.vss and ot.vss. These data files do not contain the verses
 57  
  * but rather the file names that contain the verse text.</li>
 58  
  * <li><strong>verse</strong> -- For each stored verse there is a file
 59  
  * containing the verse text. The filename is a zero padded number corresponding
 60  
  * to the current increment from incfile, when it was created. It is this 7
 61  
  * character name that is stored in a dat file.</li>
 62  
  * </ul>
 63  
  * 
 64  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 65  
  * @author mbergmann
 66  
  * @author DM Smith
 67  
  */
 68  0
 public class RawFileBackend extends RawBackend<RawFileBackendState> {
 69  
 
 70  
     public RawFileBackend(SwordBookMetaData sbmd, int datasize) {
 71  0
         super(sbmd, datasize);
 72  0
     }
 73  
 
 74  
     @Override
 75  
     public RawFileBackendState initState() throws BookException {
 76  0
         return OpenFileStateManager.instance().getRawFileBackendState(getBookMetaData());
 77  
     }
 78  
 
 79  
     /* (non-Javadoc)
 80  
      * @see org.crosswire.jsword.book.sword.RawBackend#getEntry(java.lang.String, org.crosswire.jsword.versification.Testament, long)
 81  
      */
 82  
     @Override
 83  
     protected String getEntry(RawBackendState state, String name, Testament testament, long index) throws IOException {
 84  
         RandomAccessFile idxRaf;
 85  
         RandomAccessFile txtRaf;
 86  0
         idxRaf = state.getIdxRaf(testament);
 87  0
         txtRaf = state.getTextRaf(testament);
 88  
 
 89  0
         DataIndex dataIndex = getIndex(idxRaf, index);
 90  0
         int size = dataIndex.getSize();
 91  0
         if (size == 0) {
 92  0
             return "";
 93  
         }
 94  
 
 95  0
         if (size < 0) {
 96  0
             log.error("In {}: Verse {} has a bad index size of {}.", getBookMetaData().getInitials(), name, Integer.toString(size));
 97  0
             return "";
 98  
         }
 99  
 
 100  
         try {
 101  0
             File dataFile = getDataTextFile(txtRaf, dataIndex);
 102  0
             byte[] textBytes = readTextDataFile(dataFile);
 103  0
             decipher(textBytes);
 104  0
             return SwordUtil.decode(name, textBytes, getBookMetaData().getBookCharset());
 105  0
         } catch (BookException e) {
 106  0
             throw new IOException(e.getMessage());
 107  
         }
 108  
     }
 109  
 
 110  
     /* (non-Javadoc)
 111  
      * @see org.crosswire.jsword.book.sword.RawBackend#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
 112  
      * 
 113  
      */
 114  
     public void setRawText(RawFileBackendState state, Key key, String text) throws BookException, IOException {
 115  
 
 116  0
         String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
 117  0
         Versification v11n = Versifications.instance().getVersification(v11nName);
 118  0
         Verse verse = KeyUtil.getVerse(key);
 119  0
         int index = verse.getOrdinal();
 120  0
         Testament testament = v11n.getTestament(index);
 121  0
         index = v11n.getTestamentOrdinal(index);
 122  
 
 123  0
         RandomAccessFile idxRaf = state.getIdxRaf(testament);
 124  0
         RandomAccessFile txtRaf = state.getTextRaf(testament);
 125  0
         File txtFile = state.getTextFile(testament);
 126  
 
 127  0
         DataIndex dataIndex = getIndex(idxRaf, index);
 128  
         File dataFile;
 129  0
         if (dataIndex.getSize() == 0) {
 130  0
             dataFile = createDataTextFile(state.getIncfileValue());
 131  0
             updateIndexFile(idxRaf, index, txtRaf.length());
 132  0
             updateDataFile(state.getIncfileValue(), txtFile);
 133  0
             checkAndIncrementIncfile(state, state.getIncfileValue());
 134  
         } else {
 135  0
             dataFile = getDataTextFile(txtRaf, dataIndex);
 136  
         }
 137  
 
 138  0
         byte[] textData = text.getBytes("UTF-8");
 139  0
         encipher(textData);
 140  0
         writeTextDataFile(dataFile, textData);
 141  0
     }
 142  
 
 143  
     public void setAliasKey(RawFileBackendState state, Key alias, Key source) throws IOException {
 144  0
         String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
 145  0
         Versification v11n = Versifications.instance().getVersification(v11nName);
 146  0
         Verse aliasVerse = KeyUtil.getVerse(alias);
 147  0
         Verse sourceVerse = KeyUtil.getVerse(source);
 148  0
         int aliasIndex = aliasVerse.getOrdinal();
 149  0
         Testament testament = v11n.getTestament(aliasIndex);
 150  0
         aliasIndex = v11n.getTestamentOrdinal(aliasIndex);
 151  0
         RandomAccessFile idxRaf = state.getIdxRaf(testament);
 152  
 
 153  0
         int sourceOIndex = sourceVerse.getOrdinal();
 154  0
         sourceOIndex = v11n.getTestamentOrdinal(sourceOIndex);
 155  0
         DataIndex dataIndex = getIndex(idxRaf, sourceOIndex);
 156  
 
 157  
         // Only the index is updated to point to the same place as what is
 158  
         // linked.
 159  0
         updateIndexFile(idxRaf, aliasIndex, dataIndex.getOffset());
 160  0
     }
 161  
 
 162  
     private File createDataTextFile(int index) throws BookException, IOException {
 163  0
         String dataPath = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
 164  0
         dataPath += File.separator + String.format("%07d", Integer.valueOf(index));
 165  0
         File dataFile = new File(dataPath);
 166  0
         if (!dataFile.exists() && !dataFile.createNewFile()) {
 167  0
             throw new IOException("Could not create data file.");
 168  
         }
 169  0
         return dataFile;
 170  
     }
 171  
 
 172  
     /**
 173  
      * Gets the Filename for the File having the verse text.
 174  
      * 
 175  
      * @param txtRaf
 176  
      *            The random access file containing the file names for the verse
 177  
      *            storage.
 178  
      * @param dataIndex
 179  
      *            The index of where to get the data
 180  
      * @return the file having the verse text.
 181  
      * @throws IOException
 182  
      */
 183  
     private String getTextFilename(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException {
 184  
         // data size to be read from the data file (ot or nt) should be 9 bytes
 185  
         // this will be the filename of the actual text file "\r\n"
 186  0
         byte[] data = SwordUtil.readRAF(txtRaf, dataIndex.getOffset(), dataIndex.getSize());
 187  0
         decipher(data);
 188  0
         if (data.length == 7) {
 189  0
             return new String(data, 0, 7);
 190  
         }
 191  0
         log.error("Read data is not of appropriate size of 9 bytes!");
 192  0
         throw new IOException("Datalength is not 9 bytes!");
 193  
     }
 194  
 
 195  
     /**
 196  
      * Gets the File having the verse text.
 197  
      * 
 198  
      * @param txtRaf
 199  
      *            The random access file containing the file names for the verse
 200  
      *            storage.
 201  
      * @param dataIndex
 202  
      *            The index of where to get the data
 203  
      * @return the file having the verse text.
 204  
      * @throws IOException
 205  
      * @throws BookException
 206  
      */
 207  
     private File getDataTextFile(RandomAccessFile txtRaf, DataIndex dataIndex) throws IOException, BookException {
 208  0
         String dataFilename = getTextFilename(txtRaf, dataIndex);
 209  0
         String dataPath = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath() + File.separator + dataFilename;
 210  0
         return new File(dataPath);
 211  
     }
 212  
 
 213  
     protected void updateIndexFile(RandomAccessFile idxRaf, long index, long dataFileStartPosition) throws IOException {
 214  0
         long indexFileWriteOffset = index * entrysize;
 215  0
         int dataFileLengthValue = 7; // filename is 7 bytes + 2 bytes for "\r\n"
 216  0
         byte[] startPositionData = littleEndian32BitByteArrayFromInt((int) dataFileStartPosition);
 217  0
         byte[] lengthValueData = littleEndian16BitByteArrayFromShort((short) dataFileLengthValue);
 218  0
         byte[] indexFileWriteData = new byte[6];
 219  
 
 220  0
         indexFileWriteData[0] = startPositionData[0];
 221  0
         indexFileWriteData[1] = startPositionData[1];
 222  0
         indexFileWriteData[2] = startPositionData[2];
 223  0
         indexFileWriteData[3] = startPositionData[3];
 224  0
         indexFileWriteData[4] = lengthValueData[0];
 225  0
         indexFileWriteData[5] = lengthValueData[1];
 226  
 
 227  0
         SwordUtil.writeRAF(idxRaf, indexFileWriteOffset, indexFileWriteData);
 228  0
     }
 229  
 
 230  
     protected void updateDataFile(long ordinal, File txtFile) throws IOException {
 231  0
         String fileName = String.format("%07d\r\n", Long.valueOf(ordinal));
 232  0
         BufferedOutputStream bos = null;
 233  
         try {
 234  0
             bos = new BufferedOutputStream(new FileOutputStream(txtFile, true));
 235  0
             bos.write(fileName.getBytes(getBookMetaData().getBookCharset()));
 236  
         } finally {
 237  0
             if (bos != null) {
 238  0
                 bos.close();
 239  
             }
 240  
         }
 241  0
     }
 242  
 
 243  
     private void checkAndIncrementIncfile(RawFileBackendState state, int index) throws IOException {
 244  0
         if (index >= state.getIncfileValue()) {
 245  0
             int incValue = index + 1;
 246  
 
 247  
             // FIXME(CJB) this portion of code is unsafe for concurrency
 248  
             // operations, but without knowing the
 249  
             // reason for the inc file, I would guess it unlikely to be a
 250  
             // problem
 251  
             // this essentially destroys consistency of the state
 252  0
             state.setIncfileValue(incValue);
 253  0
             writeIncfile(state, incValue);
 254  
         }
 255  0
     }
 256  
 
 257  
     /*
 258  
      * (non-Javadoc)
 259  
      * 
 260  
      * @see org.crosswire.jsword.book.sword.RawBackend#create()
 261  
      */
 262  
     // FIXME(CJB) : DM - API change to pass in state, or is this a one-off for
 263  
     // which we want to release resources afterwards (opted for the second
 264  
     // option)
 265  
     @Override
 266  
     public void create() throws IOException, BookException {
 267  0
         super.create();
 268  
 
 269  0
         createDataFiles();
 270  0
         createIndexFiles();
 271  
 
 272  0
         RawFileBackendState state = null;
 273  
         try {
 274  0
             state = initState();
 275  
 
 276  0
             createIncfile(state);
 277  
 
 278  0
             prepopulateIndexFiles(state);
 279  0
             prepopulateIncfile(state);
 280  
         } finally {
 281  0
             OpenFileStateManager.instance().release(state);
 282  0
         }
 283  0
     }
 284  
 
 285  
     private void createDataFiles() throws IOException, BookException {
 286  0
         String path = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
 287  
 
 288  0
         File otTextFile = new File(path + File.separator + SwordConstants.FILE_OT);
 289  0
         if (!otTextFile.exists() && !otTextFile.createNewFile()) {
 290  0
             throw new IOException("Could not create ot text file.");
 291  
         }
 292  
 
 293  0
         File ntTextFile = new File(path + File.separator + SwordConstants.FILE_NT);
 294  0
         if (!ntTextFile.exists() && !ntTextFile.createNewFile()) {
 295  0
             throw new IOException("Could not create nt text file.");
 296  
         }
 297  0
     }
 298  
 
 299  
     private void createIndexFiles() throws IOException, BookException {
 300  0
         String path = SwordUtil.getExpandedDataPath(getBookMetaData()).getPath();
 301  0
         File otIndexFile = new File(path + File.separator + SwordConstants.FILE_OT + SwordConstants.EXTENSION_VSS);
 302  0
         if (!otIndexFile.exists() && !otIndexFile.createNewFile()) {
 303  0
             throw new IOException("Could not create ot index file.");
 304  
         }
 305  
 
 306  0
         File ntIndexFile = new File(path + File.separator + SwordConstants.FILE_NT + SwordConstants.EXTENSION_VSS);
 307  0
         if (!ntIndexFile.exists() && !ntIndexFile.createNewFile()) {
 308  0
             throw new IOException("Could not create nt index file.");
 309  
         }
 310  0
     }
 311  
 
 312  
     private void prepopulateIndexFiles(RawFileBackendState state) throws IOException {
 313  
 
 314  0
         String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
 315  0
         Versification v11n = Versifications.instance().getVersification(v11nName);
 316  0
         int otCount = v11n.getCount(Testament.OLD);
 317  0
         int ntCount = v11n.getCount(Testament.NEW) + 1;
 318  0
         BufferedOutputStream otIdxBos = new BufferedOutputStream(new FileOutputStream(state.getIdxFile(Testament.OLD), false));
 319  
         try {
 320  0
             for (int i = 0; i < otCount; i++) {
 321  0
                 writeInitialIndex(otIdxBos);
 322  
             }
 323  
         } finally {
 324  0
             otIdxBos.close();
 325  0
         }
 326  
 
 327  0
         BufferedOutputStream ntIdxBos = new BufferedOutputStream(new FileOutputStream(state.getIdxFile(Testament.NEW), false));
 328  
         try {
 329  0
             for (int i = 0; i < ntCount; i++) {
 330  0
                 writeInitialIndex(ntIdxBos);
 331  
             }
 332  
         } finally {
 333  0
             ntIdxBos.close();
 334  0
         }
 335  0
     }
 336  
 
 337  
     private void createIncfile(RawFileBackendState state) throws IOException, BookException {
 338  0
         File tempIncfile = new File(SwordUtil.getExpandedDataPath(getBookMetaData()).getPath() + File.separator + RawFileBackendState.INCFILE);
 339  0
         if (!tempIncfile.exists() && !tempIncfile.createNewFile()) {
 340  0
             throw new IOException("Could not create incfile file.");
 341  
         }
 342  0
         state.setIncfile(tempIncfile);
 343  0
     }
 344  
 
 345  
     private void prepopulateIncfile(RawFileBackendState state) throws IOException {
 346  0
         writeIncfile(state, 1);
 347  0
     }
 348  
 
 349  
     private void writeIncfile(RawFileBackendState state, int value) throws IOException {
 350  0
         FileOutputStream fos = null;
 351  
         try {
 352  0
             fos = new FileOutputStream(state.getIncfile(), false);
 353  0
             fos.write(littleEndian32BitByteArrayFromInt(value));
 354  0
         } catch (FileNotFoundException e) {
 355  0
             log.error("Error on writing to incfile, file should exist already!", e);
 356  
         } finally {
 357  0
             if (fos != null) {
 358  0
                 fos.close();
 359  
             }
 360  
         }
 361  0
     }
 362  
 
 363  
     private void writeInitialIndex(BufferedOutputStream outStream) throws IOException {
 364  0
         outStream.write(littleEndian32BitByteArrayFromInt(0)); // offset
 365  0
         outStream.write(littleEndian16BitByteArrayFromShort((short) 0)); // length
 366  0
     }
 367  
 
 368  
     private byte[] readTextDataFile(File dataFile) throws IOException {
 369  0
         BufferedInputStream inStream = null;
 370  
         try {
 371  0
             int len = (int) dataFile.length();
 372  0
             byte[] textData = new byte[len];
 373  0
             inStream = new BufferedInputStream(new FileInputStream(dataFile));
 374  0
             if (inStream.read(textData) != len) {
 375  0
                 log.error("Read data is not of appropriate size of {} bytes!", Integer.toString(len));
 376  0
                 throw new IOException("data is not " + len + " bytes long");
 377  
             }
 378  0
             return textData;
 379  0
         } catch (FileNotFoundException ex) {
 380  0
             log.error("Could not read text data file, file not found: {}", dataFile.getName(), ex);
 381  0
             throw ex;
 382  
         } finally {
 383  0
             if (inStream != null) {
 384  0
                 inStream.close();
 385  
             }
 386  
         }
 387  
     }
 388  
 
 389  
     private void writeTextDataFile(File dataFile, byte[] textData) throws IOException {
 390  0
         BufferedOutputStream bos = null;
 391  
         try {
 392  0
             bos = new BufferedOutputStream(new FileOutputStream(dataFile, false));
 393  0
             bos.write(textData);
 394  
         } finally {
 395  0
             if (bos != null) {
 396  0
                 bos.close();
 397  
             }
 398  
         }
 399  0
     }
 400  
 
 401  
     private byte[] littleEndian32BitByteArrayFromInt(int val) {
 402  0
         byte[] buffer = new byte[4];
 403  0
         SwordUtil.encodeLittleEndian32(val, buffer, 0);
 404  0
         return buffer;
 405  
     }
 406  
 
 407  
     private byte[] littleEndian16BitByteArrayFromShort(short val) {
 408  0
         byte[] buffer = new byte[2];
 409  0
         SwordUtil.encodeLittleEndian16(val, buffer, 0);
 410  0
         return buffer;
 411  
     }
 412  
 
 413  0
     private static final Logger log = LoggerFactory.getLogger(RawFileBackend.class);
 414  
 }