Coverage Report - org.crosswire.jsword.book.Books
 
Classes in this File Line Coverage Branch Coverage Complexity
Books
0%
0/77
0%
0/30
3.273
 
 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;
 21  
 
 22  
 import java.lang.reflect.InvocationTargetException;
 23  
 import java.lang.reflect.Method;
 24  
 import java.util.ArrayList;
 25  
 import java.util.HashMap;
 26  
 import java.util.HashSet;
 27  
 import java.util.List;
 28  
 import java.util.Map;
 29  
 import java.util.Set;
 30  
 import java.util.TreeSet;
 31  
 
 32  
 import org.crosswire.common.activate.Activator;
 33  
 import org.crosswire.common.util.CollectionUtil;
 34  
 import org.crosswire.common.util.PluginUtil;
 35  
 import org.crosswire.common.util.Reporter;
 36  
 import org.crosswire.jsword.JSOtherMsg;
 37  
 import org.slf4j.Logger;
 38  
 import org.slf4j.LoggerFactory;
 39  
 
 40  
 /**
 41  
  * The Books class (along with Book) is the central point of contact between the
 42  
  * rest of the world and this set of packages.
 43  
  * 
 44  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 45  
  * @author Joe Walker
 46  
  * @author DM Smith
 47  
  */
 48  
 public final class Books extends AbstractBookList {
 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  0
         super();
 55  0
         initials = new HashMap<String, Book>();
 56  0
         names = new HashMap<String, Book>();
 57  0
         drivers = new HashSet<BookDriver>();
 58  0
         books = new TreeSet();
 59  0
     }
 60  
 
 61  
     /**
 62  
      * Accessor for the singleton instance
 63  
      * 
 64  
      * @return The singleton instance
 65  
      */
 66  
     public static Books installed() {
 67  0
         return instance;
 68  
     }
 69  
 
 70  
     /* (non-Javadoc)
 71  
      * @see org.crosswire.jsword.book.BookList#getBooks()
 72  
      */
 73  
     public synchronized List<Book> getBooks() {
 74  0
         return CollectionUtil.createList(books);
 75  
     }
 76  
 
 77  
     /* (non-Javadoc)
 78  
      * @see org.crosswire.jsword.book.BookList#getBooks(org.crosswire.jsword.book.BookFilter)
 79  
      */
 80  
     @Override
 81  
     public synchronized List<Book> getBooks(BookFilter filter) {
 82  0
         return CollectionUtil.createList(new BookFilterIterator(books, filter));
 83  
     }
 84  
 
 85  
     /**
 86  
      * Search for the book by initials and name.
 87  
      * Looks for exact matches first, then searches case insensitive. 
 88  
      * In all cases the whole initials or the whole name has to match.
 89  
      * 
 90  
      * @param name The initials or name of the book to find
 91  
      * @return the book or null
 92  
      */
 93  
     public synchronized Book getBook(String name) {
 94  0
         if (name == null) {
 95  0
             return null;
 96  
         }
 97  
 
 98  0
         Book book = initials.get(name);
 99  0
         if (book != null) {
 100  0
             return book;
 101  
         }
 102  
 
 103  0
         book = names.get(name);
 104  0
         if (book != null) {
 105  0
             return book;
 106  
         }
 107  
 
 108  
         // Check for case-insensitive initial and name matches
 109  0
         for (Book b : books) {
 110  0
             if (name.equalsIgnoreCase(b.getInitials()) || name.equalsIgnoreCase(b.getName())) {
 111  0
                 return b;
 112  
             }
 113  
         }
 114  
 
 115  0
         return null;
 116  
     }
 117  
 
 118  
     /**
 119  
      * Add a Book to the current list of Books. This method should only be
 120  
      * called by BibleDrivers, it is not a method for general consumption.
 121  
      * 
 122  
      * @param book the book to add to this book list
 123  
      */
 124  
     public synchronized void addBook(Book book) {
 125  0
         if (book != null && books.add(book)) {
 126  0
             initials.put(book.getInitials(), book);
 127  0
             names.put(book.getName(), book);
 128  0
             fireBooksChanged(instance, book, true);
 129  
         }
 130  0
     }
 131  
 
 132  
     /**
 133  
      * Remove a Book from the current list of Books. This method should only be
 134  
      * called by BibleDrivers, it is not a method for general consumption.
 135  
      * 
 136  
      * @param book the book to be removed from this book list
 137  
      * @throws BookException when an error occurs when performing this method
 138  
      */
 139  
     public synchronized void removeBook(Book book) throws BookException {
 140  
         // log.debug("unregistering book: {}", bmd.getName());
 141  
 
 142  0
         Activator.deactivate(book);
 143  
 
 144  0
         boolean removed = books.remove(book);
 145  0
         if (removed) {
 146  0
             initials.remove(book.getInitials());
 147  0
             names.remove(book.getName());
 148  0
             fireBooksChanged(instance, book, false);
 149  
         } else {
 150  0
             throw new BookException(JSOtherMsg.lookupText("Could not remove unregistered Book: {0}", book.getName()));
 151  
         }
 152  0
     }
 153  
 
 154  
     /**
 155  
      * Register the driver, adding its books to the list. Any books that this
 156  
      * driver used, but not any more are removed. This can be called repeatedly
 157  
      * to re-register the driver.
 158  
      * 
 159  
      * @param driver
 160  
      *            The BookDriver to add
 161  
      * @throws BookException when an error occurs when performing this method
 162  
      */
 163  
     public synchronized void registerDriver(BookDriver driver) throws BookException {
 164  0
         log.debug("begin registering driver: {}", driver.getClass().getName());
 165  
 
 166  0
         drivers.add(driver);
 167  
 
 168  
         // Go through all the books and add all the new ones.
 169  
         // Remove those that are not known to the driver, but used to be.
 170  0
         Book[] bookArray = driver.getBooks();
 171  0
         Set<Book> current = CollectionUtil.createSet(new BookFilterIterator(books, BookFilters.getBooksByDriver(driver)));
 172  
 
 173  0
         for (int j = 0; j < bookArray.length; j++) {
 174  0
             Book b = bookArray[j];
 175  0
             if (current.contains(b)) {
 176  
                 // Since it was already in there, we don't add it.
 177  
                 // By removing it from current we will be left with
 178  
                 // what is not now known by the driver.
 179  0
                 current.remove(b);
 180  
             } else {
 181  0
                 addBook(bookArray[j]);
 182  
             }
 183  
         }
 184  
 
 185  
         // Remove the books from the previous version of the driver
 186  
         // that are not in this version.
 187  0
         for (Book book : current) {
 188  0
             removeBook(book);
 189  
         }
 190  
 
 191  0
         log.debug("end registering driver: {}", driver.getClass().getName());
 192  0
     }
 193  
 
 194  
     /**
 195  
      * Since Books keeps a track of drivers itself, including creating them when
 196  
      * registered it can be hard to get a hold of the current book driver. This
 197  
      * method gives access to the registered instances.
 198  
      * 
 199  
      * @param type the type of BookDriver
 200  
      * @return matching BookDrivers
 201  
      */
 202  
     public synchronized BookDriver[] getDriversByClass(Class<? extends BookDriver> type) {
 203  0
         List<BookDriver> matches = new ArrayList<BookDriver>();
 204  0
         for (BookDriver driver : drivers) {
 205  0
             if (driver.getClass() == type) {
 206  0
                 matches.add(driver);
 207  
             }
 208  
         }
 209  
 
 210  0
         return matches.toArray(new BookDriver[matches.size()]);
 211  
     }
 212  
 
 213  
     /**
 214  
      * Get an array of all the known drivers
 215  
      * 
 216  
      * @return Found int or the default value
 217  
      */
 218  
     public synchronized BookDriver[] getDrivers() {
 219  0
         return drivers.toArray(new BookDriver[drivers.size()]);
 220  
     }
 221  
 
 222  
     /**
 223  
      * Registers all the drivers known to the program.
 224  
      */
 225  
     private void autoRegister() {
 226  
         // This will classload them all and they will register themselves.
 227  0
         Class<? extends BookDriver>[] types = PluginUtil.getImplementors(BookDriver.class);
 228  
 
 229  0
         log.debug("begin auto-registering {} drivers:", Integer.toString(types.length));
 230  
 
 231  0
         for (int i = 0; i < types.length; i++) {
 232  
             // job.setProgress(Msg.JOB_DRIVER.toString() +
 233  
             // ClassUtils.getShortClassName(types[i]));
 234  
 
 235  
             try {
 236  0
                 Method driverInstance = types[i].getMethod("instance", new Class[0]);
 237  0
                 BookDriver driver = (BookDriver) driverInstance.invoke(null, new Object[0]); // types[i].newInstance();
 238  0
                 registerDriver(driver);
 239  0
             } catch (NoSuchMethodException e) {
 240  0
                 Reporter.informUser(Books.class, e);
 241  0
             } catch (IllegalArgumentException e) {
 242  0
                 Reporter.informUser(Books.class, e);
 243  0
             } catch (IllegalAccessException e) {
 244  0
                 Reporter.informUser(Books.class, e);
 245  0
             } catch (InvocationTargetException e) {
 246  0
                 Reporter.informUser(Books.class, e);
 247  0
             } catch (BookException e) {
 248  0
                 Reporter.informUser(Books.class, e);
 249  0
             }
 250  
         }
 251  0
     }
 252  
 
 253  
     /**
 254  
      * The collection of Books
 255  
      */
 256  
     private Set<Book> books;
 257  
 
 258  
     /**
 259  
      * The map of book initials
 260  
      */
 261  
     private Map<String, Book> initials;
 262  
 
 263  
     /**
 264  
      * The map of book names
 265  
      */
 266  
     private Map<String, Book> names;
 267  
 
 268  
     /**
 269  
      * An array of BookDrivers
 270  
      */
 271  
     private Set<BookDriver> drivers;
 272  
 
 273  
     /**
 274  
      * The log stream
 275  
      */
 276  0
     private static final Logger log = LoggerFactory.getLogger(Books.class);
 277  
 
 278  
     /**
 279  
      * The singleton instance.
 280  
      * This needs to be declared after all other statics it uses.
 281  
      */
 282  0
     private static final Books instance = new Books();
 283  
     // And it cannot register books until it is fully constructed
 284  
     // When this was the last call in the constructor it resulted
 285  
     // in "instance" being null in something it called.
 286  
     static {
 287  0
         log.trace("Auto-registering start @ {}", Long.toString(System.currentTimeMillis()));
 288  0
         instance.autoRegister();
 289  0
         log.trace("Auto-registering stop @ {}", Long.toString(System.currentTimeMillis()));
 290  0
     }
 291  
 }