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