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

Quick Search    Search Deep

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


1   
2   package com.eireneh.bible.passage;
3   
4   import java.io.*;
5   import java.util.*;
6   
7   import com.eireneh.util.*;
8   
9   /**
10   * This is a base class to help with some of the common implementation
11   * details of being a Passage.
12   * <p>Importantly, this class takes care of Serialization in a general yet
13   * optimized way. I think I am going to have a look at replacement here.
14   *
15   * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
16   * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
17   * Distribution Licence:<br />
18   * Project B is free software; you can redistribute it
19   * and/or modify it under the terms of the GNU General Public License,
20   * version 2 as published by the Free Software Foundation.<br />
21   * This program is distributed in the hope that it will be useful,
22   * but WITHOUT ANY WARRANTY; without even the implied warranty of
23   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24   * General Public License for more details.<br />
25   * The License is available on the internet
26   * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
27   * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
28   * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
29   * The copyright to this program is held by it's authors.
30   * </font></td></tr></table>
31   * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
32   * @see docs.Licence
33   * @author Joe Walker
34   * @version $Id:$
35   * @stereotype container
36   */
37  public abstract class AbstractPassage implements Passage
38  {
39      /**
40       * Setup that leaves original name being null
41       */
42      protected AbstractPassage()
43      {
44      }
45  
46      /**
47       * Setup the original name of this reference
48       * @param original_name The text originally used to create this Passage.
49       */
50      protected AbstractPassage(String original_name)
51      {
52          this.original_name = original_name;
53      }
54  
55      /**
56       * Create a copy of ourselves
57       * @return A complete copy of ourselves
58       * @exception CloneNotSupportedException We don't do this but our kids might
59       */
60      public Object clone() throws CloneNotSupportedException
61      {
62          // This gets us a shallow copy
63          AbstractPassage copy = (AbstractPassage) super.clone();
64  
65          copy.listeners = (Vector) listeners.clone();
66          copy.original_name  = original_name;
67  
68          return copy;
69      }
70  
71      /**
72       * Is this Object equal to us. Points to note:<ul>
73       * <li>If you override equals(), you must override hashCode() too.
74       * <li>If you are doing this it is a good idea to be immutable.
75       * </ul>
76       * @param obj The thing to test against
77       * @return True/False is we are or are not equal to obj
78       */
79      public boolean equals(Object obj)
80      {
81          // Since this can not be null
82          if (obj == null) return false;
83  
84          // This is cheating beacuse I am supposed to say:
85          // <code>!obj.getClass().equals(this.getClass())</code>
86          // However I think it is entirely valid for a RangedPassage
87          // to equal a DistinctPassage since the point of the Factory
88          // is that the user does not need to know the actual type of the
89          // Object he is using.
90          if (!(obj instanceof Passage)) return false;
91  
92          Passage ref = (Passage) obj;
93          // The real test
94          if (!ref.getName().equals(getName())) return false;
95  
96          return true;
97      }
98  
99      /**
100      * @return The hashing number
101      */
102     public int hashCode()
103     {
104         return getName().hashCode();
105     }
106 
107     /**
108      * A Human readable version of the verse list. Uses short books names,
109      * and the shortest possible rendering eg "Mat 3:1-4, 6"
110      * @returns a String containing a description of the verses
111      */
112     public String getName()
113     {
114         if (PassageUtil.isPersistentNaming() && original_name != null)
115         {
116             return original_name;
117         }
118 
119         StringBuffer retcode = new StringBuffer();
120 
121         Enumeration en = rangeElements();
122         Verse current = null;
123         while (en.hasMoreElements())
124         {
125             VerseRange range = (VerseRange) en.nextElement();
126             retcode.append(range.getName(current));
127 
128             if (en.hasMoreElements())
129                 retcode.append(REF_PREF_DELIM);
130 
131             current = range.getStart();
132         }
133 
134         return retcode.toString();
135     }
136 
137     /**
138      * Simply bounce to getName() to help String concatenation.
139      * @returns a String containing a description of the verses
140      */
141     public String toString()
142     {
143         return getName();
144     }
145 
146     /**
147      * A summary of the verses in this Passage
148      * For example "Search (10 matches in 4 books)"
149      * @returns a String containing an overview of the verses
150      */
151     public String getOverview()
152     {
153         int verse_count = countVerses();
154         int book_count = booksInPassage();
155 
156         String verses = (verse_count == 1)
157                       ? PassageUtil.getResource("abstract_verse_singular")
158                       : PassageUtil.getResource("abstract_verse_plural");
159 
160         String books = (book_count == 1)
161                      ? PassageUtil.getResource("abstract_book_singular")
162                      : PassageUtil.getResource("abstract_book_plural");
163 
164         return verse_count+" "+verses+" "+book_count+" "+books;
165     }
166 
167     /**
168      * Does this Passage have 0 members
169      * @return true if the Passage is empty
170      */
171     public boolean isEmpty()
172     {
173         return countVerses() == 0;
174     }
175 
176     /**
177      * Returns the number of verses in this collection. Like Collection.size()
178      * This does not mean the Passage needs to use Verses, just that it understands the concept.
179      * @return the number of Verses in this collection
180      * @see Verse
181      */
182     public int countVerses()
183     {
184         int count = 0;
185 
186         Enumeration en = verseElements();
187         while (en.hasMoreElements())
188         {
189             en.nextElement();
190             count++;
191         }
192 
193         return count;
194     }
195 
196     /**
197      * Like countVerses() that counts VerseRanges instead of Verses
198      * Returns the number of fragments in this collection.
199      * This does not mean the Passage needs to use VerseRanges, just that it understands the concept.
200      * @return the number of VerseRanges in this collection
201      * @see VerseRange
202      */
203     public int countRanges()
204     {
205         int count = 0;
206 
207         Enumeration en = rangeElements();
208         while (en.hasMoreElements())
209         {
210             en.nextElement();
211             count++;
212         }
213 
214         return count;
215     }
216 
217     /**
218      * How many books are there in this Passage
219      * @param ref The Passage to be checking
220      * @return The number of distinct books
221      */
222     public int booksInPassage()
223     {
224         int current_book = 0;
225         int book_count = 0;
226 
227         Enumeration en = verseElements();
228         while (en.hasMoreElements())
229         {
230             Verse verse = (Verse) en.nextElement();
231             if (current_book != verse.getBook())
232             {
233                 current_book = verse.getBook();
234                 book_count++;
235             }
236         }
237 
238         return book_count;
239     }
240 
241     /**
242      * How many chapters are there in a particular book in this Passage
243      * @param book The book to be checking (0 for distinct chapters in all books)
244      * @return The number of distinct chapters
245      */
246     public int chaptersInPassage(int book) throws NoSuchVerseException
247     {
248         if (book != 0)  Books.validate(book, 1, 1);
249 
250         int current_chapter = 0;
251         int chapter_count = 0;
252 
253         Enumeration en = verseElements();
254         while (en.hasMoreElements())
255         {
256             Verse verse = (Verse) en.nextElement();
257 
258             if ((book == 0 || verse.getBook() == book)
259                 && current_chapter != verse.getChapter())
260             {
261                 current_chapter = verse.getChapter();
262                 chapter_count++;
263             }
264         }
265 
266         return chapter_count;
267     }
268 
269     /**
270      * How many chapters are there in a particular book in this Passage.
271      * Note that <code>versesInPassage(ref, 0, 0) == ref.countVerses()</code>
272      * for all ref.
273      * @param book The book to be checking (0 for distinct chapters in all books)
274      * @param chapter The chapter to be checking (0 for distinct verses in all chapters)
275      * @return The number of distinct chapters
276      */
277     public int versesInPassage(int book, int chapter) throws NoSuchVerseException
278     {
279         Books.validate((book == 0 ? 1 : book), (chapter == 0 ? 1 : chapter), 1);
280 
281         int verse_count = 0;
282 
283         Enumeration en = verseElements();
284         while (en.hasMoreElements())
285         {
286             Verse verse = (Verse) en.nextElement();
287 
288             if ((book == 0 || verse.getBook() == book)
289                 && (chapter == 0 || verse.getChapter() == chapter))
290             {
291                 verse_count++;
292             }
293         }
294 
295         return verse_count;
296     }
297 
298     /**
299      * Get a specific Verse from this collection
300      * @param offset The verse offset (legal values are 0 to countVerses()-1)
301      * @return The Verse
302      * @throws ArrayIndexOutOfBoundsException If the offset is out of range
303      */
304     public Verse getVerseAt(int offset) throws ArrayIndexOutOfBoundsException
305     {
306         Enumeration en = verseElements();
307         Object retcode = null;
308 
309         for (int i=0; i<=offset; i++)
310         {
311             if (!en.hasMoreElements())
312             {
313                 String message = PassageUtil.getResource("passg_error_index", new Object[] { new Integer(offset) });
314                 throw new ArrayIndexOutOfBoundsException(message);
315             }
316 
317             retcode = en.nextElement();
318         }
319 
320         return (Verse) retcode;
321     }
322 
323     /**
324      * Get a specific VerseRange from this collection
325      * @param offset The verse range offset (legal values are 0 to countRanges()-1)
326      * @return The Verse Range
327      * @throws ArrayIndexOutOfBoundsException If the offset is out of range
328      */
329     public VerseRange getVerseRangeAt(int offset) throws ArrayIndexOutOfBoundsException
330     {
331         Enumeration en = rangeElements();
332         Object retcode = null;
333 
334         for (int i=0; i<=offset; i++)
335         {
336             if (!en.hasMoreElements())
337             {
338                 String message = PassageUtil.getResource("passg_error_index", new Object[] { new Integer(offset) });
339                 throw new ArrayIndexOutOfBoundsException(message);
340             }
341 
342             retcode = en.nextElement();
343         }
344 
345         return (VerseRange) retcode;
346     }
347 
348     /**
349      * Enumerate over the VerseRanges
350      * @return A list enumerator
351      */
352     public Enumeration rangeElements()
353     {
354         return new VerseRangeEnumeration();
355     }
356 
357     /**
358      * Returns true if this Passage contains all of the Verses
359      * in the that Passage.
360      * @param that Passage to be checked for containment in this Passage
361      * @return true if this reference contains all of the Verses in that Passage
362      */
363     public boolean containsAll(Passage that)
364     {
365         Enumeration that_en = null;
366 
367         if (that instanceof RangedPassage) that_en = ((RangedPassage) that).rangeElements();
368         else                               that_en = that.verseElements();
369 
370         while (that_en.hasMoreElements())
371         {
372             if (!contains((VerseBase) that_en.nextElement()))
373                 return false;
374         }
375 
376         return true;
377     }
378 
379     /**
380      * Ensures that there are a maximum of <code>count</code> Verses in
381      * this Passage. If there were more than <code>count</code> Verses
382      * then a new Passage is created containing the Verses from
383      * <code>count</code>+1 onwards. If there was not greater than
384      * <code>count</code> in the Passage, then the passage remains
385      * unchanged, and null is returned.
386      * @param count The maximum number of Verses to allow in this collection
387      * @return A new Passage conatining the remaining verses or null
388      * @see Verse
389      */
390     public Passage trimVerses(int count)
391     {
392         optimizeWrites();
393         raiseNormalizeProtection();
394 
395         Passage remainder = null;
396         int i = 0;
397         boolean overflow = false;
398 
399         try
400         {
401             remainder = (Passage) this.clone();
402 
403             Enumeration en = verseElements();
404             while (en.hasMoreElements())
405             {
406                 i++;
407                 Verse verse = (Verse) en.nextElement();
408 
409                 if (i > count)
410                 {
411                     remove(verse);
412                     overflow = true;
413                 }
414                 else
415                 {
416                     remainder.remove(verse);
417                 }
418             }
419         }
420         catch (CloneNotSupportedException ex)
421         {
422             throw new LogicError(ex);
423         }
424 
425         lowerNormalizeProtection();
426         // The event notification is done by the remove above
427 
428         if (overflow)   return remainder;
429         else            return null;
430     }
431 
432     /**
433      * Ensures that there are a maximum of <code>count</code> VerseRanges
434      * in this Passage. If there were more than <code>count</code>
435      * VerseRanges then a new Passage is created containing the
436      * VerseRanges from <code>count</code>+1 onwards. If there was not
437      * greater than <code>count</code> in the Passage, then the passage
438      * remains unchanged, and null is returned.
439      * @param count The maximum number of VerseRanges to allow in this collection
440      * @return A new Passage conatining the remaining verses or null
441      * @see VerseRange
442      */
443     public Passage trimRanges(int count)
444     {
445         optimizeWrites();
446         raiseNormalizeProtection();
447 
448         Passage remainder = null;
449         int i = 0;
450         boolean overflow = false;
451 
452         try
453         {
454             remainder = (Passage) this.clone();
455 
456             Enumeration en = rangeElements();
457             while (en.hasMoreElements())
458             {
459                 i++;
460                 VerseRange range = (VerseRange) en.nextElement();
461 
462                 if (i > count)
463                 {
464                     remove(range);
465                     overflow = true;
466                 }
467                 else
468                 {
469                     remainder.remove(range);
470                 }
471             }
472         }
473         catch (CloneNotSupportedException ex)
474         {
475             throw new LogicError(ex);
476         }
477 
478         lowerNormalizeProtection();
479         // The event notification is done by the remove above
480 
481         if (overflow)   return remainder;
482         else            return null;
483     }
484 
485     /**
486      * Adds all of the elements in that Passage to this Passage.
487      * The behavior of this operation is undefined if that
488      * Passage is modified while the operation is in progress
489      * @param that elements to be inserted into this Passage
490      */
491     public void addAll(Passage that)
492     {
493         optimizeWrites();
494         raiseEventSuppresion();
495         raiseNormalizeProtection();
496 
497         Enumeration that_en = null;
498 
499         if (that instanceof Passage)  that_en = ((Passage) that).rangeElements();
500         else                          that_en = that.verseElements();
501 
502         while (that_en.hasMoreElements())
503         {
504             // Avoid touching store to make thread safety easier.
505             add((VerseBase) that_en.nextElement());
506         }
507 
508         lowerNormalizeProtection();
509         if (lowerEventSuppresionAndTest())
510             fireIntervalAdded(this, that.getVerseAt(0), that.getVerseAt(that.countVerses()-1));
511     }
512 
513     /**
514      * Removes all this Passage's Verses that are also contained in the
515      * that Passage.  After this call returns, this Passage
516      * will contain no Verses in common with the that Passage
517      * @param that Verses to be removed from this Passage
518      */
519     public void removeAll(Passage that)
520     {
521         optimizeWrites();
522         raiseEventSuppresion();
523         raiseNormalizeProtection();
524 
525         Enumeration that_en = null;
526 
527         if (that instanceof Passage)  that_en = ((Passage) that).rangeElements();
528         else                          that_en = that.verseElements();
529 
530         while (that_en.hasMoreElements())
531         {
532             // Avoid touching store to make thread safety easier.
533             remove((VerseBase) that_en.nextElement());
534         }
535 
536         lowerNormalizeProtection();
537         if (lowerEventSuppresionAndTest())
538             fireIntervalRemoved(this, that.getVerseAt(0), that.getVerseAt(that.countVerses()-1));
539     }
540 
541     /**
542      * Retains only the Verses in this Passage that are contained in that
543      * Passage. In other words, removes from this Passage all of its
544      * Verses that are not contained in that Passage
545      * @param that Verses to be retained in this Passage
546      */
547     public void retainAll(Passage that)
548     {
549         optimizeWrites();
550         raiseEventSuppresion();
551         raiseNormalizeProtection();
552 
553         try
554         {
555             Passage temp = (Passage) this.clone();
556             Enumeration en = temp.verseElements();
557 
558             while (en.hasMoreElements())
559             {
560                 Verse verse = (Verse) en.nextElement();
561                 if (!that.contains(verse))
562                 {
563                     remove(verse);
564                 }
565             }
566         }
567         catch (CloneNotSupportedException ex)
568         {
569             throw new LogicError(ex);
570         }
571 
572         lowerNormalizeProtection();
573         if (lowerEventSuppresionAndTest())
574             fireIntervalRemoved(this, null, null);
575     }
576 
577     /**
578      * Removes all of the Verses from this Passage.
579      * This implementation is ripe for optimization
580      */
581     public void clear()
582     {
583         optimizeWrites();
584         raiseNormalizeProtection();
585 
586         remove(VerseRange.getWholeBibleVerseRange());
587 
588         if (lowerEventSuppresionAndTest())
589             fireIntervalRemoved(this, null, null);
590     }
591 
592     /**
593      * Widen the range of the verses in this list. This is primarily for
594      * "find x within n verses of y" type applications.
595      * @param verses The number of verses to widen by
596      * @param restrict How should we restrict the blurring?
597      * @see com.eireneh.bible.passage.Passage
598      */
599     public void blur(int verses, int restrict)
600     {
601         optimizeWrites();
602         raiseEventSuppresion();
603         raiseNormalizeProtection();
604 
605         try
606         {
607             Passage temp = (Passage) this.clone();
608             Enumeration en = temp.rangeElements();
609 
610             while (en.hasMoreElements())
611             {
612                 VerseRange range = new VerseRange((VerseRange) en.nextElement(), verses, verses, restrict);
613                 add(range);
614             }
615         }
616         catch (CloneNotSupportedException ex)
617         {
618             throw new LogicError(ex);
619         }
620 
621         lowerNormalizeProtection();
622         if (lowerEventSuppresionAndTest())
623             fireIntervalAdded(this, null, null);
624     }
625 
626     /**
627      * To be compatible with humans we read/write ourselves to a file that
628      * a human can read and even edit. OLB verse.lst integration is a good
629      * goal here.
630      * @param out The stream to write to
631      * @exception java.io.IOException If the file/network etc breaks
632      */
633     public void writeDescription(Writer out) throws IOException
634     {
635         BufferedWriter bout = new BufferedWriter(out);
636 
637         Enumeration en = rangeElements();
638 
639         while (en.hasMoreElements())
640         {
641             VerseRange range = (VerseRange) en.nextElement();
642             bout.write(range.getName());
643             bout.newLine();
644         }
645 
646         bout.flush();
647     }
648 
649     /**
650      * To be compatible with humans we read/write ourselves to a file that
651      * a human can read and even edit. OLB verse.lst integration is a good
652      * goal here. This method does not clear before it starts reading.
653      * @param in The stream to read from
654      * @exception java.io.IOException If the file/network etc breaks
655      */
656     public void readDescription(Reader in) throws IOException, NoSuchVerseException
657     {
658         raiseEventSuppresion();
659         raiseNormalizeProtection();
660 
661         BufferedReader bin = new BufferedReader(in);
662         while (true)
663         {
664             String line = bin.readLine();
665             if (line == null) break;
666             addVerses(line);
667         }
668 
669         lowerNormalizeProtection();
670         if (lowerEventSuppresionAndTest())
671             fireIntervalAdded(this, getVerseAt(0), getVerseAt(countVerses()-1));
672     }
673 
674     /**
675      * For preformance reasons we may well want to hint to the Passage that we
676      * have done editing it for now and that it is safe to cache certain
677      * values to speed up future reads. Any action taken by this method will be
678      * undone simply by making a future edit, and the only loss in calling
679      * optimizeReads() is a loss of time if you then persist in writing to the
680      * Passage.
681      */
682     public void optimizeReads()
683     {
684     }
685 
686     /**
687      * Simple method to instruct children to stop caching results
688      */
689     protected void optimizeWrites()
690     {
691     }
692 
693     /**
694      * Event Listeners - Add Listener
695      * @param li The listener to add
696      */
697     public void addPassageListener(PassageListener li)
698     {
699         synchronized (listeners)
700         {
701             listeners.addElement(li);
702         }
703     }
704 
705     /**
706      * Event Listeners - Remove Listener
707      * @param li The listener to remove
708      */
709     public void removePassageListener(PassageListener li)
710     {
711         synchronized (listeners)
712         {
713             listeners.removeElement(li);
714         }
715     }
716 
717     /**
718      * AbstractPassage subclasses must call this method <b>after</b> one
719      * or more elements of the list are added.  The changed elements are
720      * specified by a closed interval from start to end.
721      * @param source The thing that changed, typically "this".
722      * @param start One end of the new interval.
723      * @param end The other end of the new interval.
724      * @see PassageListener
725      */
726     protected void fireIntervalAdded(Object source, Verse start, Verse end)
727     {
728         if (suppress_events != 0) return;
729 
730         Vector temp;
731 
732         // Create Event
733         PassageEvent ev = new PassageEvent(source, PassageEvent.VERSES_ADDED, start, end);
734 
735         // Copy listener vector so it won't change while firing
736         synchronized (listeners)
737         {
738             temp = (Vector) listeners.clone();
739         }
740 
741         // And run throught the list shouting
742         for (int i=0; i<temp.size(); i++)
743         {
744             PassageListener rl = (PassageListener) temp.elementAt(i);
745             rl.versesAdded(ev);
746         }
747     }
748 
749     /**
750      * AbstractPassage subclasses must call this method <b>before</b> one
751      * or more elements of the list are added.  The changed elements are
752      * specified by a closed interval from start to end.
753      * @param source The thing that changed, typically "this".
754      * @param start One end of the new interval.
755      * @param end The other end of the new interval.
756      * @see PassageListener
757      */
758     protected void fireIntervalRemoved(Object source, Verse start, Verse end)
759     {
760         if (suppress_events != 0) return;
761 
762         Vector temp;
763 
764         // Create Event
765         PassageEvent ev = new PassageEvent(source, PassageEvent.VERSES_REMOVED, start, end);
766 
767         // Copy listener vector so it won't change while firing
768         synchronized (listeners)
769         {
770             temp = (Vector) listeners.clone();
771         }
772 
773         // And run throught the list shouting
774         for (int i=0; i<temp.size(); i++)
775         {
776             PassageListener rl = (PassageListener) temp.elementAt(i);
777             rl.versesRemoved(ev);
778         }
779     }
780 
781     /**
782      * AbstractPassage subclasses must call this method <b>before</b> one
783      * or more elements of the list are added.  The changed elements are
784      * specified by a closed interval from start to end.
785      * @param source The thing that changed, typically "this".
786      * @param start One end of the new interval.
787      * @param end The other end of the new interval.
788      * @see PassageListener
789      */
790     protected void fireContentsChanged(Object source, Verse start, Verse end)
791     {
792         if (suppress_events != 0) return;
793 
794         Vector temp;
795 
796         // Create Event
797         PassageEvent ev = new PassageEvent(source, PassageEvent.VERSES_CHANGED, start, end);
798 
799         // Copy listener vector so it won't change while firing
800         synchronized (listeners)
801         {
802             temp = (Vector) listeners.clone();
803         }
804 
805         // And run throught the list shouting
806         for (int i=0; i<temp.size(); i++)
807         {
808             PassageListener rl = (PassageListener) temp.elementAt(i);
809             rl.versesChanged(ev);
810         }
811     }
812 
813     /**
814      * Create a Passage from a human readable string. The opposite of
815      * <code>toString()</code>. Since this method is not public it
816      * leaves control of <code>suppress_events<code> up to the people
817      * that call it.
818      * @param refs A String containing the text of the RangedPassage
819      */
820     protected void addVerses(String refs) throws NoSuchVerseException
821     {
822         optimizeWrites();
823 
824         String[] parts = PassageUtil.tokenize(refs, REF_ALLOWED_DELIMS);
825         if (parts.length == 0) return;
826 
827         // We treat the first as a special case because there is
828         // nothing to sensibly base this reference on
829         VerseRange basis = new VerseRange(parts[0].trim());
830         add(basis);
831 
832         // Loop for the other verses, interpreting each on the
833         // basis of the one before.
834         for (int i=1; i<parts.length; i++)
835         {
836             VerseRange next = new VerseRange(parts[i].trim(), basis);
837             add(next);
838             basis = next;
839         }
840     }
841 
842     /**
843      * We sometimes need to sort ourselves out ...
844      * I don't think we need to be synchronised since we are private
845      * and we could check that all public calling of normalize() are
846      * synchronised, however this is safe, and I don't think there is
847      * a cost associated with a double synchronize. (?)
848      */
849     protected void normalize()
850     {
851         // before doing any normalization we should be checking that
852         // skip_normalization == 0, and just returning if so.
853     }
854 
855     /**
856      * If things want to prevent normalization because they are doing
857      * a set of changes that should be normalized in one go, this is
858      * what to call. Be sure to call lowerNormalizeProtection() when
859      * you are done.
860      */
861     protected void raiseNormalizeProtection()
862     {
863         skip_normalization++;
864 
865         if (skip_normalization > 10)
866         {
867             // This is a bit drastic and does not give us much
868             // chance to fix the error
869             //   throw new LogicError();
870 
871             log.log(Level.WARNING, "skip_normalization="+skip_normalization, new Exception());
872         }
873     }
874 
875     /**
876      * If things want to prevent normalization because they are doing
877      * a set of changes that should be normalized in one go, they should
878      * call raiseNormalizeProtection() and when done call this. This also
879      * calls normalize() if the count reaches zero.
880      */
881     protected void lowerNormalizeProtection()
882     {
883         skip_normalization--;
884 
885         if (skip_normalization == 0)
886             normalize();
887 
888         if (skip_normalization < 0)
889             throw new LogicError();
890     }
891 
892     /**
893      * If things want to prevent event firing because they are doing
894      * a set of changes that should be notified in one go, this is
895      * what to call. Be sure to call lowerEventSuppression() when
896      * you are done.
897      */
898     protected void raiseEventSuppresion()
899     {
900         suppress_events++;
901 
902         if (suppress_events > 10)
903         {
904             // This is a bit drastic and does not give us much
905             // chance to fix the error
906             //   throw new LogicError();
907 
908             log.log(Level.WARNING, "suppress_events="+suppress_events, new Exception());
909         }
910     }
911 
912     /**
913      * If things want to prevent event firing because they are doing
914      * a set of changes that should be notified in one go, they should
915      * call raiseEventSuppression() and when done call this. This returns
916      * true if it is then safe to fire an event.
917      */
918     protected boolean lowerEventSuppresionAndTest()
919     {
920         suppress_events--;
921 
922         if (suppress_events < 0)
923             throw new LogicError();
924 
925         return (suppress_events == 0);
926     }
927 
928     /**
929      * Convert the Object to a VerseRange. If base is a Verse then return a
930      * VerseRange of zero length.
931      * @param base The object to be cast
932      * @return The VerseRange
933      * @exception java.lang.ClassCastException If this is not a Verse or a VerseRange
934      */
935     protected static VerseRange toVerseRange(Object base) throws ClassCastException
936     {
937         if (base == null)
938             throw new NullPointerException();
939 
940         if (base instanceof VerseRange)
941         {
942             return (VerseRange) base;
943         }
944         else if (base instanceof Verse)
945         {
946             return new VerseRange((Verse) base);
947         }
948 
949         throw new ClassCastException(PassageUtil.getResource("abstract_error_cast"));
950     }
951 
952     /**
953      * Convert the Object to an array of Verses. If base is a VerseRange then return a
954      * Verse array of the VersesRanges Verses.
955      * @param base The Object to be cast
956      * @return The Verse array
957      * @exception java.lang.ClassCastException If this is not a Verse or a VerseRange
958      */
959     protected static Verse[] toVerseArray(Object base) throws ClassCastException
960     {
961         if (base == null)
962             throw new NullPointerException();
963 
964         if (base instanceof VerseRange)
965         {
966             VerseRange range = (VerseRange) base;
967             return range.toVerseArray();
968         }
969         else if (base instanceof Verse)
970         {
971             return new Verse[] { (Verse) base };
972         }
973 
974         throw new ClassCastException(PassageUtil.getResource("abstract_error_cast"));
975     }
976 
977     /**
978      * Skip over verses that are part of a range
979      */
980     protected final class VerseRangeEnumeration implements Enumeration
981     {
982         /** iterate, amalgumating Verses into VerseRanges */
983         public VerseRangeEnumeration()
984         {
985             en = verseElements();
986             if (en.hasMoreElements()) next_verse = (Verse) en.nextElement();
987             calculateNext();
988         }
989 
990         /** Returns true if the iteration has more elements */
991         public final boolean hasMoreElements()
992         {
993             return next_range != null;
994         }
995 
996         /** Returns the next element in the interation */
997         public final Object nextElement() throws NoSuchElementException
998         {
999             Object retcode = next_range;
1000            calculateNext();
1001            return retcode;
1002        }
1003
1004        /** Find the next VerseRange */
1005        private void calculateNext()
1006        {
1007            if (next_verse == null)
1008            {
1009                next_range = null;
1010                return;
1011            }
1012
1013            Verse start = next_verse;
1014            Verse end = next_verse;
1015
1016            while (true)
1017            {
1018                if (!en.hasMoreElements())
1019                {
1020                    next_verse = null;
1021                    break;
1022                }
1023
1024                next_verse = (Verse) en.nextElement();
1025                if (!end.adjacentTo(next_verse)) break;
1026                end = next_verse;
1027            }
1028
1029            next_range = new VerseRange(start, end);
1030        }
1031
1032        /** The Iterator that we are proxying to */
1033        private Enumeration en;
1034
1035        /** What is the next VerseRange to be considered */
1036        private VerseRange next_range = null;
1037
1038        /** What is the next Verse to be considered */
1039        private Verse next_verse = null;
1040    }
1041
1042    /**
1043     * Write out the object to the given ObjectOutputStream. There are 3
1044     * ways of doing this - according to the 3 implementations of
1045     * Passage.<ul>
1046     * <li>Distinct: If we write out a list if verse ordinals then the
1047     *     space used is 4 bytes per verse.
1048     * <li>Bitwise: If we write out a bitmap then the space used is
1049     *     something like 31104/8 = 4k bytes.
1050     * <li>Ranged: The we write a list of start/end pairs then the space
1051     *     used is 8 bytes per range.
1052     * </ul>
1053     * Since we can take our time about this section, we calculate the
1054     * optimal storage method before we do the saving. If some methods
1055     * come out equal first then bitwise is preferred, then distinct,
1056     * then ranged, because I imagine that for speed of de-serialization
1057     * this is the sensible order. I've not tested it though.
1058     * @param out The stream to write our state to
1059     */
1060    protected void writeObjectSupport(ObjectOutputStream out) throws IOException
1061    {
1062        // This allows our children to have default serializable fields
1063        // even though we have none.
1064        out.defaultWriteObject();
1065
1066        // the size in bits of teach storage method
1067        int bitwise_size = Books.versesInBible();
1068        int ranged_size =  8 * countRanges();
1069        int distinct_size = 4 * countVerses();
1070
1071        // if bitwise is equal smallest
1072        if (bitwise_size <= ranged_size && bitwise_size <= distinct_size)
1073        {
1074            out.writeInt(BITWISE);
1075
1076            BitSet store = new BitSet(Books.versesInBible());
1077            Enumeration en = verseElements();
1078            while (en.hasMoreElements())
1079            {
1080                Verse verse = (Verse) en.nextElement();
1081                store.set(verse.getOrdinal()-1);
1082            }
1083
1084            out.writeObject(store);
1085        }
1086
1087        // if distinct is not bigger than ranged
1088        else if (distinct_size <= ranged_size)
1089        {
1090            // write the Passage type and the number of verses
1091            out.writeInt(DISTINCT);
1092            out.writeInt(countVerses());
1093
1094            // write the verse ordinals in a loop
1095            Enumeration en = verseElements();
1096            while (en.hasMoreElements())
1097            {
1098                Verse verse = (Verse) en.nextElement();
1099                out.writeInt(verse.getOrdinal());
1100            }
1101        }
1102
1103        // otherwise use ranges
1104        else
1105        {
1106            // write the Passage type and the number of ranges
1107            out.writeInt(RANGED);
1108            out.writeInt(countRanges());
1109
1110            // write the verse ordinals in a loop
1111            Enumeration en = rangeElements();
1112            while (en.hasMoreElements())
1113            {
1114                VerseRange range = (VerseRange) en.nextElement();
1115                out.writeInt(range.getStart().getOrdinal());
1116                out.writeInt(range.getVerseCount());
1117            }
1118        }
1119
1120        // Ignore the original name. Is this wise?
1121        // I am expecting that people are not that fussed about it and
1122        // it could make everything far more verbose
1123    }
1124
1125    /**
1126     * Write out the object to the given ObjectOutputStream
1127     * @param in The stream to read our state from
1128     */
1129    protected void readObjectSupport(ObjectInputStream in) throws IOException, ClassNotFoundException
1130    {
1131        raiseEventSuppresion();
1132        raiseNormalizeProtection();
1133
1134        // This allows our children to have default serializable fields
1135        // even though we have none.
1136        in.defaultReadObject();
1137
1138        // Setup
1139        listeners = new Vector();
1140
1141        try
1142        {
1143            int type = in.readInt();
1144            switch (type)
1145            {
1146            case BITWISE:
1147                BitSet store = (BitSet) in.readObject();
1148                for (int i=0; i<Books.versesInBible(); i++)
1149                {
1150                    if (store.get(i))
1151                        add(new Verse(i+1));
1152                }
1153                break;
1154
1155            case DISTINCT:
1156                int verses = in.readInt();
1157                for (int i=0; i<verses; i++)
1158                {
1159                    int ord = in.readInt();
1160                    add(new Verse(ord));
1161                }
1162                break;
1163
1164            case RANGED:
1165                int ranges = in.readInt();
1166                for (int i=0; i<ranges; i++)
1167                {
1168                    int ord = in.readInt();
1169                    int count = in.readInt();
1170                    add(new VerseRange(new Verse(ord), count));
1171                }
1172                break;
1173
1174            default:
1175                throw new ClassCastException(PassageUtil.getResource("abstract_error_cast"));
1176            }
1177        }
1178        catch (NoSuchVerseException ex)
1179        {
1180            throw new IOException(ex.getMessage());
1181        }
1182
1183        // We are ignoring the original_name. It was set to null in the
1184        // default ctor so I will ignore it here.
1185
1186        // We don't bother to call fireContentsChanged(...) because
1187        // nothing can have registered at this point
1188        lowerEventSuppresionAndTest();
1189        lowerNormalizeProtection();
1190    }
1191
1192    /** The log stream */
1193    protected static Logger log = Logger.getLogger("bible.passage");
1194
1195    /** Serialization type constant for a BitWise layout */
1196    protected static final int BITWISE = 0;
1197
1198    /** Serialization type constant for a Distinct layout */
1199    protected static final int DISTINCT = 1;
1200
1201    /** Serialization type constant for a Ranged layout */
1202    protected static final int RANGED = 2;
1203
1204    /** Count of serializations methods */
1205    protected static final int METHOD_COUNT = 3;
1206
1207    /** Support for change notification */
1208    protected transient Vector listeners = new Vector();
1209
1210    /** The original string for picky users */
1211    protected transient String original_name = null;
1212
1213    /**
1214     * If we have several changes to make then we increment this and then
1215     * decrement it when done (and fire an event off). If the cost of
1216     * calculating the parameters to the fire is high then we can check that
1217     * this is 0 before doing the calculation.
1218     */
1219    protected transient int suppress_events = 0;
1220
1221    /**
1222     * Do we skip normalization for now - if we want to skip then we increment
1223     * this, and the decrement it when done.
1224     */
1225    protected transient int skip_normalization = 0;
1226}