| 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 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: 2012
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: org.eclipse.jdt.ui.prefs 1178 2006-11-06 12:48:02Z dmsmith $
21 */
22 package org.crosswire.jsword.versification;
23
24 import java.io.PrintStream;
25 import java.io.Serializable;
26
27 import org.crosswire.jsword.JSMsg;
28 import org.crosswire.jsword.JSOtherMsg;
29 import org.crosswire.jsword.book.ReferenceSystem;
30 import org.crosswire.jsword.passage.NoSuchVerseException;
31 import org.crosswire.jsword.passage.Verse;
32 import org.crosswire.jsword.passage.VerseRange;
33
34 /**
35 * A named Versification defines
36 * the order of BibleBooks by Testament,
37 * the number of chapters in each BibleBook,
38 * the number of verses in each chapter.
39 *
40 * @see gnu.lgpl.License for license details.<br>
41 * The copyright to this program is held by it's authors.
42 * @author DM Smith [dmsmith555 at yahoo dot com]
43 */
44 public class Versification implements ReferenceSystem, Serializable {
45 public Versification() {
46 }
47
48 /**
49 * Construct a Versification.
50 *
51 * @param name
52 * The name of this reference system
53 * @param booksOT
54 * An ordered list of books in this reference system. The list
55 * should not include INTRO_BIBLE, or INTRO_OT.
56 * @param booksNT
57 * An ordered list of books in this reference system. The list
58 * should not include INTRO_NT.
59 * @param lastVerseOT
60 * For each book in booksOT, this has an array with one entry for
61 * each chapter whose value is the highest numbered verse in that
62 * chapter. Do not include chapter 0.
63 * @param lastVerseNT
64 * For each book in booksNT, this has an array with one entry for
65 * each chapter whose value is the highest numbered verse in that
66 * chapter. Do not include chapter 0.
67 */
68 public Versification(String name, BibleBook[] booksOT, BibleBook[] booksNT, int[][] lastVerseOT, int[][] lastVerseNT) {
69 this.name = name;
70
71 // Copy the books into an aggregated BibleBook array
72 // including INTRO_BIBLE and INTRO_OT/INTRO_NT for non-null book lists
73 int bookCount = 1; // Always include the INTRO_BIBLE
74 if (booksOT.length > 0) {
75 bookCount += booksOT.length + 1; // All of the OT books and INTRO_OT
76 }
77
78 int ntStart = bookCount;
79 if (booksNT.length > 0) {
80 bookCount += booksNT.length + 1; // All of the NT books and INTRO_NT
81 }
82 BibleBook[] books = new BibleBook[bookCount];
83 books[0] = BibleBook.INTRO_BIBLE;
84 if (booksOT.length > 0) {
85 books[1] = BibleBook.INTRO_OT;
86 System.arraycopy(booksOT, 0, books, 2, booksOT.length);
87 }
88
89 if (booksNT.length > 0) {
90 books[ntStart] = BibleBook.INTRO_NT;
91 System.arraycopy(booksNT, 0, books, ntStart + 1, booksNT.length);
92 }
93
94 this.bookList = new BibleBookList(books);
95
96 int ordinal = 0;
97
98 // Create an independent copy of lastVerse.
99 this.lastVerse = new int[bookCount][];
100 int bookIndex = 0;
101
102 // Add in the bible introduction
103 int[] chapters = new int[1];
104 chapters[0] = 0;
105 this.lastVerse[bookIndex++] = chapters;
106
107 // Now append the OT info
108 if (lastVerseOT.length > 0) {
109 // Add in the testament intro
110 chapters = new int[1];
111 chapters[0] = 0;
112 this.lastVerse[bookIndex++] = chapters;
113 // then all the testament info
114 for (int i = 0; i < lastVerseOT.length; i++) {
115 int[] src = lastVerseOT[i];
116 // Add one as the location for chapter 0.
117 int[] dest = new int[src.length + 1];
118 this.lastVerse[bookIndex++] = dest;
119 // The last verse of chapter 0 is 0
120 dest[0] = 0;
121 // copy the last verse array for the chapter
122 System.arraycopy(src, 0, dest, 1, src.length);
123 }
124 }
125
126 // Now append the NT info
127 if (lastVerseNT.length > 0) {
128 // Add in the testament intro
129 chapters = new int[1];
130 chapters[0] = 0;
131 this.lastVerse[bookIndex++] = chapters;
132 // then all the testament info
133 for (int i = 0; i < lastVerseNT.length; i++) {
134 int[] src = lastVerseNT[i];
135 // Add one as the location for chapter 0.
136 int[] dest = new int[src.length + 1];
137 this.lastVerse[bookIndex++] = dest;
138 // The last verse of chapter 0 is 0
139 dest[0] = 0;
140 // copy the last verse array for the chapter
141 System.arraycopy(src, 0, dest, 1, src.length);
142 }
143 }
144
145 // Initialize chapterStarts to be a parallel array to lastVerse,
146 // but with chapter starts
147 this.chapterStarts = new int[bookCount][];
148 for (bookIndex = 0; bookIndex < bookCount; bookIndex++) {
149
150 // Remember where the OT ends
151 if (bookList.getBook(bookIndex) == BibleBook.INTRO_NT) {
152 this.otMaxOrdinal = ordinal - 1;
153 }
154
155 // Save off the chapter starts
156 int[] src = this.lastVerse[bookIndex];
157 int numChapters = src.length;
158 int[] dest = new int[numChapters];
159 this.chapterStarts[bookIndex] = dest;
160 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
161 // Save off the chapter start
162 dest[chapterIndex] = ordinal;
163
164 // Set ordinal to the start of the next chapter or book introduction.
165 // The number of verses in each chapter, when including verse 0,
166 // is one more that the largest numbered verse in the chapter.
167 ordinal += src[chapterIndex] + 1;
168 }
169 }
170
171 // Remember where the NT ends
172 this.ntMaxOrdinal = ordinal - 1;
173 // Versification.dump(System.out, this.osisName, this.bookList, this.lastVerse);
174 // Versification.dump(System.out, this.osisName, this.bookList, this.chapterStarts);
175 }
176
177 /**
178 * Get the OSIS name for this Versification.
179 * @return the OSIS name of the Versification
180 */
181 public String getName() {
182 return name;
183 }
184
185 public BibleBookList getBooks() {
186 return bookList;
187 }
188
189 /**
190 * Get the last valid chapter number for a book.
191 *
192 * @param book
193 * The book part of the reference.
194 * @return The last valid chapter number for a book.
195 */
196 public int getLastChapter(BibleBook book) {
197 // This is faster than doing the check explicitly, unless
198 // The exception is actually thrown, then it is a lot slower
199 // I'd like to think that the norm is to get it right
200 try {
201 return lastVerse[bookList.getOrdinal(book)].length - 1;
202 } catch (NullPointerException ex) {
203 return 0;
204 } catch (ArrayIndexOutOfBoundsException ex) {
205 return 0;
206 }
207 }
208
209 /**
210 * Get the last valid verse number for a chapter.
211 *
212 * @param book
213 * The book part of the reference.
214 * @param chapter
215 * The current chapter
216 * @return The last valid verse number for a chapter
217 */
218 public int getLastVerse(BibleBook book, int chapter) {
219 // This is faster than doing the check explicitly, unless
220 // The exception is actually thrown, then it is a lot slower
221 // I'd like to think that the norm is to get it right
222 try {
223 return lastVerse[bookList.getOrdinal(book)][chapter];
224 } catch (NullPointerException ex) {
225 return 0;
226 } catch (ArrayIndexOutOfBoundsException ex) {
227 return 0;
228 }
229 }
230
231 /**
232 * Get a VerseRange encompassing this Versification.
233 */
234 public VerseRange getAllVerses() {
235 BibleBook book = bookList.getFirstBook();
236 int chapter = getLastChapter(book);
237 Verse first = new Verse(book, chapter, getLastVerse(book, chapter));
238 book = bookList.getLastBook();
239 chapter = getLastChapter(book);
240 Verse last = new Verse(book, chapter, getLastVerse(book, chapter));
241 return new VerseRange(this, first, last);
242 }
243
244 /**
245 * Create a new Verse being the last verse in the current book
246 *
247 * @return The last verse in this book
248 */
249 public Verse getLastVerseInBook(Verse verse) {
250 BibleBook book = verse.getBook();
251 int lastchap = getLastChapter(book);
252 int lastverse = getLastVerse(book, lastchap);
253
254 return new Verse(book, lastchap, lastverse);
255 }
256
257 /**
258 * Create a new Verse being the last verse in the current book
259 *
260 * @return The last verse in this book
261 */
262 public Verse getLastVerseInChapter(Verse verse) {
263 BibleBook book = verse.getBook();
264 int chapter = verse.getChapter();
265 int lastverse = getLastVerse(book, chapter);
266
267 return new Verse(book, chapter, lastverse);
268 }
269
270 /**
271 * Create a new Verse being the first verse in the current book
272 *
273 * @return The first verse in this book
274 */
275 public Verse getFirstVerseInBook(Verse verse) {
276 return new Verse(verse.getBook(), 1, 1);
277 }
278
279 /**
280 * Create a new Verse being the first verse in the current book
281 *
282 * @return The first verse in this book
283 */
284 public Verse getFirstVerseInChapter(Verse verse) {
285 return new Verse(verse.getBook(), verse.getChapter(), 1);
286 }
287
288 /**
289 * Is this verse the first in a chapter
290 *
291 * @return true or false ...
292 */
293 public boolean isStartOfChapter(Verse verse) {
294 int v = verse.getVerse();
295 return v == 0;
296 }
297
298 /**
299 * Is this verse the first in a chapter
300 *
301 * @return true or false ...
302 */
303 public boolean isEndOfChapter(Verse verse) {
304 BibleBook b = verse.getBook();
305 int v = verse.getVerse();
306 int c = verse.getChapter();
307 return v == getLastVerse(b, c);
308 }
309
310 /**
311 * Is this verse the first in a chapter
312 *
313 * @return true or false ...
314 */
315 public boolean isStartOfBook(Verse verse) {
316 int v = verse.getVerse();
317 int c = verse.getChapter();
318 return v == 0 && c == 0;
319 }
320
321 /**
322 * Is this verse the last in the book
323 *
324 * @return true or false ...
325 */
326 public boolean isEndOfBook(Verse verse) {
327 BibleBook b = verse.getBook();
328 int v = verse.getVerse();
329 int c = verse.getChapter();
330 return v == getLastVerse(b, c) && c == getLastChapter(b);
331 }
332
333 /**
334 * Is this verse in the same chapter as that one
335 *
336 * @param that
337 * The verse to compare to
338 * @return true or false ...
339 */
340 public boolean isSameChapter(Verse a, Verse that) {
341 return a.getBook() == that.getBook() && a.getChapter() == that.getChapter();
342 }
343
344 /**
345 * Is this verse in the same book as that one
346 *
347 * @param a
348 * The verse to compare to
349 * @param b
350 * The verse to compare to
351 * @return true or false ...
352 */
353 public boolean isSameBook(Verse a, Verse b) {
354 return a.getBook() == b.getBook();
355 }
356
357 /**
358 * Is this verse adjacent to another verse
359 *
360 * @param first
361 * The first verse in the comparison
362 * @param second
363 * The second verse in the comparison
364 * @return true if the verses are adjacent.
365 */
366 public boolean adjacentTo(Verse first, Verse second) {
367 return Math.abs(getOrdinal(second) - getOrdinal(first)) == 1;
368 }
369
370 /**
371 * How many verses are there in between the 2 Verses. The answer is -ve if
372 * start is bigger than end. The answer is inclusive of start and exclusive
373 * of end, so that <code>distance(gen11, gen12) == 1</code>
374 *
375 * @param start
376 * The first Verse in the range
377 * @param end The last Verse in the range
378 * @return The count of verses between this and that.
379 */
380 public int distance(Verse start, Verse end) {
381 return getOrdinal(end) - getOrdinal(start);
382 }
383
384 /**
385 * Determine the earlier of the two verses. If a == b then return a.
386 * @param a the first verse to compare
387 * @param b the second verse to compare
388 * @return The earlier of the two verses
389 */
390 public Verse min(Verse a, Verse b) {
391 return getOrdinal(a) <= getOrdinal(b) ? a : b;
392 }
393
394 /**
395 * Determine the later of the two verses. If a == b then return b.
396 * @param a the first verse to compare
397 * @param b the second verse to compare
398 * @return The later of the two verses
399 */
400 public Verse max(Verse a, Verse b) {
401 return getOrdinal(a) > getOrdinal(b) ? a : b;
402 }
403
404 /**
405 * Get the verse n down from here this Verse.
406 *
407 * @param n
408 * The number to count down by
409 * @return The new Verse
410 */
411 public Verse subtract(Verse verse, int n) {
412 return decodeOrdinal(getOrdinal(verse) - n);
413 }
414
415 /**
416 * Get the verse that is a few verses on from the one we've got.
417 *
418 * @param n
419 * the number of verses later than the one we're one
420 * @return The new verse
421 */
422 public Verse add(Verse verse, int n) {
423 return decodeOrdinal(getOrdinal(verse) + n);
424 }
425
426 /**
427 * How many chapters in this range
428 *
429 * @return The number of chapters. Always >= 1.
430 */
431 public int getChapterCount(Verse start, Verse end) {
432 BibleBook startBook = start.getBook();
433 int startChap = start.getChapter();
434 BibleBook endBook = end.getBook();
435 int endChap = end.getChapter();
436
437 if (startBook == endBook) {
438 return endChap - startChap + 1;
439 }
440
441 // So we are going to have to count up chapters from start to end
442 int total = getLastChapter(startBook) - startChap;
443 BibleBookList books = getBooks();
444 startBook = books.getNextBook(startBook);
445 endBook = books.getPreviousBook(endBook);
446 for (BibleBook b = startBook; b != endBook; b = books.getNextBook(b)) {
447 total += getLastChapter(b);
448 }
449 total += endChap;
450
451 return total;
452 }
453
454 /**
455 * How many books in this range
456 *
457 * @return The number of books. Always >= 1.
458 */
459 public int getBookCount(Verse start, Verse end) {
460 int startBook = getBooks().getOrdinal(start.getBook());
461 int endBook = getBooks().getOrdinal(end.getBook());
462
463 return endBook - startBook + 1;
464 }
465
466 /**
467 * The maximum number of verses in the Bible, including module, testament, book and chapter introductions.
468 *
469 * @return the number of addressable verses in this versification.
470 */
471 public int maximumOrdinal() {
472 // This is the same as the last ordinal in the Reference System.
473 return ntMaxOrdinal;
474 }
475
476 /**
477 * Where does this verse come in the Bible. The value that this returns should be treated as opaque, useful for a bit set.
478 * The introductions to the Book, OT/NT Testaments, Bible books and chapters are included here.
479 * <ul>
480 * <li>0 - INTRO_BIBLE 0:0 - The Book introduction</li>
481 * <li>1 - INTRO_OT 0:0 - The OT Testament introduction</li>
482 * <li>2 - Gen 0:0 - The introduction to the book of Genesis</li>
483 * <li>3 - Gen 1:0 - The introduction to Genesis chapter 1</li>
484 * <li>4 - Gen 1:1</li>
485 * <li>...</li>
486 * <li>35 - Gen 1:31</li>
487 * <li>36 - Gen 2:0 - The introduction to Genesis chapter 2</li>
488 * <li>37 - Gen 2:1</li>
489 * <li>...</li>
490 * <li>n - last verse in the OT</li>
491 * <li>n + 1 - INTRO_NT, 0, 0 - The New Testament introduction</li>
492 * <li>n + 2 - Matt 0:0 - The introduction to Matt</li>
493 * <li>n + 3 - Matt 1:0 - The introduction to Matt 1</li>
494 * <li>n + 4 - Matt 1:1</li>
495 * <li>...</li>
496 * </ul>
497 *
498 * @param verse
499 * The verse to convert
500 * @return The ordinal number of verses
501 */
502 public int getOrdinal(Verse verse) {
503 return chapterStarts[bookList.getOrdinal(verse.getBook())][verse.getChapter()] + verse.getVerse();
504 }
505
506 /**
507 * Where does this verse come in the Bible. The value that this returns should be treated as opaque, useful for a bit set.
508 * The introductions to the Book, OT/NT Testaments, Bible books and chapters are included here.
509 * <ul>
510 * <li>0 - INTRO_BIBLE 0:0 - The Book introduction</li>
511 * <li>1 - INTRO_OT 0:0 - The OT Testament introduction</li>
512 * <li>2 - Gen 0:0 - The introduction to the book of Genesis</li>
513 * <li>3 - Gen 1:0 - The introduction to Genesis chapter 1</li>
514 * <li>4 - Gen 1:1</li>
515 * <li>...</li>
516 * <li>35 - Gen 1:31</li>
517 * <li>36 - Gen 2:0 - The introduction to Genesis chapter 2</li>
518 * <li>37 - Genesis 2:1</li>
519 * <li>...</li>
520 * <li>n - last verse in the OT</li>
521 * <li>0 - INTRO_NT, 0, 0 - The New Testament introduction</li>
522 * <li>1 - Matt 0:0 - The introduction to Matt</li>
523 * <li>2 - Matt 1:0 - The introduction to Matt 1</li>
524 * <li>3 - Matt 1:1</li>
525 * <li>...</li>
526 * </ul>
527 *
528 * @param verse
529 * The verse to convert
530 * @return The ordinal number of verses
531 */
532 public int getTestamentOrdinal(int ordinal) {
533 int nt_ordinal = otMaxOrdinal + 1;
534 if (ordinal >= nt_ordinal) {
535 return ordinal - nt_ordinal + 1;
536 }
537 return ordinal;
538 }
539
540 /**
541 * Get the testament of a given verse
542 */
543 public Testament getTestament(int ordinal) {
544 if (ordinal > otMaxOrdinal) {
545 // This is an NT verse
546 return Testament.NEW;
547 }
548 // This is an OT verse
549 return Testament.OLD;
550 }
551
552 /**
553 * Give the count of verses in the testament or the whole Bible.
554 *
555 * @param testament The testament to count. If null, then all testaments.
556 * @return the number of verses in the testament
557 */
558 public int getCount(Testament testament) {
559 int total = ntMaxOrdinal + 1;
560 if (testament == null) {
561 return total;
562 }
563
564 int otCount = otMaxOrdinal + 1;
565 if (testament == Testament.OLD) {
566 return otCount;
567 }
568
569 return total - otCount;
570 }
571
572 /**
573 * Where does this verse come in the Bible. This will unwind the value returned by getOrdinal(Verse).
574 * If the ordinal value is less than 0 or greater than the last verse in this Versification,
575 * then constrain it to the first or last verse in this Versification.
576 *
577 * @param ordinal
578 * The ordinal number of the verse
579 * @return A Verse
580 */
581 public Verse decodeOrdinal(int ordinal) {
582 int ord = ordinal;
583
584 if (ord < 0) {
585 ord = 0;
586 } else if (ord > ntMaxOrdinal) {
587 ord = ntMaxOrdinal;
588 }
589
590 // Handle three special cases
591 // Book/Module introduction
592 if (ord == 0) {
593 return new Verse(BibleBook.INTRO_BIBLE, 0, 0);
594 }
595
596 // OT introduction
597 if (ord == 1) {
598 return new Verse(BibleBook.INTRO_OT, 0, 0);
599 }
600
601 // NT introduction
602 if (ord == otMaxOrdinal + 1) {
603 return new Verse(BibleBook.INTRO_NT, 0, 0);
604 }
605
606 // To find the book, do a binary search in chapterStarts against chapter 0
607 int low = 0;
608 int high = chapterStarts.length;
609 int match = -1;
610
611 while (high - low > 1) {
612 // use >>> to keep mid always in range
613 int mid = (low + high) >>> 1;
614
615 // Compare the for the item at "mid"
616 int cmp = chapterStarts[mid][0] - ord;
617 if (cmp < 0) {
618 low = mid;
619 } else if (cmp > 0) {
620 high = mid;
621 } else {
622 match = mid;
623 break;
624 }
625 }
626
627 // If we didn't have an exact match then use the low value
628 int bookIndex = match >= 0 ? match : low;
629 BibleBook book = bookList.getBook(bookIndex);
630
631 // To find the chapter, do a binary search in chapterStarts against bookIndex
632 low = 0;
633 high = chapterStarts[bookIndex].length;
634 match = -1;
635
636 while (high - low > 1) {
637 // use >>> to keep mid always in range
638 int mid = (low + high) >>> 1;
639
640 // Compare the for the item at "mid"
641 int cmp = chapterStarts[bookIndex][mid] - ord;
642 if (cmp < 0) {
643 low = mid;
644 } else if (cmp > 0) {
645 high = mid;
646 } else {
647 match = mid;
648 break;
649 }
650 }
651
652 // If we didn't have an exact match then use the low value
653 int chapterIndex = match >= 0 ? match : low;
654 int verse = chapterIndex == 0 ? 0 : ord - chapterStarts[bookIndex][chapterIndex];
655 return new Verse(book, chapterIndex, verse);
656 }
657
658 /**
659 * Does the following represent a real verse?. It is code like this that
660 * makes me wonder if I18 is done well/worth doing. All this code does is
661 * check if the numbers are valid, but the exception handling code is huge
662 * :(
663 *
664 * @param book
665 * The book part of the reference.
666 * @param chapter
667 * The chapter part of the reference.
668 * @param verse
669 * The verse part of the reference.
670 * @exception NoSuchVerseException
671 * If the reference is illegal
672 */
673 public void validate(BibleBook book, int chapter, int verse) throws NoSuchVerseException {
674
675 // Check the book
676 if (book == null) {
677 // TRANSLATOR: The user did not supply a book for a verse reference.
678 throw new NoSuchVerseException(JSOtherMsg.lookupText("Book must not be null"));
679 }
680
681 // Check the chapter
682 int maxChapter = getLastChapter(book);
683 if (chapter < 0 || chapter > maxChapter) {
684 // TRANSLATOR: The user supplied a chapter that was out of bounds. This tells them what is allowed.
685 // {0} is the lowest value that is allowed. This is always 0.
686 // {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.
687 // {2} is a placeholder for the Bible book name.
688 // {3,number,integer} is a placeholder for the chapter number that the user gave.
689 throw new NoSuchVerseException(JSMsg.gettext("Chapter should be between {0} and {1,number,integer} for {2} (given {3,number,integer}).",
690 Integer.valueOf(0), Integer.valueOf(maxChapter), book.getPreferredName(), Integer.valueOf(chapter)
691 ));
692 }
693
694 // Check the verse
695 int maxVerse = getLastVerse(book, chapter);
696 if (verse < 0 || verse > maxVerse) {
697 // TRANSLATOR: The user supplied a verse number that was out of bounds. This tells them what is allowed.
698 // {0} is the lowest value that is allowed. This is always 0.
699 // {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.
700 // {2} is a placeholder for the Bible book name.
701 // {3,number,integer} is a placeholder for the chapter number that the user gave.
702 // {4,number,integer} is a placeholder for the verse number that the user gave.
703 throw new NoSuchVerseException(JSMsg.gettext("Verse should be between {0} and {1,number,integer} for {2} {3,number,integer} (given {4,number,integer}).",
704 Integer.valueOf(0), Integer.valueOf(maxVerse), book.getPreferredName(), Integer.valueOf(chapter), Integer.valueOf(verse)
705 ));
706 }
707 }
708
709 /**
710 * Fix up these verses so that they are as valid a possible. This is
711 * currently done so that we can say "Gen 1:1" + 31 = "Gen 1:32" and
712 * "Gen 1:32".patch() is "Gen 2:1".
713 * <p>
714 * There is another patch system that allows us to use large numbers to mean
715 * "the end of" so "Gen 1:32".otherPatch() gives "Gen 1:31". This could be
716 * useful to allow the user to enter things like "Gen 1:99" meaning the end
717 * of the chapter. Or "Isa 99:1" to mean the last chapter in Isaiah verse 1
718 * or even "Rev 99:99" to mean the last verse in the Bible.
719 * <p>
720 * However I have not implemented this because I've used a different
721 * convention: "Gen 1:$" (OLB compatible) or "Gen 1:ff" (common commentary
722 * usage) to mean the end of the chapter - So the functionality is there
723 * anyway.
724 * <p>
725 * I think that getting into the habit of typing "Gen 1:99" is bad. It could
726 * be the source of surprises "Psa 119:99" is not what you'd might expect,
727 * and neither is "Psa 99:1" is you wanted the last chapter in Psalms -
728 * expecting us to type "Psa 999:1" seems like we're getting silly.
729 * <p>
730 * However despite this maybe we should provide the functionality anyway.
731 *
732 * @param book the book to obtain
733 * @param chapter the supposed chapter
734 * @param verse the supposed verse
735 * @return The resultant verse.
736 */
737 public Verse patch(BibleBook book, int chapter, int verse) {
738 BibleBook patchedBook = book;
739 int patchedChapter = chapter;
740 int patchedVerse = verse;
741
742 // If the book is null,
743 // then patch to the first book in the reference system
744 if (patchedBook == null) {
745 patchedBook = getBooks().getFirstBook();
746 }
747 // If they are too small
748 if (patchedChapter < 0) {
749 patchedChapter = 0;
750 }
751 if (patchedVerse < 0) {
752 patchedVerse = 0;
753 }
754
755 // Goal is to start in the current book and go forward that number of chapters
756 // which might cause one to land in a later book.
757 // For each book, the chapters number from 0 to n, where n is the last chapter number.
758 // So if we want Genesis 53, then that would be 3 chapters into Exodus,
759 // which would be chapter 2.
760 while (patchedChapter > getLastChapter(patchedBook)) {
761 patchedChapter -= getLastChapter(patchedBook) + 1;
762 patchedBook = bookList.getNextBook(patchedBook);
763
764 // If we have gone beyond the last book
765 // then return the last chapter and verse in that book
766 if (patchedBook == null) {
767 patchedBook = getBooks().getLastBook();
768 patchedChapter = getLastChapter(patchedBook);
769 patchedVerse = getLastVerse(patchedBook, patchedChapter);
770 return new Verse(patchedBook, patchedChapter, patchedVerse);
771 }
772 }
773
774 // At this point we have a valid chapter.
775 // Now we do the same for the verses.
776 // For each book, the chapters number from 0 to n, where n is the last chapter number.
777 // So if we want Genesis 49:36, then that would be 3 verses into Genesis 50,
778 // which would be verse 50:2.
779 while (patchedVerse > getLastVerse(patchedBook, patchedChapter)) {
780 patchedVerse -= getLastVerse(patchedBook, patchedChapter) + 1;
781 patchedChapter += 1;
782
783 if (patchedChapter > getLastChapter(patchedBook)) {
784 patchedChapter -= getLastChapter(patchedBook) + 1;
785 patchedBook = bookList.getNextBook(patchedBook);
786
787 // If we have gone beyond the last book
788 // then return the last chapter and verse in that book
789 if (patchedBook == null) {
790 patchedBook = getBooks().getLastBook();
791 patchedChapter = getLastChapter(patchedBook);
792 patchedVerse = getLastVerse(patchedBook, patchedChapter);
793 return new Verse(patchedBook, patchedChapter, patchedVerse);
794 }
795 }
796 }
797
798 return new Verse(patchedBook, patchedChapter, patchedVerse);
799 }
800
801 /** The OSIS name of the reference system. */
802 private String name;
803
804
805 private BibleBookList bookList;
806
807 /** The last ordinal number of the Old Testament */
808 private int otMaxOrdinal;
809
810 /** The last ordinal number of the New Testament and the maximum ordinal number of this Reference System */
811 private int ntMaxOrdinal;
812
813 /** Constant for the max verse number in each chapter */
814 private int[][] lastVerse;
815
816 /**
817 * Constant for the ordinal number of the first verse in each chapter.
818 */
819 private int[][] chapterStarts;
820
821 /**
822 * Serialization ID
823 */
824 private static final long serialVersionUID = -6226916242596368765L;
825
826 public static void dump(PrintStream out, String name, BibleBookList bookList, int[][] array) {
827 String vstr1 = "";
828 String vstr2 = "";
829 int count = 0;
830 out.println(" private final int[][] " + name + " =");
831 out.println(" {");
832 // Output an array just like lastVerse, indexed by book and chapter,
833 // that accumulates verse counts for offsets,
834 // having a sentinel at the end.
835 int bookCount = array.length;
836 for (int bookIndex = 0; bookIndex < bookCount; bookIndex++) {
837 count = 0;
838 out.print(" // ");
839 if (bookIndex < bookList.getBookCount()) {
840 BibleBook book = bookList.getBook(bookIndex);
841 out.println(book.getOSIS());
842 } else {
843 out.println("Sentinel");
844 }
845 out.print(" { ");
846
847 int numChapters = array[bookIndex].length;
848 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
849
850 // Pretty print with 10 items per line
851 if (count++ % 10 == 0) {
852 out.println();
853 out.print(" ");
854 }
855
856 // Output the offset for the chapter introduction
857 // This is referenced with a verse number of 0
858 vstr1 = " " + array[bookIndex][chapterIndex];
859 vstr2 = vstr1.substring(vstr1.length() - 5);
860 out.print(vstr2 + ", ");
861 }
862 out.println();
863 out.println(" },");
864 }
865 out.println(" };");
866 }
867
868 public static void optimize(PrintStream out, BibleBookList bookList, int[][] lastVerse) {
869 String vstr1 = "";
870 String vstr2 = "";
871 int count = 0;
872 int ordinal = 0;
873 out.println(" private final int[][] chapterStarts =");
874 out.println(" {");
875 // Output an array just like lastVerse, indexed by book and chapter,
876 // that accumulates verse counts for offsets,
877 // having a sentinel at the end.
878 int bookIndex = 0;
879 int ntStartOrdinal = 0;
880 for (BibleBook book = bookList.getBook(0); book != null; book = bookList.getNextBook(book)) {
881 count = 0;
882 out.print(" // ");
883 out.println(book.getOSIS());
884 out.print(" { ");
885
886 // Remember where the NT Starts
887 if (book == BibleBook.INTRO_NT) {
888 ntStartOrdinal = ordinal;
889 }
890
891 int numChapters = lastVerse[bookIndex].length;
892 for (int chapterIndex = 0; chapterIndex < numChapters; chapterIndex++) {
893
894 // Pretty print with 10 items per line
895 if (count++ % 10 == 0) {
896 out.println();
897 out.print(" ");
898 }
899
900 // Output the offset for the chapter introduction
901 // This is referenced with a verse number of 0
902 vstr1 = " " + ordinal;
903 vstr2 = vstr1.substring(vstr1.length() - 5);
904 out.print(vstr2 + ", ");
905 // Set ordinal to the start of the next chapter or book introduction
906 int versesInChapter = lastVerse[bookIndex][chapterIndex] + 1;
907 ordinal += versesInChapter;
908 }
909 out.println();
910 out.println(" },");
911 bookIndex++;
912 }
913
914 // Output a sentinel value:
915 // It is a book of one chapter starting with what would be the ordinal of the next chapter's introduction.
916 vstr1 = " " + ordinal;
917 vstr2 = vstr1.substring(vstr1.length() - 5);
918 out.println(" // Sentinel");
919 out.println(" { ");
920 out.println(" " + vstr2 + ", ");
921 out.println(" },");
922 out.println(" };");
923 out.println();
924 out.println(" /** The last ordinal number of the Old Testament */");
925 out.println(" private int otMaxOrdinal = " + (ntStartOrdinal - 1) + ";");
926 out.println(" /** The last ordinal number of the New Testament and the maximum ordinal number of this Reference System */");
927 out.println(" private int ntMaxOrdinal = " + (ordinal - 1) + ";");
928 }
929
930 }
931