Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/eireneh/bible/passage/Verse.java


1   
2   package com.eireneh.bible.passage;
3   
4   import java.io.*;
5   import java.util.StringTokenizer;
6   import java.util.Enumeration;
7   
8   import com.sun.java.util.collections.*;
9   
10  import com.eireneh.util.*;
11  
12  /**
13   * A Passage is a pointer to a single verse. Externally its unique
14   * identifier is a String of the form "Gen 1:1" Internally we use
15   * <code>int[] { book, chapter, verse }</code>
16   *
17   * <p>A Verse is designed to be immutable. This is a necessary from a
18   * collections point of view. A Verse should always be valid, although
19   * some versions may not return any text for verses that they consider to
20   * be mis-translated in some way.</p>
21   *
22   * <p>Optimization information: I spent some time optimizing this class
23   * because it is at the heart of things. My benchmark started st 11.25s.
24   * By taking the int[] and turning it into 3 ints and it took 10.8s.<br />
25   * Cacheing the ordinal number just took the time from 12s to 12s! I guess
26   * that the time and extra memory taken up by the extra int overrode the
27   * time it saved by repeated queries to the same verse. I guess this would
28   * change if we were using a [Ranged|Distinct]Passage instead of a Bitwise
29   * Passage (as in the test). Maybe it would be a good idea to have an
30   * extra class OrdCacheVerse (or something) that gave us the best of both
31   * worlds?<br />
32   * Removing the default initialization of the ints took the time down by
33   * about 0.25s also.
34   * </p>
35   *
36   * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
37   * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
38   * Distribution Licence:<br />
39   * Project B is free software; you can redistribute it
40   * and/or modify it under the terms of the GNU General Public License,
41   * version 2 as published by the Free Software Foundation.<br />
42   * This program is distributed in the hope that it will be useful,
43   * but WITHOUT ANY WARRANTY; without even the implied warranty of
44   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45   * General Public License for more details.<br />
46   * The License is available on the internet
47   * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
48   * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
49   * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
50   * The copyright to this program is held by it's authors.
51   * </font></td></tr></table>
52   * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
53   * @see docs.Licence
54   * @author Joe Walker
55   * @version D9.I9.T7
56   * @stereotype thing
57   */
58  public class Verse implements VerseBase
59  {
60      /**
61       * The default Verse is Genesis 1:1. I didn't want to provide this
62       * constructor however, you are supposed to provide a default ctor
63       * for all beans. For this reason I suggest you don't use it.
64       */
65      public Verse()
66      {
67          original_name = null;
68  
69          book = DEFAULT.book;
70          chapter = DEFAULT.chapter;
71          verse = DEFAULT.verse;
72      }
73  
74      /**
75       * Construct a Verse from a String - something like "Gen 1:1".
76       * in case the user does not want to have their typing 'fixed' by a
77       * meddling patronizing computer. The following initial letters can
78       * not be matched at all - 'bfquvwx'.
79       * @param desc The text string to be validated
80       * @exception NoSuchVerseException If the text can not be understood
81       */
82      public Verse(String desc) throws NoSuchVerseException
83      {
84          this(desc, DEFAULT);
85      }
86  
87      /**
88       * Construct a Ref from a String and a Verse. For example given "2:2"
89       * and a basis of Gen 1:1 the result would be Gen 2:2
90       * @param desc The string describing the verse e.g "2:2"
91       * @param basic The basis by which to understand the desc.
92       * @exception NoSuchVerseException If the reference is illegal
93       */
94      public Verse(String desc, Verse basis) throws NoSuchVerseException
95      {
96          String[] parts = tokenize(desc, VERSE_ALLOWED_DELIMS);
97          original_name = desc;
98  
99          switch (getAccuracy(parts))
100         {
101         case ACCURACY_BOOK_VERSE:
102             if (parts.length == 3) set(parts[0], parts[1], parts[2]);
103             else                   set(parts[0], 1, parts[1]);
104             break;
105 
106         case ACCURACY_BOOK_CHAPTER:
107             set(parts[0], parts[1], 1);
108             break;
109 
110         case ACCURACY_BOOK_ONLY:
111             set(parts[0], 1, 1);
112             break;
113 
114         case ACCURACY_CHAPTER_VERSE:
115             set(basis.getBook(), parts[0], parts[1]);
116             break;
117 
118         case ACCURACY_VERSE_ONLY:
119             set(basis.getBook(), basis.getChapter(), parts[0]);
120             break;
121 
122         case ACCURACY_NONE:
123             set(basis.getBook(), basis.getChapter(), basis.getVerse());
124             break;
125 
126         default:
127             throw new LogicError();
128         }
129     }
130 
131     /**
132      * Create a Verse from book, chapter and verse numbers, throwing up
133      * if the specified Verse does not exist
134      * @param book The book number (Genesis = 1)
135      * @param chapter The chapter number
136      * @param verse The verse number
137      * @exception NoSuchVerseException If the reference is illegal
138      */
139     public Verse(int book, int chapter, int verse) throws NoSuchVerseException
140     {
141         original_name = null;
142         set(book, chapter, verse);
143     }
144 
145     /**
146      * Create a Verse from book, chapter and verse numbers, patching up if
147      * the specified verse does not exist.
148      * <p>The actual value of the boolean is ignored. However for future
149      * proofing you should only use 'true'. Do not use patch_up=false, use
150      * <code>Verse(int, int, int)</code> This so that we can declare this
151      * constructor to not throw an exception. Is there a better way of
152      * doing this?
153      * @param book The book number (Genesis = 1)
154      * @param chapter The chapter number
155      * @param verse The verse number
156      * @param patch_up True to trigger reference fixing
157      */
158     public Verse(int book, int chapter, int verse, boolean patch_up)
159     {
160         if (!patch_up)
161             throw new IllegalArgumentException(PassageUtil.getResource("verse_error_patch"));
162 
163         original_name = null;
164         setAndPatch(book, chapter, verse);
165     }
166 
167     /**
168      * Set a Verse using a Verse Ordinal number - WARNING Do not use this
169      * method unless you really know the dangers of doing so. Ordinals are
170      * not always going to be the same. So you should use a Verse or an
171      * int[3] in preference to an int ordinal whenever possible. Ordinal
172      * numbers are 1 based and not 0 based.
173      * @param ordinal The verse id
174      * @exception NoSuchVerseException If the reference is illegal
175      */
176     public Verse(int ordinal) throws NoSuchVerseException
177     {
178         original_name = null;
179         set(ordinal);
180     }
181 
182     /**
183      * Translate the Passage into a human readable string. This is
184      * simply an alias for getName();
185      * @return The string representation
186      */
187     public String toString()
188     {
189         return getName();
190     }
191 
192     /**
193      * Translate the Passage into a human readable string
194      * @return The string representation
195      */
196     public String getName()
197     {
198         try
199         {
200             if (PassageUtil.isPersistentNaming() && original_name != null)
201             {
202                 return original_name;
203             }
204 
205             // To cope with thing like Jude 2...
206             if (Books.chaptersInBook(book) == 1)
207             {
208                 return Books.getShortBookName(book) + VERSE_PREF_DELIM1 + verse;
209             }
210             else
211             {
212                 return Books.getShortBookName(book) + VERSE_PREF_DELIM1 + chapter + VERSE_PREF_DELIM2 + verse;
213             }
214         }
215         catch (Exception ex)
216         {
217             throw new LogicError(ex);
218         }
219     }
220 
221     /**
222      * Translate the Passage into a human readable string
223      * @param base The verse to use to cut down unnecessary output.
224      * @return The string representation
225      */
226     public String getName(Verse base)
227     {
228         if (base == null) return getName();
229 
230         try
231         {
232             if (PassageUtil.isPersistentNaming() && original_name != null)
233             {
234                 return original_name;
235             }
236 
237             // To cope with thing like Jude 2...
238             if (Books.chaptersInBook(book) == 1)
239             {
240                 if (base.book != book)
241                     return Books.getShortBookName(book) + VERSE_PREF_DELIM1 + verse;
242 
243                 return ""+verse;
244             }
245             else
246             {
247                 if (base.book != book)
248                     return Books.getShortBookName(book) + VERSE_PREF_DELIM1 + chapter + VERSE_PREF_DELIM2 + verse;
249 
250                 if (base.chapter != chapter)
251                     return chapter + VERSE_PREF_DELIM2 + verse;
252 
253                 return ""+verse;
254             }
255         }
256         catch (Exception ex)
257         {
258             throw new LogicError(ex);
259         }
260     }
261 
262     /**
263      * Get a copy of ourselves. Points to note:
264      *   Call clone() not new() on member Objects, and on us.
265      *   Do not use Copy Constructors! - they do not inherit well.
266      *   Think about this needing to be synchronized
267      *   If this is not cloneable then writing cloneable children is harder
268      * @return A complete copy of ourselves
269      * @exception CloneNotSupportedException We don't do this but our kids might
270      */
271     public Object clone() throws CloneNotSupportedException
272     {
273         Verse copy = (Verse) super.clone();
274 
275         copy.book = book;
276         copy.chapter = chapter;
277         copy.verse = verse;
278         //copy.ord = ord;
279         copy.original_name = original_name;
280 
281         return copy;
282     }
283 
284     /**
285      * Is this Object equal to us. Points to note:
286      *   If you override equals(), you must override hashCode() too.
287      *   If you are doing this it is a good idea to be immutable.
288      * @param obj The thing to test against
289      * @return True/False is we are or are not equal to obj
290      */
291     public boolean equals(Object obj)
292     {
293         // Since this can not be null
294         if (obj == null) return false;
295 
296         // Check that that is the same as this
297         // Don't use instanceOf since that breaks inheritance
298         if (!obj.getClass().equals(this.getClass())) return false;
299 
300         Verse v = (Verse) obj;
301 
302         // The real tests
303         if (v.getBook() != getBook()) return false;
304         if (v.getChapter() != getChapter()) return false;
305         if (v.getVerse() != getVerse()) return false;
306 
307         return true;
308     }
309 
310     /**
311      * This returns the ordinal number of the verse
312      * so <code>new Verse("Rev 22:21").hashCode() = 31104</code>
313      * <p><b>However should should not reply on this being true</b>
314      * @return The hashing number
315      */
316     public int hashCode()
317     {
318         return getOrdinal();
319     }
320 
321     /**
322      * Compare this to a given object
323      * @param obj The thing to compare against
324      * @return 1 means he is earlier than me, -1 means he is later ...
325      */
326     public int compareTo(Object obj)
327     {
328         Verse that = null;
329         if (obj instanceof Verse)   that = (Verse) obj;
330         else                        that = ((VerseRange) obj).getStart();
331 
332         int that_start = that.getOrdinal();
333         int this_start = this.getOrdinal();
334 
335         if (that_start > this_start) return -1;
336         if (that_start < this_start) return 1;
337         return 0;
338     }
339 
340     /**
341      * Is this verse adjacent to another verse
342      * @param obj The thing to compare against
343      * @return 1 means he is earlier than me, -1 means he is later ...
344      */
345     public boolean adjacentTo(Verse that)
346     {
347         return Math.abs(that.getOrdinal() - getOrdinal()) == 1;
348     }
349 
350     /**
351      * How many verses are there in between the 2 Verses.
352      * The answer is -ve if that is bigger than this.
353      * The answer is inclusive of that and exclusive of this, so that
354      * <code>gen11.difference(gen12) == 1</code>
355      * @param that The Verse to compare this to
356      * @return The count of verses between this and that.
357      */
358     public int subtract(Verse that)
359     {
360         return getOrdinal() - that.getOrdinal();
361     }
362 
363     /**
364      * Get the verse n down from here this Verse.
365      * @param n The number to count down by
366      * @return The new Verse
367      */
368     public Verse subtract(int n)
369     {
370         try
371         {
372             int new_ordinal = Math.max(getOrdinal() - n, 1);
373             return new Verse(new_ordinal);
374         }
375         catch (NoSuchVerseException ex)
376         {
377             throw new LogicError(ex);
378         }
379     }
380 
381     /**
382      * Get the verse that is a few verses on from the one
383      * we've got.
384      * @param extra the number of verses later than the one we're one
385      * @return The new verse
386      */
387     public Verse add(int extra)
388     {
389         try
390         {
391             int new_ordinal = Math.min(getOrdinal() + extra, Books.versesInBible());
392             return new Verse(new_ordinal);
393         }
394         catch (NoSuchVerseException ex)
395         {
396             throw new LogicError(ex);
397         }
398     }
399 
400     /**
401      * Return the book that we refer to
402      * @return The book number (Genesis = 1)
403      */
404     public int getBook()
405     {
406         return book;
407     }
408 
409     /**
410      * Return the chapter that we refer to
411      * @return The chapter number
412      */
413     public int getChapter()
414     {
415         return chapter;
416     }
417 
418     /**
419      * Return the verse that we refer to
420      * @return The verse number
421      */
422     public int getVerse()
423     {
424         return verse;
425     }
426 
427     /**
428      * Is this verse the first in a chapter
429      * @return true or false ...
430      */
431     public boolean isStartOfChapter()
432     {
433         return verse == 1;
434     }
435 
436     /**
437      * Is this verse the first in a chapter
438      * @return true or false ...
439      */
440     public boolean isEndOfChapter()
441     {
442         try
443         {
444             return verse == Books.versesInChapter(book, chapter);
445         }
446         catch (NoSuchVerseException ex)
447         {
448             throw new LogicError(ex);
449         }
450     }
451 
452     /**
453      * Is this verse the first in a chapter
454      * @return true or false ...
455      */
456     public boolean isStartOfBook()
457     {
458         return verse == 1 && chapter == 1;
459     }
460 
461     /**
462      * Is this verse the first in a chapter
463      * @return true or false ...
464      */
465     public boolean isEndOfBook()
466     {
467         try
468         {
469             return verse == Books.versesInChapter(book, chapter)
470                    && chapter == Books.chaptersInBook(book);
471         }
472         catch (NoSuchVerseException ex)
473         {
474             throw new LogicError(ex);
475         }
476     }
477 
478     /**
479      * Is this verse in the same chapter as that one
480      * @param that The verse to compate to
481      * @return true or false ...
482      */
483     public boolean isSameChapter(Verse that)
484     {
485         return book == that.book && chapter == that.chapter;
486     }
487 
488     /**
489      * Is this verse in the same book as that one
490      * @param that The verse to compate to
491      * @return true or false ...
492      */
493     public boolean isSameBook(Verse that)
494     {
495         return book == that.book;
496     }
497 
498     /**
499      * Return the verse that we refer to
500      * @return An array of 3 ints 0=book, 1=chapter, 2=verse
501      */
502     public int[] getRefArray()
503     {
504         return new int[] { book, chapter, verse };
505     }
506 
507     /**
508      * Return the verse id that we refer to, where Gen 1:1 = 1, and
509      * Rev 22:21 = 31104
510      * @return The verse number
511      */
512     public int getOrdinal()
513     {
514         //if (ord == -1)
515         {
516             try
517             {
518                 return /*ord =*/ Books.verseOrdinal(book, chapter, verse);
519             }
520             catch (NoSuchVerseException ex)
521             {
522                 // A verse should never be illegal so
523                 log.warning("ref="+book+", "+chapter+", "+verse);
524                 throw new LogicError(ex);
525             }
526         }
527 
528         //return ord;
529     }
530 
531     /**
532      * Does this string exactly define a Verse. For example:<ul>
533      * <li>getAccuracy("Gen") == ACCURACY_BOOK_ONLY;
534      * <li>getAccuracy("Gen 1:1") == ACCURACY_BOOK_VERSE;
535      * <li>getAccuracy("Gen 1") == ACCURACY_BOOK_CHAPTER;
536      * <li>getAccuracy("Jude 1") == ACCURACY_BOOK_VERSE;
537      * <li>getAccuracy("Jude 1:1") == ACCURACY_BOOK_VERSE;
538      * <li>getAccuracy("1:1") == ACCURACY_CHAPTER_VERSE;
539      * <li>getAccuracy("1") == ACCURACY_VERSE_ONLY;
540      * <li>getAccuracy("") == ACCURACY_NONE;
541      * <ul>
542      * @param desc The string to be tested for Rangeness
543      * @return A constant specifing how precise the Verse is.
544      * @exception NoSuchVerseException If the text can not be understood
545      * @see Passage
546      */
547     public static int getAccuracy(String desc) throws NoSuchVerseException
548     {
549         String[] parts = tokenize(desc, VERSE_ALLOWED_DELIMS);
550 
551         return getAccuracy(parts);
552     }
553 
554     /**
555      * Does this string exactly define a Verse. For example:<ul>
556      * <li>getAccuracy("Gen") == ACCURACY_BOOK_ONLY;
557      * <li>getAccuracy("Gen 1:1") == ACCURACY_BOOK_VERSE;
558      * <li>getAccuracy("Gen 1") == ACCURACY_BOOK_CHAPTER;
559      * <li>getAccuracy("Jude 1") == ACCURACY_BOOK_VERSE;
560      * <li>getAccuracy("Jude 1:1") == ACCURACY_BOOK_VERSE;
561      * <li>getAccuracy("1:1") == ACCURACY_CHAPTER_VERSE;
562      * <li>getAccuracy("1") == ACCURACY_VERSE_ONLY;
563      * <li>getAccuracy("") == ACCURACY_NONE;
564      * <ul>
565      * @param desc The string array to be tested for Rangeness
566      * @return A constant specifing how precise the Verse is.
567      * @exception NoSuchVerseException If the text can not be understood
568      * @see Passage
569      */
570     private static int getAccuracy(String[] parts) throws NoSuchVerseException
571     {
572         switch (parts.length)
573         {
574         case 0:
575             return ACCURACY_NONE;
576 
577         case 1:
578             if (Books.isBookName(parts[0])) return ACCURACY_BOOK_ONLY;
579             checkValidChapterOrVerse(parts[0]);
580             return ACCURACY_VERSE_ONLY;
581 
582         case 2:
583             try
584             {
585                 // Does it start with a book?
586                 int book = Books.getBookNumber(parts[0]);
587                 if (Books.chaptersInBook(book) == 1)    return ACCURACY_BOOK_VERSE;
588                 else                                    return ACCURACY_BOOK_CHAPTER;
589             }
590             catch (NoSuchVerseException ex) { }
591             checkValidChapterOrVerse(parts[0]);
592             checkValidChapterOrVerse(parts[1]);
593             return ACCURACY_CHAPTER_VERSE;
594 
595         case 3:
596             Books.getBookNumber(parts[0]);
597             checkValidChapterOrVerse(parts[1]);
598             checkValidChapterOrVerse(parts[2]);
599             return ACCURACY_BOOK_VERSE;
600         }
601 
602         throw new NoSuchVerseException("passg_verse_parts", new Object[] { VERSE_ALLOWED_DELIMS });
603     }
604 
605     /**
606      * Is this text valid in a chapter/verse context
607      * @param text The string to test for validity
608      * @return true or false if the string is or isn't valid
609      */
610     private final static void checkValidChapterOrVerse(String text) throws NoSuchVerseException
611     {
612         if (!isEndMarker(text))
613             parseInt(text);
614     }
615 
616     /**
617      * Return the bigger of the 2 verses. If the verses are equal()
618      * then return Verse a
619      * @param a The first verse to compare
620      * @param v The second verse to compare
621      * @return The bigger of the 2 verses
622      */
623     public final static Verse max(Verse a, Verse b)
624     {
625         if (a.compareTo(b) == -1) return b;
626         else                      return a;
627     }
628 
629     /**
630      * Return the smaller of the 2 verses. If the verses are equal()
631      * then return Verse a
632      * @param a The first verse to compare
633      * @param v The second verse to compare
634      * @return The smaller of the 2 verses
635      */
636     public final static Verse min(Verse a, Verse b)
637     {
638         if (a.compareTo(b) == 1) return b;
639         else                     return a;
640     }
641 
642     /**
643      * Is this string a legal marker for 'to the end of the chapter'
644      * @param text The string to be checked
645      * @return true if this is a legal marker
646      */
647     public static boolean isEndMarker(String text)
648     {
649         if (text.equals(VERSE_END_MARK1)) return true;
650         if (text.equals(VERSE_END_MARK2)) return true;
651 
652         return false;
653     }
654 
655 
656     /**
657      * Create an array of Verses
658      * @return The array of verses that this makes up
659      */
660     public Verse[] toVerseArray()
661     {
662         return new Verse[] { this };
663     }
664 
665     /**
666      * Enumerate over the verse in this verse! This may seem silly,
667      * however is is very useful to be able to treat Verses and Ranges
668      * the same (VerseBase) and this is a common accessor.
669      * @return A verse iterator
670      */
671     public Enumeration verseElements()
672     {
673         return new Enumeration()
674         {
675             private boolean done = false;
676 
677             public boolean hasMoreElements()
678             {
679                 return !done;
680             }
681 
682             public Object nextElement()
683             {
684                 done = true;
685                 return this;
686             }
687         };
688     }
689 
690     /**
691      * Take a string and parse it into an Array of Strings where each
692      * part is likely to be a verse part (book, chapter, verse, ...)
693      * @param command The string to parse.
694      * @param delim A string containing the spacing characters.
695      * @return The string array
696      */
697     private static String[] tokenize(String command, String delim)
698     {
699         // The substrings "ch" and "v" are really a book/chapter or
700         // chapter/verse separators we should swap them for normal delims
701         // I recon it is safe to assume that the is no more than one of
702         // each
703         int idx = command.lastIndexOf("v");
704         if (idx != -1)
705         {
706             // Check that the "v" is surrounded my non letters - i.e.
707             // it is not part of "prov"
708             if (!Character.isLetter(command.charAt(idx-1)) &&
709                 !Character.isLetter(command.charAt(idx+1)))
710             {
711                 command = command.substring(0, idx) + VERSE_PREF_DELIM2 + command.substring(idx+1);
712             }
713         }
714         idx = command.lastIndexOf("ch");
715         if (idx != -1)
716         {
717             // Check that the "ch" is surrounded my non letters - i.e.
718             // it is not part of "chronicles"
719             if (!Character.isLetter(command.charAt(idx-1)) &&
720                 !Character.isLetter(command.charAt(idx+2)))
721             {
722                 command = command.substring(0, idx) + VERSE_PREF_DELIM1 + command.substring(idx+2);
723             }
724         }
725 
726         // Create the original string array
727         StringTokenizer tokenize = new StringTokenizer(command, delim);
728         String[] args = new String[tokenize.countTokens()];
729         int argc = 0;
730         while (tokenize.hasMoreTokens())
731         {
732             args[argc++] = tokenize.nextToken();
733         }
734 
735         // If the first word is a number, and the second a word, but not an
736         // EndMarker then this must be something like "2 Ki ...", so join
737         // them together to get "2Ki ..."
738         if (args.length > 1)
739         {
740             if (Character.isDigit(args[0].charAt(0))
741                 && Character.isLetter(args[1].charAt(0))
742                 && !isEndMarker(args[1]))
743             {
744                 String[] oldargs = args;
745                 args = new String[oldargs.length - 1];
746 
747                 args[0] = oldargs[0] + oldargs[1];
748                 for (int i=1; i<args.length; i++)
749                 {
750                     args[i] = oldargs[i+1];
751                 }
752             }
753         }
754 
755         // If the first word contains letters, but ends with a number
756         // then this must be something like "Gen1" to split them up
757         // to get "Gen 1"
758         if (args.length > 0
759             && Character.isDigit(args[0].charAt(args[0].length()-1))
760             && PassageUtil.containsLetter(args[0]))
761         {
762             // This might make the code quicker (less array subscripting)
763             // It certainly makes for more readable code.
764             String word = args[0];
765 
766             // The caveat here is that - We should not split if the bit
767             // before the number is one of the numeric book identifiers,
768             // in that case #2 means Exo and not the book of # chapter 2
769             boolean is_numeric_book = false;
770             for (int i=0; i<VERSE_NUMERIC_BOOK.length && is_numeric_book==false; i++)
771             {
772                 // so if we start with a book number id mark
773                 if (word.startsWith(VERSE_NUMERIC_BOOK[i]))
774                     is_numeric_book = true;
775             }
776 
777             if (!is_numeric_book)
778             {
779                 boolean found_letters = false;
780                 int i = 0;
781 
782                 // Find the split
783                 for (i=0; i<word.length(); i++)
784                 {
785                     if (!found_letters)
786                     {
787                         if (Character.isLetter(word.charAt(i))) found_letters = true;
788                     }
789                     else
790                     {
791                         if (!Character.isLetter(word.charAt(i))) break;
792                     }
793                 }
794 
795                 String[] oldargs = args;
796                 args = new String[oldargs.length + 1];
797 
798                 args[0] = oldargs[0].substring(0, i);
799                 args[1] = oldargs[0].substring(i);
800                 for (int j=2; j<args.length; j++)
801                 {
802                     args[j] = oldargs[j-1];
803                 }
804             }
805         }
806 
807         // The last 2 sections join and split up parts of the array, should
808         // I combine them to make for less array manipulation?
809         // This would only speed things up in the rare case where someone
810         // enters "2 Tim3:16" or something. The above method will still work
811         // it will just be slower, and it is a heck of a lot easier to
812         // understand and debug. Optimize when you need to not before.
813 
814         return args;
815     }
816 
817     /**
818      * This is simply a convenience function to wrap Integer.parseInt()
819      * and give us a reasonable exception on failure. It is called by
820      * VerseRange hence protected, however I would prefer private
821      * @param text The string to be parsed
822      * @return The correctly parsed chapter or verse
823      * @exception NoSuchVerseException If the reference is illegal
824      */
825     protected static int parseInt(String text) throws NoSuchVerseException
826     {
827         try
828         {
829             return Integer.parseInt(text);
830         }
831         catch (NumberFormatException ex)
832         {
833             throw new NoSuchVerseException("passg_verse_parse", new Object[] { text });
834         }
835     }
836 
837     /**
838      * Mutate into this reference and fix the reference if needed.
839      * This nust only be called from a ctor to maintain immutability
840      * @param book The book to set (Genesis = 1)
841      * @param chapter The chapter to set
842      * @param verse The verse to set
843      */
844     private final void setAndPatch(int book, int chapter, int verse)
845     {
846         int[] ref = { book, chapter, verse };
847 
848         Books.patch(ref);
849 
850         this.book = ref[BOOK];
851         this.chapter = ref[CHAPTER];
852         this.verse = ref[VERSE];
853     }
854 
855     /**
856      * Mutate into this reference and fix the reference if needed.
857      * This must only be called from a ctor to maintain immutability
858      * @param ref An array of the book, chapter and verse to set
859      */
860     private final void setAndPatch(int[] ref)
861     {
862         setAndPatch(ref[BOOK], ref[CHAPTER], ref[VERSE]);
863     }
864 
865     /**
866      * Verify and set the references.
867      * This must only be called from a ctor to maintain immutability
868      * @param book The book to set in String form (Genesis = 1)
869      * @param chapter The chapter to set
870      * @param verse The verse to set
871      * @exception NoSuchVerseException If the verse can not be understood
872      */
873     private final void set(String book_str, int chapter, int verse) throws NoSuchVerseException
874     {
875         int book = Books.getBookNumber(book_str);
876 
877         set(book, chapter, verse);
878     }
879 
880     /**
881      * Verify and set the references.
882      * This must only be called from a ctor to maintain immutability
883      * @param book The book to set in String form (Genesis = 1)
884      * @param chapter The chapter to set in String form
885      * @param verse The verse to set
886      * @exception NoSuchVerseException If the verse can not be understood
887      */
888     private final void set(String book_str, String chapter_str, int verse) throws NoSuchVerseException
889     {
890         int book = Books.getBookNumber(book_str);
891 
892         int chapter = 0;
893         if (isEndMarker(chapter_str))   chapter = Books.chaptersInBook(book);
894         else                            chapter = parseInt(chapter_str);
895 
896         set(book, chapter, verse);
897     }
898 
899     /**
900      * Verify and set the references.
901      * This must only be called from a ctor to maintain immutability
902      * @param book The book to set in String form (Genesis = 1)
903      * @param chapter The chapter to set in String form
904      * @param verse The verse to set in String form
905      * @exception NoSuchVerseException If the verse can not be understood
906      */
907     private final void set(String book_str, String chapter_str, String verse_str) throws NoSuchVerseException
908     {
909         int book = Books.getBookNumber(book_str);
910 
911         int chapter = 0;
912         if (isEndMarker(chapter_str))   chapter = Books.chaptersInBook(book);
913         else                            chapter = parseInt(chapter_str);
914 
915         int verse = 0;
916         if (isEndMarker(verse_str))     verse = Books.versesInChapter(book, chapter);
917         else                            verse = parseInt(verse_str);
918 
919         set(book, chapter, verse);
920     }
921 
922     /**
923      * Verify and set the references.
924      * This must only be called from a ctor to maintain immutability
925      * @param book The book to set in String form (Genesis = 1)
926      * @param chapter The chapter to set
927      * @param verse The verse to set in String form
928      * @exception NoSuchVerseException If the verse can not be understood
929      */
930     private final void set(String book_str, int chapter, String verse_str) throws NoSuchVerseException
931     {
932         int book = Books.getBookNumber(book_str);
933 
934         int verse = 0;
935         if (isEndMarker(verse_str))     verse = Books.versesInChapter(book, chapter);
936         else                            verse = parseInt(verse_str);
937 
938         set(book, chapter, verse);
939     }
940 
941     /**
942      * Verify and set the references.
943      * This must only be called from a ctor to maintain immutability
944      * @param book The book to set (Genesis = 1)
945      * @param chapter The chapter to set in String form
946      * @param verse The verse to set in String form
947      * @exception NoSuchVerseException If the verse can not be understood
948      */
949     private final void set(int book, String chapter_str, String verse_str) throws NoSuchVerseException
950     {
951         int chapter = 0;
952         if (isEndMarker(chapter_str))   chapter = Books.chaptersInBook(book);
953         else                            chapter = parseInt(chapter_str);
954 
955         int verse = 0;
956         if (isEndMarker(verse_str))     verse = Books.versesInChapter(book, chapter);
957         else                            verse = parseInt(verse_str);
958 
959         set(book, chapter, verse);
960     }
961 
962     /**
963      * Verify and set the references.
964      * This must only be called from a ctor to maintain immutability
965      * @param book The book to set (Genesis = 1)
966      * @param chapter The chapter to set
967      * @param verse The verse to set in String form
968      * @exception NoSuchVerseException If the verse can not be understood
969      */
970     private final void set(int book, int chapter, String verse_str) throws NoSuchVerseException
971     {
972         int verse = 0;
973         if (isEndMarker(verse_str))     verse = Books.versesInChapter(book, chapter);
974         else                            verse = parseInt(verse_str);
975 
976         set(book, chapter, verse);
977     }
978 
979     /**
980      * Verify and set the references.
981      * This must only be called from a ctor to maintain immutability
982      * @param book The book to set (Genesis = 1)
983      * @param chapter The chapter to set
984      * @param verse The verse to set
985      * @exception NoSuchVerseException If the verse can not be understood
986      */
987     private final void set(int book, int chapter, int verse) throws NoSuchVerseException
988     {
989         Books.validate(book, chapter, verse);
990 
991         this.book = book;
992         this.chapter = chapter;
993         this.verse = verse;
994     }
995 
996     /**
997      * Set the references.
998      * This must only be called from a ctor to maintain immutability
999      * @param ref An array of the book, chapter and verse to set
1000     * @exception NoSuchVerseException If the verse can not be understood
1001     */
1002    private final void set(int[] ref) throws NoSuchVerseException
1003    {
1004        Books.validate(ref[BOOK], ref[CHAPTER], ref[VERSE]);
1005
1006        book = ref[BOOK];
1007        chapter = ref[CHAPTER];
1008        verse = ref[VERSE];
1009    }
1010
1011    /**
1012     * Set the references.
1013     * This must only be called from a ctor to maintain immutability
1014     * @param ref The ordinal of the verse
1015     * @exception NoSuchVerseException If the verse can not be understood
1016     */
1017    private final void set(int ordinal) throws NoSuchVerseException
1018    {
1019        int[] ref = Books.decodeOrdinal(ordinal);
1020
1021        book = ref[BOOK];
1022        chapter = ref[CHAPTER];
1023        verse = ref[VERSE];
1024    }
1025
1026    /**
1027     * Write out the object to the given ObjectOutputStream
1028     * @param out The stream to write our state to
1029     * @serialData Write the ordinal number of this verse
1030     */
1031    private void writeObject(ObjectOutputStream out) throws IOException
1032    {
1033        // Call even if there is no default serializable fields.
1034        out.defaultWriteObject();
1035
1036        // save the ordinal of the verse
1037        out.writeInt(getOrdinal());
1038
1039        // Ignore the original name. Is this wise?
1040        // I am expecting that people are not that fussed about it and
1041        // it could make everything far more verbose
1042    }
1043
1044    /**
1045     * Write out the object to the given ObjectOutputStream
1046     * @param in The stream to read our state from
1047     * @serialData Write the ordinal number of this verse
1048     */
1049    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
1050    {
1051        // Call even if there is no default serializable fields.
1052        in.defaultReadObject();
1053
1054        try
1055        {
1056            set(in.readInt());
1057        }
1058        catch (NoSuchVerseException ex)
1059        {
1060            throw new IOException(ex.getMessage());
1061        }
1062
1063        // We are ignoring the original_name. It was set to null in the
1064        // default ctor so I will ignore it here.
1065    }
1066
1067    /** To make serialization work across new versions */
1068    static final long serialVersionUID = -4033921076023185171L;
1069
1070    /** To make the code more readible, the book is the first part of a int[] */
1071    private static final int BOOK = 0;
1072
1073    /** To make the code more readible, the chapter is the second part of a int[] */
1074    private static final int CHAPTER = 1;
1075
1076    /** To make the code more readible, the verse is the third part of a int[] */
1077    private static final int VERSE = 2;
1078
1079    /** The default verse */
1080    protected static final Verse DEFAULT = new Verse(1, 1, 1, true);
1081
1082    /** The book number. Genesis=1 */
1083    private transient int book;
1084
1085    /** The chapter number */
1086    private transient int chapter;
1087
1088    /** The verse number */
1089    private transient int verse;
1090
1091    /** The ordinal number. Cache only. */
1092    // private transient int ord = -1;
1093
1094    /** The original string for picky users */
1095    private transient String original_name;
1096
1097    /** The log stream */
1098    protected static Logger log = Logger.getLogger("bible.passage");
1099}