1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: VerseRange.java 2226 2012-02-02 19:25:21Z dmsmith $
21   */
22  package org.crosswire.jsword.passage;
23  
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.util.Iterator;
28  import java.util.NoSuchElementException;
29  
30  import org.crosswire.common.icu.NumberShaper;
31  import org.crosswire.common.util.Logger;
32  import org.crosswire.jsword.versification.BibleBook;
33  import org.crosswire.jsword.versification.Versification;
34  import org.crosswire.jsword.versification.system.Versifications;
35  
36  /**
37   * A VerseRange is one step between a Verse and a Passage - it is a Verse plus a
38   * verseCount. Every VerseRange has a start, a verseCount and an end. A
39   * VerseRange is designed to be immutable. This is a necessary from a
40   * collections point of view. A VerseRange should always be valid, although some
41   * versions may not return any text for verses that they consider to be
42   * miss-translated in some way.
43   * 
44   * @see gnu.lgpl.License for license details.<br>
45   *      The copyright to this program is held by it's authors.
46   * @author Joe Walker [joe at eireneh dot com]
47   * @author DM Smith [dmsmith555 at yahoo dot com]
48   */
49  public final class VerseRange implements Key {
50      /**
51       * The default VerseRange is a single verse - Genesis 1:1. I didn't want to
52       * provide this constructor however, you are supposed to provide a default
53       * ctor for all beans. For this reason I suggest you don't use it.
54       * @deprecated  use {@link #VerseRange(Versification)} instead
55       */
56      @Deprecated
57      public VerseRange() {
58          this(null, null, Verse.DEFAULT, Verse.DEFAULT);
59      }
60  
61      /**
62       * Construct a VerseRange from a Verse. The resultant VerseRange will be 1
63       * verse in verseCount.
64       * 
65       * @param start
66       *            The verse to start from
67       * @deprecated  use {@link #VerseRange(Versification, Verse)} instead
68       */
69      @Deprecated
70      public VerseRange(Verse start) {
71          this(null, null, start, start);
72      }
73  
74      /**
75       * @param start
76       * @param end
77       * @deprecated  use {@link #VerseRange(Versification, Verse, Verse)} instead
78       */
79      @Deprecated
80      public VerseRange(Verse start, Verse end) {
81          this(null, null, start, end);
82      }
83  
84      /**
85       * The default VerseRange is a single verse - Genesis 1:1. I didn't want to
86       * provide this constructor however, you are supposed to provide a default
87       * ctor for all beans. For this reason I suggest you don't use it.
88       */
89      public VerseRange(Versification v11n) {
90          this(v11n, null, Verse.DEFAULT, Verse.DEFAULT);
91      }
92  
93      /**
94       * Construct a VerseRange from a Verse. The resultant VerseRange will be 1
95       * verse in verseCount.
96       * 
97       * @param start
98       *            The verse to start from
99       */
100     public VerseRange(Versification v11n, Verse start) {
101         this(v11n, null, start, start);
102     }
103 
104     public VerseRange(Versification v11n, Verse start, Verse end) {
105         this(v11n, null, start, end);
106     }
107 
108     /**
109      * Construct a VerseRange from 2 Verses If start is later than end then swap
110      * the two around. This constructor is deliberately package protected so
111      * that is used only by VerseFactory.
112      * 
113      * @param start
114      *            The verse to start from
115      * @param end
116      *            The verse to end with
117      * @deprecated  use {@link #VerseRange(Versification, String, Verse, Verse)} instead
118      */
119     @Deprecated
120     /* package */VerseRange(String original, Verse start, Verse end) {
121         this(null, original, start, end);
122     }
123 
124     /* package */VerseRange(Versification v11n, String original, Verse start, Verse end) {
125         assert start != null;
126         assert end != null;
127 
128         this.v11n = v11n;
129         if (this.v11n == null) {
130             this.v11n = Versifications.instance().getDefaultVersification();
131         }
132 
133         this.originalName = original;
134         shaper = new NumberShaper();
135 
136         switch (start.compareTo(end)) {
137         case -1:
138             this.start = start;
139             this.end = end;
140             this.verseCount = calcVerseCount();
141             break;
142 
143         case 0:
144             this.start = start;
145             this.end = start;
146             this.verseCount = 1;
147             break;
148 
149         case 1:
150             this.start = end;
151             this.end = start;
152             this.verseCount = calcVerseCount();
153             break;
154 
155         default:
156             assert false;
157         }
158 
159         verifyData();
160     }
161 
162     /**
163      * Get the Versification to which this VerseRange participates.
164      * 
165      * @return the reference system.
166      */
167     public Versification getVersification() {
168         return v11n;
169     }
170 
171     /**
172      * Merge 2 VerseRanges together. The resulting range will encompass
173      * Everything in-between the extremities of the 2 ranges.
174      * 
175      * @param a
176      *            The first verse range to be merged
177      * @param b
178      *            The second verse range to be merged
179      */
180     public VerseRange(VerseRange a, VerseRange b) {
181         v11n = a.v11n;
182         shaper = new NumberShaper();
183         start = v11n.min(a.getStart(), b.getStart());
184         end = v11n.max(a.getEnd(), b.getEnd());
185         verseCount = calcVerseCount();
186     }
187 
188     /* (non-Javadoc)
189      * @see org.crosswire.jsword.passage.Key#getName()
190      */
191     public String getName() {
192         return getName(null);
193     }
194 
195     /* (non-Javadoc)
196      * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key)
197      */
198     public String getName(Key base) {
199         if (PassageUtil.isPersistentNaming() && originalName != null) {
200             return originalName;
201         }
202 
203         try {
204             String rangeName = doGetName(base);
205             // Only shape it if it can be unshaped.
206             if (shaper.canUnshape()) {
207                 return shaper.shape(rangeName);
208             }
209 
210             return rangeName;
211         } catch (NoSuchVerseException ex) {
212             assert false : ex;
213             return "!Error!";
214         }
215     }
216 
217     /* (non-Javadoc)
218      * @see org.crosswire.jsword.passage.Key#getRootName()
219      */
220     public String getRootName() {
221         return start.getRootName();
222     }
223 
224     /* (non-Javadoc)
225      * @see org.crosswire.jsword.passage.Key#getOsisRef()
226      */
227     public String getOsisRef() {
228         BibleBook startBook = start.getBook();
229         BibleBook endBook = end.getBook();
230         int startChapter = start.getChapter();
231         int endChapter = end.getChapter();
232 
233         // If this is in 2 separate books
234         if (startBook != endBook) {
235             StringBuilder buf = new StringBuilder();
236             if (v11n.isStartOfBook(start)) {
237                 buf.append(startBook.getOSIS());
238             } else if (v11n.isStartOfChapter(start)) {
239                 buf.append(startBook.getOSIS());
240                 buf.append(Verse.VERSE_OSIS_DELIM);
241                 buf.append(startChapter);
242             } else {
243                 buf.append(start.getOsisRef());
244             }
245 
246             buf.append(VerseRange.RANGE_PREF_DELIM);
247 
248             if (v11n.isEndOfBook(end)) {
249                 buf.append(endBook.getOSIS());
250             } else if (v11n.isEndOfChapter(end)) {
251                 buf.append(endBook.getOSIS());
252                 buf.append(Verse.VERSE_OSIS_DELIM);
253                 buf.append(endChapter);
254             } else {
255                 buf.append(end.getOsisRef());
256             }
257 
258             return buf.toString();
259         }
260 
261         // This range is exactly a whole book
262         if (isWholeBook()) {
263             // Just report the name of the book, we don't need to worry
264             // about the
265             // base since we start at the start of a book, and should have
266             // been
267             // recently normalized()
268             return startBook.getOSIS();
269         }
270 
271         // If this is 2 separate chapters in the same book
272         if (startChapter != endChapter) {
273             StringBuilder buf = new StringBuilder();
274             if (v11n.isStartOfChapter(start)) {
275                 buf.append(startBook.getOSIS());
276                 buf.append(Verse.VERSE_OSIS_DELIM);
277                 buf.append(startChapter);
278             } else {
279                 buf.append(start.getOsisRef());
280             }
281 
282             buf.append(VerseRange.RANGE_PREF_DELIM);
283 
284             if (v11n.isEndOfChapter(end)) {
285                 buf.append(endBook.getOSIS());
286                 buf.append(Verse.VERSE_OSIS_DELIM);
287                 buf.append(endChapter);
288             } else {
289                 buf.append(end.getOsisRef());
290             }
291 
292             return buf.toString();
293         }
294 
295         // If this range is exactly a whole chapter
296         if (isWholeChapter()) {
297             // Just report the name of the book and the chapter
298             StringBuilder buf = new StringBuilder();
299             buf.append(startBook.getOSIS());
300             buf.append(Verse.VERSE_OSIS_DELIM);
301             buf.append(startChapter);
302             return buf.toString();
303         }
304 
305         // If this is 2 separate verses
306         if (start.getVerse() != end.getVerse()) {
307             StringBuilder buf = new StringBuilder();
308             buf.append(start.getOsisRef());
309             buf.append(VerseRange.RANGE_PREF_DELIM);
310             buf.append(end.getOsisRef());
311             return buf.toString();
312         }
313 
314         // The range is a single verse
315         return start.getOsisRef();
316     }
317 
318     /* (non-Javadoc)
319      * @see org.crosswire.jsword.passage.Key#getOsisID()
320      */
321     public String getOsisID() {
322 
323         // This range is exactly a whole book
324         if (isWholeBook()) {
325             // Just report the name of the book, we don't need to worry
326             // about the base since we start at the start of a book, and
327             // should have been recently normalized()
328             return start.getBook().getOSIS();
329         }
330 
331         // If this range is exactly a whole chapter
332         if (isWholeChapter()) {
333             // Just report the name of the book and the chapter
334             return start.getBook().getOSIS() + Verse.VERSE_OSIS_DELIM + start.getChapter();
335         }
336 
337         int startOrdinal = v11n.getOrdinal(start);
338         int endOrdinal = v11n.getOrdinal(end);
339 
340         // to see if it is wholly contained in the range and output it if it is.
341 
342         // Estimate the size of the buffer: book.dd.dd (where book is 3-5, 3 typical)
343         StringBuilder buf = new StringBuilder((endOrdinal - startOrdinal + 1) * 10);
344         buf.append(start.getOsisID());
345         for (int i = startOrdinal + 1; i < endOrdinal; i++) {
346             buf.append(AbstractPassage.REF_OSIS_DELIM);
347             buf.append(v11n.decodeOrdinal(i).getOsisID());
348         }
349 
350         // It just might be a single verse range!
351         if (startOrdinal != endOrdinal) {
352             buf.append(AbstractPassage.REF_OSIS_DELIM);
353             buf.append(end.getOsisID());
354         }
355 
356         return buf.toString();
357     }
358 
359     @Override
360     public String toString() {
361         return getName();
362     }
363 
364     /**
365      * Fetch the first verse in this range.
366      * 
367      * @return The first verse in the range
368      */
369     public Verse getStart() {
370         return start;
371     }
372 
373     /**
374      * Fetch the last verse in this range.
375      * 
376      * @return The last verse in the range
377      */
378     public Verse getEnd() {
379         return end;
380     }
381 
382     /**
383      * How many chapters in this range
384      * 
385      * @return The number of chapters. Always >= 1.
386      * @deprecated  use {@link org.crosswire.jsword.versification.Versification#getChapterCount(Verse, Verse)} instead
387      */
388     @Deprecated
389     public int getChapterCount() {
390         return v11n.getChapterCount(start, end);
391     }
392 
393     /**
394      * How many books in this range
395      * 
396      * @return The number of books. Always >= 1.
397      * @deprecated  use {@link org.crosswire.jsword.versification.Versification#getBookCount(Verse, Verse)} instead
398      */
399     @Deprecated
400     public int getBookCount() {
401         return v11n.getBookCount(start, end);
402     }
403 
404     @Override
405     public VerseRange clone() {
406         // This gets us a shallow copy
407         VerseRange copy = null;
408         try {
409             copy = (VerseRange) super.clone();
410             copy.start = start;
411             copy.end = end;
412             copy.verseCount = verseCount;
413             copy.originalName = originalName;
414             copy.shaper = new NumberShaper();
415             copy.v11n = v11n;
416         } catch (CloneNotSupportedException e) {
417             assert false : e;
418         }
419 
420         return copy;
421     }
422 
423     @Override
424     public boolean equals(Object obj) {
425         // Since this can not be null
426         if (obj == null) {
427             return false;
428         }
429 
430         // Check that that is the same as this
431         // Don't use instanceOf since that breaks inheritance
432         if (!obj.getClass().equals(this.getClass())) {
433             return false;
434         }
435 
436         VerseRange vr = (VerseRange) obj;
437 
438         // The real tests
439         if (!vr.getStart().equals(getStart())) {
440             return false;
441         }
442 
443         if (vr.getCardinality() != getCardinality()) {
444             return false;
445         }
446 
447         // We don't really need to check this one too.
448         // if (!vr.getEnd().equals(getEnd())) return false;
449 
450         return true;
451     }
452 
453     @Override
454     public int hashCode() {
455         return (v11n.getOrdinal(start) << 16) + verseCount;
456     }
457 
458     /* (non-Javadoc)
459      * @see java.lang.Comparable#compareTo(java.lang.Object)
460      */
461     public int compareTo(Key obj) {
462         // This ensures a ClassCastException without further test
463         Verse that = null;
464         if (obj instanceof Verse) {
465             that = (Verse) obj;
466         } else {
467             that = ((VerseRange) obj).getStart();
468         }
469 
470         int start_compare = getStart().compareTo(that);
471         if (start_compare != 0) {
472             return start_compare;
473         }
474 
475         // So the start verses are the same, but the Verse(Range)s may not
476         // be equal() since they have lengths
477         int that_length = 1;
478         if (obj instanceof VerseRange) {
479             that_length = ((VerseRange) obj).getCardinality();
480         }
481 
482         if (that_length == getCardinality()) {
483             return 0;
484         }
485 
486         if (that_length < getCardinality()) {
487             return 1;
488         }
489 
490         return -1;
491     }
492 
493     /**
494      * Are the 2 VerseRanges in question contiguous. that is - could they be
495      * represented by a single VerseRange. Note that one range could be entirely
496      * contained within the other and they would be considered adjacentTo() For
497      * example Gen 1:1-2 is adjacent to Gen 1:1-5 and Gen 1:3-4 but not to Gen
498      * 1:4-10. Also Gen 1:29-30 is adjacent to Gen 2:1-10
499      * 
500      * @param that
501      *            The VerseRange to compare to
502      * @return true if the ranges are adjacent
503      */
504     public boolean adjacentTo(VerseRange that) {
505         int thatStart = v11n.getOrdinal(that.getStart());
506         int thatEnd = v11n.getOrdinal(that.getEnd());
507         int thisStart = v11n.getOrdinal(getStart());
508         int thisEnd = v11n.getOrdinal(getEnd());
509 
510         // if that starts inside or is next to this we are adjacent.
511         if (thatStart >= thisStart - 1 && thatStart <= thisEnd + 1) {
512             return true;
513         }
514 
515         // if this starts inside or is next to that we are adjacent.
516         if (thisStart >= thatStart - 1 && thisStart <= thatEnd + 1) {
517             return true;
518         }
519 
520         // otherwise we're not adjacent
521         return false;
522     }
523 
524     /**
525      * Do the 2 VerseRanges in question actually overlap. This is slightly more
526      * restrictive than the adjacentTo() test which could be satisfied by ranges
527      * like Gen 1:1-2 and Gen 1:3-4. overlaps() however would return false given
528      * these ranges. For example Gen 1:1-2 is adjacent to Gen 1:1-5 but not to
529      * Gen 1:3-4 not to Gen 1:4-10. Also Gen 1:29-30 does not overlap Gen 2:1-10
530      * 
531      * @param that
532      *            The VerseRange to compare to
533      * @return true if the ranges are adjacent
534      */
535     public boolean overlaps(VerseRange that) {
536         int thatStart = v11n.getOrdinal(that.getStart());
537         int thatEnd = v11n.getOrdinal(that.getEnd());
538         int thisStart = v11n.getOrdinal(getStart());
539         int thisEnd = v11n.getOrdinal(getEnd());
540 
541         // if that starts inside this we are adjacent.
542         if (thatStart >= thisStart && thatStart <= thisEnd) {
543             return true;
544         }
545 
546         // if this starts inside that we are adjacent.
547         if (thisStart >= thatStart && thisStart <= thatEnd) {
548             return true;
549         }
550 
551         // otherwise we're not adjacent
552         return false;
553     }
554 
555     /**
556      * Is the given verse entirely within our range. For example if this =
557      * "Gen 1:1-31" then: <tt>contains(Verse("Gen 1:3")) == true</tt>
558      * <tt>contains(Verse("Gen 2:1")) == false</tt>
559      * 
560      * @param that
561      *            The Verse to compare to
562      * @return true if we contain it.
563      */
564     public boolean contains(Verse that) {
565         if (start.compareTo(that) == 1) {
566             return false;
567         }
568 
569         if (end.compareTo(that) == -1) {
570             return false;
571         }
572 
573         return true;
574     }
575 
576     /**
577      * Is the given range within our range. For example if this = "Gen 1:1-31"
578      * then: <tt>this.contains(Verse("Gen 1:3-10")) == true</tt>
579      * <tt>this.contains(Verse("Gen 2:1-1")) == false</tt>
580      * 
581      * @param that
582      *            The Verse to compare to
583      * @return true if we contain it.
584      */
585     public boolean contains(VerseRange that) {
586         if (start.compareTo(that.getStart()) == 1) {
587             return false;
588         }
589 
590         if (end.compareTo(that.getEnd()) == -1) {
591             return false;
592         }
593 
594         return true;
595     }
596 
597     /**
598      * Does this range represent exactly one chapter, no more or less.
599      * 
600      * @return true if we are exactly one chapter.
601      */
602     public boolean isWholeChapter() {
603         if (!v11n.isStartOfChapter(start)) {
604             return false;
605         }
606 
607         if (!v11n.isEndOfChapter(end)) {
608             return false;
609         }
610 
611         if (!v11n.isSameChapter(start, end)) {
612             return false;
613         }
614 
615         return true;
616     }
617 
618     /**
619      * Does this range represent a number of whole chapters
620      * 
621      * @return true if we are a whole number of chapters.
622      */
623     public boolean isWholeChapters() {
624         if (!v11n.isStartOfChapter(start)) {
625             return false;
626         }
627 
628         if (!v11n.isEndOfChapter(end)) {
629             return false;
630         }
631 
632         return true;
633     }
634 
635     /**
636      * Does this range represent exactly one book, no more or less.
637      * 
638      * @return true if we are exactly one book.
639      */
640     public boolean isWholeBook() {
641         if (!v11n.isStartOfBook(start)) {
642             return false;
643         }
644 
645         if (!v11n.isEndOfBook(end)) {
646             return false;
647         }
648 
649         if (!v11n.isSameBook(start, end)) {
650             return false;
651         }
652 
653         return true;
654     }
655 
656     /**
657      * Does this range represent a whole number of books.
658      * 
659      * @return true if we are a whole number of books.
660      */
661     public boolean isWholeBooks() {
662         if (!v11n.isStartOfBook(start)) {
663             return false;
664         }
665 
666         if (!v11n.isEndOfBook(end)) {
667             return false;
668         }
669 
670         return true;
671     }
672 
673     /**
674      * Does this range occupy more than one book;
675      * 
676      * @return true if we occupy 2 or more books
677      */
678     public boolean isMultipleBooks() {
679         return start.getBook() != end.getBook();
680     }
681 
682     /**
683      * Create an array of Verses
684      * 
685      * @return The array of verses that this makes up
686      */
687     public Verse[] toVerseArray() {
688         Verse[] retcode = new Verse[verseCount];
689         int ord = v11n.getOrdinal(start);
690         for (int i = 0; i < verseCount; i++) {
691             retcode[i] = v11n.decodeOrdinal(ord + i);
692         }
693 
694         return retcode;
695     }
696 
697     /**
698      * Enumerate the subranges in this range
699      * 
700      * @return a range iterator
701      */
702     public Iterator<Key> rangeIterator(RestrictionType restrict) {
703         return new AbstractPassage.VerseRangeIterator(v11n, iterator(), restrict);
704     }
705 
706     /* (non-Javadoc)
707      * @see org.crosswire.jsword.passage.Key#getParent()
708      */
709     public Key getParent() {
710         return parent;
711     }
712 
713     /**
714      * Set a parent Key. This allows us to follow the Key interface more
715      * closely, although the concept of a parent for a verse is fairly alien.
716      * 
717      * @param parent
718      *            The parent Key for this verse
719      */
720     public void setParent(Key parent) {
721         this.parent = parent;
722     }
723 
724     /**
725      * Create a VerseRange that is the stuff left of VerseRange a when you
726      * remove the stuff in VerseRange b.
727      * 
728      * @param a
729      *            Verses at the start or end of b
730      * @param b
731      *            All the verses
732      * @return A list of the Verses outstanding
733      */
734     public static VerseRange[] remainder(VerseRange a, VerseRange b) {
735         VerseRange rstart = null;
736         VerseRange rend = null;
737 
738         Versification v11n = a.getVersification();
739 
740         // If a starts before b get the Range of the prequel
741         if (v11n.distance(a.getStart(), b.getStart()) > 0) {
742             rstart = new VerseRange(v11n, a.getStart(), v11n.subtract(b.getEnd(), 1));
743         }
744 
745         // If a ends after b get the Range of the sequel
746         if (v11n.distance(a.getEnd(), b.getEnd()) < 0) {
747             rend = new VerseRange(v11n, v11n.add(b.getEnd(), 1), a.getEnd());
748         }
749 
750         if (rstart == null) {
751             if (rend == null) {
752                 return new VerseRange[] {};
753             }
754             return new VerseRange[] {
755                 rend
756             };
757         }
758 
759         if (rend == null) {
760             return new VerseRange[] {
761                 rstart
762             };
763         }
764         return new VerseRange[] {
765                 rstart, rend
766         };
767     }
768 
769     /**
770      * Create a VerseRange that is the stuff in VerseRange a that is also
771      * in VerseRange b.
772      * 
773      * @param a
774      *            The verses that you might want
775      * @param b
776      *            The verses that you definitely don't
777      * @return A list of the Verses outstanding
778      */
779     public static VerseRange intersection(VerseRange a, VerseRange b) {
780         Versification v11n = a.getVersification();
781         Verse new_start = v11n.max(a.getStart(), b.getStart());
782         Verse new_end = v11n.min(a.getEnd(), b.getEnd());
783 
784         if (new_start.compareTo(new_end) < 1) {
785             return new VerseRange(a.getVersification(), new_start, new_end);
786         }
787 
788         return null;
789     }
790 
791     private String doGetName(Key base) throws NoSuchVerseException {
792         // Cache these we're going to be using them a lot.
793         BibleBook startBook = start.getBook();
794         int startChapter = start.getChapter();
795         int startVerse = start.getVerse();
796         BibleBook endBook = end.getBook();
797         int endChapter = end.getChapter();
798         int endVerse = end.getVerse();
799 
800         // If this is in 2 separate books
801         if (startBook != endBook) {
802             // This range is exactly a whole book
803             if (isWholeBooks()) {
804                 // Just report the name of the book, we don't need to worry
805                 // about the
806                 // base since we start at the start of a book, and should have
807                 // been
808                 // recently normalized()
809                 return startBook.getPreferredName() + VerseRange.RANGE_PREF_DELIM + endBook.getPreferredName();
810             }
811 
812             // If this range is exactly a whole chapter
813             if (isWholeChapters()) {
814                 // Just report book and chapter names
815                 return startBook.getPreferredName() + Verse.VERSE_PREF_DELIM1 + startChapter + VerseRange.RANGE_PREF_DELIM
816                         + endBook.getPreferredName() + Verse.VERSE_PREF_DELIM1 + endChapter;
817             }
818 
819             return start.getName(base) + VerseRange.RANGE_PREF_DELIM + end.getName(base);
820         }
821 
822         // This range is exactly a whole book
823         if (isWholeBook()) {
824             // Just report the name of the book, we don't need to worry about
825             // the
826             // base since we start at the start of a book, and should have been
827             // recently normalized()
828             return startBook.getPreferredName();
829         }
830 
831         // If this is 2 separate chapters
832         if (startChapter != endChapter) {
833             // If this range is a whole number of chapters
834             if (isWholeChapters()) {
835                 // Just report the name of the book and the chapters
836                 return startBook.getPreferredName() + Verse.VERSE_PREF_DELIM1 + startChapter + VerseRange.RANGE_PREF_DELIM + endChapter;
837             }
838 
839             return start.getName(base) + VerseRange.RANGE_PREF_DELIM + endChapter + Verse.VERSE_PREF_DELIM2 + endVerse;
840         }
841 
842         // If this range is exactly a whole chapter
843         if (isWholeChapter()) {
844             // Just report the name of the book and the chapter
845             return startBook.getPreferredName() + Verse.VERSE_PREF_DELIM1 + startChapter;
846         }
847 
848         // If this is 2 separate verses
849         if (startVerse != endVerse) {
850             return start.getName(base) + VerseRange.RANGE_PREF_DELIM + endVerse;
851         }
852 
853         // The range is a single verse
854         return start.getName(base);
855     }
856 
857     /**
858      * Calculate the last verse in this range.
859      * 
860      * @param start
861      *            The first verse in the range
862      * @param verseCount
863      *            The number of verses
864      * @return The last verse in the range
865      */
866     private Verse calcEnd() {
867         if (verseCount == 1) {
868             return start;
869         }
870         return v11n.add(start, verseCount - 1);
871     }
872 
873     /**
874      * Calculate how many verses in this range
875      * 
876      * @param a
877      *            The first verse in the range
878      * @param b
879      *            The last verse in the range
880      * @return The number of verses. Always >= 1.
881      */
882     private int calcVerseCount() {
883         return v11n.distance(start, end) + 1;
884     }
885 
886     /**
887      * Check to see that everything is ok with the Data
888      */
889     private void verifyData() {
890         assert verseCount == calcVerseCount() : "start=" + start + ", end=" + end + ", verseCount=" + verseCount;
891     }
892 
893     /**
894      * Write out the object to the given ObjectOutputStream
895      * 
896      * @param out
897      *            The stream to write our state to
898      * @throws IOException
899      *             If the write fails
900      * @serialData Write the ordinal number of this verse
901      */
902     private void writeObject(ObjectOutputStream out) throws IOException {
903         // Call even if there is no default serializable fields.
904         out.defaultWriteObject();
905 
906         out.writeUTF(v11n.getName());
907         out.writeInt(v11n.getOrdinal(start));
908         out.writeInt(verseCount);
909 
910         // Ignore the original name. Is this wise?
911         // I am expecting that people are not that fussed about it and
912         // it could make everything far more verbose
913     }
914 
915     /**
916      * Write out the object to the given ObjectOutputStream
917      * 
918      * @param in
919      *            The stream to read our state from
920      * @throws IOException
921      *             If the write fails
922      * @throws ClassNotFoundException
923      *             If the read data is incorrect
924      * @serialData Write the ordinal number of this verse
925      */
926     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
927         // Call even if there is no default serializable fields.
928         in.defaultReadObject();
929 
930         String v11nName = in.readUTF();
931         v11n = Versifications.instance().getVersification(v11nName);
932         start = v11n.decodeOrdinal(in.readInt());
933         verseCount = in.readInt();
934         end = calcEnd();
935         shaper = new NumberShaper();
936 
937         verifyData();
938 
939         // We are ignoring the originalName. It was set to null in the
940         // default ctor so I will ignore it here.
941     }
942 
943     /**
944      * Iterate over the Verses in the VerseRange
945      */
946     private static final class VerseIterator implements Iterator<Key> {
947         /**
948          * Ctor
949          */
950         protected VerseIterator(VerseRange range) {
951             v11n = range.getVersification();
952             next = v11n.getOrdinal(range.getStart());
953             last = v11n.getOrdinal(range.getEnd());
954         }
955 
956         /* (non-Javadoc)
957          * @see java.util.Iterator#hasNext()
958          */
959         public boolean hasNext() {
960             return next <= last;
961         }
962 
963         /* (non-Javadoc)
964          * @see java.util.Iterator#next()
965          */
966         public Key next() throws NoSuchElementException {
967             if (next > last) {
968                 throw new NoSuchElementException();
969             }
970 
971             return v11n.decodeOrdinal(next++);
972         }
973 
974         /* (non-Javadoc)
975          * @see java.util.Iterator#remove()
976          */
977         public void remove() throws UnsupportedOperationException {
978             throw new UnsupportedOperationException();
979         }
980 
981         private Versification v11n;
982         private int next;
983         private int last;
984     }
985 
986     /* (non-Javadoc)
987      * @see org.crosswire.jsword.passage.Key#canHaveChildren()
988      */
989     public boolean canHaveChildren() {
990         return false;
991     }
992 
993     /* (non-Javadoc)
994      * @see org.crosswire.jsword.passage.Key#getChildCount()
995      */
996     public int getChildCount() {
997         return 0;
998     }
999 
1000    /* (non-Javadoc)
1001     * @see org.crosswire.jsword.passage.Key#getCardinality()
1002     */
1003    public int getCardinality() {
1004        return verseCount;
1005    }
1006
1007    /* (non-Javadoc)
1008     * @see org.crosswire.jsword.passage.Key#isEmpty()
1009     */
1010    public boolean isEmpty() {
1011        return verseCount == 0;
1012    }
1013
1014    /* (non-Javadoc)
1015     * @see org.crosswire.jsword.passage.Key#contains(org.crosswire.jsword.passage.Key)
1016     */
1017    public boolean contains(Key key) {
1018        if (key instanceof VerseRange) {
1019            return contains((VerseRange) key);
1020        }
1021        return false;
1022    }
1023
1024    /*
1025     * (non-Javadoc)
1026     * 
1027     * @see org.crosswire.jsword.passage.Key#iterator()
1028     */
1029    public Iterator<Key> iterator() {
1030        return new VerseIterator(this);
1031    }
1032
1033    /* (non-Javadoc)
1034     * @see org.crosswire.jsword.passage.Key#addAll(org.crosswire.jsword.passage.Key)
1035     */
1036    public void addAll(Key key) {
1037        throw new UnsupportedOperationException();
1038    }
1039
1040    /* (non-Javadoc)
1041     * @see org.crosswire.jsword.passage.Key#removeAll(org.crosswire.jsword.passage.Key)
1042     */
1043    public void removeAll(Key key) {
1044        throw new UnsupportedOperationException();
1045    }
1046
1047    /* (non-Javadoc)
1048     * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key)
1049     */
1050    public void retainAll(Key key) {
1051        throw new UnsupportedOperationException();
1052    }
1053
1054    /* (non-Javadoc)
1055     * @see org.crosswire.jsword.passage.Key#clear()
1056     */
1057    public void clear() {
1058    }
1059
1060    /* (non-Javadoc)
1061     * @see org.crosswire.jsword.passage.Key#get(int)
1062     */
1063    public Key get(int index) {
1064        return null;
1065    }
1066
1067    /* (non-Javadoc)
1068     * @see org.crosswire.jsword.passage.Key#indexOf(org.crosswire.jsword.passage.Key)
1069     */
1070    public int indexOf(Key that) {
1071        return -1;
1072    }
1073
1074    /* (non-Javadoc)
1075     * @see org.crosswire.jsword.passage.Key#blur(int, org.crosswire.jsword.passage.RestrictionType)
1076     */
1077    public void blur(int by, RestrictionType restrict) {
1078        VerseRange newRange = restrict.blur(v11n, this, by, by);
1079        start = newRange.start;
1080        end = newRange.end;
1081        verseCount = newRange.verseCount;
1082    }
1083
1084    /**
1085     * What characters can we use to separate the 2 parts to a VerseRanges
1086     */
1087    public static final String RANGE_ALLOWED_DELIMS = "-";
1088
1089    /**
1090     * What characters should we use to separate VerseRange parts on output
1091     */
1092    public static final String RANGE_PREF_DELIM = RANGE_ALLOWED_DELIMS;
1093
1094    /**
1095     * To make serialization work across new versions
1096     */
1097    static final long serialVersionUID = 8307795549869653580L;
1098
1099    /**
1100     * The Versification with which this range is defined.
1101     */
1102    private transient Versification v11n;
1103
1104    /**
1105     * The real data - how many verses long are we?. All ctors init this so
1106     * leave default
1107     */
1108    private transient int verseCount;
1109
1110    /**
1111     * The real data - where do we start?. All ctors init this so leave default
1112     */
1113    private transient Verse start;
1114
1115    /**
1116     * The real data - where do we end?. All ctors init this so leave default
1117     */
1118    private transient Verse end;
1119
1120    /**
1121     * Allow the conversion to and from other number representations.
1122     */
1123    private transient NumberShaper shaper;
1124
1125    /**
1126     * The parent key. See the key interface for more information. NOTE(joe):
1127     * These keys are not serialized, should we?
1128     * 
1129     * @see Key
1130     */
1131    private transient Key parent;
1132
1133    /**
1134     * The original string for picky users
1135     */
1136    private transient String originalName;
1137
1138    /**
1139     * The log stream
1140     */
1141    /* pkg protected */static final transient Logger log = Logger.getLogger(VerseRange.class);
1142
1143}
1144