1   /**
2    * Distribution License:
3    * BibleDesktop is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU General Public License, version 2 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 General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/gpl.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: BooksListModel.java 2230 2012-02-08 00:00:10Z dmsmith $
21   */
22  package org.crosswire.bibledesktop.book;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Comparator;
29  import java.util.List;
30  
31  import javax.swing.AbstractListModel;
32  import javax.swing.event.ListDataListener;
33  
34  import org.crosswire.common.util.Logger;
35  import org.crosswire.jsword.book.Book;
36  import org.crosswire.jsword.book.BookFilter;
37  import org.crosswire.jsword.book.BookList;
38  import org.crosswire.jsword.book.Books;
39  import org.crosswire.jsword.book.BooksEvent;
40  import org.crosswire.jsword.book.BooksListener;
41  
42  /**
43   * BooksListModel creates a Swing ListModel from the available Bibles. I would
44   * normally implement BooksListener in an inner class however doing that would
45   * stop me calling fireInterval*() in AbstractListModel because that is a
46   * protected method and the inner class is neither in the same package or a sub
47   * class.
48   * 
49   * @see gnu.gpl.License for license details.<br>
50   *      The copyright to this program is held by it's authors.
51   * @author Joe Walker [joe at eireneh dot com]
52   */
53  public class BooksListModel extends AbstractListModel {
54      /**
55       * Basic constructor
56       */
57      public BooksListModel() {
58          this(null, null);
59      }
60  
61      /**
62       * Basic constructor
63       */
64      public BooksListModel(BookFilter filter) {
65          this(filter, Books.installed(), null);
66      }
67  
68      /**
69       * Basic constructor, redefining ordering.
70       */
71      public BooksListModel(BookFilter filter, Comparator<Book> comp) {
72          this(filter, Books.installed(), comp);
73      }
74  
75      /**
76       * Basic constructor for a filtered list of books, ordered as requested.
77       */
78      public BooksListModel(BookFilter filter, BookList bookList, Comparator<Book> comparator) {
79          this.filter = filter;
80          this.bookList = bookList;
81          this.comparator = comparator;
82  
83          cacheData();
84      }
85  
86      /* (non-Javadoc)
87       * @see javax.swing.ListModel#getSize()
88       */
89      public synchronized int getSize() {
90          return books.size();
91      }
92  
93      /* (non-Javadoc)
94       * @see javax.swing.ListModel#getElementAt(int)
95       */
96      public synchronized Object getElementAt(int index) {
97          // PARANOIA(joe): this check shouldn't be needed
98          if (index > books.size()) {
99              log.error("trying to get book at " + index + " when there are only " + books.size() + " known books.");
100             return null;
101         }
102 
103         return books.get(index);
104     }
105 
106     /**
107      * Returns the index-position of the specified object in the list.
108      * 
109      * @param test
110      *            the object to find
111      * @return an int representing the index position, where 0 is the first
112      *         position
113      */
114     public synchronized int getIndexOf(Object test) {
115         return books.indexOf(test);
116     }
117 
118     /**
119      * @param filter
120      */
121     public void setFilter(BookFilter filter) {
122         synchronized (this) {
123             this.filter = filter;
124         }
125         cacheData();
126 
127         fireContentsChanged(this, 0, getSize());
128     }
129 
130     /* (non-Javadoc)
131      * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
132      */
133     @Override
134     public void addListDataListener(ListDataListener li) {
135         if (listenerList.getListenerCount() == 0) {
136             bookList.addBooksListener(listener);
137         }
138 
139         super.addListDataListener(li);
140     }
141 
142     /* (non-Javadoc)
143      * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
144      */
145     @Override
146     public void removeListDataListener(ListDataListener li) {
147         super.removeListDataListener(li);
148 
149         if (listenerList.getListenerCount() == 0) {
150             bookList.removeBooksListener(listener);
151         }
152     }
153 
154     /**
155      * Setup the data-stores of the current Bibles and drivers
156      */
157     protected final synchronized void cacheData() {
158         books = new ArrayList<Book>();
159         books.addAll(bookList.getBooks(filter));
160         Collections.sort(books, comparator);
161     }
162 
163     /**
164      * So we can get a handle on what Bibles there are
165      */
166     class CustomListDataListener implements BooksListener {
167         /* (non-Javadoc)
168          * @see org.crosswire.jsword.book.BooksListener#bookAdded(org.crosswire.jsword.book.BooksEvent)
169          */
170         public void bookAdded(BooksEvent ev) {
171             int oldsize = getSize();
172             cacheData();
173             fireContentsChanged(ev.getSource(), 0, oldsize);
174         }
175 
176         /* (non-Javadoc)
177          * @see org.crosswire.jsword.book.BooksListener#bookRemoved(org.crosswire.jsword.book.BooksEvent)
178          */
179         public void bookRemoved(BooksEvent ev) {
180             int oldsize = getSize();
181             cacheData();
182             fireContentsChanged(ev.getSource(), 0, oldsize);
183         }
184     }
185 
186     /* (non-Javadoc)
187      * @see javax.swing.AbstractListModel#fireContentsChanged(java.lang.Object, int, int)
188      */
189     @Override
190     protected void fireContentsChanged(Object source, int index0, int index1) {
191         super.fireContentsChanged(source, index0, index1);
192     }
193 
194     /**
195      * Serialization support.
196      * 
197      * @param is
198      * @throws IOException
199      * @throws ClassNotFoundException
200      */
201     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
202         listener = new CustomListDataListener();
203         filter = null;
204         // This is not quite right. Probably should write out the Book initials
205         // and read them in here.
206         // But at this time we don't serialize views.
207         bookList = Books.installed();
208         books = new ArrayList<Book>();
209 
210         is.defaultReadObject();
211     }
212 
213     /**
214      * The list of books in this tree
215      */
216     private transient BookList bookList;
217 
218     /**
219      * The filter used to choose Bibles
220      */
221     private transient BookFilter filter;
222 
223     /**
224      * The listener
225      */
226     private transient CustomListDataListener listener = new CustomListDataListener();
227 
228     /**
229      * The array of versions. All methods that access this variable have been
230      * marked synchronized to ensure that one thread can't update the list of
231      * books while another is trying to create a JList based on this class.
232      */
233     protected transient List<Book> books;
234 
235     /**
236      * The sort algorithm to use.
237      */
238     protected transient Comparator<Book> comparator;
239 
240     /**
241      * The log stream
242      */
243     private static final Logger log = Logger.getLogger(BooksListModel.class);
244 
245     /**
246      * Serialization ID
247      */
248     private static final long serialVersionUID = 3257568408165036595L;
249 }
250