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: GenBookBackend.java 2221 2012-01-25 21:32:57Z dmsmith $
21   */
22  package org.crosswire.jsword.book.sword;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.RandomAccessFile;
27  import java.net.URI;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.crosswire.common.activate.Activator;
32  import org.crosswire.common.activate.Lock;
33  import org.crosswire.common.util.FileUtil;
34  import org.crosswire.common.util.Logger;
35  import org.crosswire.common.util.Reporter;
36  import org.crosswire.jsword.JSMsg;
37  import org.crosswire.jsword.book.BookException;
38  import org.crosswire.jsword.passage.DefaultKeyList;
39  import org.crosswire.jsword.passage.Key;
40  import org.crosswire.jsword.passage.TreeKey;
41  
42  /**
43   * Backend for General Books.
44   * 
45   * @see gnu.lgpl.License for license details.<br>
46   *      The copyright to this program is held by it's authors.
47   * @author DM Smith [dmsmith555 at yahoo dot com]
48   */
49  public class GenBookBackend extends AbstractBackend {
50      /**
51       * Simple ctor
52       */
53      public GenBookBackend(SwordBookMetaData sbmd) {
54          super(sbmd);
55          index = new TreeKeyIndex(sbmd);
56      }
57  
58      /* (non-Javadoc)
59       * @see org.crosswire.common.activate.Activatable#activate(org.crosswire.common.activate.Lock)
60       */
61      public final void activate(Lock lock) {
62          Activator.activate(index);
63  
64          URI path = null;
65          try {
66              path = getExpandedDataPath();
67          } catch (BookException e) {
68              Reporter.informUser(this, e);
69              return;
70          }
71  
72          bdtFile = new File(path.getPath() + EXTENSION_BDT);
73  
74          if (!bdtFile.canRead()) {
75              // TRANSLATOR: Common error condition: The file could not be read. There can be many reasons.
76              // {0} is a placeholder for the file.
77              Reporter.informUser(this, new BookException(JSMsg.gettext("Error reading {0}", bdtFile.getAbsolutePath())));
78              return;
79          }
80  
81          try {
82              bdtRaf = new RandomAccessFile(bdtFile, FileUtil.MODE_READ);
83          } catch (IOException ex) {
84              log.error("failed to open files", ex);
85              bdtRaf = null;
86          }
87          active = true;
88      }
89  
90      /* (non-Javadoc)
91       * @see org.crosswire.common.activate.Activatable#deactivate(org.crosswire.common.activate.Lock)
92       */
93      public final void deactivate(Lock lock) {
94          try {
95              if (bdtRaf != null) {
96                  bdtRaf.close();
97              }
98          } catch (IOException ex) {
99              log.error("failed to close gen book files", ex);
100         } finally {
101             bdtRaf = null;
102         }
103         active = false;
104 
105         // Also deactivate the index
106         Activator.deactivate(index);
107     }
108 
109     @Override
110     public boolean contains(Key key) {
111         checkActive();
112 
113         try {
114             return null != find(key);
115         } catch (IOException e) {
116             return false;
117         }
118     }
119 
120     @Override
121     public String getRawText(Key key) throws BookException {
122         checkActive();
123 
124         try {
125             TreeNode node = find(key);
126 
127             if (node == null) {
128                 // TRANSLATOR: Error condition: Indicates that something could not be found in the book.
129                 // {0} is a placeholder for the unknown key.
130                 // {1} is the short name of the book
131                 throw new BookException(JSMsg.gettext("No entry for '{0}' in {1}.", key.getName(), getBookMetaData().getInitials()));
132             }
133 
134             byte[] userData = node.getUserData();
135 
136             // Some entries may be empty.
137             if (userData.length == 8) {
138                 int start = SwordUtil.decodeLittleEndian32(userData, 0);
139                 int size = SwordUtil.decodeLittleEndian32(userData, 4);
140                 byte[] data = SwordUtil.readRAF(bdtRaf, start, size);
141                 decipher(data);
142                 return SwordUtil.decode(key.getName(), data, getBookMetaData().getBookCharset());
143             }
144 
145             return "";
146         } catch (IOException e) {
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             throw new BookException(JSMsg.gettext("Error reading {0}", key.getName()), e);
150         }
151     }
152 
153     /**
154      * Given a Key, find the TreeNode for it.
155      * 
156      * @param key
157      *            The key to use for searching
158      * @return the found node, null otherwise
159      * @throws IOException
160      */
161     private TreeNode find(Key key) throws IOException {
162         // We need to search from the root, so navigate to the root, saving as
163         // we go.
164         List<String> path = new ArrayList<String>();
165         for (Key parentKey = key; parentKey != null && parentKey.getName().length() > 0; parentKey = parentKey.getParent()) {
166             path.add(parentKey.getName());
167         }
168 
169         TreeNode node = index.getRoot();
170 
171         node = index.getFirstChild(node);
172 
173         for (int i = path.size() - 1; i >= 0; i--) {
174             String name = path.get(i);
175 
176             // Search among the siblings for the current level.
177             while (node != null && !name.equals(node.getName())) {
178                 if (node.hasNextSibling()) {
179                     node = index.getNextSibling(node);
180                 } else {
181                     log.error("Could not find " + name);
182                     node = null;
183                 }
184             }
185 
186             // If we have found it but have not exhausted the path
187             // we need to get more
188             if (node != null && name.equals(node.getName()) && i > 0) {
189                 node = index.getFirstChild(node);
190             }
191         }
192 
193         // At this point we have either found it, returning it or have not,
194         // returning null
195         if (node != null && node.getName().equals(key.getName())) {
196             return node;
197         }
198 
199         return null;
200     }
201 
202     @Override
203     public Key readIndex() {
204         SwordBookMetaData bmd = getBookMetaData();
205         Key reply = new DefaultKeyList(null, bmd.getName());
206 
207         try {
208             TreeNode node = index.getRoot();
209             reply = new TreeKey(node.getName(), null);
210             doReadIndex(node, reply);
211         } catch (IOException e) {
212             log.error("Could not get read GenBook index", e);
213         }
214 
215         return reply;
216     }
217 
218     @Override
219     public void setAliasKey(Key alias, Key source) throws IOException {
220         throw new UnsupportedOperationException();
221     }
222 
223     @Override
224     public void setRawText(Key key, String text) throws BookException, IOException {
225         throw new UnsupportedOperationException();
226     }
227 
228     /**
229      * A helper function to recursively read the entire tree.
230      * 
231      * @param parentNode
232      *            the current node whose children are being sought
233      * @param parentKey
234      * @throws IOException
235      */
236     private void doReadIndex(TreeNode parentNode, Key parentKey) throws IOException {
237         TreeNode currentNode = parentNode;
238         if (currentNode.hasChildren()) {
239             TreeNode childNode = index.getFirstChild(currentNode);
240             do {
241                 TreeKey childKey = new TreeKey(childNode.getName(), parentKey);
242                 parentKey.addAll(childKey);
243 
244                 // Build the tree as deep as possible
245                 doReadIndex(childNode, childKey);
246 
247                 if (!childNode.hasNextSibling()) {
248                     break;
249                 }
250 
251                 childNode = index.getNextSibling(childNode);
252             } while (true);
253         }
254     }
255 
256     /**
257      * Helper method so we can quickly activate ourselves on access
258      */
259     protected final void checkActive() {
260         if (!active) {
261             Activator.activate(this);
262         }
263     }
264 
265     /**
266      * Raw GenBook file extensions
267      */
268     private static final String EXTENSION_BDT = ".bdt";
269 
270     /**
271      * The raw data file
272      */
273     private File bdtFile;
274 
275     /**
276      * The random access file for the raw data
277      */
278     private RandomAccessFile bdtRaf;
279 
280     /**
281      * The raw index file
282      */
283     private TreeKeyIndex index;
284     /**
285      * Are we active
286      */
287     private boolean active;
288 
289     /**
290      * The log stream
291      */
292     private static final Logger log = Logger.getLogger(GenBookBackend.class);
293 }
294