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   * Copyright: 2005-2013
18   *     The copyright to this program is held by it's authors.
19   *
20   */
21  package org.crosswire.jsword.book.sword;
22  
23  import java.io.IOException;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.crosswire.common.activate.Activator;
30  import org.crosswire.common.activate.Lock;
31  import org.crosswire.common.util.IOUtil;
32  import org.crosswire.jsword.JSMsg;
33  import org.crosswire.jsword.JSOtherMsg;
34  import org.crosswire.jsword.book.BookException;
35  import org.crosswire.jsword.book.basic.AbstractBook;
36  import org.crosswire.jsword.book.filter.Filter;
37  import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor;
38  import org.crosswire.jsword.book.sword.state.OpenFileState;
39  import org.crosswire.jsword.passage.DefaultKeyList;
40  import org.crosswire.jsword.passage.Key;
41  import org.crosswire.jsword.passage.NoSuchKeyException;
42  import org.crosswire.jsword.passage.ReadOnlyKeyList;
43  import org.crosswire.jsword.passage.VerseRange;
44  import org.jdom2.Content;
45  
46  /**
47   * A Sword version of Dictionary.
48   * 
49   * @see gnu.lgpl.License for license details.<br>
50   *      The copyright to this program is held by it's authors.
51   * @author Joe Walker [joe at eireneh dot com]
52   */
53  public class SwordGenBook extends AbstractBook {
54      /**
55       * Start and to as much checking as we can without using memory. (i.e.
56       * actually reading the indexes)
57       */
58      protected SwordGenBook(SwordBookMetaData sbmd, AbstractBackend backend) {
59          super(sbmd);
60  
61          this.backend = backend;
62          this.filter = sbmd.getFilter();
63          map = null;
64          set = null;
65          global = null;
66          active = false;
67      }
68  
69      /* (non-Javadoc)
70       * @see org.crosswire.jsword.book.basic.AbstractBook#activate(org.crosswire.common.activate.Lock)
71       */
72      @Override
73      public final void activate(Lock lock) {
74          super.activate(lock);
75  
76          set = backend.readIndex();
77  
78          map = new HashMap<String, Key>();
79          for (Key key : set) {
80              map.put(key.getName(), key);
81          }
82  
83          global = new ReadOnlyKeyList(set, false);
84  
85          active = true;
86  
87          // We don't need to activate the backend because it should be capable
88          // of doing it for itself.
89      }
90  
91      /* (non-Javadoc)
92       * @see org.crosswire.jsword.book.basic.AbstractBook#deactivate(org.crosswire.common.activate.Lock)
93       */
94      @Override
95      public final void deactivate(Lock lock) {
96          super.deactivate(lock);
97  
98          map = null;
99          set = null;
100         global = null;
101 
102         active = false;
103     }
104 
105     /* (non-Javadoc)
106      * @see org.crosswire.jsword.book.Book#getOsisIterator(org.crosswire.jsword.passage.Key, boolean)
107      */
108     public Iterator<Content> getOsisIterator(Key key, boolean allowEmpty) throws BookException {
109         checkActive();
110 
111         assert key != null;
112         assert backend != null;
113 
114         return backend.readToOsis(key, new RawTextToXmlProcessor() {
115             public void preRange(VerseRange range, List<Content> partialDom) {
116                 // no - op
117             }
118 
119             public void postVerse(Key verse, List<Content> partialDom, String rawText) {
120                 partialDom.addAll(filter.toOSIS(SwordGenBook.this, verse, rawText));
121             }
122 
123             public void init(List<Content> partialDom) {
124                 // no-op
125             }
126         }).iterator();
127     }
128 
129     /* (non-Javadoc)
130      * @see org.crosswire.jsword.book.Book#getRawText(org.crosswire.jsword.passage.Key)
131      */
132     public String getRawText(Key key) throws BookException {
133         OpenFileState state = null;
134         try {
135             state = backend.initState();
136             return backend.readRawContent(state, key);
137         } catch (IOException e) {
138             throw new BookException("Unable to obtain raw content from backend", e);
139         } finally {
140             IOUtil.close(state);
141         }
142     }
143 
144     /* (non-Javadoc)
145      * @see org.crosswire.jsword.book.Book#contains(org.crosswire.jsword.passage.Key)
146      */
147     public boolean contains(Key key) {
148         return backend != null && backend.contains(key);
149     }
150 
151     /*
152      * (non-Javadoc)
153      * @see org.crosswire.jsword.book.basic.AbstractBook#getOsis(org.crosswire.jsword.passage.Key, org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor)
154      */
155     @Override
156     public List<Content> getOsis(Key key, RawTextToXmlProcessor processor) throws BookException {
157         checkActive();
158 
159         assert key != null;
160         assert backend != null;
161 
162         return backend.readToOsis(key, processor);
163     }
164 
165     /* (non-Javadoc)
166      * @see org.crosswire.jsword.book.Book#isWritable()
167      */
168     public boolean isWritable() {
169         return backend.isWritable();
170     }
171 
172     /* (non-Javadoc)
173      * @see org.crosswire.jsword.book.Book#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
174      */
175     public void setRawText(Key key, String rawData) throws BookException {
176         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
177     }
178 
179     /* (non-Javadoc)
180      * @see org.crosswire.jsword.book.Book#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
181      */
182     public void setAliasKey(Key alias, Key source) throws BookException {
183         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
184     }
185 
186     /* (non-Javadoc)
187      * @see org.crosswire.jsword.passage.KeyFactory#getGlobalKeyList()
188      */
189     public Key getGlobalKeyList() {
190         checkActive();
191 
192         return global;
193     }
194 
195     /* (non-Javadoc)
196      * @see org.crosswire.jsword.passage.KeyFactory#getValidKey(java.lang.String)
197      */
198     public Key getValidKey(String name) {
199         try {
200             return getKey(name);
201         } catch (NoSuchKeyException e) {
202             return createEmptyKeyList();
203         }
204     }
205 
206     /* (non-Javadoc)
207      * @see org.crosswire.jsword.passage.KeyFactory#getKey(java.lang.String)
208      */
209     public Key getKey(String text) throws NoSuchKeyException {
210         checkActive();
211 
212         Key key = map.get(text);
213         if (key != null) {
214             return key;
215         }
216 
217         // First check for keys that match ignoring case
218         for (String keyName : map.keySet()) {
219             if (keyName.equalsIgnoreCase(text)) {
220                 return map.get(keyName);
221             }
222         }
223 
224         // Next keys that start with the given text
225         for (String keyName : map.keySet()) {
226             if (keyName.startsWith(text)) {
227                 return map.get(keyName);
228             }
229         }
230 
231         // Next try keys that contain the given text
232         for (String keyName : map.keySet()) {
233             if (keyName.indexOf(text) != -1) {
234                 return map.get(keyName);
235             }
236         }
237 
238         // TRANSLATOR: Error condition: Indicates that something could not be
239         // found in the book.
240         // {0} is a placeholder for the unknown key.
241         // {1} is the short name of the book
242         throw new NoSuchKeyException(JSMsg.gettext("No entry for '{0}' in {1}.", text, getInitials()));
243     }
244 
245     /* (non-Javadoc)
246      * @see org.crosswire.jsword.passage.KeyFactory#createEmptyKeyList()
247      */
248     public Key createEmptyKeyList() {
249         return new DefaultKeyList();
250     }
251 
252     /**
253      * Helper method so we can quickly activate ourselves on access
254      */
255     private void checkActive() {
256         if (!active) {
257             Activator.activate(this);
258         }
259     }
260 
261     /**
262      * The global key list
263      */
264     private Key global;
265 
266     /**
267      * Are we active
268      */
269     private boolean active;
270 
271     /**
272      * So we can quickly find a Key given the text for the key
273      */
274     private Map<String, Key> map;
275 
276     /**
277      * So we can implement getIndex() easily
278      */
279     private Key set;
280 
281     /**
282      * To read the data from the disk
283      */
284     private AbstractBackend backend;
285 
286     /**
287      * The filter to use to convert to OSIS.
288      */
289     protected Filter filter;
290 
291 }
292