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: ZLDBackend.java 2099 2011-03-07 17:13:00Z dmsmith $
21   */
22  package org.crosswire.jsword.book.sword;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.ObjectInputStream;
27  import java.io.RandomAccessFile;
28  import java.net.URI;
29  
30  import org.crosswire.common.activate.Lock;
31  import org.crosswire.common.compress.CompressorType;
32  import org.crosswire.common.util.FileUtil;
33  import org.crosswire.common.util.Logger;
34  import org.crosswire.common.util.Reporter;
35  import org.crosswire.jsword.JSMsg;
36  import org.crosswire.jsword.book.BookException;
37  
38  /**
39   * An extension of RawLDBackend to read Z format files.
40   * 
41   * @see gnu.lgpl.License for license details.<br>
42   *      The copyright to this program is held by it's authors.
43   * @author Joe Walker [joe at eireneh dot com]
44   * @author DM Smith [dmsmith555 at yahoo dot com]
45   */
46  public class ZLDBackend extends RawLDBackend {
47      /**
48       * Simple ctor
49       */
50      public ZLDBackend(SwordBookMetaData sbmd) {
51          super(sbmd, 4);
52          this.lastBlockNum = -1;
53          this.lastUncompressed = EMPTY_BYTES;
54      }
55  
56      @Override
57      protected String getRawText(DataEntry entry) {
58          DataIndex blockIndex = entry.getBlockIndex();
59          long blockNum = blockIndex.getOffset();
60          int blockEntry = blockIndex.getSize();
61  
62          // Can we get the data from the cache
63          byte[] uncompressed = null;
64          if (blockNum == lastBlockNum) {
65              uncompressed = lastUncompressed;
66          } else {
67              byte[] temp;
68              try {
69                  temp = SwordUtil.readRAF(zdxRaf, blockNum * ZDX_ENTRY_SIZE, ZDX_ENTRY_SIZE);
70                  if (temp == null || temp.length == 0) {
71                      return "";
72                  }
73  
74                  int blockStart = SwordUtil.decodeLittleEndian32(temp, 0);
75                  int blockSize = SwordUtil.decodeLittleEndian32(temp, 4);
76  
77                  temp = SwordUtil.readRAF(zdtRaf, blockStart, blockSize);
78  
79                  decipher(temp);
80  
81                  String compressType = (String) getBookMetaData().getProperty(ConfigEntryType.COMPRESS_TYPE);
82                  uncompressed = CompressorType.fromString(compressType).getCompressor(temp).uncompress().toByteArray();
83  
84                  // cache the uncompressed data for next time
85                  lastBlockNum = blockNum;
86                  lastUncompressed = uncompressed;
87              } catch (IOException e) {
88                  return "";
89              }
90          }
91  
92          // get the "entry" from this block.
93          int entryCount = SwordUtil.decodeLittleEndian32(uncompressed, 0);
94          if (blockEntry >= entryCount) {
95              return "";
96          }
97  
98          int entryOffset = BLOCK_ENTRY_COUNT + (BLOCK_ENTRY_SIZE * blockEntry);
99          int entryStart = SwordUtil.decodeLittleEndian32(uncompressed, entryOffset);
100         // Note: the actual entry is '\0' terminated
101         int entrySize = SwordUtil.decodeLittleEndian32(uncompressed, entryOffset + 4);
102         byte[] entryBytes = new byte[entrySize];
103         System.arraycopy(uncompressed, entryStart, entryBytes, 0, entrySize);
104 
105         return SwordUtil.decode(entry.getName(), entryBytes, getBookMetaData().getBookCharset()).trim();
106     }
107 
108     /*
109      * (non-Javadoc)
110      * 
111      * @see
112      * org.crosswire.common.activate.Activatable#activate(org.crosswire.common
113      * .activate.Lock)
114      */
115     @Override
116     public void activate(Lock lock) {
117         super.activate(lock);
118 
119         active = false;
120         zdxFile = null;
121         zdtFile = null;
122         zdxRaf = null;
123         zdtRaf = null;
124         lastBlockNum = -1;
125         lastUncompressed = EMPTY_BYTES;
126 
127         URI path = null;
128         try {
129             path = getExpandedDataPath();
130         } catch (BookException e) {
131             Reporter.informUser(this, e);
132             return;
133         }
134 
135         try {
136             zdxFile = new File(path.getPath() + EXTENSION_Z_INDEX);
137             zdtFile = new File(path.getPath() + EXTENSION_Z_DATA);
138 
139             if (!zdxFile.canRead()) {
140                 // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
141                 // {0} is a placeholder for the file.
142                 Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", zdtFile.getAbsolutePath())));
143                 return;
144             }
145 
146             if (!zdtFile.canRead()) {
147                 // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
148                 // {0} is a placeholder for the file.
149                 Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", zdtFile.getAbsolutePath())));
150                 return;
151             }
152 
153             // Open the files
154             zdxRaf = new RandomAccessFile(zdxFile, FileUtil.MODE_READ);
155             zdtRaf = new RandomAccessFile(zdtFile, FileUtil.MODE_READ);
156         } catch (IOException ex) {
157             log.error("failed to open files", ex);
158             zdxRaf = null;
159             zdtRaf = null;
160             return;
161         }
162 
163         active = true;
164     }
165 
166     /*
167      * (non-Javadoc)
168      * 
169      * @see
170      * org.crosswire.common.activate.Activatable#deactivate(org.crosswire.common
171      * .activate.Lock)
172      */
173     @Override
174     public void deactivate(Lock lock) {
175         super.deactivate(lock);
176         lastBlockNum = -1;
177         lastUncompressed = EMPTY_BYTES;
178 
179         try {
180             if (zdxRaf != null) {
181                 zdxRaf.close();
182             }
183             if (zdtRaf != null) {
184                 zdtRaf.close();
185             }
186         } catch (IOException ex) {
187             log.error("failed to close files", ex);
188         } finally {
189             zdxRaf = null;
190             zdtRaf = null;
191         }
192 
193         active = false;
194     }
195 
196     /**
197      * Determine whether we are active.
198      */
199     @Override
200     protected boolean isActive() {
201         return active && super.isActive();
202     }
203 
204     /**
205      * Serialization support.
206      * 
207      * @param is
208      * @throws IOException
209      * @throws ClassNotFoundException
210      */
211     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
212         active = false;
213         zdxFile = null;
214         zdtFile = null;
215         zdxRaf = null;
216         zdtRaf = null;
217         lastBlockNum = -1;
218         lastUncompressed = EMPTY_BYTES;
219         is.defaultReadObject();
220     }
221 
222     private static final String EXTENSION_Z_INDEX = ".zdx";
223     private static final String EXTENSION_Z_DATA = ".zdt";
224 
225     private static final int ZDX_ENTRY_SIZE = 8;
226     private static final int BLOCK_ENTRY_COUNT = 4;
227     private static final int BLOCK_ENTRY_SIZE = 8;
228     private static final byte[] EMPTY_BYTES = new byte[0];
229 
230     /**
231      * Flags whether there are open files or not
232      */
233     private transient boolean active;
234 
235     /**
236      * The compressed index.
237      */
238     private transient File zdxFile;
239 
240     /**
241      * The compressed index random access file.
242      */
243     private transient RandomAccessFile zdxRaf;
244 
245     /**
246      * The compressed text.
247      */
248     private transient File zdtFile;
249 
250     /**
251      * The compressed text random access file.
252      */
253     private transient RandomAccessFile zdtRaf;
254 
255     /**
256      * The index of the block that is cached.
257      */
258     private transient long lastBlockNum;
259 
260     /**
261      * The cache for a read of a compressed block.
262      */
263     private transient byte[] lastUncompressed;
264 
265     /**
266      * Serialization ID
267      */
268     private static final long serialVersionUID = 3536098410391064446L;
269 
270     /**
271      * The log stream
272      */
273     private static final Logger log = Logger.getLogger(ZLDBackend.class);
274 }
275