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

Quick Search    Search Deep

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


1   
2   package com.eireneh.bible.passage;
3   
4   import java.io.*;
5   import java.util.*;
6   import com.sun.java.util.collections.*;
7   
8   import com.eireneh.util.*;
9   
10  /**
11  * A VerseRange is one step between a Verse and a Passage - it is a
12  * Verse plus a verse_count. Every VerseRange has a start, a verse_count
13  * and an end. A VerseRange is designed to be immutable. This is a
14  * necessary from a collections point of view. A VerseRange should always
15  * be valid, although some versions may not return any text for verses
16  * that they consider to be mis-translated in some way.
17  *
18  * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
19  * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
20  * Distribution Licence:<br />
21  * Project B is free software; you can redistribute it
22  * and/or modify it under the terms of the GNU General Public License,
23  * version 2 as published by the Free Software Foundation.<br />
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27  * General Public License for more details.<br />
28  * The License is available on the internet
29  * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
30  * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
31  * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
32  * The copyright to this program is held by it's authors.
33  * </font></td></tr></table>
34  * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
35  * @see docs.Licence
36  * @author Joe Walker
37  * @version D9.I9.T7
38  * @stereotype thing
39  */
40  public class VerseRange implements VerseBase
41  {
42      /**
43      * The default VerseRange is a single verse - Genesis 1:1. I didn't
44      * want to provide this constructor however, you are supposed to
45      * provide a default ctor for all beans. For this reason I suggest you
46      * don't use it.
47      */
48      public VerseRange()
49      {
50          this.original_name = null;
51          this.start = Verse.DEFAULT;
52          this.end = Verse.DEFAULT;
53          this.verse_count = 1;
54  
55          verifyData();
56      }
57  
58      /**
59      * Construct a VerseRange from a human readable string. For example
60      * "Gen 1:1-3" in case the user does not want to have their typing
61      * 'fixed' by a meddling patronizing computer.
62      * @param desc The textual representation
63      * @exception NoSuchVerseException If the text can not be understood
64      */
65      public VerseRange(String desc) throws NoSuchVerseException
66      {
67          this(desc, Verse.DEFAULT);
68      }
69  
70      /**
71      * Construct a VerseRange from a String and a Verse. For example given
72      * "2:2" and a basis of Gen 1:1 the result would be range of 1 verse
73      * starting at Gen 2:2. Also given "2:2-5" and a basis of Gen 1:1 the
74      * result would be a range of 5 verses starting at Gen 1:1.
75      * @param desc The string describing the verse e.g "2:2"
76      * @param basis The basis by which to understand the desc.
77      * @exception NoSuchVerseException If the reference is illegal
78      */
79      public VerseRange(String desc, Verse basis) throws NoSuchVerseException
80      {
81          original_name = desc;
82  
83          // Do we need this?
84          String[] parts = PassageUtil.tokenize(desc, RANGE_ALLOWED_DELIMS);
85  
86          switch (parts.length)
87          {
88          case 0:
89              start = basis;
90              verse_count = 1;
91              end = calcEnd(start, verse_count);
92              break;
93  
94          case 1:
95              switch (Verse.getAccuracy(parts[0]))
96              {
97              case ACCURACY_BOOK_VERSE:
98              case ACCURACY_CHAPTER_VERSE:
99              case ACCURACY_VERSE_ONLY:
100             case ACCURACY_NONE:
101                 start = new Verse(parts[0], basis);
102                 end = start;
103                 verse_count = 1;
104                 break;
105 
106             case ACCURACY_BOOK_CHAPTER:
107                 start = new Verse(parts[0], basis);
108                 verse_count = Books.versesInChapter(start.getBook(), start.getChapter());
109                 end = calcEnd(start, verse_count);
110                 break;
111 
112             case ACCURACY_BOOK_ONLY:
113                 start = new Verse(parts[0], basis);
114                 verse_count = Books.versesInBook(start.getBook());
115                 end = calcEnd(start, verse_count);
116                 break;
117 
118             default:
119                 throw new LogicError();
120             }
121             break;
122 
123         case 2:
124             start = new Verse(parts[0], basis);
125             switch (Verse.getAccuracy(parts[1]))
126             {
127             case ACCURACY_BOOK_VERSE:
128             case ACCURACY_CHAPTER_VERSE:
129             case ACCURACY_VERSE_ONLY:
130             case ACCURACY_NONE:
131                 end = new Verse(parts[1], start);
132                 break;
133 
134             case ACCURACY_BOOK_CHAPTER:
135             case ACCURACY_BOOK_ONLY:
136                 end = new VerseRange(parts[1], start).getEnd();
137                 break;
138 
139             default:
140                 throw new LogicError();
141             }
142             verse_count = calcVerseCount(start, end);
143             break;
144 
145         default:
146             throw new NoSuchVerseException("passg_range_parts",
147                                            new Object[] { RANGE_ALLOWED_DELIMS, desc });
148         }
149 
150         verifyData();
151     }
152 
153     /**
154     * Construct a VerseRange from a String and a VerseRange. For example given "2:2"
155     * and a basis of Gen 1:1-2 the result would be range of 1 verse starting at
156     * Gen 2:2. Also given "2:2-5" and a basis of Gen 1:1-2 the result would be a
157     * range of 5 verses starting at Gen 1:1.
158     * <p>This constructor is different from the (String, Verse) constructor in that
159     * if the basis is a range that exactly covers a chapter and the string is a
160     * single number, then we assume that the number referrs to a chapter and not to
161     * a verse. This allows us to have a Passage like "Gen 1,2" and have the 2
162     * understood as chapter 2 and not verse 2 of Gen 1, which would have occured
163     * otherwise.
164     * @param desc The string describing the verse e.g "2:2"
165     * @param basis The verse that forms the basis by which to understand the desc.
166     * @exception com.eireneh.bible.NoSuchVerseException If the reference is illegal
167     */
168     public VerseRange(String desc, VerseRange basis) throws NoSuchVerseException
169     {
170         original_name = desc;
171 
172         String[] parts = PassageUtil.tokenize(desc, RANGE_ALLOWED_DELIMS);
173 
174         switch (parts.length)
175         {
176         case 0:
177             // This happens when someone types "Gen 1:1-" not sure what
178             // someone would expect to happen as a result, maybe 1 verse,
179             // maybe the whole chapter, maybe the whole book. I think that
180             // we'd be silly to try to be too clever. So I'm assuming the
181             // former. If you want either latter option use "Gen 1:1-$:$"
182             start = basis.getStart();
183             verse_count = 1;
184             end = calcEnd(start, verse_count);
185             break;
186 
187         case 1:
188             // We need to cope with 2 special cases here:
189             // o The first is of "Gen 1, 2": If there is 1 part to a verse,
190             //   and the basis is a whole chapter then we are ACCURACY_CHAPTER
191             // o The second is of "Mat, Mar": If there is 1 part to the verse
192             //   and the basis is a whole book, then we are ACCURACY_BOOK
193             //   However the second case is dealt with automatically because
194             //   "Mar" can only be a book and never a chapter or verse
195             switch (Verse.getAccuracy(parts[0]))
196             {
197             case ACCURACY_BOOK_VERSE:
198                 start = new Verse(parts[0], basis.getStart());
199                 end = start;
200                 verse_count = 1;
201                 break;
202 
203             case ACCURACY_VERSE_ONLY:
204                 if (basis.isChapter())
205                 {
206                     // This should be ACCURACY_CHAPTER_ONLY if it existed
207                     int book = basis.getStart().getBook();
208                     int chapter = 0;
209                     if (Verse.isEndMarker(parts[0]))    chapter = Books.chaptersInBook(book);
210                     else                                chapter = Verse.parseInt(parts[0]);
211 
212                     start = new Verse(book, chapter, 1);
213                     end = new Verse(book, chapter, Books.versesInChapter(book, chapter));
214                     verse_count = calcVerseCount(start, end);
215                 }
216                 else
217                 {
218                     start = new Verse(parts[0], basis.getStart());
219                     end = start;
220                     verse_count = 1;
221                 }
222                 break;
223 
224             case ACCURACY_CHAPTER_VERSE:
225             case ACCURACY_NONE:
226                 start = new Verse(parts[0], basis.getStart());
227                 end = start;
228                 verse_count = 1;
229                 break;
230 
231             case ACCURACY_BOOK_CHAPTER:
232                 start = new Verse(parts[0], basis.getStart());
233                 verse_count = Books.versesInChapter(start.getBook(), start.getChapter());
234                 end = calcEnd(start, verse_count);
235                 break;
236 
237             case ACCURACY_BOOK_ONLY:
238                 start = new Verse(parts[0], basis.getStart());
239                 verse_count = Books.versesInBook(start.getBook());
240                 end = calcEnd(start, verse_count);
241                 break;
242 
243             default:
244                 throw new LogicError();
245             }
246             break;
247 
248         case 2:
249             start = new Verse(parts[0], basis.getStart());
250             end = new Verse(parts[1], start);
251             verse_count = calcVerseCount(start, end);
252             break;
253 
254         default:
255             throw new NoSuchVerseException("passg_range_parts",
256                                            new Object[] { RANGE_ALLOWED_DELIMS, desc });
257         }
258 
259         verifyData();
260     }
261 
262     /**
263     * Construct a VerseRange from a Verse. The resultant VerseRange will be
264     * 1 verse in verse_count.
265     * @param start The verse to start from
266     */
267     public VerseRange(Verse start)
268     {
269         if (start == null)
270             throw new NullPointerException(PassageUtil.getResource("range_error_null"));
271 
272         this.original_name = null;
273         this.start = start;
274         this.end = start;
275         this.verse_count = 1;
276 
277         verifyData();
278     }
279 
280     /**
281     * Construct a VerseRange from a Verse and a range.
282     * @param start The verse to start from
283     * @param verse_count The number of verses
284     * @exception com.eireneh.bible.passage.NoSuchVerseException If there arn't that many verses
285     */
286     public VerseRange(Verse start, int verse_count) throws NoSuchVerseException
287     {
288         if (verse_count < 1)
289             throw new NoSuchVerseException(PassageUtil.getResource("range_error_count"));
290 
291         if (start.getOrdinal()+verse_count-1 > Books.versesInBible())
292         {
293             Object[] params =
294             {
295                 start.getName(),
296                 new Integer(Books.versesInBible()-start.getOrdinal()),
297                 new Integer(verse_count)
298             };
299             throw new NoSuchVerseException(PassageUtil.getResource("range_error_size", params));
300         }
301 
302         this.original_name = null;
303         this.start = start;
304         this.verse_count = verse_count;
305         this.end = calcEnd(start, verse_count);
306 
307         verifyData();
308     }
309 
310     /**
311     * Construct a VerseRange from a Verse and a range.
312     * Now the actual value of the boolean is ignored. However for future proofing
313     * you should only use 'true'. Do not use patch_up=false, use Verse(int, int, int)
314     * This so that we can declare this constructor to not throw an exception.
315     * Is there a better way of doing this?
316     * @param start The verse to start from
317     * @param verse_count The number of verse to count
318     * @param patch_up True to trigger reference fixing
319     */
320     public VerseRange(Verse start, int verse_count, boolean patch_up)
321     {
322         if (!patch_up)
323             throw new IllegalArgumentException(PassageUtil.getResource("range_error_patch"));
324 
325         // Not sure that any of the code below (except verifyData() which may not stay there)
326         // Checks for null so we do it explictly here.
327         if (start == null)
328             throw new NullPointerException(PassageUtil.getResource("range_error_null"));
329 
330         this.original_name = null;
331         this.start = start;
332         this.end = start.add(Math.max(verse_count, 1) - 1);
333         this.verse_count = calcVerseCount(start, end);
334 
335         verifyData();
336     }
337 
338     /**
339     * Construct a VerseRange from 2 Verses
340     * If start is later than end then swap the two around.
341     * @param start The verse to start from
342     * @param start The verse to end with
343     */
344     public VerseRange(Verse start, Verse end)
345     {
346         if (start == null || end == null)
347             throw new NullPointerException(PassageUtil.getResource("range_error_null"));
348 
349         this.original_name = null;
350 
351         switch (start.compareTo(end))
352         {
353         case -1:
354             this.start = start;
355             this.end = end;
356             this.verse_count = calcVerseCount(start, end);
357             break;
358 
359         case 0:
360             this.start = start;
361             this.end = start;
362             this.verse_count = 1;
363             break;
364 
365         case 1:
366             this.start = end;
367             this.end = start;
368             this.verse_count = calcVerseCount(this.start, this.end);
369             break;
370 
371         default:
372             throw new LogicError();
373         }
374 
375         verifyData();
376     }
377 
378     /**
379     * Widen the range of the verses in this list. This is primarily for
380     * "find x within n verses of y" type applications.
381     * @param base_start The verse to start from
382     * @param blur_down The number of verses to extend down by
383     * @param blur_up The number of verses to extend up by
384     * @param restrict How should we restrict the blurring?
385     * @exception java.lang.IllegalArgumentException If a blurring is negative or the restrict mode is illegal
386     * @see com.eireneh.bible.passage.Passage
387     */
388     public VerseRange(Verse base_start, int blur_down, int blur_up, int restrict)
389     {
390         if (base_start == null)
391             throw new NullPointerException(PassageUtil.getResource("range_error_null"));
392 
393         if (blur_down < 0 || blur_up < 0)
394             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_negative"));
395 
396         this.original_name = null;
397 
398         switch (restrict)
399         {
400         case RESTRICT_CHAPTER:
401             try
402             {
403                 int start_book = base_start.getBook();
404                 int start_chapter = base_start.getChapter();
405                 int start_verse = base_start.getVerse() - blur_down;
406                 int end_verse = base_start.getVerse() + blur_up;
407 
408                 start_verse = Math.max(start_verse, 1);
409                 end_verse = Math.min(end_verse, Books.versesInChapter(start_book, start_chapter));
410 
411                 start = new Verse(start_book, start_chapter, start_verse);
412                 verse_count = end_verse - start_verse + 1;
413                 end = calcEnd(start, verse_count);
414             }
415             catch (NoSuchVerseException ex)
416             {
417                 throw new LogicError(ex);
418             }
419             break;
420 
421         case RESTRICT_NONE:
422             start = base_start.subtract(blur_down);
423             end = base_start.add(blur_up);
424             verse_count = calcVerseCount(start, end);
425             break;
426 
427         case RESTRICT_BOOK:
428             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_book"));
429 
430         default:
431             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_mode"));
432         }
433 
434         verifyData();
435     }
436 
437     /**
438     * Widen the range of the verses in this list. This is primarily for
439     * "find x within n verses of y" type applications.
440     * @param base_start The verse range to start from
441     * @param blur_down The number of verses to extend down by
442     * @param blur_up The number of verses to extend up by
443     * @param restrict How should we restrict the blurring?
444     * @exception java.lang.IllegalArgumentException If a blurring is negative or the restrict mode is illegal
445     * @see com.eireneh.bible.passage.Passage
446     */
447     public VerseRange(VerseRange base_start, int blur_down, int blur_up, int restrict)
448     {
449         if (base_start == null)
450             throw new NullPointerException(PassageUtil.getResource("range_error_null"));
451 
452         if (blur_down < 0 || blur_up < 0)
453             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_negative"));
454 
455         this.original_name = null;
456 
457         switch (restrict)
458         {
459         case RESTRICT_CHAPTER:
460             try
461             {
462 
463                 int start_book = base_start.getStart().getBook();
464                 int start_chapter = base_start.getStart().getChapter();
465                 int start_verse = base_start.getStart().getVerse() - blur_down;
466 
467                 int end_book = base_start.getEnd().getBook();
468                 int end_chapter = base_start.getEnd().getChapter();
469                 int end_verse = base_start.getEnd().getVerse() + blur_up;
470 
471                 start_verse = Math.max(start_verse, 1);
472                 end_verse = Math.min(end_verse, Books.versesInChapter(end_book, end_chapter));
473 
474                 start = new Verse(start_book, start_chapter, start_verse);
475                 end = new Verse(end_book, end_chapter, end_verse);
476                 verse_count = calcVerseCount(start, end);
477             }
478             catch (NoSuchVerseException ex)
479             {
480                 throw new LogicError(ex);
481             }
482             break;
483 
484         case RESTRICT_NONE:
485             start = base_start.getStart().subtract(blur_down);
486             end = base_start.getEnd().add(blur_up);
487             verse_count = calcVerseCount(start, end);
488             break;
489 
490         case RESTRICT_BOOK:
491             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_book"));
492 
493         default:
494             throw new IllegalArgumentException(PassageUtil.getResource("range_error_blur_mode"));
495         }
496 
497         verifyData();
498     }
499 
500     /**
501     * Merge 2 VerseRanges together. The resulting range will encompass
502     * Everying in-between the extremities of the 2 ranges.
503     * @param a The first verse range to be merged
504     * @param a The second verse range to be merged
505     */
506     public VerseRange(VerseRange a, VerseRange b)
507     {
508         original_name = null;
509         start = Verse.min(a.getStart(), b.getStart());
510         end = Verse.max(a.getEnd(), b.getEnd());
511         verse_count = calcVerseCount(start, end);
512     }
513 
514     /**
515     * Fetch a more sensible shortened version of the name
516     * @return A string like 'Gen 1:1-2'
517     */
518     public String getName()
519     {
520         return getName(null);
521     }
522 
523     /**
524     * Fetch a more sensible shortened version of the name
525     * @param base A reference to allow things like Gen 1:1,3,5 as an output
526     * @return A string like 'Gen 1:1-2'
527     */
528     public String getName(Verse base)
529     {
530         if (PassageUtil.isPersistentNaming() && original_name != null)
531         {
532             return original_name;
533         }
534 
535         // Cache these we're going to be using them a lot.
536         int start_book = start.getBook();
537         int start_chapter = start.getChapter();
538         int start_verse = start.getVerse();
539         int end_book = end.getBook();
540         int end_chapter = end.getChapter();
541         int end_verse = end.getVerse();
542 
543         try
544         {
545             // If this is in 2 separate books
546             if (start_book != end_book)
547             {
548                 // This range is exactly a whole book
549                 if (isBooks())
550                 {
551                     // Just report the name of the book, we don't need to worry about the
552                     // base since we start at the start of a book, and should have been
553                     // recently normalized()
554                     return Books.getShortBookName(start_book)
555                          + RANGE_PREF_DELIM
556                          + Books.getShortBookName(end_book);
557                 }
558 
559                 // If this range is exactly a whole chapter
560                 if (isChapters())
561                 {
562                     // Just report book and chapter names
563                     return Books.getShortBookName(start_book)
564                          + VERSE_PREF_DELIM1 + start_chapter
565                          + RANGE_PREF_DELIM + Books.getShortBookName(end_book)
566                          + VERSE_PREF_DELIM1 + end_chapter;
567                 }
568 
569                 return start.getName(base) + RANGE_PREF_DELIM + end.getName(base);
570             }
571 
572             // This range is exactly a whole book
573             if (isBook())
574             {
575                 // Just report the name of the book, we don't need to worry about the
576                 // base since we start at the start of a book, and should have been
577                 // recently normalized()
578                 return Books.getShortBookName(start_book);
579             }
580 
581             // If this is 2 separate chapters
582             if (start_chapter != end_chapter)
583             {
584                 // If this range is a whole number of chapters
585                 if (isChapters())
586                 {
587                     // Just report the name of the book and the chapters
588                     return Books.getShortBookName(start_book)
589                          + VERSE_PREF_DELIM1 + start_chapter
590                          + RANGE_PREF_DELIM + end_chapter;
591                 }
592 
593                 return start.getName(base)
594                      + RANGE_PREF_DELIM + end_chapter
595                      + VERSE_PREF_DELIM2 + end_verse;
596             }
597 
598             // If this range is exactly a whole chapter
599             if (isChapter())
600             {
601                 // Just report the name of the book and the chapter
602                 return Books.getShortBookName(start_book)
603                      + VERSE_PREF_DELIM1 + start_chapter;
604             }
605 
606             // If this is 2 separate verses
607             if (start_verse != end_verse)
608             {
609                 return start.getName(base)
610                      + RANGE_PREF_DELIM + end_verse;
611             }
612 
613             // The range is a single verse
614             return start.getName(base);
615         }
616         catch (NoSuchVerseException ex)
617         {
618             throw new LogicError(ex);
619         }
620     }
621 
622     /**
623     * This just clones getName which seems the most sensible
624     * type of string to return.
625     * @return A string like 'Gen 1:1-2'
626     */
627     public String toString()
628     {
629         return getName();
630     }
631 
632     /**
633     * Fetch the first verse in this range.
634     * @return The first verse in the range
635     */
636     public Verse getStart()
637     {
638         return start;
639     }
640 
641     /**
642     * Fetch the last verse in this range.
643     * @return The last verse in the range
644     */
645     public Verse getEnd()
646     {
647         return end;
648     }
649 
650     /**
651     * How many verses in this range
652     * @return The number of verses. Always >= 1.
653     */
654     public int getVerseCount()
655     {
656         return verse_count;
657     }
658 
659     /**
660     * Get a copy of ourselves. Points to note:
661     *   Call clone() not new() on member Objects, and on us.
662     *   Do not use Copy Constructors! - they do not inherit well.
663     *   Think about this needing to be synchronized
664     *   If this is not cloneable then writing cloneable children is harder
665     * @return A complete copy of ourselves
666     * @exception java.lang.CloneNotSupportedException We don't do this but our kids might
667     */
668     public Object clone() throws CloneNotSupportedException
669     {
670         // This gets us a shallow copy
671         VerseRange copy = (VerseRange) super.clone();
672 
673         copy.start = (Verse) start.clone();
674         copy.end = (Verse) end.clone();
675         copy.verse_count = verse_count;
676         copy.original_name = original_name;
677 
678         return copy;
679     }
680 
681     /**
682     * Is this Object equal to us. Points to note:
683     *   If you override equals(), you must override hashCode() too.
684     *   If you are doing this it is a good idea to be immutable.
685     * @param obj The thing to test against
686     * @return True/False is we are or are not equal to obj
687     */
688     public boolean equals(Object obj)
689     {
690         // Since this can not be null
691         if (obj == null) return false;
692 
693         // Check that that is the same as this
694         // Don't use instanceOf since that breaks inheritance
695         if (!obj.getClass().equals(this.getClass())) return false;
696 
697         VerseRange vr = (VerseRange) obj;
698 
699         // The real tests
700         if (!vr.getStart().equals(getStart())) return false;
701         if (vr.getVerseCount() != getVerseCount()) return false;
702 
703         // We don't really need to check this one too.
704         //if (!vr.getEnd().equals(getEnd())) return false;
705 
706         return true;
707     }
708 
709     /**
710     * The hashing number is currently calculated using the start ordinal
711     * in the upper 16 bits, and the verse_count in the lower.
712     * <p><b>Note that this may change and should not be relied upon</b>
713     * Use getStart().getOrdinal() and so on to get that kind of info.
714     * <p>The news from this however is that sorting by hashCode() is
715     * currently the same as sorting using compareTo().
716     * @return The hashing number
717     */
718     public int hashCode()
719     {
720         return (start.getOrdinal() << 16) + verse_count;
721     }
722 
723     /**
724     * Compare initially using the first element in a VerseRange. If the
725     * starting verses are the same then sort according to length, shortest
726     * first so:
727     * <tt>Gen 1:1 &lt; Gen 1:1-2 &lt; Gen 1:1-26 &lt; Gen 1:2</tt>
728     * <p>Note that this compares Verse("Gen 1:1") = VerseRange("Gen 1:1")
729     * I'm not sure if this is 100% pucka, but it doesn't seem to cause any
730     * problems.
731     * @param obj The thing to compare against
732     * @return 1 means he is earlier than me, -1 means he is later ...
733     */
734     public int compareTo(Object obj)
735     {
736         // This ensures a ClassCastException without further test
737         Verse that = null;
738         if (obj instanceof Verse)   that = (Verse) obj;
739         else                        that = ((VerseRange) obj).getStart();
740 
741         int start_compare = getStart().compareTo(that);
742         if (start_compare != 0) return start_compare;
743 
744         // So the start verses are the same, but the Verse(Range)s may not
745         // be equal() since they have lengths
746         int that_length = 1;
747         if (obj instanceof VerseRange) that_length = ((VerseRange) obj).getVerseCount();
748 
749         if (that_length == getVerseCount()) return 0;
750         if (that_length < getVerseCount()) return 1;
751         return -1;
752     }
753 
754     /**
755     * Are the 2 VerseRanges in question contigious. ie - could they be
756     * represented by a single VerseRange. Note that one range could be
757     * entirely contained within the other and they would be considered
758     * adjacentTo()
759     * For example Gen 1:1-2 is adjacent to Gen 1:1-5 and Gen 1:3-4 but
760     * not to Gen 1:4-10. Also Gen 1:29-30 is adjacent to Gen 2:1-10
761     * @param that The VerseRange to compare to
762     * @return true if the ranges are adjacent
763     */
764     public boolean adjacentTo(VerseRange that)
765     {
766         int that_start = that.getStart().getOrdinal();
767         int that_end = that.getEnd().getOrdinal();
768         int this_start = getStart().getOrdinal();
769         int this_end = getEnd().getOrdinal();
770 
771         // if that starts inside or is next to this we are adjacent.
772         if (that_start >= this_start-1 && that_start <= this_end+1) return true;
773 
774         // if this starts inside or is next to that we are adjacent.
775         if (this_start >= that_start-1 && this_start <= that_end+1) return true;
776 
777         // otherwise we're not adjacent
778         return false;
779     }
780 
781     /**
782     * Do the 2 VerseRanges in question actually overlap. This is slightly
783     * more restrictive than the adjacentTo() test which could be satisfied by
784     * ranges like Gen 1:1-2 and Gen 1:3-4. overlaps() however would return
785     * false given these ranges.
786     * For example Gen 1:1-2 is adjacent to Gen 1:1-5 but not to Gen 1:3-4
787     * not to Gen 1:4-10. Also Gen 1:29-30 does not overlap Gen 2:1-10
788     * @param that The VerseRange to compare to
789     * @return true if the ranges are adjacent
790     */
791     public boolean overlaps(VerseRange that)
792     {
793         int that_start = that.getStart().getOrdinal();
794         int that_end = that.getEnd().getOrdinal();
795         int this_start = getStart().getOrdinal();
796         int this_end = getEnd().getOrdinal();
797 
798         // if that starts inside this we are adjacent.
799         if (that_start >= this_start && that_start <= this_end) return true;
800 
801         // if this starts inside that we are adjacent.
802         if (this_start >= that_start && this_start <= that_end) return true;
803 
804         // otherwise we're not adjacent
805         return false;
806     }
807 
808     /**
809     * Is the given verse entirely within our range.
810     * For example if this = "Gen 1:1-31" then:
811     * <tt>contains(Verse("Gen 1:3")) == true</tt>
812     * <tt>contains(Verse("Gen 2:1")) == false</tt>
813     * @param that The Verse to compare to
814     * @return true if we contain it.
815     */
816     public boolean contains(Verse that)
817     {
818         if (start.compareTo(that) == 1) return false;
819         if (end.compareTo(that) == -1) return false;
820 
821         return true;
822     }
823 
824     /**
825     * Is the given range within our range.
826     * For example if this = "Gen 1:1-31" then:
827     * <tt>this.contains(Verse("Gen 1:3-10")) == true</tt>
828     * <tt>this.contains(Verse("Gen 2:1-1")) == false</tt>
829     * @param that The Verse to compare to
830     * @return true if we contain it.
831     */
832     public boolean contains(VerseRange that)
833     {
834         if (start.compareTo(that.getStart()) == 1) return false;
835         if (end.compareTo(that.getEnd()) == -1) return false;
836 
837         return true;
838     }
839 
840     /**
841     * Does this range represent exactly one chapter, no more or less.
842     * @return true if we are exactly one chapter.
843     */
844     public boolean isChapter()
845     {
846         if (!start.isStartOfChapter()) return false;
847         if (!end.isEndOfChapter()) return false;
848         if (!start.isSameChapter(end)) return false;
849 
850         return true;
851     }
852 
853     /**
854     * Does this range represent a number of whole chapters
855     * @return true if we are a whole number of chapters.
856     */
857     public boolean isChapters()
858     {
859         if (!start.isStartOfChapter()) return false;
860         if (!end.isEndOfChapter()) return false;
861 
862         return true;
863     }
864 
865     /**
866     * Does this range represent exactly one book, no more or less.
867     * @return true if we are exactly one book.
868     */
869     public boolean isBook()
870     {
871         if (!start.isStartOfBook()) return false;
872         if (!end.isEndOfBook()) return false;
873         if (!start.isSameBook(end)) return false;
874 
875         return true;
876     }
877 
878     /**
879     * Does this range represent a whole number of books.
880     * @return true if we are a whole number of books.
881     */
882     public boolean isBooks()
883     {
884         if (!start.isStartOfBook()) return false;
885         if (!end.isEndOfBook()) return false;
886 
887         return true;
888     }
889 
890     /**
891     * Create an array of Verses
892     * @return The array of verses that this makes up
893     */
894     public Verse[] toVerseArray()
895     {
896         try
897         {
898             Verse[] retcode = new Verse[verse_count];
899 
900             for (int i=0; i<verse_count; i++)
901             {
902                 retcode[i] = new Verse(start.getOrdinal()+i);
903             }
904 
905             return retcode;
906         }
907         catch (NoSuchVerseException ex)
908         {
909             throw new LogicError(ex);
910         }
911     }
912 
913     /**
914     * Enumerate over the verse in this range
915     * @return A verse iterator
916     */
917     public Enumeration verseElements()
918     {
919         return new Enumeration()
920         {
921             private int next_ordinal = start.getOrdinal();
922 
923             public boolean hasMoreElements()
924             {
925                 return next_ordinal <= end.getOrdinal();
926             }
927 
928             public Object nextElement()
929             {
930                 try
931                 {
932                     return new Verse(next_ordinal++);
933                 }
934                 catch (NoSuchVerseException ex)
935                 {
936                     throw new LogicError(ex);
937                 }
938             }
939         };
940     }
941 
942     /**
943     * Create a DistinctPassage that is the stuff left of VerseRange a
944     * when you remove the stuff in VerseRange b.
945     * @param a The verses that you might want
946     * @param a The verses that you definately don't
947     * @return A list of the Verses outstanding
948     */
949     public static VerseRange[] remainder(VerseRange a, VerseRange b)
950     {
951         VerseRange start = null;
952         VerseRange end = null;
953 
954         // If a starts before b get the Range of the prequel
955         if (a.getStart().compareTo(b.getStart()) == -1)
956         {
957             start = new VerseRange(a.getStart(), b.getEnd().subtract(1));
958         }
959 
960         // If a ends after b get the Range of the sequel
961         if (a.getEnd().compareTo(b.getEnd()) == 1)
962         {
963             end = new VerseRange(b.getEnd().add(1), a.getEnd());
964         }
965 
966         if (start == null)
967         {
968             if (end == null)    return new VerseRange[] { };
969             else                return new VerseRange[] { end };
970         }
971         else
972         {
973             if (end == null)    return new VerseRange[] { start };
974             else                return new VerseRange[] { start, end };
975         }
976     }
977 
978     /**
979     * Create a DistinctPassage that is the stuff in VerseRange a
980     * that is also in VerseRange b.
981     * @param a The verses that you might want
982     * @param a The verses that you definately don't
983     * @return A list of the Verses outstanding
984     */
985     public static VerseRange intersection(VerseRange a, VerseRange b)
986     {
987         Verse new_start = Verse.max(a.getStart(), b.getStart());
988         Verse new_end = Verse.min(a.getEnd(), b.getEnd());
989 
990         if (new_start.compareTo(new_end) < 1)
991         {
992             return new VerseRange(new_start, new_end);
993         }
994 
995         return null;
996     }
997 
998     /**
999     * Is the string likely to be a VerseRange and not a Verse?
1000    * @param desc The string to be tested for Rangeness
1001    * @return true/false if this is likely to be a range
1002    */
1003    public static boolean isVerseRange(String desc)
1004    {
1005        for (int i=0; i<RANGE_ALLOWED_DELIMS.length(); i++)
1006        {
1007            if (desc.indexOf(RANGE_ALLOWED_DELIMS.charAt(i)) != -1)
1008                return true;
1009        }
1010
1011        return false;
1012    }
1013
1014    /**
1015    * Returns a VerseRange that wraps the whole Bible
1016    * @return The whole bible VerseRange
1017    */
1018    public static VerseRange getWholeBibleVerseRange()
1019    {
1020        try
1021        {
1022            if (whole == null)
1023                whole = new VerseRange(new Verse(1, 1, 1), new Verse(66, 22, 21));
1024        }
1025        catch (NoSuchVerseException ex)
1026        {
1027            throw new LogicError(ex);
1028        }
1029
1030        return whole;
1031    }
1032
1033    /**
1034    * Calculate the last verse in this range.
1035    * @param start The first verse in the range
1036    * @param verse_count The number of verses
1037    * @return The last verse in the range
1038    */
1039    private static final Verse calcEnd(Verse start, int verse_count)
1040    {
1041        return start.add(verse_count - 1);
1042    }
1043
1044    /**
1045    * Calcualte how many verses in this range
1046    * @param start The first verse in the range
1047    * @param end The last verse in the range
1048    * @return The number of verses. Always >= 1.
1049    */
1050    private static final int calcVerseCount(Verse start, Verse end)
1051    {
1052        return end.subtract(start) + 1;
1053    }
1054
1055    /**
1056    * Check to see that everything is ok with the Data
1057    */
1058    private void verifyData()
1059    {
1060        if (verse_count != end.subtract(start) + 1)
1061        {
1062            log.warning("start="+start);
1063            log.warning("end="+end);
1064            log.warning("verse_count="+verse_count);
1065
1066            throw new LogicError();
1067        }
1068    }
1069
1070    /**
1071    * Write out the object to the given ObjectOutputStream
1072    * @param out The stream to write our state to
1073    * @serialData Write the ordinal number of this verse
1074    */
1075    private void writeObject(ObjectOutputStream out) throws IOException
1076    {
1077        // Call even if there is no default serializable fields.
1078        out.defaultWriteObject();
1079
1080        out.writeInt(start.getOrdinal());
1081        out.writeInt(verse_count);
1082
1083        // Ignore the original name. Is this wise?
1084        // I am expecting that people are not that fussed about it and
1085        // it could make everything far more verbose
1086    }
1087
1088    /**
1089    * Write out the object to the given ObjectOutputStream
1090    * @param in The stream to read our state from
1091    * @serialData Write the ordinal number of this verse
1092    */
1093    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
1094    {
1095        // Call even if there is no default serializable fields.
1096        in.defaultReadObject();
1097
1098        try
1099        {
1100            start = new Verse(in.readInt());
1101            verse_count = in.readInt();
1102            end = calcEnd(start, verse_count);
1103
1104            verifyData();
1105        }
1106        catch (NoSuchVerseException ex)
1107        {
1108            throw new IOException(ex.getMessage());
1109        }
1110
1111        // We are ignoring the original_name. It was set to null in the
1112        // default ctor so I will ignore it here.
1113    }
1114
1115    /** To make serialization work across new versions */
1116    static final long serialVersionUID = 8307795549869653580L;
1117
1118    /** The real data - how many verses long are we? All ctors init this so leave default */
1119    private transient int verse_count;
1120
1121    /** The real data - where do we start? All ctors init this so leave default */
1122    private transient Verse start;
1123
1124    /** The real data - where do we end? All ctors init this so leave default */
1125    private transient Verse end;
1126
1127    /** The original string for picky users */
1128    private transient String original_name;
1129
1130    /** The whole Bible VerseRange */
1131    private transient static VerseRange whole;
1132
1133    /** The log stream */
1134    protected transient static Logger log = Logger.getLogger("bible.passage");
1135}