1
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
44 public final class VerseRange implements VerseKey<VerseRange> {
45
53 public VerseRange(Versification v11n) {
54 this(v11n, Verse.DEFAULT, Verse.DEFAULT);
55 }
56
57
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
98 public Versification getVersification() {
99 return v11n;
100 }
101
102
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
123 public boolean isWhole() {
124 return start.isWhole() && end.isWhole();
125 }
126
127
130 public VerseRange getWhole() {
131 if (isWhole()) {
132 return this;
133 }
134 return new VerseRange(v11n, start.getWhole(), end.getWhole());
135 }
136
137
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
157 public String getName() {
158 return getName(null);
159 }
160
161
164 public String getName(Key base) {
165 if (PassageUtil.isPersistentNaming() && originalName != null) {
166 return originalName;
167 }
168
169 String rangeName = doGetName(base);
170 if (shaper.canUnshape()) {
172 return shaper.shape(rangeName);
173 }
174
175 return rangeName;
176 }
177
178
181 public String getRootName() {
182 return start.getRootName();
183 }
184
185
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 (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 if (isWholeBook()) {
224 return startBook.getOSIS();
230 }
231
232 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 (isWholeChapter()) {
258 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 (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 return start.getOsisRef();
277 }
278
279
282 public String getOsisID() {
283
284 if (isWholeBook()) {
286 return start.getBook().getOSIS();
290 }
291
292 if (isWholeChapter()) {
294 return start.getBook().getOSIS() + Verse.VERSE_OSIS_DELIM + start.getChapter();
296 }
297
298 int startOrdinal = start.getOrdinal();
299 int endOrdinal = end.getOrdinal();
300
301
303 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 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
330 public Verse getStart() {
331 return start;
332 }
333
334
339 public Verse getEnd() {
340 return end;
341 }
342
343 @Override
344 public VerseRange clone() {
345 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
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
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 (thatStart >= thisStart - 1 && thatStart <= thisEnd + 1) {
407 return true;
408 }
409
410 if (thisStart >= thatStart - 1 && thisStart <= thatEnd + 1) {
412 return true;
413 }
414
415 return false;
417 }
418
419
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 (thatStart >= thisStart && thatStart <= thisEnd) {
438 return true;
439 }
440
441 if (thisStart >= thatStart && thisStart <= thatEnd) {
443 return true;
444 }
445
446 return false;
448 }
449
450
459 public boolean contains(Verse that) {
460 return v11n.distance(start, that) >= 0 && v11n.distance(that, end) >= 0;
461 }
462
463
472 public boolean contains(VerseRange that) {
473 return v11n.distance(start, that.getStart()) >= 0 && v11n.distance(that.getEnd(), end) >= 0;
474 }
475
476
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
494 public boolean isWholeChapter() {
495 return v11n.isSameChapter(start, end) && isWholeChapters();
496 }
497
498
503 public boolean isWholeChapters() {
504 return v11n.isStartOfChapter(start) && v11n.isEndOfChapter(end);
505 }
506
507
512 public boolean isWholeBook() {
513 return v11n.isSameBook(start, end) && isWholeBooks();
514 }
515
516
521 public boolean isWholeBooks() {
522 return v11n.isStartOfBook(start) && v11n.isEndOfBook(end);
523 }
524
525
530 public boolean isMultipleBooks() {
531 return start.getBook() != end.getBook();
532 }
533
534
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
555 public Iterator<VerseRange> rangeIterator(RestrictionType restrict) {
556 return new AbstractPassage.VerseRangeIterator(v11n, iterator(), restrict);
557 }
558
559
562 public Key getParent() {
563 return parent;
564 }
565
566
573 public void setParent(Key parent) {
574 this.parent = parent;
575 }
576
577
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 (v11n.distance(a.getStart(), b.getStart()) > 0) {
595 rstart = new VerseRange(v11n, a.getStart(), v11n.subtract(b.getEnd(), 1));
596 }
597
598 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
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 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 (startBook != endBook) {
655 if (isWholeBooks()) {
657 return v11n.getPreferredName(startBook) + VerseRange.RANGE_PREF_DELIM + v11n.getPreferredName(endBook);
661 }
662
663 if (isWholeChapters()) {
665 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 if (isWholeBook()) {
681 return v11n.getPreferredName(startBook);
686 }
687
688 if (startChapter != endChapter) {
690 if (isWholeChapters()) {
692 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 (isWholeChapter()) {
701 return v11n.getPreferredName(startBook) + Verse.VERSE_PREF_DELIM1 + startChapter;
703 }
704
705 if (startVerse != endVerse) {
707 return start.getName(base) + VerseRange.RANGE_PREF_DELIM + endVerse;
708 }
709
710 return start.getName(base);
712 }
713
714
719 private Verse calcEnd() {
720 if (verseCount == 1) {
721 return start;
722 }
723 return v11n.add(start, verseCount - 1);
724 }
725
726
731 private int calcVerseCount() {
732 return v11n.distance(start, end) + 1;
733 }
734
735
738 private void verifyData() {
739 assert verseCount == calcVerseCount() : "start=" + start + ", end=" + end + ", verseCount=" + verseCount;
740 }
741
742
751 private void writeObject(ObjectOutputStream out) throws IOException {
752 out.defaultWriteObject();
753
754 }
758
759
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 }
780
781
784 private static final class VerseIterator implements Iterator<Key> {
785
788 protected VerseIterator(VerseRange range) {
789 v11n = range.getVersification();
790 nextVerse = range.getStart();
791 total = range.getCardinality();
792 count = 0;
793 }
794
795
798 public boolean hasNext() {
799 return nextVerse != null;
800 }
801
802
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
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
830 public boolean canHaveChildren() {
831 return false;
832 }
833
834
837 public int getChildCount() {
838 return 0;
839 }
840
841
844 public int getCardinality() {
845 return verseCount;
846 }
847
848
851 public boolean isEmpty() {
852 return verseCount == 0;
853 }
854
855
860 public Iterator<Key> iterator() {
861 return new VerseIterator(this);
862 }
863
864
867 public void addAll(Key key) {
868 throw new UnsupportedOperationException();
869 }
870
871
874 public void removeAll(Key key) {
875 throw new UnsupportedOperationException();
876 }
877
878
881 public void retainAll(Key key) {
882 throw new UnsupportedOperationException();
883 }
884
885
888 public void clear() {
889 }
890
891
894 public Key get(int index) {
895 return null;
896 }
897
898
901 public int indexOf(Key that) {
902 return -1;
903 }
904
905
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
918 public static final char RANGE_OSIS_DELIM = '-';
919
920
923 public static final char RANGE_PREF_DELIM = RANGE_OSIS_DELIM;
924
925
928 private transient Versification v11n;
929
930
933 private Verse start;
934
935
938 private int verseCount;
939
940
943 private transient Verse end;
944
945
948 private transient NumberShaper shaper;
949
950
953 private transient Key parent;
954
955
958 private transient String originalName;
959
960
963 static final long serialVersionUID = 8307795549869653580L;
964 }
965