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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: RawBackend.java 2233 2012-03-05 11:38:20Z dmsmith $
21   */
22  package org.crosswire.jsword.book.sword;
23  
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.RandomAccessFile;
28  import java.net.URI;
29  
30  import org.crosswire.common.activate.Activator;
31  import org.crosswire.common.activate.Lock;
32  import org.crosswire.common.util.FileUtil;
33  import org.crosswire.common.util.Logger;
34  import org.crosswire.common.util.NetUtil;
35  import org.crosswire.common.util.Reporter;
36  import org.crosswire.jsword.JSMsg;
37  import org.crosswire.jsword.JSOtherMsg;
38  import org.crosswire.jsword.book.BookException;
39  import org.crosswire.jsword.passage.Key;
40  import org.crosswire.jsword.passage.KeyUtil;
41  import org.crosswire.jsword.passage.Verse;
42  import org.crosswire.jsword.versification.Testament;
43  import org.crosswire.jsword.versification.Versification;
44  import org.crosswire.jsword.versification.system.Versifications;
45  
46  /**
47   * Both Books and Commentaries seem to use the same format so this class
48   * abstracts out the similarities.
49   * 
50   * @see gnu.lgpl.License for license details.<br>
51   *      The copyright to this program is held by it's authors.
52   * @author Joe Walker [joe at eireneh dot com]
53   */
54  public class RawBackend extends AbstractBackend {
55      /**
56       * Simple ctor
57       */
58      public RawBackend(SwordBookMetaData sbmd, int datasize) {
59          super(sbmd);
60          this.datasize = datasize;
61          this.entrysize = OFFSETSIZE + datasize;
62  
63          assert datasize == 2 || datasize == 4;
64      }
65  
66      /* (non-Javadoc)
67       * @see org.crosswire.jsword.book.sword.AbstractBackend#contains(org.crosswire.jsword.passage.Key)
68       */
69      @Override
70      public boolean contains(Key key) {
71          checkActive();
72  
73          Verse verse = KeyUtil.getVerse(key);
74  
75          try {
76              String v11nName = getBookMetaData().getProperty(ConfigEntryType.VERSIFICATION).toString();
77              Versification v11n = Versifications.instance().getVersification(v11nName);
78              int index = v11n.getOrdinal(verse);
79              Testament testament = v11n.getTestament(index);
80              index = v11n.getTestamentOrdinal(index);
81              RandomAccessFile idxRaf = otIdxRaf;
82              if (testament == Testament.NEW) {
83                  idxRaf = ntIdxRaf;
84              }
85  
86              // If this is a single testament Bible, return nothing.
87              if (idxRaf == null) {
88                  return false;
89              }
90  
91              DataIndex dataIndex = getIndex(idxRaf, index);
92  
93              return dataIndex.getSize() > 0;
94          } catch (IOException ex) {
95              return false;
96          }
97      }
98  
99      /* (non-Javadoc)
100      * @see org.crosswire.jsword.book.sword.AbstractBackend#getRawText(org.crosswire.jsword.passage.Key)
101      */
102     @Override
103     public String getRawText(Key key) throws BookException {
104         checkActive();
105 
106         Verse verse = KeyUtil.getVerse(key);
107         try {
108             String v11nName = getBookMetaData().getProperty(ConfigEntryType.VERSIFICATION).toString();
109             Versification v11n = Versifications.instance().getVersification(v11nName);
110             int index = v11n.getOrdinal(verse);
111             Testament testament = v11n.getTestament(index);
112             index = v11n.getTestamentOrdinal(index);
113             RandomAccessFile idxRaf = otIdxRaf;
114             if (testament == Testament.NEW) {
115                 idxRaf = ntIdxRaf;
116             }
117 
118             // If this is a single testament Bible, return nothing.
119             if (idxRaf == null) {
120                 return "";
121             }
122 
123             return getEntry(key.getName(), testament, index);
124         } catch (IOException ex) {
125             // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
126             // {0} is a placeholder for the file.
127             throw new BookException(JSMsg.gettext("Error reading {0}", verse.getName()), ex);
128         }
129     }
130 
131     /* (non-Javadoc)
132      * @see org.crosswire.jsword.book.sword.AbstractBackend#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
133      */
134     @Override
135     public void setRawText(Key key, String text) throws BookException, IOException {
136     }
137 
138     /* (non-Javadoc)
139      * @see org.crosswire.jsword.book.sword.AbstractBackend#isWritable()
140      */
141     @Override
142     public boolean isWritable() {
143         // For the module to be writable either the old testament or the new
144         // testament needs to be present
145         // (i.e. readable) and both the index and the data files need to be
146         // writable
147         if (otIdxFile.canRead() && (otIdxFile.canWrite() || !otTxtFile.canWrite())) {
148             return false;
149         }
150         if (ntIdxFile.canRead() && (ntIdxFile.canWrite() || !ntTxtFile.canWrite())) {
151             return false;
152         }
153         return otIdxFile.canRead() || ntIdxFile.canRead();
154     }
155 
156     /* (non-Javadoc)
157      * @see org.crosswire.jsword.book.sword.AbstractBackend#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
158      */
159     @Override
160     public void setAliasKey(Key alias, Key source) throws IOException {
161         throw new UnsupportedOperationException();
162     }
163 
164     /* (non-Javadoc)
165      * @see org.crosswire.common.activate.Activatable#activate(org.crosswire.common.activate.Lock)
166      */
167     public final void activate(Lock lock) {
168         URI path = null;
169         try {
170             path = getExpandedDataPath();
171         } catch (BookException e) {
172             Reporter.informUser(this, e);
173             return;
174         }
175 
176         URI otPath = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_OT);
177         otTxtFile = new File(otPath.getPath());
178         otIdxFile = new File(otPath.getPath() + SwordConstants.EXTENSION_VSS);
179 
180         URI ntPath = NetUtil.lengthenURI(path, File.separator + SwordConstants.FILE_NT);
181         ntTxtFile = new File(ntPath.getPath());
182         ntIdxFile = new File(ntPath.getPath() + SwordConstants.EXTENSION_VSS);
183 
184         // It is an error to be neither OT nor NT
185         if (!otTxtFile.canRead() && !ntTxtFile.canRead()) {
186             Reporter.informUser(this, new BookException(JSOtherMsg.lookupText("Missing data files for old and new testaments in {0}.", path)));
187             return;
188         }
189 
190         String fileMode = isWritable() ? FileUtil.MODE_WRITE : FileUtil.MODE_READ;
191 
192         if (otIdxFile.canRead()) {
193             try {
194                 otIdxRaf = new RandomAccessFile(otIdxFile, fileMode);
195                 otTxtRaf = new RandomAccessFile(otTxtFile, fileMode);
196             } catch (FileNotFoundException ex) {
197                 assert false : ex;
198                 log.error("Could not open OT", ex);
199                 ntIdxRaf = null;
200                 ntTxtRaf = null;
201             }
202         }
203 
204         if (ntIdxFile.canRead()) {
205             try {
206                 ntIdxRaf = new RandomAccessFile(ntIdxFile, fileMode);
207                 ntTxtRaf = new RandomAccessFile(ntTxtFile, fileMode);
208             } catch (FileNotFoundException ex) {
209                 assert false : ex;
210                 log.error("Could not open NT", ex);
211                 ntIdxRaf = null;
212                 ntTxtRaf = null;
213             }
214         }
215 
216         active = true;
217     }
218 
219     /* (non-Javadoc)
220      * @see org.crosswire.common.activate.Activatable#deactivate(org.crosswire.common.activate.Lock)
221      */
222     public final void deactivate(Lock lock) {
223         try {
224             otIdxRaf.close();
225             otTxtRaf.close();
226 
227             ntIdxRaf.close();
228             ntTxtRaf.close();
229         } catch (IOException ex) {
230             log.error("Failed to close files", ex);
231         } finally {
232             otIdxRaf = null;
233             otTxtRaf = null;
234 
235             ntIdxRaf = null;
236             ntTxtRaf = null;
237         }
238 
239         active = false;
240     }
241 
242     /**
243      * Helper method so we can quickly activate ourselves on access
244      */
245     protected final void checkActive() {
246         if (!active) {
247             Activator.activate(this);
248         }
249     }
250 
251     /**
252      * Get the Index (that is offset and size) for an entry.
253      * 
254      * @param entry
255      * @return the index for the entry
256      * @throws IOException
257      */
258     protected DataIndex getIndex(RandomAccessFile raf, long entry) throws IOException {
259         // Read the offset and size for this key from the index
260         byte[] buffer = SwordUtil.readRAF(raf, entry * entrysize, entrysize);
261         if (buffer == null || buffer.length == 0) {
262             return new DataIndex(0, 0);
263         }
264 
265         int entryOffset = SwordUtil.decodeLittleEndian32(buffer, 0);
266         int entrySize = -1;
267         switch (datasize) {
268         case 2:
269             entrySize = SwordUtil.decodeLittleEndian16(buffer, 4);
270             break;
271         case 4:
272             entrySize = SwordUtil.decodeLittleEndian32(buffer, 4);
273             break;
274         default:
275             assert false : datasize;
276         }
277         return new DataIndex(entryOffset, entrySize);
278     }
279 
280     /**
281      * Get the text for an indexed entry in the book.
282      * 
283      * @param index
284      *            the entry to get
285      * @param name
286      *            name of the entry
287      * @param testament
288      *            the testament for the entry
289      * @return the text for the entry.
290      * @throws IOException
291      *             on a IO problem
292      */
293     protected String getEntry(String name, Testament testament, long index) throws IOException {
294         RandomAccessFile idxRaf = otIdxRaf;
295         RandomAccessFile txtRaf = otTxtRaf;
296         if (testament == Testament.NEW) {
297             idxRaf = ntIdxRaf;
298             txtRaf = ntTxtRaf;
299         }
300 
301         DataIndex dataIndex = getIndex(idxRaf, index);
302 
303         int size = dataIndex.getSize();
304         if (size == 0) {
305             return "";
306         }
307 
308         if (size < 0) {
309             log.error("In " + getBookMetaData().getInitials() + ": Verse " + name + " has a bad index size of " + size);
310             return "";
311         }
312 
313         byte[] data = SwordUtil.readRAF(txtRaf, dataIndex.getOffset(), size);
314 
315         decipher(data);
316 
317         return SwordUtil.decode(name, data, getBookMetaData().getBookCharset());
318     }
319 
320     /**
321      * Are we active
322      */
323     protected boolean active;
324 
325     /**
326      * How many bytes in the size count in the index
327      */
328     protected int datasize;
329 
330     /**
331      * The number of bytes for each entry in the index: either 6 or 8
332      */
333     protected int entrysize;
334 
335     protected RandomAccessFile otIdxRaf;
336     protected RandomAccessFile otTxtRaf;
337     protected File otIdxFile;
338     protected File otTxtFile;
339 
340     protected RandomAccessFile ntIdxRaf;
341     protected RandomAccessFile ntTxtRaf;
342     protected File ntIdxFile;
343     protected File ntTxtFile;
344 
345     /**
346      * How many bytes in the offset pointers in the index
347      */
348     protected static final int OFFSETSIZE = 4;
349 
350     /**
351      * The log stream
352      */
353     private static final Logger log = Logger.getLogger(RawBackend.class);
354 }
355