Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/eireneh/bible/passage/RangedPassage.java


1   
2   package com.eireneh.bible.passage;
3   
4   import java.io.*;
5   import java.beans.*;
6   import java.util.*;
7   
8   import com.sun.java.util.collections.*;
9   import java.util.NoSuchElementException;
10  
11  import com.eireneh.util.LogicError;
12  import com.eireneh.util.IteratorEnumeration;
13  
14  /**
15   * A Passage that is implemented using a TreeSet of VerseRanges.
16   * The attributes of the style are:<ul>
17   * <li>Compact storage of large amounts of data
18   * <li>Fast getName()
19   * <li>Slow manipulation
20   * </ul>
21   *
22   * <p>When to normalize()? This is a slow process, but one that is perhaps
23   * done bit-by-bit instead of killing everything just to do getName().
24   * The options are:<ul>
25   * <li>Before every read</li>
26   * <li>Before reads with a background thread</li>
27   * <li>After every change</li>
28   * <li>After every change with a cacheing scheme</li>
29   * </ul>
30   * I'm not sure which will be best. So I'm starting with 1 and
31   * optimizing later ... Maybe the best is to allow the user to choose?
32   *
33   * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
34   * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
35   * Distribution Licence:<br />
36   * Project B is free software; you can redistribute it
37   * and/or modify it under the terms of the GNU General Public License,
38   * version 2 as published by the Free Software Foundation.<br />
39   * This program is distributed in the hope that it will be useful,
40   * but WITHOUT ANY WARRANTY; without even the implied warranty of
41   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
42   * General Public License for more details.<br />
43   * The License is available on the internet
44   * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
45   * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
46   * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
47   * The copyright to this program is held by it's authors.
48   * </font></td></tr></table>
49   * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
50   * @see docs.Licence
51   * @author Joe Walker
52   * @stereotype container
53   * @version $Id:$
54   */
55  public class RangedPassage extends AbstractPassage
56  {
57      /**
58       * Create an empty RangedPassage. There are no ctors from either Verse
59       * or VerseRange so you need to do new <code>RangedPassage().add(...);</code>
60       */
61      protected RangedPassage()
62      {
63      }
64  
65      /**
66       * Create a Verse from a human readable string. The opposite
67       * of getName(), Given any RangedPassage v1, and the following
68       * <code>RangedPassage v2 = new RangedPassage(v1.getName());</code>
69       * Then <code>v1.equals(v2);</code>
70       * Theoretically, since there are many ways of representing a RangedPassage as text
71       * string comparision along the lines of:
72       * <code>v1.getName().equals(v2.getName())</code> could be false.
73       * However since getName() is standardized this will be true.
74       * We don't need to worry about thread safety in a ctor since we don't exist yet.
75       * @param refs A String containing the text of the RangedPassage
76       */
77      protected RangedPassage(String refs) throws NoSuchVerseException
78      {
79          super(refs);
80  
81          store = Collections.synchronizedSortedSet(new TreeSet());
82          addVerses(refs);
83          normalize();
84      }
85  
86      /**
87       * Get a copy of ourselves. Points to note:
88       *   Call clone() not new() on member Objects, and on us.
89       *   Do not use Copy Constructors! - they do not inherit well.
90       *   Think about this needing to be synchronized
91       *   If this is not cloneable then writing cloneable children is harder
92       * @return A complete copy of ourselves
93       * @exception java.lang.CloneNotSupportedException We don't do this but our kids might
94       */
95      public Object clone() throws CloneNotSupportedException
96      {
97          // This gets us a shallow copy
98          RangedPassage copy = (RangedPassage) super.clone();
99  
100         // I want to just do the following
101         //   copy.store = (SortedSet) store.clone();
102         // However SortedSet is not Clonable so I can't
103         // Watch out for this, I'm not sure if it breaks anything.
104         copy.store = Collections.synchronizedSortedSet(new TreeSet());
105         copy.store.addAll(store);
106 
107         return copy;
108     }
109 
110     /**
111      * @return the number of VerseRanges in this Passage
112      */
113     public int countRanges()
114     {
115         return store.size();
116     }
117 
118     /**
119      * @return the number of Verses in this Passage
120      */
121     public int countVerses()
122     {
123         Enumeration en = rangeElements();
124         int count = 0;
125 
126         while (en.hasMoreElements())
127         {
128             VerseRange range = (VerseRange) en.nextElement();
129             count += range.getVerseCount();
130         }
131 
132         return count;
133     }
134 
135     /**
136      * Iterate over the Verses
137      * @return A list enumerator
138      */
139     public Enumeration verseElements()
140     {
141         return new VerseEnumeration();
142     }
143 
144     /**
145      * Iterate over the VerseRanges
146      * @return A list enumerator
147      */
148     public Enumeration rangeElements()
149     {
150         return new IteratorEnumeration(store.iterator());
151     }
152 
153     /**
154      * @returns true if this Passage contains no Verses
155      */
156     public boolean isEmpty()
157     {
158         return store.isEmpty();
159     }
160 
161     /**
162      * Returns true if this Passage contains the specified Verse.
163      * @param obj Verse whose presence in this Passage is to be tested.
164      * @return true if this Passage contains the specified Verse
165      */
166     public boolean contains(VerseBase obj)
167     {
168         // Even for the conatins(VerseRange) case, the simple
169         // 'return store.contains(that);' will not work because
170         // VerseRanges can contain others but not be equal to them.
171 
172         VerseRange that_range = toVerseRange(obj);
173 
174         Enumeration en = rangeElements();
175         while (en.hasMoreElements())
176         {
177             VerseRange this_range = (VerseRange) en.nextElement();
178             if (this_range.contains(that_range)) return true;
179         }
180 
181         // If it is not a Verse or a VerseRange then it's not here,
182         // this also copes with the searches failing.
183         return false;
184     }
185 
186     /**
187      * Ensures that this Passage contains the specified Verse
188      * @param obj Verse whose presence in this Passage is to be ensured.
189      */
190     public void add(VerseBase obj)
191     {
192         optimizeWrites();
193 
194         VerseRange that_range = toVerseRange(obj);
195         store.add(that_range);
196 
197         normalize();
198 
199         // we do an extra check here because the cost of calculating the
200         // params is non-zero an may be wasted
201         if (suppress_events == 0)
202             fireIntervalAdded(this, that_range.getStart(), that_range.getEnd());
203     }
204 
205     /**
206      * Removes all of the Verses from this Passage.
207      */
208     public void clear()
209     {
210         optimizeWrites();
211 
212         store.clear();
213         fireIntervalRemoved(this, null, null);
214     }
215 
216     /**
217      * Removes a single instance of the specified Verse from this Passage
218      * @param obj Verse to be removed from this Passage, if present.
219      */
220     public void remove(VerseBase obj)
221     {
222         optimizeWrites();
223 
224         VerseRange that_range = toVerseRange(obj);
225         boolean removed = false;
226 
227         // This allows us to modify store which iterating through a copy
228         SortedSet new_store = Collections.synchronizedSortedSet(new TreeSet());
229         new_store.addAll(store);
230         Iterator it = new_store.iterator();
231 
232         // go through all the VerseRanges
233         while (it.hasNext())
234         {
235             // if this range touches the range to be removed ...
236             VerseRange this_range = (VerseRange) it.next();
237             if (this_range.overlaps(that_range))
238             {
239                 // ... remove it and add the remainder
240                 store.remove(this_range);
241                 VerseRange[] vra = VerseRange.remainder(this_range, that_range);
242 
243                 for (int i=0; i<vra.length; i++)
244                     store.add(vra[i]);
245 
246                 removed = true;
247             }
248         }
249 
250         if (removed) normalize();
251 
252         // we do an extra check here because the cost of calculating the
253         // params is non-zero an may be wasted
254         if (suppress_events == 0)
255             fireIntervalRemoved(this, that_range.getStart(), that_range.getEnd());
256     }
257 
258     /**
259      * Retains only the Verses in this Passage that are contained in the
260      * specified Passage
261      * @param that Verses to be retained in this Passage.
262      */
263     public void retainAll(Passage that)
264     {
265         optimizeWrites();
266 
267         SortedSet new_store = Collections.synchronizedSortedSet(new TreeSet());
268 
269         Enumeration that_en = null;
270         if (that instanceof Passage)  that_en = ((Passage) that).rangeElements();
271         else                            that_en = that.verseElements();
272 
273         while (that_en.hasMoreElements())
274         {
275             VerseRange that_range = toVerseRange(that_en.nextElement());
276 
277             // go through all the VerseRanges
278             Enumeration this_en = rangeElements();
279             while (this_en.hasMoreElements())
280             {
281                 // if this range touches the range to be removed ...
282                 VerseRange this_range = (VerseRange) this_en.nextElement();
283                 if (this_range.overlaps(that_range))
284                 {
285                     // ... remove it and add the remainder
286                     VerseRange interstect = VerseRange.intersection(this_range, that_range);
287                     if (interstect != null) new_store.add(interstect);
288                 }
289             }
290         }
291 
292         store = new_store;
293         normalize();
294 
295         fireIntervalRemoved(this, null, null);
296     }
297 
298     /**
299      * We sometimes need to sort ourselves out ...
300      * I don't think we need to be synchronised since we are private
301      * and we could check that all public calling of normalize() are
302      * synchronised, however this is safe, and I don't think there is
303      * a cost associated with a double synchronize. (?)
304      */
305     protected void normalize()
306     {
307         if (skip_normalization != 0) return;
308 
309         VerseRange last = null;
310         VerseRange next = null;
311         SortedSet new_store = Collections.synchronizedSortedSet(new TreeSet());
312 
313         Enumeration en = rangeElements();
314         while (en.hasMoreElements())
315         {
316             next = (VerseRange) en.nextElement();
317 
318             if (last != null && next.adjacentTo(last))
319             {
320                 VerseRange merge = new VerseRange(last, next);
321 
322                 new_store.remove(last);
323                 new_store.add(merge);
324 
325                 last = merge;
326             }
327             else
328             {
329                 new_store.add(next);
330                 last = next;
331             }
332         }
333 
334         store = new_store;
335     }
336 
337     /**
338      * This class is here to prevent users of RangedPassage.iterator() from
339      * altering the underlying store and getting us out of sync. Right
340      * now there are no issues with someone else removing a RangedPassage
341      * without telling us, however there may be some day, and I'm not
342      * sure that we need the functionality right now.
343      * Also buy using this we get to ensure synchronization.
344      * Everything is final so to save the proxying performace hit.
345      * @author Joe Walker
346      */
347     private final class VerseEnumeration implements Enumeration
348     {
349         /**
350          * Create a basic iterator that is a proxy for the RangedPassage Passages
351          * iterator, with remove() overridden.
352          */
353         public VerseEnumeration()
354         {
355             try
356             {
357                 SortedSet temp = Collections.synchronizedSortedSet(new TreeSet());
358                 Enumeration en = rangeElements();
359 
360                 while (en.hasMoreElements())
361                 {
362                     VerseRange range = (VerseRange) en.nextElement();
363 
364                     for (int i=0; i<range.getVerseCount(); i++)
365                     {
366                         temp.add(new Verse(range.getStart().getOrdinal()+i));
367                     }
368                 }
369 
370                 real = temp.iterator();
371             }
372             catch (NoSuchVerseException ex)
373             {
374                 throw new LogicError(ex);
375             }
376         }
377 
378         /**
379          * Returns true if the iteration has more Verses.
380          * Pass the request on to the real iterator.
381          */
382         public final boolean hasMoreElements()
383         {
384             return real.hasNext();
385         }
386 
387         /**
388          * Returns the next Verse in the interation.
389          * Pass the request on to the real iterator.
390          */
391         public final Object nextElement() throws NoSuchElementException
392         {
393             return real.next();
394         }
395 
396         /** The Iterator that we are proxying to */
397         private Iterator real;
398     }
399 
400     /**
401      * Call the support mechanism in AbstractPassage
402      * @param out The stream to write our state to
403      * @serialData Write the ordinal number of this verse
404      * @see AbstractPassage#writeObjectSupport(ObjectOutputStream)
405      */
406     private void writeObject(ObjectOutputStream out) throws IOException
407     {
408         writeObjectSupport(out);
409     }
410 
411     /**
412      * Call the support mechanism in AbstractPassage
413      * @param in The stream to read our state from
414      * @serialData Write the ordinal number of this verse
415      * @see AbstractPassage#readObjectSupport(ObjectInputStream)
416      */
417     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
418     {
419         optimizeWrites();
420 
421         store = Collections.synchronizedSortedSet(new TreeSet());
422         readObjectSupport(in);
423     }
424 
425     /** To make serialization work across new versions */
426     static final long serialVersionUID = 955115811339960826L;
427 
428     /** The place the real data is stored */
429     private transient SortedSet store = Collections.synchronizedSortedSet(new TreeSet());
430 }