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.IOException;
23  import java.io.RandomAccessFile;
24  
25  import org.crosswire.jsword.JSMsg;
26  import org.crosswire.jsword.book.BookException;
27  import org.crosswire.jsword.book.BookMetaData;
28  import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
29  import org.crosswire.jsword.book.sword.state.RawBackendState;
30  import org.crosswire.jsword.passage.BitwisePassage;
31  import org.crosswire.jsword.passage.Key;
32  import org.crosswire.jsword.passage.KeyUtil;
33  import org.crosswire.jsword.passage.RocketPassage;
34  import org.crosswire.jsword.passage.Verse;
35  import org.crosswire.jsword.versification.Testament;
36  import org.crosswire.jsword.versification.Versification;
37  import org.crosswire.jsword.versification.system.Versifications;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Both Books and Commentaries seem to use the same format so this class
43   * abstracts out the similarities.
44   * 
45   * @param <T> The type of the RawBackendState that this class extends.
46   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
47   * @author Joe Walker
48   */
49  public class RawBackend<T extends RawBackendState> extends AbstractBackend<RawBackendState> {
50  
51      /**
52       * Simple ctor
53       * 
54       * @param sbmd 
55       * @param datasize 
56       */
57      public RawBackend(SwordBookMetaData sbmd, int datasize) {
58          super(sbmd);
59          this.datasize = datasize;
60          this.entrysize = OFFSETSIZE + datasize;
61  
62          assert datasize == 2 || datasize == 4;
63      }
64  
65      /* (non-Javadoc)
66       * @see org.crosswire.jsword.book.sword.AbstractBackend#contains(org.crosswire.jsword.passage.Key)
67       */
68      @Override
69      public boolean contains(Key key) {
70          return getRawTextLength(key) > 0;
71      }
72  
73      /* (non-Javadoc)
74       * @see org.crosswire.jsword.book.sword.AbstractBackend#size(org.crosswire.jsword.passage.Key)
75       */
76      @Override
77      public int getRawTextLength(Key key) {
78          String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
79          Versification v11n = Versifications.instance().getVersification(v11nName);
80          Verse verse = KeyUtil.getVerse(key);
81  
82          RawBackendState initState = null;
83          try {
84              int index = verse.getOrdinal();
85              Testament testament = v11n.getTestament(index);
86              index = v11n.getTestamentOrdinal(index);
87              initState = initState();
88              RandomAccessFile idxRaf = initState.getIdxRaf(testament);
89  
90              // If this is a single testament Bible, return nothing.
91              if (idxRaf == null) {
92                  return 0;
93              }
94  
95              DataIndex dataIndex = getIndex(idxRaf, index);
96  
97              return dataIndex.getSize();
98          } catch (IOException ex) {
99              return 0;
100         } catch (BookException e) {
101             return 0;
102         } finally {
103             OpenFileStateManager.instance().release(initState);
104         }
105     }
106 
107     @Override
108     public Key getGlobalKeyList() throws BookException {
109         RawBackendState rafBook = null;
110         try {
111             rafBook = initState();
112 
113             String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
114             Versification v11n = Versifications.instance().getVersification(v11nName);
115 
116             Testament[] testaments = new Testament[] {
117                     Testament.OLD, Testament.NEW
118             };
119 
120             BitwisePassage passage = new RocketPassage(v11n);
121             passage.raiseEventSuppresion();
122             passage.raiseNormalizeProtection();
123 
124             for (Testament currentTestament : testaments) {
125                 RandomAccessFile idxRaf = rafBook.getIdxRaf(currentTestament);
126 
127                 // If Bible does not contain the desired testament, then false
128                 if (idxRaf == null) {
129                     // no keys in this testament
130                     continue;
131                 }
132 
133                 int maxIndex = v11n.getCount(currentTestament) - 1;
134 
135                 // Read in the whole index, a few hundred Kb at most.
136                 byte[] temp = SwordUtil.readRAF(idxRaf, 0, entrysize * maxIndex);
137 
138                 // For each entry of entrysize bytes, the length of the verse in bytes
139                 // is in the last datasize bytes. If all bytes are 0, then there is no content.
140                 if (datasize == 2) {
141                     for (int ii = 0; ii < temp.length; ii += entrysize) {
142                         // This can be simplified to temp[ii + 4] == 0 && temp[ii + 5] == 0.
143                         // int verseSize = SwordUtil.decodeLittleEndian16(temp, ii + 4);
144                         // if (verseSize > 0) {
145                         if (temp[ii + 4] != 0 || temp[ii + 5] != 0) {
146                             int ordinal = ii / entrysize;
147                             passage.addVersifiedOrdinal(v11n.getOrdinal(currentTestament, ordinal));
148                         }
149                     }
150                 } else { // datasize == 4
151                     for (int ii = 0; ii < temp.length; ii += entrysize) {
152                         // This can be simplified to temp[ii + 4] == 0 && temp[ii + 5] == 0 && temp[ii + 6] == 0 && temp[ii + 7] == 0.
153                         // int verseSize = SwordUtil.decodeLittleEndian32(temp, ii + 4);
154                         // if (verseSize > 0) {
155                         if (temp[ii + 4] != 0 || temp[ii + 5] != 0 || temp[ii + 6] != 0 || temp[ii + 7] != 0) {
156                             int ordinal = ii / entrysize;
157                             passage.addVersifiedOrdinal(v11n.getOrdinal(currentTestament, ordinal));
158                         }
159                     }
160                 }
161             }
162 
163             passage.lowerNormalizeProtection();
164             passage.lowerEventSuppressionAndTest();
165 
166             return passage;
167         } catch (IOException e) {
168             throw new BookException(JSMsg.gettext("Unable to read key list from book."));
169         } finally {
170             OpenFileStateManager.instance().release(rafBook);
171         }
172     }
173 
174     public T initState() throws BookException {
175         return (T) OpenFileStateManager.instance().getRawBackendState(getBookMetaData());
176     }
177 
178     public String getRawText(RawBackendState state, Key key) throws IOException {
179         return readRawContent(state, key);
180     }
181 
182     /* (non-Javadoc)
183      * @see org.crosswire.jsword.book.sword.AbstractBackend#getRawText(org.crosswire.jsword.passage.Key)
184      */
185     public String readRawContent(RawBackendState state, Key key) throws IOException {
186         String v11nName = getBookMetaData().getProperty(BookMetaData.KEY_VERSIFICATION);
187         Versification v11n = Versifications.instance().getVersification(v11nName);
188         Verse verse = KeyUtil.getVerse(key);
189 
190         int index = verse.getOrdinal();
191 
192         Testament testament = v11n.getTestament(index);
193         index = v11n.getTestamentOrdinal(index);
194         RawBackendState initState = null;
195         try {
196             initState = initState();
197             return getEntry(state, verse.getName(), testament, index);
198         } catch (BookException e) {
199             return "";
200         } finally {
201             OpenFileStateManager.instance().release(initState);
202         }
203     }
204 
205     /* (non-Javadoc)
206      * @see org.crosswire.jsword.book.sword.AbstractBackend#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
207      */
208     public void setRawText(RawBackendState state, Key key, String text) throws BookException, IOException {
209     }
210 
211     /* (non-Javadoc)
212      * @see org.crosswire.jsword.book.sword.AbstractBackend#isWritable()
213      */
214     @Override
215     public boolean isWritable() {
216         RawBackendState rawBackendState = null;
217         try {
218         rawBackendState = initState();
219         return rawBackendState.isWritable();
220         } catch (BookException e) {
221             return false;
222         } finally {
223             OpenFileStateManager.instance().release(rawBackendState);
224         }
225     }
226 
227     /* (non-Javadoc)
228      * @see org.crosswire.jsword.book.sword.AbstractBackend#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
229      */
230     public void setAliasKey(RawBackendState state, Key alias, Key source) throws IOException {
231         throw new UnsupportedOperationException();
232     }
233 
234     /**
235      * Get the Index (that is offset and size) for an entry.
236      * 
237      * @param entry
238      * @return the index for the entry
239      * @throws IOException
240      */
241     protected DataIndex getIndex(RandomAccessFile raf, long entry) throws IOException {
242         // Read the offset and size for this key from the index
243         byte[] buffer = SwordUtil.readRAF(raf, entry * entrysize, entrysize);
244         if (buffer == null || buffer.length == 0) {
245             return new DataIndex(0, 0);
246         }
247 
248         int entryOffset = SwordUtil.decodeLittleEndian32(buffer, 0);
249         int entrySize = -1;
250         switch (datasize) {
251         case 2:
252             entrySize = SwordUtil.decodeLittleEndian16(buffer, 4);
253             break;
254         case 4:
255             entrySize = SwordUtil.decodeLittleEndian32(buffer, 4);
256             break;
257         default:
258             assert false : datasize;
259         }
260         return new DataIndex(entryOffset, entrySize);
261     }
262 
263     /**
264      * Get the text for an indexed entry in the book.
265      * @param state 
266      * 
267      * @param index
268      *            the entry to get
269      * @param name
270      *            name of the entry
271      * @param testament
272      *            the testament for the entry
273      * @return the text for the entry.
274      * @throws IOException
275      *             on a IO problem
276      */
277     protected String getEntry(RawBackendState state, String name, Testament testament, long index) throws IOException {
278         final RandomAccessFile idxRaf;
279         final RandomAccessFile txtRaf;
280         idxRaf = state.getIdxRaf(testament);
281         txtRaf = state.getTextRaf(testament);
282 
283         // It may be that this is a single testament Bible
284         if (idxRaf == null) {
285             return "";
286         }
287 
288         DataIndex dataIndex = getIndex(idxRaf, index);
289 
290         int size = dataIndex.getSize();
291         if (size == 0) {
292             return "";
293         }
294 
295         if (size < 0) {
296             log.error("In {}: Verse {} has a bad index size of {}", getBookMetaData().getInitials(), name, Integer.toString(size));
297             return "";
298         }
299 
300         byte[] data = SwordUtil.readRAF(txtRaf, dataIndex.getOffset(), size);
301 
302         decipher(data);
303 
304         return SwordUtil.decode(name, data, getBookMetaData().getBookCharset());
305     }
306 
307     /**
308      * How many bytes in the size count in the index
309      */
310     protected final int datasize;
311 
312     /**
313      * The number of bytes for each entry in the index: either 6 or 8
314      */
315     protected final int entrysize;
316 
317     /**
318      * How many bytes in the offset pointers in the index
319      */
320     protected static final int OFFSETSIZE = 4;
321 
322     /**
323      * The log stream
324      */
325     private static final Logger log = LoggerFactory.getLogger(RawBackend.class);
326 
327 
328 }
329