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.basic;
21  
22  import java.util.List;
23  import java.util.Set;
24  import java.util.concurrent.CopyOnWriteArrayList;
25  
26  import org.crosswire.common.activate.Lock;
27  import org.crosswire.common.util.Language;
28  import org.crosswire.jsword.JSOtherMsg;
29  import org.crosswire.jsword.book.Book;
30  import org.crosswire.jsword.book.BookCategory;
31  import org.crosswire.jsword.book.BookDriver;
32  import org.crosswire.jsword.book.BookException;
33  import org.crosswire.jsword.book.BookMetaData;
34  import org.crosswire.jsword.book.FeatureType;
35  import org.crosswire.jsword.book.sword.Backend;
36  import org.crosswire.jsword.book.sword.processing.NoOpRawTextProcessor;
37  import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor;
38  import org.crosswire.jsword.index.IndexStatus;
39  import org.crosswire.jsword.index.IndexStatusEvent;
40  import org.crosswire.jsword.index.IndexStatusListener;
41  import org.crosswire.jsword.index.search.DefaultSearchRequest;
42  import org.crosswire.jsword.index.search.SearchRequest;
43  import org.crosswire.jsword.index.search.Searcher;
44  import org.crosswire.jsword.index.search.SearcherFactory;
45  import org.crosswire.jsword.passage.Key;
46  import org.jdom2.Content;
47  import org.jdom2.Document;
48  
49  /**
50   * AbstractBook implements a few of the more generic methods of Book. This class
51   * does a lot of work in helping make search easier, and implementing some basic
52   * write methods.
53   * 
54   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
55   * @author Joe Walker
56   */
57  public abstract class AbstractBook implements Book {
58      /**
59       * Construct an AbstractBook given the BookMetaData and the AbstractBackend.
60       * 
61       * @param bmd the metadata that describes the book
62       * @param backend the means by which the resource is accessed
63       */
64      public AbstractBook(BookMetaData bmd, Backend backend) {
65          this.bmd = bmd;
66          this.backend = backend;
67          this.listeners = new CopyOnWriteArrayList<IndexStatusListener>();
68      }
69  
70      /* (non-Javadoc)
71       * @see org.crosswire.jsword.book.Book#getScope()
72       */
73      public Key getScope() {
74          return getGlobalKeyList();
75      }
76  
77      /* (non-Javadoc)
78       * @see org.crosswire.jsword.book.Book#getBookMetaData()
79       */
80      public final BookMetaData getBookMetaData() {
81          return bmd;
82      }
83  
84      /* (non-Javadoc)
85       * @see org.crosswire.jsword.book.Book#setBookMetaData(org.crosswire.jsword.book.BookMetaData)
86       */
87      public final void setBookMetaData(BookMetaData bmd) {
88          this.bmd = bmd;
89      }
90  
91      public final Backend getBackend() {
92          return backend;
93      }
94  
95      /* (non-Javadoc)
96       * @see org.crosswire.common.activate.Activatable#activate(org.crosswire.common.activate.Lock)
97       */
98      public void activate(Lock lock) {
99      }
100 
101     /* (non-Javadoc)
102      * @see org.crosswire.common.activate.Activatable#deactivate(org.crosswire.common.activate.Lock)
103      */
104     public void deactivate(Lock lock) {
105     }
106 
107     /* (non-Javadoc)
108      * @see org.crosswire.jsword.book.Book#find(java.lang.String)
109      */
110     public Key find(String request) throws BookException {
111         return find(new DefaultSearchRequest(request));
112     }
113 
114     /* (non-Javadoc)
115      * @see org.crosswire.jsword.book.Book#find(org.crosswire.jsword.index.search.SearchRequest)
116      */
117     public Key find(SearchRequest request) throws BookException {
118         if (searcher == null) {
119             try {
120                 searcher = SearcherFactory.createSearcher(this);
121             } catch (InstantiationException ex) {
122                 throw new BookException(JSOtherMsg.lookupText("Failed to initialize the search index"), ex);
123             }
124         }
125 
126         return searcher.search(request);
127     }
128 
129     /**
130      * Get this book.
131      * 
132      * @return this book
133      */
134     public Book getBook() {
135         return this;
136     }
137 
138     /* (non-Javadoc)
139      * @see org.crosswire.jsword.book.Book#getDriver()
140      */
141     public BookDriver getDriver() {
142         return bmd == null ? null : bmd.getDriver();
143     }
144 
145     /* (non-Javadoc)
146      * @see org.crosswire.jsword.book.Book#getDriverName()
147      */
148     public String getDriverName() {
149         return bmd == null ? null : bmd.getDriverName();
150     }
151 
152     /* (non-Javadoc)
153      * @see org.crosswire.jsword.book.Book#match(java.lang.String)
154      */
155     public boolean match(String name) {
156         if (name == null) {
157             return false;
158         }
159         if (name.equals(getInitials())) {
160             return true;
161         }
162         if (name.equals(getName())) {
163             return true;
164         }
165         if (name.equalsIgnoreCase(getInitials())) {
166             return true;
167         }
168         if (name.equalsIgnoreCase(getName())) {
169             return true;
170         }
171         if (name.startsWith(getInitials())) {
172             return true;
173         }
174         if (name.startsWith(getName())) {
175             return true;
176         }
177         return false;
178     }
179 
180     /* (non-Javadoc)
181      * @see org.crosswire.jsword.book.Book#getIndexStatus()
182      */
183     public IndexStatus getIndexStatus() {
184         return bmd.getIndexStatus();
185     }
186 
187     /* (non-Javadoc)
188      * @see org.crosswire.jsword.book.Book#setIndexStatus(org.crosswire.jsword.index.IndexStatus)
189      */
190     public void setIndexStatus(IndexStatus newStatus) {
191         IndexStatus oldStatus = bmd.getIndexStatus();
192         bmd.setIndexStatus(newStatus);
193         firePropertyChange(oldStatus, newStatus);
194     }
195 
196     /* (non-Javadoc)
197      * @see org.crosswire.jsword.book.Book#getAbbreviation()
198      */
199     public String getAbbreviation() {
200         return bmd.getAbbreviation();
201     }
202 
203     /* (non-Javadoc)
204      * @see org.crosswire.jsword.book.Book#getInitials()
205      */
206     public String getInitials() {
207         return bmd.getInitials();
208     }
209 
210     /* (non-Javadoc)
211      * @see org.crosswire.jsword.book.Book#getLanguage()
212      */
213     public Language getLanguage() {
214         return bmd.getLanguage();
215     }
216 
217     /* (non-Javadoc)
218      * @see org.crosswire.jsword.book.Book#getName()
219      */
220     public String getName() {
221         return bmd.getName();
222     }
223 
224     /* (non-Javadoc)
225      * @see org.crosswire.jsword.book.Book#getOsisID()
226      */
227     public String getOsisID() {
228         return bmd.getOsisID();
229     }
230 
231     /* (non-Javadoc)
232      * @see org.crosswire.jsword.book.Book#getPropertyKeys()
233      */
234     public Set<String> getPropertyKeys() {
235         return bmd.getPropertyKeys();
236     }
237 
238     /* (non-Javadoc)
239      * @see org.crosswire.jsword.book.Book#getProperty(java.lang.String)
240      */
241     public String getProperty(String key) {
242         return bmd.getProperty(key);
243     }
244 
245     /* (non-Javadoc)
246      * @see org.crosswire.jsword.book.Book#putProperty(java.lang.String, java.lang.Object)
247      */
248     public void putProperty(String key, String value) {
249         bmd.putProperty(key, value, false);
250     }
251 
252     /* (non-Javadoc)
253      * @see org.crosswire.jsword.book.Book#putProperty(java.lang.String, java.lang.String, boolean)
254      */
255     public void putProperty(String key, String value, boolean forFrontend) {
256         bmd.putProperty(key, value, forFrontend);
257     }
258 
259     /* (non-Javadoc)
260      * @see org.crosswire.jsword.book.Book#getBookCategory()
261      */
262     public BookCategory getBookCategory() {
263         return bmd.getBookCategory();
264     }
265 
266     /* (non-Javadoc)
267      * @see org.crosswire.jsword.book.Book#isLeftToRight()
268      */
269     public boolean isLeftToRight() {
270         return bmd.isLeftToRight();
271     }
272 
273     /* (non-Javadoc)
274      * @see org.crosswire.jsword.book.Book#isSupported()
275      */
276     public boolean isSupported() {
277         return bmd.isSupported();
278     }
279 
280     /* (non-Javadoc)
281      * @see org.crosswire.jsword.book.Book#isEnciphered()
282      */
283     public boolean isEnciphered() {
284         return bmd.isEnciphered();
285     }
286 
287     /* (non-Javadoc)
288      * @see org.crosswire.jsword.book.Book#isLocked()
289      */
290     public boolean isLocked() {
291         return bmd.isLocked();
292     }
293 
294     /* (non-Javadoc)
295      * @see org.crosswire.jsword.book.Book#unlock(java.lang.String)
296      */
297     public boolean unlock(String unlockKey) {
298         boolean state = bmd.unlock(unlockKey);
299         if (state) {
300             // Double check.
301             return isUnlockKeyValid();
302         }
303         return state;
304     }
305 
306     /**
307      * This is a heuristic that tries out the key.
308      * 
309      * @return true if there were no exceptions in reading the enciphered
310      *         module.
311      */
312     private boolean isUnlockKeyValid() {
313         try {
314             Key key = getGlobalKeyList();
315             if (key == null) {
316                 // weird key == null, assume it is valid
317                 return true;
318             }
319 
320             if (key.getCardinality() > 0) {
321                 key = key.get(0);
322             }
323 
324             getOsis(key, new NoOpRawTextProcessor());
325 
326             return true;
327         } catch (BookException ex) {
328             return false;
329         }
330     }
331 
332     protected abstract List<Content> getOsis(Key key, RawTextToXmlProcessor noOpRawTextProcessor) throws BookException;
333 
334     /* (non-Javadoc)
335      * @see org.crosswire.jsword.book.Book#getUnlockKey()
336      */
337     public String getUnlockKey() {
338         return bmd.getUnlockKey();
339     }
340 
341     /* (non-Javadoc)
342      * @see org.crosswire.jsword.book.Book#isQuestionable()
343      */
344     public boolean isQuestionable() {
345         return bmd.isQuestionable();
346     }
347 
348     /* (non-Javadoc)
349      * @see org.crosswire.jsword.book.Book#hasFeature(org.crosswire.jsword.book.FeatureType)
350      */
351     public boolean hasFeature(FeatureType feature) {
352         return bmd.hasFeature(feature);
353     }
354 
355     /* (non-Javadoc)
356      * @see org.crosswire.jsword.book.Book#addIndexStatusListener(org.crosswire.jsword.index.IndexStatusListener)
357      */
358     public void addIndexStatusListener(IndexStatusListener listener) {
359         if (listeners == null) {
360             listeners = new CopyOnWriteArrayList<IndexStatusListener>();
361         }
362         listeners.add(listener);
363     }
364 
365     /* (non-Javadoc)
366      * @see org.crosswire.jsword.book.Book#removeIndexStatusListener(org.crosswire.jsword.index.IndexStatusListener)
367      */
368     public void removeIndexStatusListener(IndexStatusListener listener) {
369         if (listeners == null) {
370             return;
371         }
372 
373         listeners.remove(listener);
374     }
375 
376     /**
377      * Reports bound property changes. If <code>oldValue</code> and
378      * <code>newValue</code> are not equal and the
379      * <code>PropertyChangeEvent</code> listener list isn't empty, then fire a
380      * <code>PropertyChange</code> event to each listener.
381      * 
382      * @param oldStatus
383      *            the old value of the property (as an Object)
384      * @param newStatus
385      *            the new value of the property (as an Object)
386      */
387     protected void firePropertyChange(IndexStatus oldStatus, IndexStatus newStatus) {
388         if (oldStatus != null && newStatus != null && oldStatus.equals(newStatus)) {
389             return;
390         }
391 
392         IndexStatusEvent ev = new IndexStatusEvent(this, newStatus);
393         for (IndexStatusListener listener : listeners) {
394             listener.statusChanged(ev);
395         }
396     }
397 
398     /* (non-Javadoc)
399      * @see org.crosswire.jsword.book.Book#toOSIS()
400      */
401     public Document toOSIS() {
402         return bmd.toOSIS();
403     }
404 
405     /* (non-Javadoc)
406      * @see java.lang.Object#equals(java.lang.Object)
407      */
408     @Override
409     public boolean equals(Object obj) {
410         // Since this can not be null
411         if (obj == null) {
412             return false;
413         }
414 
415         // We might consider checking for equality against all Books?
416         // However currently we don't.
417 
418         // Check that that is the same as this
419         // Don't use instanceof since that breaks inheritance
420         if (!obj.getClass().equals(this.getClass())) {
421             return false;
422         }
423 
424         // The real bit ...
425         Book that = (Book) obj;
426 
427         return bmd.equals(that.getBookMetaData());
428     }
429 
430     /* (non-Javadoc)
431      * @see java.lang.Object#hashCode()
432      */
433     @Override
434     public int hashCode() {
435         return bmd.hashCode();
436     }
437 
438     /* (non-Javadoc)
439      * @see java.lang.Comparable#compareTo(java.lang.Object)
440      */
441     public int compareTo(Book obj) {
442         return this.bmd.compareTo(obj.getBookMetaData());
443     }
444 
445     /* (non-Javadoc)
446      * @see java.lang.Object#toString()
447      */
448     @Override
449     public String toString() {
450         return bmd.toString();
451     }
452 
453     /**
454      * How do we perform searches
455      */
456     private Searcher searcher;
457 
458     /**
459      * The meta data for this book
460      */
461     private BookMetaData bmd;
462 
463     /**
464      * To read the data from the Book
465      */
466     private Backend backend;
467 
468 
469     /**
470      * The list of property change listeners
471      */
472     private List<IndexStatusListener> listeners;
473 }
474