| 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 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