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.ObjectInputStream;
24  
25  import org.crosswire.common.compress.CompressorType;
26  import org.crosswire.jsword.book.BookException;
27  import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
28  import org.crosswire.jsword.book.sword.state.RawLDBackendState;
29  import org.crosswire.jsword.book.sword.state.ZLDBackendState;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * An extension of RawLDBackend to read Z format files.
35   * 
36   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
37   * @author Joe Walker
38   * @author DM Smith
39   */
40  public class ZLDBackend extends RawLDBackend<ZLDBackendState> {
41      /**
42       * Simple ctor
43       * @param sbmd 
44       */
45      public ZLDBackend(SwordBookMetaData sbmd) {
46          super(sbmd, 4);
47      }
48  
49      /* (non-Javadoc)
50       * @see org.crosswire.jsword.book.sword.RawLDBackend#initState()
51       */
52      @Override
53      public ZLDBackendState initState() throws BookException {
54          return OpenFileStateManager.instance().getZLDBackendState(getBookMetaData());
55      }
56  
57      /* (non-Javadoc)
58       * @see org.crosswire.jsword.book.sword.RawLDBackend#getEntry(org.crosswire.jsword.book.sword.state.RawLDBackendState, org.crosswire.jsword.book.sword.DataEntry)
59       */
60      @Override
61      protected DataEntry getEntry(RawLDBackendState fileState, DataEntry entry) {
62          ZLDBackendState state = null;
63          if (fileState instanceof ZLDBackendState) {
64              state = (ZLDBackendState) fileState;
65          } else {
66              //something went terribly wrong
67              log.error("Backend State was not of type ZLDBackendState. Ignoring this entry and exiting.");
68              return new DataEntry(entry.getName(), new byte[0], entry.getCharset());
69          }
70  
71          DataIndex blockIndex = entry.getBlockIndex();
72          long blockNum = blockIndex.getOffset();
73          int blockEntry = blockIndex.getSize();
74  
75          // Can we get the data from the cache
76          byte[] uncompressed = null;
77          if (blockNum == state.getLastBlockNum()) {
78              uncompressed = state.getLastUncompressed();
79          } else {
80              byte[] temp;
81              try {
82                  temp = SwordUtil.readRAF(state.getZdxRaf(), blockNum * ZDX_ENTRY_SIZE, ZDX_ENTRY_SIZE);
83                  if (temp == null || temp.length == 0) {
84                      return new DataEntry(entry.getName(), new byte[0], entry.getCharset());
85                  }
86  
87                  int blockStart = SwordUtil.decodeLittleEndian32(temp, 0);
88                  int blockSize = SwordUtil.decodeLittleEndian32(temp, 4);
89  
90                  temp = SwordUtil.readRAF(state.getZdtRaf(), blockStart, blockSize);
91  
92                  decipher(temp);
93  
94                  String compressType = getBookMetaData().getProperty(SwordBookMetaData.KEY_COMPRESS_TYPE);
95                  uncompressed = CompressorType.fromString(compressType).getCompressor(temp).uncompress().toByteArray();
96  
97                  // cache the uncompressed data for next time
98                  state.setLastBlockNum(blockNum);
99                  state.setLastUncompressed(uncompressed);
100             } catch (IOException e) {
101                 return new DataEntry(entry.getName(), new byte[0], entry.getCharset());
102             }
103         }
104 
105         // get the "entry" from this block.
106         int entryCount = SwordUtil.decodeLittleEndian32(uncompressed, 0);
107         if (blockEntry >= entryCount) {
108             return new DataEntry(entry.getName(), new byte[0], entry.getCharset());
109         }
110 
111         int entryOffset = BLOCK_ENTRY_COUNT + (BLOCK_ENTRY_SIZE * blockEntry);
112         int entryStart = SwordUtil.decodeLittleEndian32(uncompressed, entryOffset);
113         int entrySize = SwordUtil.decodeLittleEndian32(uncompressed, entryOffset + 4);
114         // Note: the actual entry is '\0' terminated
115         int nullTerminator = SwordUtil.findByte(uncompressed, entryStart, (byte) 0x00);
116         if (nullTerminator - entryStart + 1 == entrySize) {
117             entrySize -= 1;
118         }
119         byte[] entryBytes = new byte[entrySize];
120         System.arraycopy(uncompressed, entryStart, entryBytes, 0, entrySize);
121         DataEntry finalEntry = new DataEntry(entry.getName(), entryBytes, getBookMetaData().getBookCharset());
122 
123         return finalEntry;
124     }
125 
126     /**
127      * Serialization support.
128      * 
129      * @param is
130      * @throws IOException
131      * @throws ClassNotFoundException
132      */
133     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
134         is.defaultReadObject();
135     }
136     /** 
137      * Experimental code.
138      */
139     @Override
140     public void dumpIdxRaf() {
141         RawLDBackendState state = null;
142         long end = -1;
143         try {
144             state = initState();
145             end = getCardinality();
146             StringBuilder buf = new StringBuilder();
147             System.out.println("index\toffset\tsize\tkey\tvalue");
148             for (long i = 0; i < end; ++i) {
149                 DataIndex index = getIndex(state, i);
150                 int offset = index.getOffset();
151                 int size   = index.getSize();
152                 buf.setLength(0);
153                 buf.append(i);
154                 buf.append('\t');
155                 buf.append(offset);
156                 buf.append('\t');
157                 buf.append(size);
158                 if (size > 0) {
159                     // Now read the data file for this key using the offset and size
160                     byte[] data = SwordUtil.readRAF(state.getDatRaf(), offset, size);
161                     DataEntry blockEntry = new DataEntry(Long.toString(i), data, getBookMetaData().getBookCharset());
162                     DataIndex block = blockEntry.getBlockIndex();
163                     DataEntry dataEntry = getEntry(state, blockEntry);
164                     String key = blockEntry.getKey();
165                     buf.append('\t');
166                     buf.append(key);
167                     buf.append('\t');
168                     buf.append(block.getOffset());
169                     buf.append('\t');
170                     buf.append(block.getSize());
171                     String raw;
172                     buf.append('\t');
173                     if (dataEntry.isLinkEntry()) {
174                         raw = dataEntry.getLinkTarget();
175                         buf.append("Linked to: ").append(raw.replace('\n', ' '));
176                     } else {
177                         raw = getRawText(dataEntry);
178                         if (raw.length() > 43) {
179                             buf.append(raw.substring(0, 40).replace('\n', ' '));
180                             buf.append("...");
181                         } else {
182                             buf.append(raw);
183                         }
184                     }
185                 }
186                 System.out.println(buf.toString());
187             }
188         } catch (IOException e) {
189             // TODO Auto-generated catch block
190             e.printStackTrace();
191         } catch (BookException e) {
192             // TODO Auto-generated catch block
193             e.printStackTrace();
194         } finally {
195             OpenFileStateManager.instance().release(state);
196         }
197     }
198 
199     private static final int ZDX_ENTRY_SIZE = 8;
200     private static final int BLOCK_ENTRY_COUNT = 4;
201     private static final int BLOCK_ENTRY_SIZE = 8;
202 
203     /**
204      * The log stream
205      */
206     private static final Logger log = LoggerFactory.getLogger(ZLDBackend.class);
207     private static final long serialVersionUID = 3536098410391064446L;
208 }
209