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