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