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}