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: 2008
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: org.eclipse.jdt.ui.prefs 1178 2006-11-06 12:48:02Z dmsmith $
21   */
22  package org.crosswire.jsword.bridge;
23  
24  import java.io.File;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Locale;
28  
29  import org.crosswire.common.xml.SAXEventProvider;
30  import org.crosswire.common.xml.SerializingContentHandler;
31  import org.crosswire.jsword.book.Book;
32  import org.crosswire.jsword.book.BookCategory;
33  import org.crosswire.jsword.book.BookData;
34  import org.crosswire.jsword.book.BookException;
35  import org.crosswire.jsword.book.sword.SwordBookPath;
36  import org.crosswire.jsword.index.IndexManagerFactory;
37  import org.crosswire.jsword.passage.Key;
38  import org.crosswire.jsword.passage.NoSuchKeyException;
39  import org.crosswire.jsword.passage.Passage;
40  import org.crosswire.jsword.versification.BookName;
41  import org.xml.sax.ContentHandler;
42  import org.xml.sax.SAXException;
43  
44  /**
45   * The DWR DwrBridge adapts JSword to DWR. This is based upon APIExamples.
46   * 
47   * @see gnu.lgpl.License for license details.<br>
48   *      The copyright to this program is held by it's authors.
49   * @author DM Smith [dmsmith555 at yahoo dot com]
50   */
51  public class DwrBridge {
52      /**
53       * Get a listing of all the available books.
54       * 
55       * @param filter
56       *            The custom filter specification string
57       * @return a list of (initial, name) string pairs
58       * @see BookInstaller#getInstalledBook(String)
59       */
60      public String[][] getInstalledBooks(String filter) {
61          List<String[]> reply = new ArrayList<String[]>();
62  
63          for (Book book : BookInstaller.getInstalledBooks(filter)) {
64              String[] rbook = new String[] {
65                      book.getInitials(), book.getName()
66              };
67              reply.add(rbook);
68          }
69  
70          // If we can't find a book, indicate that.
71          if (reply.isEmpty()) {
72              reply.add(new String[] {
73                      "", "No Books installed"});
74          }
75  
76          return reply.toArray(new String[reply.size()][]);
77      }
78  
79      /**
80       * Determine whether the named book can be searched, that is, whether the
81       * book is indexed.
82       * 
83       * @param bookInitials
84       *            the named book to check.
85       * @return true if searching can be performed
86       */
87      public boolean isIndexed(String bookInitials) {
88          return isIndexed(BookInstaller.getInstalledBook(bookInitials));
89      }
90  
91      /**
92       * Determine the size of this reference.
93       * 
94       * @param bookInitials
95       *            the book to which the reference applies.
96       * @param reference
97       *            the actual reference
98       * @return the number of entries for this reference.
99       * @throws NoSuchKeyException
100      */
101     public int getCardinality(String bookInitials, String reference) throws NoSuchKeyException {
102         Book book = BookInstaller.getInstalledBook(bookInitials);
103         if (book != null) {
104             Key key = book.getKey(reference);
105             return key.getCardinality();
106         }
107         return 0;
108     }
109 
110     /**
111      * Obtain the OSIS representation from a book for a reference, pruning a
112      * reference to a limited number of keys.
113      * 
114      * @param bookInitials
115      *            the book to use
116      * @param reference
117      *            a reference, appropriate for the book, for one or more keys
118      */
119     public String getOSISString(String bookInitials, String reference, int start, int count) throws BookException, NoSuchKeyException {
120         String result = "";
121         try {
122             SAXEventProvider sep = getOSISProvider(bookInitials, reference, start, count);
123             if (sep != null) {
124                 ContentHandler ser = new SerializingContentHandler();
125                 sep.provideSAXEvents(ser);
126                 result = ser.toString();
127             }
128             return result;
129         } catch (SAXException ex) {
130             // throw new BookException(Msg.JSWORD_SAXPARSE, ex);
131         }
132         return result;
133     }
134 
135     /**
136      * Get a reference list for a search result against a book.
137      * 
138      * @param bookInitials
139      * @param searchRequest
140      * @return The reference for the matching.
141      * @throws BookException
142      */
143     public String search(String bookInitials, String searchRequest) throws BookException {
144         Book book = BookInstaller.getInstalledBook(bookInitials);
145         if (isIndexed(book) && searchRequest != null) {
146             if (BookCategory.BIBLE.equals(book.getBookCategory())) {
147                 BookName.setFullBookName(false);
148             }
149             return book.find(searchRequest).getName();
150         }
151         return "";
152     }
153 
154     /**
155      * Get close matches for a target in a book whose keys have a meaningful
156      * sort. This is not true of keys that are numeric or contain numbers.
157      * (unless the numbers are 0 filled.)
158      */
159     public String[] match(String bookInitials, String searchRequest, int maxMatchCount) {
160         Book book = BookInstaller.getInstalledBook(bookInitials);
161         if (book == null || searchRequest == null || maxMatchCount < 1) {
162             return new String[0];
163         }
164 
165         // Need to use the locale of the book so that we can find stuff in the
166         // proper order
167         Locale sortLocale = new Locale(book.getLanguage().getCode());
168         String target = searchRequest.toLowerCase(sortLocale);
169 
170         // Get everything with target as the prefix.
171         // In Unicode \uFFFF is reserved for internal use
172         // and is greater than every character defined in Unicode
173         String endTarget = target + '\uffff';
174 
175         // This whole getGlobalKeyList is messy.
176         // 1) Some drivers cache the list which is slow.
177         // 2) Binary lookup would be much better.
178         // 3) Caching the whole list here is dumb.
179         // What is needed is that all this be pushed into JSword proper.
180         // TODO(dms): Push this into Book interface.
181         List<String> result = new ArrayList<String>();
182         int count = 0;
183         for (Key key : book.getGlobalKeyList()) {
184             String entry = key.getName().toLowerCase(sortLocale);
185             if (entry.compareTo(target) >= 0) {
186                 if (entry.compareTo(endTarget) < 0) {
187                     result.add(entry);
188                     count++;
189                 }
190 
191                 // Have we seen enough?
192                 if (count >= maxMatchCount) {
193                     break;
194                 }
195             }
196         }
197 
198         return result.toArray(new String[result.size()]);
199     }
200 
201     /**
202      * For the sake of diagnostics, return the locations that JSword will look
203      * for books.
204      * 
205      * @return the SWORD path
206      */
207     public String[] getSwordPath() {
208         File[] filePath = SwordBookPath.getSwordPath();
209         if (filePath.length == 0) {
210             return new String[] {
211                 "No path"};
212         }
213         String[] path = new String[filePath.length];
214         for (int i = 0; i < filePath.length; i++) {
215             path[i] = filePath[i].getAbsolutePath();
216         }
217         return path;
218     }
219 
220     /**
221      * Determine whether the book can be searched, that is, whether the book is
222      * indexed.
223      * 
224      * @param book
225      *            the book to check.
226      * @return true if searching can be performed
227      */
228     private boolean isIndexed(Book book) {
229         return book != null && IndexManagerFactory.getIndexManager().isIndexed(book);
230     }
231 
232     /**
233      * Get BookData representing one or more Book entries, but capped to a
234      * maximum number of entries.
235      * 
236      * @param bookInitials
237      *            the book to use
238      * @param reference
239      *            a reference, appropriate for the book, of one or more entries
240      * @param start
241      *            the starting point where 0 is the first.
242      * @param count
243      *            the maximum number of entries to use
244      * 
245      * @throws NoSuchKeyException
246      */
247     private BookData getBookData(String bookInitials, String reference, int start, int count) throws NoSuchKeyException {
248         Book book = BookInstaller.getInstalledBook(bookInitials);
249         if (book == null || reference == null || count < 1) {
250             return null;
251         }
252 
253         // TODO(dms): add trim(count) and trim(start, count) to the key
254         // interface.
255         Key key = null;
256         if (BookCategory.BIBLE.equals(book.getBookCategory())) {
257             key = book.getKey(reference);
258             Passage remainder = (Passage) key;
259             if (start > 0) {
260                 remainder = remainder.trimVerses(start);
261             }
262             remainder.trimVerses(count);
263             key = remainder;
264         } else if (BookCategory.GENERAL_BOOK.equals(book.getBookCategory())) {
265             // At this time we cannot trim a General Book
266             key = book.getKey(reference);
267         } else {
268             key = book.getKey(reference);
269 
270             // Do we need to trim?
271             if (start > 0 || key.getCardinality() > count) {
272                 key = book.createEmptyKeyList();
273                 int i = 0;
274                 for (Key aKey : key) {
275                     i++;
276                     if (i <= start) {
277                         continue;
278                     }
279                     if (i >= count) {
280                         break;
281                     }
282                     key.addAll(aKey);
283                 }
284             }
285         }
286 
287         return new BookData(book, key);
288     }
289 
290     /**
291      * Obtain a SAX event provider for the OSIS document representation of one
292      * or more book entries.
293      * 
294      * @param bookInitials
295      *            the book to use
296      * @param reference
297      *            a reference, appropriate for the book, of one or more entries
298      */
299     private SAXEventProvider getOSISProvider(String bookInitials, String reference, int start, int count) throws BookException, NoSuchKeyException {
300         BookData data = getBookData(bookInitials, reference, start, count);
301         SAXEventProvider provider = null;
302         if (data != null) {
303             provider = data.getSAXEventProvider();
304         }
305         return provider;
306     }
307 
308 }
309