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.util.ArrayList;
24  import java.util.List;
25  
26  import org.crosswire.jsword.JSMsg;
27  import org.crosswire.jsword.book.BookException;
28  import org.crosswire.jsword.book.BookMetaData;
29  import org.crosswire.jsword.book.sword.state.GenBookBackendState;
30  import org.crosswire.jsword.book.sword.state.OpenFileStateManager;
31  import org.crosswire.jsword.passage.DefaultKeyList;
32  import org.crosswire.jsword.passage.Key;
33  import org.crosswire.jsword.passage.TreeKey;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * Backend for General Books.
39   * 
40   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
41   * @author DM Smith
42   */
43  public class GenBookBackend extends AbstractBackend<GenBookBackendState> {
44      /**
45       * Simple ctor
46       * 
47       * @param sbmd 
48       */
49      public GenBookBackend(SwordBookMetaData sbmd) {
50          super(sbmd);
51          index = new TreeKeyIndex(sbmd);
52      }
53  
54      public GenBookBackendState initState() throws BookException {
55          return OpenFileStateManager.instance().getGenBookBackendState(getBookMetaData());
56      }
57  
58      /* (non-Javadoc)
59       * @see org.crosswire.jsword.book.sword.AbstractBackend#contains(org.crosswire.jsword.passage.Key)
60       */
61      @Override
62      public boolean contains(Key key) {
63          return getRawTextLength(key) > 0;
64      }
65  
66      /* (non-Javadoc)
67       * @see org.crosswire.jsword.book.sword.AbstractBackend#size(org.crosswire.jsword.passage.Key)
68       */
69      @Override
70      public int getRawTextLength(Key key) {
71          try {
72              TreeNode node = find(key);
73  
74              if (node == null) {
75                  return 0;
76              }
77  
78              byte[] userData = node.getUserData();
79  
80              // Some entries may be empty.
81              if (userData.length == 8) {
82                  return SwordUtil.decodeLittleEndian32(userData, 4);
83              }
84  
85              return 0;
86  
87          } catch (IOException e) {
88              return 0;
89          }
90      }
91  
92      /* (non-Javadoc)
93       * @see org.crosswire.jsword.book.sword.StatefulFileBackedBackend#readRawContent(org.crosswire.jsword.book.sword.state.OpenFileState, org.crosswire.jsword.passage.Key)
94       */
95      public String readRawContent(GenBookBackendState state, Key key) throws IOException, BookException {
96          TreeNode node = find(key);
97  
98          if (node == null) {
99              // TRANSLATOR: Error condition: Indicates that something could
100             // not be found in the book.
101             // {0} is a placeholder for the unknown key.
102             // {1} is the short name of the book
103             throw new BookException(JSMsg.gettext("No entry for '{0}' in {1}.", key.getName(), getBookMetaData().getInitials()));
104         }
105 
106         byte[] userData = node.getUserData();
107 
108         // Some entries may be empty.
109         if (userData.length == 8) {
110             int start = SwordUtil.decodeLittleEndian32(userData, 0);
111             int size = SwordUtil.decodeLittleEndian32(userData, 4);
112             byte[] data = SwordUtil.readRAF(state.getBdtRaf(), start, size);
113             decipher(data);
114             return SwordUtil.decode(key.getName(), data, getBookMetaData().getBookCharset());
115         }
116 
117         return "";
118     }
119 
120     /**
121      * Given a Key, find the TreeNode for it.
122      * 
123      * @param key
124      *            The key to use for searching
125      * @return the found node, null otherwise
126      * @throws IOException
127      */
128     private TreeNode find(Key key) throws IOException {
129         // We need to search from the root, so navigate to the root, saving as
130         // we go.
131         List<String> path = new ArrayList<String>();
132         for (Key parentKey = key; parentKey != null && parentKey.getName().length() > 0; parentKey = parentKey.getParent()) {
133             path.add(parentKey.getName());
134         }
135 
136         TreeNode node = index.getRoot();
137 
138         node = index.getFirstChild(node);
139 
140         for (int i = path.size() - 1; i >= 0; i--) {
141             String name = path.get(i);
142 
143             // Search among the siblings for the current level.
144             while (node != null && !name.equals(node.getName())) {
145                 if (node.hasNextSibling()) {
146                     node = index.getNextSibling(node);
147                 } else {
148                     log.error("Could not find {}", name);
149                     node = null;
150                 }
151             }
152 
153             // If we have found it but have not exhausted the path
154             // we need to get more
155             if (node != null && name.equals(node.getName()) && i > 0) {
156                 node = index.getFirstChild(node);
157             }
158         }
159 
160         // At this point we have either found it, returning it or have not,
161         // returning null
162         if (node != null && node.getName().equals(key.getName())) {
163             return node;
164         }
165 
166         return null;
167     }
168 
169     @Override
170     public Key readIndex() {
171         BookMetaData bmd = getBookMetaData();
172         Key reply = new DefaultKeyList(null, bmd.getName());
173 
174         try {
175             TreeNode node = index.getRoot();
176             reply = new TreeKey(node.getName(), null);
177             doReadIndex(node, reply);
178         } catch (IOException e) {
179             log.error("Could not get read GenBook index", e);
180         }
181 
182         return reply;
183     }
184 
185     /* (non-Javadoc)
186      * @see org.crosswire.jsword.book.sword.AbstractBackend#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
187      */
188     public void setAliasKey(GenBookBackendState state, Key alias, Key source) throws IOException {
189         throw new UnsupportedOperationException();
190     }
191 
192     /* (non-Javadoc)
193      * @see org.crosswire.jsword.book.sword.AbstractBackend#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
194      */
195     public void setRawText(GenBookBackendState rafBook, Key key, String text) throws BookException, IOException {
196         throw new UnsupportedOperationException();
197     }
198 
199     /**
200      * A helper function to recursively read the entire tree.
201      * 
202      * @param parentNode
203      *            the current node whose children are being sought
204      * @param parentKey
205      * @throws IOException
206      */
207     private void doReadIndex(TreeNode parentNode, Key parentKey) throws IOException {
208         TreeNode currentNode = parentNode;
209         if (currentNode.hasChildren()) {
210             TreeNode childNode = index.getFirstChild(currentNode);
211             do {
212                 TreeKey childKey = new TreeKey(childNode.getName(), parentKey);
213                 parentKey.addAll(childKey);
214 
215                 // Build the tree as deep as possible
216                 doReadIndex(childNode, childKey);
217 
218                 if (!childNode.hasNextSibling()) {
219                     break;
220                 }
221 
222                 childNode = index.getNextSibling(childNode);
223             } while (true);
224         }
225     }
226 
227     /**
228      * The raw index file
229      */
230     private final TreeKeyIndex index;
231 
232     /**
233      * The log stream
234      */
235     private static final Logger log = LoggerFactory.getLogger(GenBookBackend.class);
236 }
237