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: BookFilters.java 1466 2007-07-02 02:48:09Z 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.List;
28  
29  import org.crosswire.common.util.Logger;
30  
31  /**
32   * Some common implementations of BookFilter.
33   *
34   * @see gnu.lgpl.License for license details.
35   *      The copyright to this program is held by it's authors.
36   * @author Joe Walker [joe at eireneh dot com]
37   */
38  public final class BookFilters
39  {
40      /**
41       * Ensure we cant be created
42       */
43      private BookFilters()
44      {
45      }
46  
47      /**
48       * A simple default filter that returns everything
49       */
50      public static BookFilter getAll()
51      {
52          return new AllBookFilter();
53      }
54  
55      /**
56       * A filter that accepts everything that implements Bible
57       * or Commentary, when commentaries are listed with Bibles.
58       */
59      public static BookFilter getBibles()
60      {
61          if (commentariesWithBibles)
62          {
63              return either(new BookCategoryFilter(BookCategory.BIBLE), new BookCategoryFilter(BookCategory.COMMENTARY));
64          }
65          return new BookCategoryFilter(BookCategory.BIBLE);
66      }
67  
68      /**
69       * A filter that accepts everything that implements Bible.
70       */
71      public static BookFilter getOnlyBibles()
72      {
73          return new BookCategoryFilter(BookCategory.BIBLE);
74      }
75  
76      /**
77       * A filter that accepts everything that's not a Bible
78       * or a Commentary, when commentaries are listed with Bibles.
79       */
80      public static BookFilter getNonBibles()
81      {
82          if (commentariesWithBibles)
83          {
84              return both(new NotBookCategoryFilter(BookCategory.BIBLE), new NotBookCategoryFilter(BookCategory.COMMENTARY));
85          }
86          return new NotBookCategoryFilter(BookCategory.BIBLE);
87      }
88  
89      /**
90       * A filter that accepts everything that implements Dictionary
91       */
92      public static BookFilter getDictionaries()
93      {
94          return new BookCategoryFilter(BookCategory.DICTIONARY);
95      }
96  
97      /**
98       * A filter that accepts everything that implements Dictionary
99       */
100     public static BookFilter getGlossaries()
101     {
102         return new BookCategoryFilter(BookCategory.GLOSSARY);
103     }
104 
105     /**
106      * A filter that accepts everything that implements DailyDevotionals
107      */
108     public static BookFilter getDailyDevotionals()
109     {
110         return new BookCategoryFilter(BookCategory.DAILY_DEVOTIONS);
111     }
112 
113     /**
114      * A filter that accepts everything that implements Commentary
115      */
116     public static BookFilter getCommentaries()
117     {
118         return new BookCategoryFilter(BookCategory.COMMENTARY);
119     }
120 
121     /**
122      * A filter that accepts everything that implements GeneralBook
123      */
124     public static BookFilter getGeneralBooks()
125     {
126         return new BookCategoryFilter(BookCategory.GENERAL_BOOK);
127     }
128 
129     /**
130      * A filter that accepts everything that is a
131      * Greek Definition Dictionary
132      */
133     public static BookFilter getGreekDefinitions()
134     {
135         return new BookFeatureFilter(FeatureType.GREEK_DEFINITIONS);
136     }
137 
138     /**
139      * A filter that accepts everything that is a
140      * Greek Parse/Morphology Dictionary
141      */
142     public static BookFilter getGreekParse()
143     {
144         return new BookFeatureFilter(FeatureType.GREEK_PARSE);
145     }
146 
147     /**
148      * A filter that accepts everything that is a
149      * Hebrew Definition Dictionary
150      */
151     public static BookFilter getHebrewDefinitions()
152     {
153         return new BookFeatureFilter(FeatureType.HEBREW_DEFINITIONS);
154     }
155 
156     /**
157      * A filter that accepts everything that is a
158      * Hebrew Parse/Morphology Dictionary
159      */
160     public static BookFilter getHebrewParse()
161     {
162         return new BookFeatureFilter(FeatureType.HEBREW_PARSE);
163     }
164 
165     /**
166      * Determine whether the getBible should return the current Bible
167      * or the user's chosen default.
168      * @return true if the bible tracks the user's selection
169      */
170     public static boolean isCommentariesWithBibles()
171     {
172         return commentariesWithBibles;
173     }
174 
175     /**
176      * Establish whether the getBible should return the current Bible
177      * or the user's chosen default.
178      * @param current
179      */
180     public static void setCommentariesWithBibles(boolean current)
181     {
182         commentariesWithBibles = current;
183     }
184 
185     /**
186      * Whether biblesBookFilter includes commentaries. Initally false.
187      */
188     private static boolean    commentariesWithBibles;
189 
190 
191     /**
192      * Filter for all books
193      */
194     static class AllBookFilter implements BookFilter
195     {
196         /* (non-Javadoc)
197          * @see org.crosswire.jsword.book.BookFilter#test(org.crosswire.jsword.book.Book)
198          */
199         public boolean test(Book book)
200         {
201             return true;
202         }
203     }
204 
205     /**
206      * Filter for books by category
207      */
208     static class BookCategoryFilter implements BookFilter
209     {
210         BookCategoryFilter(BookCategory category)
211         {
212             this.category = category;
213         }
214 
215         /* (non-Javadoc)
216          * @see org.crosswire.jsword.book.BookFilter#test(org.crosswire.jsword.book.Book)
217          */
218         public boolean test(Book book)
219         {
220             return book.getBookCategory().equals(category) && !book.isLocked();
221         }
222 
223         private BookCategory category;
224     }
225 
226     /**
227      * Filter for books by category
228      */
229     static class NotBookCategoryFilter implements BookFilter
230     {
231         NotBookCategoryFilter(BookCategory category)
232         {
233             this.category = category;
234         }
235 
236         /* (non-Javadoc)
237          * @see org.crosswire.jsword.book.BookFilter#test(org.crosswire.jsword.book.Book)
238          */
239         public boolean test(Book book)
240         {
241             return !book.getBookCategory().equals(category) && !book.isLocked();
242         }
243 
244         private BookCategory category;
245     }
246 
247     /**
248      * Filter for books by feature
249      */
250     public static class BookFeatureFilter implements BookFilter
251     {
252         public BookFeatureFilter(FeatureType feature)
253         {
254             this.feature = feature;
255         }
256 
257         /* (non-Javadoc)
258          * @see org.crosswire.jsword.book.BookFilter#test(org.crosswire.jsword.book.Book)
259          */
260         public boolean test(Book book)
261         {
262             return book.hasFeature(feature) && !book.isLocked();
263         }
264 
265         private FeatureType feature;
266     }
267 
268     /**
269      * A filter that accepts Books that match two criteria.
270      */
271     public static BookFilter both(final BookFilter b1, final BookFilter b2)
272     {
273         return new BookFilter()
274         {
275             public boolean test(Book book)
276             {
277                 return b1.test(book) && b2.test(book);
278             }
279         };
280     }
281 
282     /**
283      * A filter that accepts Books that match either of two criteria.
284      */
285     public static BookFilter either(final BookFilter b1, final BookFilter b2)
286     {
287         return new BookFilter()
288         {
289             public boolean test(Book book)
290             {
291                 return b1.test(book) || b2.test(book);
292             }
293         };
294     }
295 
296     /**
297      * A filter that accepts Books that match by book driver.
298      */
299     public static BookFilter getBooksByDriver(final BookDriver driver)
300     {
301         return new BookFilter()
302         {
303             public boolean test(Book book)
304             {
305                 return book.getDriver() == driver;
306             }
307         };
308     }
309 
310     /**
311      * A simple default filter that returns everything.
312      * The match parameter is a set of name value pairs like this:
313      * <br/>
314      * <code>initials=ESV;type=Bible;driverName=Sword</code><br/>
315      * Before the = there must be the name of a property on Book and after
316      * the value to match (.toString()) is called on the results of the getter.
317      * @param match a ; separated list of properties (of Book) to match
318      * @see Book
319      */
320     public static BookFilter getCustom(String match)
321     {
322         return new CustomBookFilter(match);
323     }
324 
325     /**
326      * Custom Filter
327      */
328     static class CustomBookFilter implements BookFilter
329     {
330         /**
331          * Ctor
332          * @param match The match spec.
333          * @see BookFilters#getCustom(String)
334          */
335         public CustomBookFilter(String match)
336         {
337             List cache = new ArrayList();
338             String[] filters = match.split(";"); //$NON-NLS-1$
339             for (int i = 0; i < filters.length; i++)
340             {
341                 String[] parts = filters[i].split("="); //$NON-NLS-1$
342                 if (parts.length != 2 || parts[0].length() == 0 || parts[1].length() == 0)
343                 {
344                     throw new IllegalArgumentException("Filter format is 'property=value', given: " + filters[i]); //$NON-NLS-1$
345                 }
346 
347                 Test test = new Test();
348 
349                 String gettername = "get" + Character.toTitleCase(parts[0].charAt(0)) + parts[0].substring(1); //$NON-NLS-1$
350                 try
351                 {
352                     test.property = Book.class.getMethod(gettername, (Class[]) null);
353                     test.result = parts[1];
354                 }
355                 catch (NoSuchMethodException ex)
356                 {
357                     throw new IllegalArgumentException("Missing property: " + parts[0] + " in Book"); //$NON-NLS-1$ //$NON-NLS-2$
358                 }
359 
360                 cache.add(test);
361             }
362 
363             tests = (Test[]) cache.toArray(new Test[cache.size()]);
364         }
365 
366         /* (non-Javadoc)
367          * @see org.crosswire.jsword.book.BookFilter#test(org.crosswire.jsword.book.Book)
368          */
369         public boolean test(Book book)
370         {
371             for (int i = 0; i < tests.length; i++)
372             {
373                 Test test = tests[i];
374                 try
375                 {
376                     Object result = test.property.invoke(book, (Object[]) null);
377                     if (!test.result.equals(result.toString()))
378                     {
379                         return false;
380                     }
381                 }
382                 catch (IllegalArgumentException e)
383                 {
384                     log.warn("Error while testing property " + test.property.getName() + " on " + book.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
385                     return false;
386                 }
387                 catch (IllegalAccessException e)
388                 {
389                     log.warn("Error while testing property " + test.property.getName() + " on " + book.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
390                     return false;
391                 }
392                 catch (InvocationTargetException e)
393                 {
394                     log.warn("Error while testing property " + test.property.getName() + " on " + book.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
395                     return false;
396                 }
397             }
398 
399             return true;
400         }
401 
402         private Test[] tests;
403 
404         /**
405          *
406          */
407         static class Test
408         {
409             protected String result;
410             protected Method property;
411         }
412     }
413 
414     /**
415      * The log stream
416      */
417     static final Logger log = Logger.getLogger(BookFilters.class);
418 }
419