| Versification.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, 2012 - 2016
18 *
19 */
20 package org.crosswire.jsword.versification;
21
22 import java.io.PrintStream;
23 import java.io.Serializable;
24 import java.util.Iterator;
25
26 import org.crosswire.jsword.JSMsg;
27 import org.crosswire.jsword.JSOtherMsg;
28 import org.crosswire.jsword.book.ReferenceSystem;
29 import org.crosswire.jsword.passage.NoSuchVerseException;
30 import org.crosswire.jsword.passage.Verse;
31 import org.crosswire.jsword.passage.VerseRange;
32
33 /**
34 * A named Versification defines
35 * the order of BibleBooks by Testament,
36 * the number of chapters in each BibleBook,
37 * the number of verses in each chapter.
38 *
39 * @see gnu.lgpl.License The GNU Lesser General Public License for details.
40 * @author DM Smith
41 */
42 public class Versification implements ReferenceSystem, Serializable {
43 public Versification() {
44 }
45
46 /**
47 * Construct a Versification.
48 *
49 * @param name
50 * The name of this reference system
51 * @param booksOT
52 * An ordered list of books in this reference system. The list
53 * should not include INTRO_BIBLE, or INTRO_OT.
54 * @param booksNT
55 * An ordered list of books in this reference system. The list
56 * should not include INTRO_NT.
57 * @param lastVerseOT
58 * For each book in booksOT, this has an array with one entry for
59 * each chapter whose value is the highest numbered verse in that
60 * chapter. Do not include chapter 0.
61 * @param lastVerseNT
62 * For each book in booksNT, this has an array with one entry for
63 * each chapter whose value is the highest numbered verse in that
64 * chapter. Do not include chapter 0.
65 */
66 public Versification(String name, BibleBook[] booksOT, BibleBook[] booksNT, int[][] lastVerseOT, int[][] lastVerseNT) {
67 this.name = name;
68
69 // Copy the books into an aggregated BibleBook array
70 // including INTRO_BIBLE and INTRO_OT/INTRO_NT for non-null book lists
71 int bookCount = 1; // Always include the INTRO_BIBLE
72 if (booksOT.length > 0) {
73 bookCount += booksOT.length + 1; // All of the OT books and INTRO_OT
74 }
75
76 int ntStart = bookCount;
77 if (booksNT.length > 0) {
78 bookCount += booksNT.length + 1; // All of the NT books and INTRO_NT
79 }
80 BibleBook[] books = new BibleBook[bookCount];
81 books[0] = BibleBook.INTRO_BIBLE;
82 if (booksOT.length > 0) {
83 books[1] = BibleBook.INTRO_OT;
84 System.arraycopy(booksOT, 0, books, 2, booksOT.length);
85 }
86
87 if (booksNT.length > 0) {
88 books[ntStart] = BibleBook.INTRO_NT;
89 System.arraycopy(booksNT, 0, books, ntStart + 1, booksNT.length);
90 }
91
92 this.bookList = new BibleBookList(books);
93
94 int ordinal = 0;
95
96 // Create an independent copy of lastVerse.
97 this.lastVerse = new int[bookCount][];
98 int bookIndex = 0;
99
100 // Add in the bible introduction
101 int[] chapters = new int[1];
102 chapters[0] = 0;
103 this.lastVerse[bookIndex++] = chapters;
104
105 // Now append the OT info
106 if (lastVerseOT.length > 0) {
107 // Add in the testament intro
108 chapters = new int[1];
109 chapters[0] = 0;
110 this.lastVerse[bookIndex++] = chapters;
111 // then all the testament info
112 for (int i = 0; i < lastVerseOT.length; i++) {
113 int[] src = lastVerseOT[i];
114 // Add one as the location for chapter 0.
115 int[] dest = new int[src.length + 1];
116 this.lastVerse[bookIndex++] = dest;
117 // The last verse of chapter 0 is 0
118 dest[0] = 0;
119 // copy the last verse array for the chapter
120 System.arraycopy(src, 0, dest, 1, src.length);
121 }
122 }
123
124 // Now append the NT info
125 if (lastVerseNT.length > 0) {
126 // Add in the testament intro
127 chapters = new int[1];
128 chapters[0] = 0;
129 this.lastVerse[bookIndex++] = chapters;
130 // then all the testament info
131 for (int i = 0; i < lastVerseNT.length; i++) {
132 int[] src = lastVerseNT[i];
133 // Add one as the location for chapter 0.
134 int[] dest = new int[src.length + 1];
135 this.lastVerse[bookIndex++] = dest;
136 // The last verse of chapter 0 is 0
137 dest[0] = 0;
138 // copy the last verse array for the chapter
139 System.arraycopy(src, 0, dest, 1, src.length);
140 }
141 }
142
143 // Initialize chapterStarts to be a parallel array to lastVerse,
144 // but with chapter starts
145 this.chapterStarts = new int[bookCount][];
146 for (bookIndex = 0; bookIndex < bookCount; bookIndex++) {
147
148 // Remember where the OT ends
149 if (bookList.getBook(bookIndex) == BibleBook.INTRO_NT) {
150 // This is not reached for a v11n without a NT.
151 this.otMaxOrdinal = ordinal - 1;
152 }
153
154 // Save off the chapter starts
155 int[] src = this.lastVerse[bookIndex];
156 int numChapters = src.length;
157 int[] dest = new int[numChapters];
158 this.chapterStarts[bookIndex] = dest;
159 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
160 // Save off the chapter start
161 dest[chapterIndex] = ordinal;
162
163 // Set ordinal to the start of the next chapter or book introduction.
164 // The number of verses in each chapter, when including verse 0,
165 // is one more that the largest numbered verse in the chapter.
166 ordinal += src[chapterIndex] + 1;
167 }
168 }
169
170 // Remember where the NT ends
171 this.ntMaxOrdinal = ordinal - 1;
172
173 // The MT v11n has no NT, so at this point otMaxOrdinal == 0
174 if (booksNT.length == 0) {
175 this.otMaxOrdinal = this.ntMaxOrdinal;
176 }
177 // Versification.dump(System.out, this.osisName, this.bookList, this.lastVerse);
178 // Versification.dump(System.out, this.osisName, this.bookList, this.chapterStarts);
179 }
180
181 /**
182 * Get the OSIS name for this Versification.
183 * @return the OSIS name of the Versification
184 */
185 public String getName() {
186 return name;
187 }
188
189 /**
190 * Does this Versification contain the BibleBook.
191 *
192 * @param book
193 * @return true if it is present.
194 */
195 public boolean containsBook(BibleBook book) {
196 return bookList.contains(book);
197 }
198
199 /**
200 * Get the BibleBook by its position in this Versification.
201 * If the position is negative, return the first book.
202 * If the position is greater than the last, return the last book.
203 *
204 * @param ordinal
205 * @return the indicated book
206 */
207 public BibleBook getBook(int ordinal) {
208 return bookList.getBook(ordinal);
209 }
210
211 /**
212 * Get a book from its name.
213 *
214 * @param find
215 * The string to identify
216 * @return The BibleBook, On error null
217 */
218 public BibleBook getBook(String find) {
219 BibleBook book = BibleNames.instance().getBook(find);
220 if (containsBook(book)) {
221 return book;
222 }
223 return null;
224 }
225
226 /**
227 * Get the number of books in this Versification.
228 * @return the number of books
229 */
230 public int getBookCount() {
231 return bookList.getBookCount();
232 }
233
234 /**
235 * The number of books between two verses includes
236 * the books of the two verses and everything in between.
237 *
238 * @param start
239 * The first Verse in the range
240 * @param end The last Verse in the range
241 * @return The number of books. Always >= 1.
242 */
243 public int getBookCount(Verse start, Verse end) {
244 int startBook = bookList.getOrdinal(start.getBook());
245 int endBook = bookList.getOrdinal(end.getBook());
246
247 return endBook - startBook + 1;
248 }
249
250 /**
251 * Return the first book in the list.
252 *
253 * @return the first book in the list
254 */
255 public BibleBook getFirstBook() {
256 return bookList.getFirstBook();
257 }
258
259 /**
260 * Return the first book in the list.
261 *
262 * @return the first book in the list
263 */
264 public BibleBook getLastBook() {
265 return bookList.getLastBook();
266 }
267
268 /**
269 * Given a BibleBook, get the next BibleBook in this Versification. If it is the last book, return null.
270 * @param book A BibleBook in the Versification
271 * @return the previous BibleBook or null.
272 */
273 public BibleBook getNextBook(BibleBook book) {
274 return bookList.getNextBook(book);
275 }
276
277 /**
278 * Given a BibleBook, get the previous BibleBook in this Versification. If it is the first book, return null.
279 * @param book A BibleBook in the Versification
280 * @return the previous BibleBook or null.
281 */
282 public BibleBook getPreviousBook(BibleBook book) {
283 return bookList.getPreviousBook(book);
284 }
285
286 /**
287 * Get the BibleBooks in this Versification.
288 *
289 * @return an Iterator over the books
290 */
291 public Iterator<BibleBook> getBookIterator() {
292 return bookList.iterator();
293 }
294
295 /**
296 * Get the BookName.
297 *
298 * @param book the desired book
299 * @return The requested BookName or null if not in this versification
300 */
301 public BookName getBookName(BibleBook book) {
302 if (containsBook(book)) {
303 return BibleNames.instance().getBookName(book);
304 }
305 return null;
306 }
307
308 /**
309 * Get the preferred name of a book. Altered by the case setting (see
310 * setBookCase() and isFullBookName())
311 *
312 * @param book the desired book
313 * @return The full name of the book or null if not in this versification
314 */
315 public String getPreferredName(BibleBook book) {
316 if (containsBook(book)) {
317 return BibleNames.instance().getPreferredName(book);
318 }
319 return null;
320 }
321
322 /**
323 * Get the full name of a book (e.g. "Genesis"). Altered by the case setting
324 * (see setBookCase())
325 *
326 * @param book the book of the Bible
327 * @return The full name of the book or null if not in this versification
328 */
329 public String getLongName(BibleBook book) {
330 if (containsBook(book)) {
331 return BibleNames.instance().getLongName(book);
332 }
333 return null;
334 }
335
336 /**
337 * Get the short name of a book (e.g. "Gen"). Altered by the case setting
338 * (see setBookCase())
339 *
340 * @param book the book of the Bible
341 * @return The short name of the book or null if not in this versification
342 */
343 public String getShortName(BibleBook book) {
344 if (containsBook(book)) {
345 return BibleNames.instance().getShortName(book);
346 }
347 return null;
348 }
349
350 /**
351 * Is the given string a valid book name. If this method returns true then
352 * getBook() will return a BibleBook and not null.
353 *
354 * @param find
355 * The string to identify
356 * @return true when the book name is recognized
357 */
358 public boolean isBook(String find) {
359 return getBook(find) != null;
360 }
361
362 /**
363 * Get the last valid chapter number for a book.
364 *
365 * @param book
366 * The book part of the reference.
367 * @return The last valid chapter number for a book.
368 */
369 public int getLastChapter(BibleBook book) {
370 // This is faster than doing the check explicitly, unless
371 // The exception is actually thrown, then it is a lot slower
372 // I'd like to think that the norm is to get it right
373 try {
374 return lastVerse[bookList.getOrdinal(book)].length - 1;
375 } catch (NullPointerException ex) {
376 return 0;
377 } catch (ArrayIndexOutOfBoundsException ex) {
378 return 0;
379 }
380 }
381
382 /**
383 * Get the last valid verse number for a chapter.
384 *
385 * @param book
386 * The book part of the reference.
387 * @param chapter
388 * The current chapter
389 * @return The last valid verse number for a chapter
390 */
391 public int getLastVerse(BibleBook book, int chapter) {
392 // This is faster than doing the check explicitly, unless
393 // The exception is actually thrown, then it is a lot slower
394 // I'd like to think that the norm is to get it right
395 try {
396 return lastVerse[bookList.getOrdinal(book)][chapter];
397 } catch (NullPointerException ex) {
398 return 0;
399 } catch (ArrayIndexOutOfBoundsException ex) {
400 return 0;
401 }
402 }
403
404 /**
405 * Get a VerseRange encompassing this Versification.
406 *
407 * @return a VerseRange for the whole versification
408 */
409 public VerseRange getAllVerses() {
410 Verse first = new Verse(this, bookList.getFirstBook(), 0, 0);
411 BibleBook book = bookList.getLastBook();
412 int chapter = getLastChapter(book);
413 Verse last = new Verse(this, book, chapter, getLastVerse(book, chapter));
414 return new VerseRange(this, first, last);
415 }
416
417 /**
418 * An introduction is a Verse that has a verse number of 0.
419 *
420 * @param verse the verse to test
421 * @return true or false ...
422 */
423 public boolean isIntro(Verse verse) {
424 int v = verse.getVerse();
425 return v == 0;
426 }
427
428 /**
429 * A book introduction is an introduction
430 * that has a chapter of 0.
431 *
432 * @param verse the verse to test
433 * @return true or false ...
434 */
435 public boolean isBookIntro(Verse verse) {
436 return 0 == verse.getChapter() && isIntro(verse);
437 }
438
439 /**
440 * A chapter introduction is an introduction
441 * that has a chapter other than 0
442 *
443 * @param verse the verse to test
444 * @return true or false ...
445 */
446 public boolean isChapterIntro(Verse verse) {
447 return 0 != verse.getChapter() && isIntro(verse);
448 }
449
450 /**
451 * The start of a chapter is indicated by
452 * a verse number of 0 or 1
453 *
454 * @param verse the verse to test
455 * @return true or false ...
456 */
457 public boolean isStartOfChapter(Verse verse) {
458 int v = verse.getVerse();
459 return v <= 1;
460 }
461
462 /**
463 * The end of the chapter is indicated by
464 * the verse number matching the last in the chapter.
465 *
466 * @param verse the verse to test
467 * @return true or false ...
468 */
469 public boolean isEndOfChapter(Verse verse) {
470 BibleBook b = verse.getBook();
471 int v = verse.getVerse();
472 int c = verse.getChapter();
473 return v == getLastVerse(b, c);
474 }
475
476 /**
477 * The start of a book is indicated by
478 * a chapter number of 0 or 1 and
479 * a verse number of 0 or 1.
480 *
481 * @param verse the verse to test
482 * @return true or false ...
483 */
484 public boolean isStartOfBook(Verse verse) {
485 int v = verse.getVerse();
486 int c = verse.getChapter();
487 return v <= 1 && c <= 1;
488 }
489
490 /**
491 * The end of the book is indicated by
492 * the chapter number matching the last chapter
493 * in the book and the verse number matching
494 * the last verse in the chapter.
495 *
496 * @param verse the verse to test
497 * @return true or false ...
498 */
499 public boolean isEndOfBook(Verse verse) {
500 BibleBook b = verse.getBook();
501 int v = verse.getVerse();
502 int c = verse.getChapter();
503 return v == getLastVerse(b, c) && c == getLastChapter(b);
504 }
505
506 /**
507 * Two verses are in the same chapter if both
508 * the book and chapter agree.
509 *
510 * @param first
511 * The verse to compare to
512 * @param second
513 * The verse to compare to
514 * @return true or false ...
515 */
516 public boolean isSameChapter(Verse first, Verse second) {
517 return first.getBook() == second.getBook() && first.getChapter() == second.getChapter();
518 }
519
520 /**
521 * Two verse are adjacent if one immediately follows the other,
522 * even across book boundaries. Introductions are considered
523 * as having "zero width" in this determination. That is the
524 * last verse in a chapter or book is adjacent every verse that
525 * follows up to and including verse 1 of the next chapter in
526 * the versification.
527 * <br>
528 * For example:<br>
529 * The last verse in the Old Testament is adjacent to:
530 * <ul>
531 * <li>Intro.NT - the New Testament introduction</li>
532 * <li>Matt 0:0 - the book introduction</li>
533 * <li>Matt 1:0 - the chapter introduction</li>
534 * <li>Matt 1:1 - the first verse of Matt</li>
535 * </ul>
536 * Note: the verses can be in any order.
537 *
538 * @param first
539 * The verse to compare to
540 * @param second
541 * The verse to compare to
542 * @return true or false ...
543 */
544 public boolean isAdjacentChapter(Verse first, Verse second) {
545 Verse before = min(first, second);
546 Verse after = max(first, second);
547 if (isSameBook(first, second)) {
548 return after.getChapter() - before.getChapter() == 1;
549 }
550 // The earlier verse has to be the last chapter
551 return isAdjacentBook(before, after)
552 && getLastChapter(before.getBook()) == before.getChapter()
553 && after.getChapter() <= 1;
554 }
555
556 /**
557 * Two verses are in the same book
558 * when they have the same book.
559 *
560 * @param first
561 * The verse to compare to
562 * @param second
563 * The verse to compare to
564 * @return true or false ...
565 */
566 public boolean isSameBook(Verse first, Verse second) {
567 return first.getBook() == second.getBook();
568 }
569
570 /**
571 * Two verses are in adjacent books if one book
572 * follows the other in this versification.
573 * Note: the verses can be in any order.
574 *
575 * @param first
576 * The verse to compare to
577 * @param second
578 * The verse to compare to
579 * @return true or false ...
580 */
581 public boolean isAdjacentBook(Verse first, Verse second) {
582 return Math.abs(bookList.getOrdinal(second.getBook()) - bookList.getOrdinal(first.getBook())) == 1;
583 }
584
585 /**
586 * Is this verse adjacent to another verse
587 *
588 * @param first
589 * The first verse in the comparison
590 * @param second
591 * The second verse in the comparison
592 * @return true if the verses are adjacent.
593 */
594 public boolean isAdjacentVerse(Verse first, Verse second) {
595 Verse before = min(first, second);
596 Verse after = max(first, second);
597 if (isSameChapter(first, second)) {
598 return after.getVerse() - before.getVerse() == 1;
599 }
600 // The earlier verse has to be the last verse in the chapter
601 return isAdjacentChapter(before, after)
602 && getLastVerse(before.getBook(), before.getChapter()) == before.getVerse()
603 && after.getVerse() <= 1;
604 }
605
606 /**
607 * How many verses are there in between the 2 Verses. The answer is -ve if
608 * start is bigger than end. The answer is inclusive of start and exclusive
609 * of end, so that <code>distance(gen11, gen12) == 1</code>
610 *
611 * @param start
612 * The first Verse in the range
613 * @param end The last Verse in the range
614 * @return The count of verses between this and that.
615 */
616 public int distance(Verse start, Verse end) {
617 return end.getOrdinal() - start.getOrdinal();
618 }
619
620 /**
621 * Determine the earlier of the two verses.
622 * If first == second then return first.
623 *
624 * @param first the first verse to compare
625 * @param second the second verse to compare
626 * @return The earlier of the two verses
627 */
628 public Verse min(Verse first, Verse second) {
629 return first.getOrdinal() <= second.getOrdinal() ? first : second;
630 }
631
632 /**
633 * Determine the later of the two verses.
634 * If first == second then return first.
635 *
636 * @param first the first verse to compare
637 * @param second the second verse to compare
638 * @return The later of the two verses
639 */
640 public Verse max(Verse first, Verse second) {
641 return first.getOrdinal() > second.getOrdinal() ? first : second;
642 }
643
644 /**
645 * Get the verse n down from here this Verse.
646 *
647 * @param verse
648 * The verse to use as a start
649 * @param n
650 * The number to count down by
651 * @return The new Verse
652 */
653 public Verse subtract(Verse verse, int n) {
654 int newVerse = verse.getVerse() - n;
655 // Try the simple case of the verse being in the same chapter
656 if (newVerse >= 0) {
657 return new Verse(verse.getVersification(), verse.getBook(), verse.getChapter(), newVerse);
658 }
659 return decodeOrdinal(verse.getOrdinal() - n);
660 }
661
662 /**
663 * Get the verse that is a verses on from the one we've got.
664 *
665 * @param verse
666 * The verse to use as a start
667 * @return The new verse or null if there is no next verse
668 */
669 public Verse next(Verse verse) {
670 // Cannot increment past the end.
671 if (verse.getOrdinal() == ntMaxOrdinal) {
672 return null;
673 }
674
675 BibleBook nextBook = verse.getBook();
676 int nextChapter = verse.getChapter();
677 int nextVerse = verse.getVerse() + 1;
678 if (nextVerse > getLastVerse(nextBook, nextChapter)) {
679 // Go to an introduction.
680 nextVerse = 0;
681 // of the next chapter
682 nextChapter += 1;
683 // check to see that the chapter is valid for the book
684 if (nextChapter > getLastChapter(nextBook)) {
685 // To to an introduction
686 nextChapter = 0;
687 // of the next book
688 nextBook = bookList.getNextBook(verse.getBook());
689 }
690 }
691
692 // nextBook is null when we try to increment past the last verse
693 // The test at the beginning is designed to prevent that
694 if (nextBook == null) {
695 assert false;
696 return null;
697 }
698
699 return new Verse(this, nextBook, nextChapter, nextVerse);
700 }
701
702 /**
703 * Get the verse that is a few verses on from the one we've got.
704 *
705 * @param verse
706 * The verse to use as a start
707 * @param n
708 * the number of verses later than the one we're one
709 * @return The new verse
710 */
711 public Verse add(Verse verse, int n) {
712 int newVerse = verse.getVerse() + n;
713 // Try the simple case of the verse being in the same chapter
714 if (newVerse <= getLastVerse(verse.getBook(), verse.getChapter())) {
715 return new Verse(verse.getVersification(), verse.getBook(), verse.getChapter(), newVerse);
716 }
717 return decodeOrdinal(verse.getOrdinal() + n);
718 }
719
720 /**
721 * The number of chapters between two verses includes
722 * the chapters of the two verses and everything in between.
723 *
724 * @param start
725 * The first Verse in the range
726 * @param end The last Verse in the range
727 * @return The number of chapters. Always >= 1.
728 */
729 public int getChapterCount(Verse start, Verse end) {
730 BibleBook startBook = start.getBook();
731 int startChap = start.getChapter();
732 BibleBook endBook = end.getBook();
733 int endChap = end.getChapter();
734
735 if (startBook == endBook) {
736 return endChap - startChap + 1;
737 }
738
739 // So we are going to have to count up chapters from start to end
740 int total = getLastChapter(startBook) - startChap;
741 startBook = bookList.getNextBook(startBook);
742 endBook = bookList.getPreviousBook(endBook);
743 for (BibleBook b = startBook; b != endBook; b = bookList.getNextBook(b)) {
744 total += getLastChapter(b);
745 }
746 total += endChap;
747
748 return total;
749 }
750
751 /**
752 * The maximum number of verses in the Bible, including module, testament, book and chapter introductions.
753 *
754 * @return the number of addressable verses in this versification.
755 */
756 public int maximumOrdinal() {
757 // This is the same as the last ordinal in the Reference System.
758 return ntMaxOrdinal;
759 }
760
761 /**
762 * Where does this verse come in the Bible. The value that this returns should be treated as opaque, useful for a bit set.
763 * The introductions to the Book, OT/NT Testaments, Bible books and chapters are included here.
764 * <ul>
765 * <li>0 - INTRO_BIBLE 0:0 - The Book introduction</li>
766 * <li>1 - INTRO_OT 0:0 - The OT Testament introduction</li>
767 * <li>2 - Gen 0:0 - The introduction to the book of Genesis</li>
768 * <li>3 - Gen 1:0 - The introduction to Genesis chapter 1</li>
769 * <li>4 - Gen 1:1</li>
770 * <li>...</li>
771 * <li>35 - Gen 1:31</li>
772 * <li>36 - Gen 2:0 - The introduction to Genesis chapter 2</li>
773 * <li>37 - Gen 2:1</li>
774 * <li>...</li>
775 * <li>n - last verse in the OT</li>
776 * <li>n + 1 - INTRO_NT, 0, 0 - The New Testament introduction</li>
777 * <li>n + 2 - Matt 0:0 - The introduction to Matt</li>
778 * <li>n + 3 - Matt 1:0 - The introduction to Matt 1</li>
779 * <li>n + 4 - Matt 1:1</li>
780 * <li>...</li>
781 * </ul>
782 *
783 * If the verse is not in this versification, return 0.
784 *
785 * @param verse
786 * The verse to convert
787 * @return The ordinal number of verses
788 */
789 public int getOrdinal(Verse verse) {
790 try {
791 return chapterStarts[bookList.getOrdinal(verse.getBook())][verse.getChapter()] + verse.getVerse();
792 } catch (ArrayIndexOutOfBoundsException e) {
793 return 0;
794 }
795 }
796
797 /**
798 * Determine the ordinal value for this versification given the
799 * ordinal value in a testament. If the ordinal is out of bounds it
800 * is constrained to be within the boundaries of the testament.
801 * This unwinds getTestamentOrdinal.
802 *
803 * @param testament the testament in which the ordinal value pertains
804 * @param testamentOrdinal the ordinal value within the testament
805 * @return the ordinal value for the versification as a whole
806 */
807 public int getOrdinal(Testament testament, int testamentOrdinal) {
808 int ordinal = testamentOrdinal >= 0 ? testamentOrdinal : 0;
809 if (Testament.NEW == testament) {
810 ordinal = otMaxOrdinal + testamentOrdinal;
811 return ordinal <= ntMaxOrdinal ? ordinal : ntMaxOrdinal;
812 }
813 return ordinal <= otMaxOrdinal ? ordinal : otMaxOrdinal;
814 }
815
816 /**
817 * Where does this verse come in the Bible. The value that this returns should be treated as opaque, useful for a bit set.
818 * The introductions to the Book, OT/NT Testaments, Bible books and chapters are included here.
819 * <ul>
820 * <li>0 - INTRO_BIBLE 0:0 - The Book introduction</li>
821 * <li>1 - INTRO_OT 0:0 - The OT Testament introduction</li>
822 * <li>2 - Gen 0:0 - The introduction to the book of Genesis</li>
823 * <li>3 - Gen 1:0 - The introduction to Genesis chapter 1</li>
824 * <li>4 - Gen 1:1</li>
825 * <li>...</li>
826 * <li>35 - Gen 1:31</li>
827 * <li>36 - Gen 2:0 - The introduction to Genesis chapter 2</li>
828 * <li>37 - Genesis 2:1</li>
829 * <li>...</li>
830 * <li>n - last verse in the OT</li>
831 * <li>0 - INTRO_NT, 0, 0 - The New Testament introduction</li>
832 * <li>1 - Matt 0:0 - The introduction to Matt</li>
833 * <li>2 - Matt 1:0 - The introduction to Matt 1</li>
834 * <li>3 - Matt 1:1</li>
835 * <li>...</li>
836 * </ul>
837 *
838 * @param ordinal
839 * The ordinal number of the verse to convert
840 * @return The ordinal number of the Verse within its Testament
841 */
842 public int getTestamentOrdinal(int ordinal) {
843 int ntOrdinal = otMaxOrdinal + 1;
844 if (ordinal >= ntOrdinal) {
845 return ordinal - ntOrdinal + 1;
846 }
847 return ordinal;
848 }
849
850 /**
851 * Get the testament of a given verse
852 * @param ordinal the ordinal position of the verse in the whole Bible
853 * @return the testament in which that verse is found
854 */
855 public Testament getTestament(int ordinal) {
856 if (ordinal > otMaxOrdinal) {
857 // This is an NT verse
858 return Testament.NEW;
859 }
860 // This is an OT verse
861 return Testament.OLD;
862 }
863
864 /**
865 * Give the count of verses in the testament or the whole Bible.
866 *
867 * @param testament The testament to count. If null, then all testaments.
868 * @return the number of verses in the testament
869 */
870 public int getCount(Testament testament) {
871 int total = ntMaxOrdinal + 1;
872 if (testament == null) {
873 return total;
874 }
875
876 int otCount = otMaxOrdinal + 1;
877 if (testament == Testament.OLD) {
878 return otCount;
879 }
880
881 return total - otCount;
882 }
883
884 /**
885 * Where does this verse come in the Bible. This will unwind the value returned by getOrdinal(Verse).
886 * If the ordinal value is less than 0 or greater than the last verse in this Versification,
887 * then constrain it to the first or last verse in this Versification.
888 *
889 * @param ordinal
890 * The ordinal number of the verse
891 * @return A Verse
892 */
893 public Verse decodeOrdinal(int ordinal) {
894 int ord = ordinal;
895
896 if (ord < 0) {
897 ord = 0;
898 } else if (ord > ntMaxOrdinal) {
899 ord = ntMaxOrdinal;
900 }
901
902 // Handle three special cases
903 // Book/Module introduction
904 if (ord == 0) {
905 return new Verse(this, BibleBook.INTRO_BIBLE, 0, 0);
906 }
907
908 // OT introduction
909 if (ord == 1) {
910 return new Verse(this, BibleBook.INTRO_OT, 0, 0);
911 }
912
913 // NT introduction
914 if (ord == otMaxOrdinal + 1) {
915 return new Verse(this, BibleBook.INTRO_NT, 0, 0);
916 }
917
918 // To find the book, do a binary search in chapterStarts against chapter 0
919 int low = 0;
920 int high = chapterStarts.length;
921 int match = -1;
922
923 while (high - low > 1) {
924 // use >>> to keep mid always in range
925 int mid = (low + high) >>> 1;
926
927 // Compare the for the item at "mid"
928 int cmp = chapterStarts[mid][0] - ord;
929 if (cmp < 0) {
930 low = mid;
931 } else if (cmp > 0) {
932 high = mid;
933 } else {
934 match = mid;
935 break;
936 }
937 }
938
939 // If we didn't have an exact match then use the low value
940 int bookIndex = match >= 0 ? match : low;
941 BibleBook book = bookList.getBook(bookIndex);
942
943 // To find the chapter, do a binary search in chapterStarts against bookIndex
944 low = 0;
945 high = chapterStarts[bookIndex].length;
946 match = -1;
947
948 while (high - low > 1) {
949 // use >>> to keep mid always in range
950 int mid = (low + high) >>> 1;
951
952 // Compare the for the item at "mid"
953 int cmp = chapterStarts[bookIndex][mid] - ord;
954 if (cmp < 0) {
955 low = mid;
956 } else if (cmp > 0) {
957 high = mid;
958 } else {
959 match = mid;
960 break;
961 }
962 }
963
964 // If we didn't have an exact match then use the low value
965 int chapterIndex = match >= 0 ? match : low;
966 int verse = chapterIndex == 0 ? 0 : ord - chapterStarts[bookIndex][chapterIndex];
967 return new Verse(this, book, chapterIndex, verse);
968 }
969
970 /**
971 * Does the following represent a real verse?. It is code like this that
972 * makes me wonder if I18 is done well/worth doing. All this code does is
973 * check if the numbers are valid, but the exception handling code is huge
974 * :(
975 *
976 * @param book
977 * The book part of the reference.
978 * @param chapter
979 * The chapter part of the reference.
980 * @param verse
981 * The verse part of the reference.
982 * @exception NoSuchVerseException
983 * If the reference is illegal
984 */
985 public void validate(BibleBook book, int chapter, int verse) throws NoSuchVerseException {
986 validate(book, chapter, verse, false);
987 }
988
989 /**
990 * Does the following represent a real verse?. It is code like this that
991 * makes me wonder if I18 is done well/worth doing. All this code does is
992 * check if the numbers are valid, but the exception handling code is huge
993 * :(
994 *
995 * @param book
996 * The book part of the reference.
997 * @param chapter
998 * The chapter part of the reference.
999 * @param verse
1000 * The verse part of the reference.
1001 * @param silent
1002 * true to indicate we do not want to throw an exception
1003 * @return true if validation was succesful
1004 * @exception NoSuchVerseException
1005 * If the reference is illegal and silent was false
1006 */
1007 public boolean validate(BibleBook book, int chapter, int verse, boolean silent) throws NoSuchVerseException {
1008 // Check the book
1009 if (book == null) {
1010 if (silent) {
1011 return false;
1012 }
1013 // TRANSLATOR: The user did not supply a book for a verse reference.
1014 throw new NoSuchVerseException(JSOtherMsg.lookupText("Book must not be null"));
1015 }
1016
1017 // Check the chapter
1018 int maxChapter = getLastChapter(book);
1019 if (chapter < 0 || chapter > maxChapter) {
1020 if (silent) {
1021 return false;
1022 }
1023 // TRANSLATOR: The user supplied a chapter that was out of bounds. This tells them what is allowed.
1024 // {0} is the lowest value that is allowed. This is always 0.
1025 // {1,number,integer} is the place holder for the highest chapter number in the book. The format is special in that it will present it in the user's preferred format.
1026 // {2} is a placeholder for the Bible book name.
1027 // {3,number,integer} is a placeholder for the chapter number that the user gave.
1028 throw new NoSuchVerseException(JSMsg.gettext("Chapter should be between {0} and {1,number,integer} for {2} (given {3,number,integer}).",
1029 Integer.valueOf(0), Integer.valueOf(maxChapter), getPreferredName(book), Integer.valueOf(chapter)
1030 ));
1031 }
1032
1033 // Check the verse
1034 int maxVerse = getLastVerse(book, chapter);
1035 if (verse < 0 || verse > maxVerse) {
1036 if (silent) {
1037 return false;
1038 }
1039 // TRANSLATOR: The user supplied a verse number that was out of bounds. This tells them what is allowed.
1040 // {0} is the lowest value that is allowed. This is always 0.
1041 // {1,number,integer} is the place holder for the highest verse number in the chapter. The format is special in that it will present it in the user's preferred format.
1042 // {2} is a placeholder for the Bible book name.
1043 // {3,number,integer} is a placeholder for the chapter number that the user gave.
1044 // {4,number,integer} is a placeholder for the verse number that the user gave.
1045 throw new NoSuchVerseException(JSMsg.gettext("Verse should be between {0} and {1,number,integer} for {2} {3,number,integer} (given {4,number,integer}).",
1046 Integer.valueOf(0), Integer.valueOf(maxVerse), getPreferredName(book), Integer.valueOf(chapter), Integer.valueOf(verse)
1047 ));
1048 }
1049 return true;
1050 }
1051
1052 /**
1053 * Fix up these verses so that they are as valid a possible. This is
1054 * currently done so that we can say "Gen 1:1" + 31 = "Gen 1:32" and
1055 * "Gen 1:32".patch() is "Gen 2:1".
1056 * <p>
1057 * There is another patch system that allows us to use large numbers to mean
1058 * "the end of" so "Gen 1:32".otherPatch() gives "Gen 1:31". This could be
1059 * useful to allow the user to enter things like "Gen 1:99" meaning the end
1060 * of the chapter. Or "Isa 99:1" to mean the last chapter in Isaiah verse 1
1061 * or even "Rev 99:99" to mean the last verse in the Bible.
1062 * <p>
1063 * However I have not implemented this because I've used a different
1064 * convention: "Gen 1:$" (OLB compatible) or "Gen 1:ff" (common commentary
1065 * usage) to mean the end of the chapter - So the functionality is there
1066 * anyway.
1067 * <p>
1068 * I think that getting into the habit of typing "Gen 1:99" is bad. It could
1069 * be the source of surprises "Psa 119:99" is not what you'd might expect,
1070 * and neither is "Psa 99:1" is you wanted the last chapter in Psalms -
1071 * expecting us to type "Psa 999:1" seems like we're getting silly.
1072 * <p>
1073 * However despite this maybe we should provide the functionality anyway.
1074 *
1075 * @param book the book to obtain
1076 * @param chapter the supposed chapter
1077 * @param verse the supposed verse
1078 * @return The resultant verse.
1079 */
1080 public Verse patch(BibleBook book, int chapter, int verse) {
1081 BibleBook patchedBook = book;
1082 int patchedChapter = chapter;
1083 int patchedVerse = verse;
1084
1085 // If the book is null,
1086 // then patch to the first book in the reference system
1087 if (patchedBook == null) {
1088 patchedBook = bookList.getFirstBook();
1089 }
1090 // If they are too small
1091 if (patchedChapter < 0) {
1092 patchedChapter = 0;
1093 }
1094 if (patchedVerse < 0) {
1095 patchedVerse = 0;
1096 }
1097
1098 // Goal is to start in the current book and go forward that number of chapters
1099 // which might cause one to land in a later book.
1100 // For each book, the chapters number from 0 to n, where n is the last chapter number.
1101 // So if we want Genesis 53, then that would be 3 chapters into Exodus,
1102 // which would be chapter 2.
1103 while (patchedBook != null && patchedChapter > getLastChapter(patchedBook)) {
1104 patchedChapter -= getLastChapter(patchedBook) + 1;
1105 patchedBook = bookList.getNextBook(patchedBook);
1106 }
1107
1108 // At this point we have a valid chapter.
1109 // Now we do the same for the verses.
1110 // For each book, the chapters number from 0 to n, where n is the last chapter number.
1111 // So if we want Genesis 49:36, then that would be 3 verses into Genesis 50,
1112 // which would be verse 50:2.
1113 while (patchedBook != null && patchedVerse > getLastVerse(patchedBook, patchedChapter)) {
1114 patchedVerse -= getLastVerse(patchedBook, patchedChapter) + 1;
1115 patchedChapter += 1;
1116
1117 if (patchedChapter > getLastChapter(patchedBook)) {
1118 patchedChapter -= getLastChapter(patchedBook) + 1;
1119 patchedBook = bookList.getNextBook(patchedBook);
1120 }
1121 }
1122
1123 // If we have gone beyond the last book
1124 // then return the last chapter and verse in the last book
1125 if (patchedBook == null) {
1126 patchedBook = bookList.getLastBook();
1127 patchedChapter = getLastChapter(patchedBook);
1128 patchedVerse = getLastVerse(patchedBook, patchedChapter);
1129 }
1130
1131 return new Verse(this, patchedBook, patchedChapter, patchedVerse);
1132 }
1133
1134 public static void dump(PrintStream out, String name, BibleBookList bookList, int[][] array) {
1135 String vstr1 = "";
1136 String vstr2 = "";
1137 int count = 0;
1138 out.println(" private final int[][] " + name + " =");
1139 out.println(" {");
1140 // Output an array just like lastVerse, indexed by book and chapter,
1141 // that accumulates verse counts for offsets,
1142 // having a sentinel at the end.
1143 int bookCount = array.length;
1144 for (int bookIndex = 0; bookIndex < bookCount; bookIndex++) {
1145 count = 0;
1146 out.print(" // ");
1147 if (bookIndex < bookList.getBookCount()) {
1148 BibleBook book = bookList.getBook(bookIndex);
1149 out.println(book.getOSIS());
1150 } else {
1151 out.println("Sentinel");
1152 }
1153 out.print(" { ");
1154
1155 int numChapters = array[bookIndex].length;
1156 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
1157
1158 // Pretty print with 10 items per line
1159 if (count++ % 10 == 0) {
1160 out.println();
1161 out.print(" ");
1162 }
1163
1164 // Output the offset for the chapter introduction
1165 // This is referenced with a verse number of 0
1166 vstr1 = " " + array[bookIndex][chapterIndex];
1167 vstr2 = vstr1.substring(vstr1.length() - 5);
1168 out.print(vstr2 + ", ");
1169 }
1170 out.println();
1171 out.println(" },");
1172 }
1173 out.println(" };");
1174 }
1175
1176 public static void optimize(PrintStream out, BibleBookList bookList, int[][] lastVerse) {
1177 String vstr1 = "";
1178 String vstr2 = "";
1179 int count = 0;
1180 int ordinal = 0;
1181 out.println(" private final int[][] chapterStarts =");
1182 out.println(" {");
1183 // Output an array just like lastVerse, indexed by book and chapter,
1184 // that accumulates verse counts for offsets,
1185 // having a sentinel at the end.
1186 int bookIndex = 0;
1187 int ntStartOrdinal = 0;
1188 for (BibleBook book = bookList.getBook(0); book != null; book = bookList.getNextBook(book)) {
1189 count = 0;
1190 out.print(" // ");
1191 out.println(book.getOSIS());
1192 out.print(" { ");
1193
1194 // Remember where the NT Starts
1195 if (book == BibleBook.INTRO_NT) {
1196 ntStartOrdinal = ordinal;
1197 }
1198
1199 int numChapters = lastVerse[bookIndex].length;
1200 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
1201
1202 // Pretty print with 10 items per line
1203 if (count++ % 10 == 0) {
1204 out.println();
1205 out.print(" ");
1206 }
1207
1208 // Output the offset for the chapter introduction
1209 // This is referenced with a verse number of 0
1210 vstr1 = " " + ordinal;
1211 vstr2 = vstr1.substring(vstr1.length() - 5);
1212 out.print(vstr2 + ", ");
1213 // Set ordinal to the start of the next chapter or book introduction
1214 int versesInChapter = lastVerse[bookIndex][chapterIndex] + 1;
1215 ordinal += versesInChapter;
1216 }
1217 out.println();
1218 out.println(" },");
1219 bookIndex++;
1220 }
1221
1222 // Output a sentinel value:
1223 // It is a book of one chapter starting with what would be the ordinal of the next chapter's introduction.
1224 vstr1 = " " + ordinal;
1225 vstr2 = vstr1.substring(vstr1.length() - 5);
1226 out.println(" // Sentinel");
1227 out.println(" { ");
1228 out.println(" " + vstr2 + ", ");
1229 out.println(" },");
1230 out.println(" };");
1231 out.println();
1232 out.println(" /** The last ordinal number of the Old Testament */");
1233 out.println(" private int otMaxOrdinal = " + (ntStartOrdinal - 1) + ";");
1234 out.println(" /** The last ordinal number of the New Testament and the maximum ordinal number of this Reference System */");
1235 out.println(" private int ntMaxOrdinal = " + (ordinal - 1) + ";");
1236 }
1237
1238
1239 /** The OSIS name of the reference system. */
1240 private String name;
1241
1242 /** The ordered list of books in this versification. */
1243 private BibleBookList bookList;
1244
1245 /** The last ordinal number of the Old Testament */
1246 private int otMaxOrdinal;
1247
1248 /** The last ordinal number of the New Testament and the maximum ordinal number of this Reference System */
1249 private int ntMaxOrdinal;
1250
1251 /** Constant for the max verse number in each chapter */
1252 private int[][] lastVerse;
1253
1254 /**
1255 * Constant for the ordinal number of the first verse in each chapter.
1256 */
1257 private int[][] chapterStarts;
1258
1259 /**
1260 * Serialization ID
1261 */
1262 private static final long serialVersionUID = -6226916242596368765L;
1263}
1264