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.Collection;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.Set;
27  import java.util.TreeSet;
28  
29  import org.crosswire.common.util.Filter;
30  
31  /**
32   * BookSet represents a collection of descriptions about Books which may be
33   * subsetted into other BookMetaDataSets. Each set is naturally ordered.
34   * 
35   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
36   * @author DM Smith
37   */
38  public class BookSet extends ArrayList<Book> implements Set<Book> {
39      public BookSet() {
40      }
41  
42      public BookSet(Collection<Book> books) {
43          this();
44          addAll(books);
45      }
46  
47      /**
48       * Gets the sorted set of all keys which can be used for groupings. These
49       * are all the property keys across the BookMetaDatas in this list.
50       * 
51       * @return the set of all keys which can be used for grouping.
52       */
53      public Set<String> getGroups() {
54          Set<String> results = new TreeSet<String>();
55          for (Book book : this) {
56              results.addAll(book.getPropertyKeys());
57          }
58          return results;
59      }
60  
61      /**
62       * Get the sorted set of all values for a particular key. If there is a
63       * BookMetaData that does not have a value for that key, then null will be
64       * in the set. This can be use to categorize books that don't have that key.
65       * For example, "Language" will return all the languages for this
66       * BookMetaDataList and null for which the language is unknown.
67       * 
68       * @param key the property key
69       * @return the values for a particular key.
70       */
71      public Set<Object> getGroup(String key) {
72          Set<Object> results = new TreeSet<Object>();
73          for (Book book : this) {
74              Object property = book.getProperty(key);
75              if (property != null) {
76                  results.add(property);
77              }
78          }
79          return results;
80      }
81  
82      public BookSet filter(String key, Object value) {
83          return filter(new GroupFilter(key, value));
84      }
85  
86      /**
87       * Get a set of books that satisfy the condition imposed by the filter.
88       * 
89       * @param filter the condition on which to select books
90       * @return the set of matching books
91       */
92      public BookSet filter(Filter<Book> filter) {
93          // create a copy of the list and
94          // remove everything that fails the test.
95          BookSet listSet = (BookSet) clone();
96          Iterator<Book> iter = listSet.iterator();
97          while (iter.hasNext()) {
98              Book obj = iter.next();
99              if (!filter.test(obj)) {
100                 iter.remove();
101             }
102         }
103         return listSet;
104     }
105 
106     /* (non-Javadoc)
107      * @see java.util.ArrayList#add(int, java.lang.Object)
108      */
109     @Override
110     public void add(int index, Book element) {
111         // ignore the requested index
112         add(element);
113     }
114 
115     /* (non-Javadoc)
116      * @see java.util.ArrayList#add(java.lang.Object)
117      */
118     @Override
119     public final boolean add(Book book) {
120         // Add the item only if it is not in the list.
121         // Add it into the list so that it is in sorted order.
122         int pos = Collections.binarySearch(this, book);
123         if (pos < 0) {
124             super.add(-pos - 1, book);
125             return true;
126         }
127         return false;
128     }
129 
130     /* (non-Javadoc)
131      * @see java.util.ArrayList#addAll(java.util.Collection)
132      */
133     @Override
134     public final boolean addAll(Collection<? extends Book> c) {
135         // Might be better to add the list to the end
136         // and then sort the list.
137         // This can be revisited if the list performs badly.
138         boolean added = false;
139         for (Book book : c) {
140             if (add(book)) {
141                 added = true;
142             }
143         }
144         return added;
145     }
146 
147     /* (non-Javadoc)
148      * @see java.util.ArrayList#addAll(int, java.util.Collection)
149      */
150     @Override
151     public final boolean addAll(int index, Collection<? extends Book> c) {
152         // Ignore the index
153         return addAll(c);
154     }
155 
156     /* (non-Javadoc)
157      * @see java.util.ArrayList#set(int, java.lang.Object)
158      */
159     @Override
160     public Book set(int index, Book element) {
161         // remove the item at the index (keep it to return it),
162         // then insert the item into the sorted list.
163         Book item = remove(index);
164         add(element);
165         return item;
166     }
167 
168     /**
169      * GroupFilter does the SQL traditional group by.
170      */
171     private static final class GroupFilter implements Filter<Book> {
172         GroupFilter(String aKey, Object aValue) {
173             key = aKey;
174             value = aValue;
175         }
176 
177         public boolean test(Book book) {
178             String property = book.getProperty(key);
179             return property != null && property.equals(value);
180         }
181 
182         private String key;
183         private Object value;
184     }
185 
186     /**
187      * Serialization ID
188      */
189     private static final long serialVersionUID = 3258688806185154867L;
190 
191 }
192