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}