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.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.crosswire.common.activate.Activator;
28  import org.crosswire.common.activate.Lock;
29  import org.crosswire.jsword.JSMsg;
30  import org.crosswire.jsword.JSOtherMsg;
31  import org.crosswire.jsword.book.BookException;
32  import org.crosswire.jsword.book.basic.AbstractBook;
33  import org.crosswire.jsword.book.filter.SourceFilter;
34  import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor;
35  import org.crosswire.jsword.passage.DefaultKeyList;
36  import org.crosswire.jsword.passage.Key;
37  import org.crosswire.jsword.passage.NoSuchKeyException;
38  import org.crosswire.jsword.passage.ReadOnlyKeyList;
39  import org.crosswire.jsword.passage.VerseRange;
40  import org.jdom2.Content;
41  
42  /**
43   * A Sword version of a generic book.
44   * 
45   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
46   * @author Joe Walker
47   */
48  public class SwordGenBook extends AbstractBook {
49      /**
50       * Construct an SwordGenBook given the BookMetaData and the AbstractBackend.
51       * 
52       * @param sbmd the metadata that describes the book
53       * @param backend the means by which the resource is accessed
54       */
55      protected SwordGenBook(SwordBookMetaData sbmd, Backend backend) {
56          super(sbmd, backend);
57  
58          if (backend == null) {
59              throw new IllegalArgumentException("AbstractBackend must not be null.");
60          }
61  
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 = getBackend().readIndex();
77  
78          map = new HashMap<String, Key>();
79          for (Key key : set) {
80              map.put(key.getOsisRef(), 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, boolean)
107      */
108     public Iterator<Content> getOsisIterator(Key key, final boolean allowEmpty, final boolean allowGenTitle) throws BookException {
109         checkActive();
110 
111         assert key != null;
112 
113         return getBackend().readToOsis(key, new RawTextToXmlProcessor() {
114             public void preRange(VerseRange range, List<Content> partialDom) {
115                 // no - op
116             }
117 
118             public void postVerse(Key verse, List<Content> partialDom, String rawText) {
119                 partialDom.addAll(filter.toOSIS(SwordGenBook.this, verse, rawText));
120             }
121 
122             public void init(List<Content> partialDom) {
123                 // no-op
124             }
125         }).iterator();
126     }
127 
128     /* (non-Javadoc)
129      * @see org.crosswire.jsword.book.Book#getRawText(org.crosswire.jsword.passage.Key)
130      */
131     public String getRawText(Key key) throws BookException {
132         return getBackend().getRawText(key);
133     }
134 
135     /* (non-Javadoc)
136      * @see org.crosswire.jsword.book.Book#contains(org.crosswire.jsword.passage.Key)
137      */
138     public boolean contains(Key key) {
139         return getBackend().contains(key);
140     }
141 
142     /*
143      * (non-Javadoc)
144      * @see org.crosswire.jsword.book.basic.AbstractBook#getOsis(org.crosswire.jsword.passage.Key, org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor)
145      */
146     @Override
147     public List<Content> getOsis(Key key, RawTextToXmlProcessor processor) throws BookException {
148         checkActive();
149 
150         assert key != null;
151 
152         return getBackend().readToOsis(key, processor);
153     }
154 
155     /* (non-Javadoc)
156      * @see org.crosswire.jsword.book.Book#isWritable()
157      */
158     public boolean isWritable() {
159         return getBackend().isWritable();
160     }
161 
162     /* (non-Javadoc)
163      * @see org.crosswire.jsword.book.Book#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
164      */
165     public void setRawText(Key key, String rawData) throws BookException {
166         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
167     }
168 
169     /* (non-Javadoc)
170      * @see org.crosswire.jsword.book.Book#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
171      */
172     public void setAliasKey(Key alias, Key source) throws BookException {
173         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
174     }
175 
176     /* (non-Javadoc)
177      * @see org.crosswire.jsword.passage.KeyFactory#getGlobalKeyList()
178      */
179     public Key getGlobalKeyList() {
180         checkActive();
181 
182         return global;
183     }
184 
185     /* (non-Javadoc)
186      * @see org.crosswire.jsword.passage.KeyFactory#getValidKey(java.lang.String)
187      */
188     public Key getValidKey(String name) {
189         try {
190             return getKey(name);
191         } catch (NoSuchKeyException e) {
192             return createEmptyKeyList();
193         }
194     }
195 
196     /* (non-Javadoc)
197      * @see org.crosswire.jsword.passage.KeyFactory#getKey(java.lang.String)
198      */
199     public Key getKey(String text) throws NoSuchKeyException {
200         checkActive();
201 
202         Key key = map.get(text);
203         if (key != null) {
204             return key;
205         }
206 
207         // First check for keys that match ignoring case
208         for (String keyName : map.keySet()) {
209             if (keyName.equalsIgnoreCase(text)) {
210                 return map.get(keyName);
211             }
212         }
213 
214         // Next keys that start with the given text
215         for (String keyName : map.keySet()) {
216             if (keyName.startsWith(text)) {
217                 return map.get(keyName);
218             }
219         }
220 
221         // Next try keys that contain the given text
222         for (String keyName : map.keySet()) {
223             if (keyName.indexOf(text) != -1) {
224                 return map.get(keyName);
225             }
226         }
227 
228         // TRANSLATOR: Error condition: Indicates that something could not be
229         // found in the book.
230         // {0} is a placeholder for the unknown key.
231         // {1} is the short name of the book
232         throw new NoSuchKeyException(JSMsg.gettext("No entry for '{0}' in {1}.", text, getInitials()));
233     }
234 
235     /* (non-Javadoc)
236      * @see org.crosswire.jsword.passage.KeyFactory#createEmptyKeyList()
237      */
238     public Key createEmptyKeyList() {
239         return new DefaultKeyList();
240     }
241 
242     /**
243      * Helper method so we can quickly activate ourselves on access
244      */
245     private void checkActive() {
246         if (!active) {
247             Activator.activate(this);
248         }
249     }
250 
251     /**
252      * The global key list
253      */
254     private Key global;
255 
256     /**
257      * Are we active
258      */
259     private boolean active;
260 
261     /**
262      * So we can quickly find a Key given the text for the key
263      */
264     private Map<String, Key> map;
265 
266     /**
267      * So we can implement getIndex() easily
268      */
269     private Key set;
270 
271     /**
272      * The filter to use to convert to OSIS.
273      */
274     protected SourceFilter filter;
275 
276 }
277