| Books.java |
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: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: Books.java 2099 2011-03-07 17:13:00Z dmsmith $
21 */
22 package org.crosswire.jsword.book;
23
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30
31 import org.crosswire.common.activate.Activator;
32 import org.crosswire.common.util.CollectionUtil;
33 import org.crosswire.common.util.EventListenerList;
34 import org.crosswire.common.util.Logger;
35 import org.crosswire.common.util.PluginUtil;
36 import org.crosswire.common.util.Reporter;
37 import org.crosswire.jsword.JSOtherMsg;
38
39 /**
40 * The Books class (along with Book) is the central point of contact between the
41 * rest of the world and this set of packages.
42 *
43 * @see gnu.lgpl.License for license details.<br>
44 * The copyright to this program is held by it's authors.
45 * @author Joe Walker [joe at eireneh dot com]
46 * @author DM Smith [dmsmith555 at yahoo dot com]
47 */
48 public final class Books implements BookList {
49 /**
50 * Create a singleton instance of the class. This is private to ensure that
51 * only one can be created. This also makes the class final!
52 */
53 private Books() {
54 books = new BookSet();
55 drivers = new HashSet<BookDriver>();
56 listeners = new EventListenerList();
57 threaded = false;
58
59 initialize(threaded);
60 }
61
62 /**
63 * Accessor for the singleton instance
64 *
65 * @return The singleton instance
66 */
67 public static Books installed() {
68 return instance;
69 }
70
71 /*
72 * (non-Javadoc)
73 *
74 * @see org.crosswire.jsword.book.BookList#getBooks()
75 */
76 public synchronized List<Book> getBooks() {
77 return new BookSet(books);
78 }
79
80 /*
81 * (non-Javadoc)
82 *
83 * @see org.crosswire.jsword.book.BookList#getBook(java.lang.String)
84 */
85 public synchronized Book getBook(String name) {
86 if (name == null) {
87 return null;
88 }
89
90 // Check name first
91 // First check for exact matches
92 for (Book book : books) {
93 if (name.equals(book.getName())) {
94 return book;
95 }
96 }
97
98 // Next check for case-insensitive matches
99 for (Book book : books) {
100 if (name.equalsIgnoreCase(book.getName())) {
101 return book;
102 }
103 }
104
105 // Then check initials
106 // First check for exact matches
107 for (Book book : books) {
108 BookMetaData bmd = book.getBookMetaData();
109 if (name.equals(bmd.getInitials())) {
110 return book;
111 }
112 }
113
114 // Next check for case-insensitive matches
115 for (Book book : books) {
116 if (name.equalsIgnoreCase(book.getInitials())) {
117 return book;
118 }
119 }
120 return null;
121 }
122
123 /*
124 * (non-Javadoc)
125 *
126 * @see
127 * org.crosswire.jsword.book.BookList#getBooks(org.crosswire.jsword.book
128 * .BookFilter)
129 */
130 public synchronized List<Book> getBooks(BookFilter filter) {
131 List<Book> temp = CollectionUtil.createList(new BookFilterIterator(getBooks(), filter));
132 return new BookSet(temp);
133 }
134
135 /**
136 * Get the maximum string length of a property
137 *
138 * @param propertyKey
139 * The desired property
140 * @return -1 if there is no match, otherwise the maximum length.
141 */
142 public int getMaxLength(String propertyKey) {
143 int max = -1;
144 for (Book book : getBooks()) {
145 Object property = book.getProperty(propertyKey);
146 if (property != null) {
147 String value = property instanceof String ? (String) property : property.toString();
148 max = Math.max(max, value.length());
149 }
150 }
151 return max;
152 }
153
154 /**
155 * Get the maximum string length of a property on a subset of books.
156 *
157 * @param propertyKey
158 * The desired property
159 * @param filter
160 * The filter
161 * @return -1 if there is no match, otherwise the maximum length.
162 */
163 public int getMaxLength(String propertyKey, BookFilter filter) {
164 int max = -1;
165 for (Book book : getBooks(filter)) {
166 Object property = book.getProperty(propertyKey);
167 if (property != null) {
168 String value = property instanceof String ? (String) property : property.toString();
169 max = Math.max(max, value.length());
170 }
171 }
172 return max;
173 }
174
175 /*
176 * (non-Javadoc)
177 *
178 * @see
179 * org.crosswire.jsword.book.BookList#addBooksListener(org.crosswire.jsword
180 * .book.BooksListener)
181 */
182 public synchronized void addBooksListener(BooksListener li) {
183 listeners.add(BooksListener.class, li);
184 }
185
186 /*
187 * (non-Javadoc)
188 *
189 * @see
190 * org.crosswire.jsword.book.BookList#removeBooksListener(org.crosswire.
191 * jsword.book.BooksListener)
192 */
193 public synchronized void removeBooksListener(BooksListener li) {
194 listeners.remove(BooksListener.class, li);
195 }
196
197 /**
198 * Kick of an event sequence
199 *
200 * @param source
201 * The event source
202 * @param book
203 * The changed Book
204 * @param added
205 * Is it added?
206 */
207 protected synchronized void fireBooksChanged(Object source, Book book, boolean added) {
208 // Guaranteed to return a non-null array
209 Object[] contents = listeners.getListenerList();
210
211 // Process the listeners last to first, notifying
212 // those that are interested in this event
213 BooksEvent ev = null;
214 for (int i = contents.length - 2; i >= 0; i -= 2) {
215 if (contents[i] == BooksListener.class) {
216 if (ev == null) {
217 ev = new BooksEvent(source, book, added);
218 }
219
220 if (added) {
221 ((BooksListener) contents[i + 1]).bookAdded(ev);
222 } else {
223 ((BooksListener) contents[i + 1]).bookRemoved(ev);
224 }
225 }
226 }
227 }
228
229 /**
230 * Add a Book to the current list of Books. This method should only be
231 * called by BibleDrivers, it is not a method for general consumption.
232 */
233 public synchronized void addBook(Book book) {
234 // log.debug("registering book: "+bmd.getName());
235
236 books.add(book);
237 fireBooksChanged(instance, book, true);
238 }
239
240 /**
241 * Remove a Book from the current list of Books. This method should only be
242 * called by BibleDrivers, it is not a method for general consumption.
243 */
244 public synchronized void removeBook(Book book) throws BookException {
245 // log.debug("unregistering book: "+bmd.getName());
246
247 Activator.deactivate(book);
248
249 boolean removed = books.remove(book);
250 if (removed) {
251 fireBooksChanged(instance, book, true);
252 } else {
253 throw new BookException(JSOtherMsg.lookupText("Could not remove unregistered Book: {0}", book.getName()));
254 }
255 }
256
257 /**
258 * Register the driver, adding its books to the list. Any books that this
259 * driver used, but not any more are removed. This can be called repeatedly
260 * to re-register the driver.
261 *
262 * @param driver
263 * The BookDriver to add
264 */
265 public synchronized void registerDriver(BookDriver driver) throws BookException {
266 log.debug("begin registering driver: " + driver.getClass().getName());
267
268 drivers.add(driver);
269
270 // Go through all the books and add all the new ones.
271 // Remove those that are not known to the driver, but used to be.
272 Book[] bookArray = driver.getBooks();
273 Set<Book> current = CollectionUtil.createSet(new BookFilterIterator(getBooks(), BookFilters.getBooksByDriver(driver)));
274
275 for (int j = 0; j < bookArray.length; j++) {
276 Book b = bookArray[j];
277 if (current.contains(b)) {
278 // Since it was already in there, we don't add it.
279 // By removing it from current we will be left with
280 // what is not now known by the driver.
281 current.remove(b);
282 } else {
283 addBook(bookArray[j]);
284 }
285 }
286
287 // Remove the books from the previous version of the driver
288 // that are not in this version.
289 for (Book book : current) {
290 removeBook(book);
291 }
292
293 log.debug("end registering driver: " + driver.getClass().getName());
294 }
295
296 /**
297 * Remove from the list of drivers
298 *
299 * @param driver
300 * The BookDriver to remove
301 */
302 public synchronized void unregisterDriver(BookDriver driver) throws BookException {
303 log.debug("begin un-registering driver: " + driver.getClass().getName());
304
305 Book[] bookArray = driver.getBooks();
306 for (int j = 0; j < bookArray.length; j++) {
307 removeBook(bookArray[j]);
308 }
309
310 if (!drivers.remove(driver)) {
311 throw new BookException(JSOtherMsg.lookupText("Could not remove unregistered Driver: {0}", driver.getClass().getName()));
312 }
313
314 log.debug("end un-registering driver: " + driver.getClass().getName());
315 }
316
317 /**
318 * Since Books keeps a track of drivers itself, including creating them when
319 * registered it can be hard to get a hold of the current book driver. This
320 * method gives access to the registered instances.
321 */
322 public synchronized BookDriver[] getDriversByClass(Class<? extends BookDriver> type) {
323 List<BookDriver> matches = new ArrayList<BookDriver>();
324 for (BookDriver driver : drivers) {
325 if (driver.getClass() == type) {
326 matches.add(driver);
327 }
328 }
329
330 return matches.toArray(new BookDriver[matches.size()]);
331 }
332
333 /**
334 * Get an array of all the known drivers
335 *
336 * @return Found int or the default value
337 */
338 public synchronized BookDriver[] getDrivers() {
339 return drivers.toArray(new BookDriver[drivers.size()]);
340 }
341
342 /**
343 * Get an array of all the known drivers
344 *
345 * @return Found int or the default value
346 */
347 public synchronized BookDriver[] getWritableDrivers() {
348 int i = 0;
349 for (BookDriver driver : drivers) {
350 if (driver.isWritable()) {
351 i++;
352 }
353 }
354
355 BookDriver[] reply = new BookDriver[i];
356
357 i = 0;
358 for (BookDriver driver : drivers) {
359 if (driver.isWritable()) {
360 reply[i++] = driver;
361 }
362 }
363
364 return reply;
365 }
366
367 /**
368 * Registers all the drivers known to the program. Either in a thread or in
369 * the main thread
370 */
371 private void initialize(boolean doThreading) {
372 if (doThreading) {
373 Runnable runner = new Runnable() {
374 public void run() {
375 autoRegister();
376 }
377 };
378
379 Thread init = new Thread(runner, "book-driver-registration");
380 init.setPriority(Thread.MIN_PRIORITY);
381 init.start();
382 } else {
383 autoRegister();
384 }
385 }
386
387 /**
388 * Registers all the drivers known to the program.
389 */
390 protected void autoRegister() {
391 // This will classload them all and they will register themselves.
392 Class<? extends BookDriver>[] types = PluginUtil.getImplementors(BookDriver.class);
393
394 log.debug("begin auto-registering " + types.length + " drivers:");
395
396 for (int i = 0; i < types.length; i++) {
397 // job.setProgress(Msg.JOB_DRIVER.toString() +
398 // ClassUtils.getShortClassName(types[i]));
399
400 try {
401 Method driverInstance = types[i].getMethod("instance", new Class[0]);
402 BookDriver driver = (BookDriver) driverInstance.invoke(null, new Object[0]); // types[i].newInstance();
403 registerDriver(driver);
404 } catch (NoSuchMethodException e) {
405 Reporter.informUser(Books.class, e);
406 } catch (IllegalArgumentException e) {
407 Reporter.informUser(Books.class, e);
408 } catch (IllegalAccessException e) {
409 Reporter.informUser(Books.class, e);
410 } catch (InvocationTargetException e) {
411 Reporter.informUser(Books.class, e);
412 } catch (BookException e) {
413 Reporter.informUser(Books.class, e);
414 }
415 }
416 }
417
418 /**
419 * The list of Books
420 */
421 private BookSet books;
422
423 /**
424 * An array of BookDrivers
425 */
426 private Set<BookDriver> drivers;
427
428 /**
429 * The list of listeners
430 */
431 private EventListenerList listeners;
432
433 /**
434 * Do we try to get clever in registering books?. Not until we can get it to
435 * work! At this time there is no way to set this or influence it So it just
436 * acts as a means of commenting out code.
437 */
438 private boolean threaded;
439
440 /**
441 * The log stream
442 */
443 private static final Logger log = Logger.getLogger(Books.class);
444
445 /**
446 * The singleton instance. This needs to be declared after all other statics
447 * it uses.
448 */
449 private static final Books instance = new Books();
450 }
451