| 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 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;
22
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.util.ArrayList;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29
30 import org.crosswire.common.activate.Activator;
31 import org.crosswire.common.util.CollectionUtil;
32 import org.crosswire.common.util.PluginUtil;
33 import org.crosswire.common.util.Reporter;
34 import org.crosswire.jsword.JSOtherMsg;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39 * The Books class (along with Book) is the central point of contact between the
40 * rest of the world and this set of packages.
41 *
42 * @see gnu.lgpl.License for license details.<br>
43 * The copyright to this program is held by it's authors.
44 * @author Joe Walker [joe at eireneh dot com]
45 * @author DM Smith
46 */
47 public final class Books extends AbstractBookList {
48 /**
49 * Create a singleton instance of the class. This is private to ensure that
50 * only one can be created. This also makes the class final!
51 */
52 private Books() {
53 super();
54 books = new BookSet();
55 drivers = new HashSet<BookDriver>();
56 }
57
58 /**
59 * Accessor for the singleton instance
60 *
61 * @return The singleton instance
62 */
63 public static Books installed() {
64 return instance;
65 }
66
67 /* (non-Javadoc)
68 * @see org.crosswire.jsword.book.BookList#getBooks()
69 */
70 public synchronized List<Book> getBooks() {
71 return new BookSet(books);
72 }
73
74 /**
75 * Search for the book by name. Looks for exact matches first,
76 * then searches case insensitive. If that doesn't work, matches
77 * the initials of the book first case sensitive, then insensitive.
78 * In all cases the whole name or the whole initials has to match.
79 *
80 * @param name The book to find
81 * @return the book or null
82 */
83 public synchronized Book getBook(String name) {
84 if (name == null) {
85 return null;
86 }
87
88 // Check name first
89 // First check for exact matches
90 for (Book book : books) {
91 if (name.equals(book.getName())) {
92 return book;
93 }
94 }
95
96 // Next check for case-insensitive matches
97 for (Book book : books) {
98 if (name.equalsIgnoreCase(book.getName())) {
99 return book;
100 }
101 }
102
103 // Then check initials
104 // First check for exact matches
105 for (Book book : books) {
106 BookMetaData bmd = book.getBookMetaData();
107 if (name.equals(bmd.getInitials())) {
108 return book;
109 }
110 }
111
112 // Next check for case-insensitive matches
113 for (Book book : books) {
114 if (name.equalsIgnoreCase(book.getInitials())) {
115 return book;
116 }
117 }
118 return null;
119 }
120
121 /* (non-Javadoc)
122 * @see org.crosswire.jsword.book.BookList#getBooks(org.crosswire.jsword.book.BookFilter)
123 */
124 @Override
125 public synchronized List<Book> getBooks(BookFilter filter) {
126 List<Book> temp = CollectionUtil.createList(new BookFilterIterator(getBooks(), filter));
127 return new BookSet(temp);
128 }
129
130 /**
131 * Get the maximum string length of a property
132 *
133 * @param propertyKey
134 * The desired property
135 * @return -1 if there is no match, otherwise the maximum length.
136 */
137 public int getMaxLength(String propertyKey) {
138 int max = -1;
139 for (Book book : getBooks()) {
140 Object property = book.getProperty(propertyKey);
141 if (property != null) {
142 String value = property instanceof String ? (String) property : property.toString();
143 max = Math.max(max, value.length());
144 }
145 }
146 return max;
147 }
148
149 /**
150 * Get the maximum string length of a property on a subset of books.
151 *
152 * @param propertyKey
153 * The desired property
154 * @param filter
155 * The filter
156 * @return -1 if there is no match, otherwise the maximum length.
157 */
158 public int getMaxLength(String propertyKey, BookFilter filter) {
159 int max = -1;
160 for (Book book : getBooks(filter)) {
161 Object property = book.getProperty(propertyKey);
162 if (property != null) {
163 String value = property instanceof String ? (String) property : property.toString();
164 max = Math.max(max, value.length());
165 }
166 }
167 return max;
168 }
169
170 /**
171 * Add a Book to the current list of Books. This method should only be
172 * called by BibleDrivers, it is not a method for general consumption.
173 */
174 public synchronized void addBook(Book book) {
175 // log.debug("registering book: "+bmd.getName());
176
177 books.add(book);
178 fireBooksChanged(instance, book, true);
179 }
180
181 /**
182 * Remove a Book from the current list of Books. This method should only be
183 * called by BibleDrivers, it is not a method for general consumption.
184 */
185 public synchronized void removeBook(Book book) throws BookException {
186 // log.debug("unregistering book: {}", bmd.getName());
187
188 Activator.deactivate(book);
189
190 boolean removed = books.remove(book);
191 if (removed) {
192 fireBooksChanged(instance, book, false);
193 } else {
194 throw new BookException(JSOtherMsg.lookupText("Could not remove unregistered Book: {0}", book.getName()));
195 }
196 }
197
198 /**
199 * Register the driver, adding its books to the list. Any books that this
200 * driver used, but not any more are removed. This can be called repeatedly
201 * to re-register the driver.
202 *
203 * @param driver
204 * The BookDriver to add
205 */
206 public synchronized void registerDriver(BookDriver driver) throws BookException {
207 log.debug("begin registering driver: {}", driver.getClass().getName());
208
209 drivers.add(driver);
210
211 // Go through all the books and add all the new ones.
212 // Remove those that are not known to the driver, but used to be.
213 Book[] bookArray = driver.getBooks();
214 Set<Book> current = CollectionUtil.createSet(new BookFilterIterator(getBooks(), BookFilters.getBooksByDriver(driver)));
215
216 for (int j = 0; j < bookArray.length; j++) {
217 Book b = bookArray[j];
218 if (current.contains(b)) {
219 // Since it was already in there, we don't add it.
220 // By removing it from current we will be left with
221 // what is not now known by the driver.
222 current.remove(b);
223 } else {
224 addBook(bookArray[j]);
225 }
226 }
227
228 // Remove the books from the previous version of the driver
229 // that are not in this version.
230 for (Book book : current) {
231 removeBook(book);
232 }
233
234 log.debug("end registering driver: {}", driver.getClass().getName());
235 }
236
237 /**
238 * Remove from the list of drivers
239 *
240 * @param driver
241 * The BookDriver to remove
242 */
243 public synchronized void unregisterDriver(BookDriver driver) throws BookException {
244 log.debug("begin un-registering driver: {}", driver.getClass().getName());
245
246 Book[] bookArray = driver.getBooks();
247 for (int j = 0; j < bookArray.length; j++) {
248 removeBook(bookArray[j]);
249 }
250
251 if (!drivers.remove(driver)) {
252 throw new BookException(JSOtherMsg.lookupText("Could not remove unregistered Driver: {0}", driver.getClass().getName()));
253 }
254
255 log.debug("end un-registering driver: {}", driver.getClass().getName());
256 }
257
258 /**
259 * Since Books keeps a track of drivers itself, including creating them when
260 * registered it can be hard to get a hold of the current book driver. This
261 * method gives access to the registered instances.
262 */
263 public synchronized BookDriver[] getDriversByClass(Class<? extends BookDriver> type) {
264 List<BookDriver> matches = new ArrayList<BookDriver>();
265 for (BookDriver driver : drivers) {
266 if (driver.getClass() == type) {
267 matches.add(driver);
268 }
269 }
270
271 return matches.toArray(new BookDriver[matches.size()]);
272 }
273
274 /**
275 * Get an array of all the known drivers
276 *
277 * @return Found int or the default value
278 */
279 public synchronized BookDriver[] getDrivers() {
280 return drivers.toArray(new BookDriver[drivers.size()]);
281 }
282
283 /**
284 * Get an array of all the known drivers
285 *
286 * @return Found int or the default value
287 */
288 public synchronized BookDriver[] getWritableDrivers() {
289 int i = 0;
290 for (BookDriver driver : drivers) {
291 if (driver.isWritable()) {
292 i++;
293 }
294 }
295
296 BookDriver[] reply = new BookDriver[i];
297
298 i = 0;
299 for (BookDriver driver : drivers) {
300 if (driver.isWritable()) {
301 reply[i++] = driver;
302 }
303 }
304
305 return reply;
306 }
307
308 /**
309 * Registers all the drivers known to the program.
310 */
311 private void autoRegister() {
312 // This will classload them all and they will register themselves.
313 Class<? extends BookDriver>[] types = PluginUtil.getImplementors(BookDriver.class);
314
315 log.debug("begin auto-registering {} drivers:", Integer.toString(types.length));
316
317 for (int i = 0; i < types.length; i++) {
318 // job.setProgress(Msg.JOB_DRIVER.toString() +
319 // ClassUtils.getShortClassName(types[i]));
320
321 try {
322 Method driverInstance = types[i].getMethod("instance", new Class[0]);
323 BookDriver driver = (BookDriver) driverInstance.invoke(null, new Object[0]); // types[i].newInstance();
324 registerDriver(driver);
325 } catch (NoSuchMethodException e) {
326 Reporter.informUser(Books.class, e);
327 } catch (IllegalArgumentException e) {
328 Reporter.informUser(Books.class, e);
329 } catch (IllegalAccessException e) {
330 Reporter.informUser(Books.class, e);
331 } catch (InvocationTargetException e) {
332 Reporter.informUser(Books.class, e);
333 } catch (BookException e) {
334 Reporter.informUser(Books.class, e);
335 }
336 }
337 }
338
339 /**
340 * The list of Books
341 */
342 private BookSet books;
343
344 /**
345 * An array of BookDrivers
346 */
347 private Set<BookDriver> drivers;
348
349 /**
350 * The log stream
351 */
352 private static final Logger log = LoggerFactory.getLogger(Books.class);
353
354 /**
355 * The singleton instance.
356 * This needs to be declared after all other statics it uses.
357 */
358 private static final Books instance = new Books();
359 // And it cannot register books until it is fully constructed
360 // When this was the last call in the constructor it resulted
361 // in "instance" being null in something it called.
362 static {
363 instance.autoRegister();
364 }
365 }
366