| VerseRange.java |
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>= 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