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