| Verse.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2005
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: Verse.java 2226 2012-02-02 19:25:21Z dmsmith $
21 */
22 package org.crosswire.jsword.passage;
23
24 import java.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.util.Iterator;
28
29 import org.crosswire.common.icu.NumberShaper;
30 import org.crosswire.common.util.ItemIterator;
31 import org.crosswire.jsword.JSMsg;
32 import org.crosswire.jsword.JSOtherMsg;
33 import org.crosswire.jsword.versification.BibleBook;
34 import org.crosswire.jsword.versification.Versification;
35 import org.crosswire.jsword.versification.system.Versifications;
36
37 /**
38 * A Verse is a pointer to a single verse. Externally its unique identifier is
39 * a String of the form "Gen 1:1" Internally we use
40 * <code>( book, chapter, verse )</code>
41 *
42 * <p>
43 * A Verse is designed to be immutable. This is a necessary from a collections
44 * point of view. A Verse should always be valid, although some versions may not
45 * return any text for verses that they consider to be mis-translated in some
46 * way.
47 * </p>
48 *
49 * @see gnu.lgpl.License for license details.<br>
50 * The copyright to this program is held by it's authors.
51 * @author Joe Walker [joe at eireneh dot com]
52 */
53 public final class Verse implements Key {
54 /**
55 * The default Verse is Genesis 1:1. I didn't want to provide this
56 * constructor however, you are supposed to provide a default ctor for all
57 * beans. For this reason I suggest you don't use it.
58 */
59 public Verse() {
60 originalName = null;
61
62 book = DEFAULT.book;
63 chapter = DEFAULT.chapter;
64 verse = DEFAULT.verse;
65 }
66
67 /**
68 * Create a Verse from book, chapter and verse numbers, throwing up if the
69 * specified Verse does not exist. This constructor is deliberately package
70 * protected so that is used only by VerseFactory.
71 *
72 * @param original
73 * The original verse reference
74 * @param book
75 * The book number (Genesis = 1)
76 * @param chapter
77 * The chapter number
78 * @param verse
79 * The verse number
80 */
81 /* package */Verse(String original, BibleBook book, int chapter, int verse) {
82 originalName = original;
83 set(book, chapter, verse);
84 }
85
86 /**
87 * Create a Verse from book, chapter and verse numbers, throwing up if the
88 * specified Verse does not exist.
89 *
90 * @param book
91 * The book number (Genesis = 1)
92 * @param chapter
93 * The chapter number
94 * @param verse
95 * The verse number
96 */
97 public Verse(BibleBook book, int chapter, int verse) {
98 this(null, book, chapter, verse);
99 }
100
101 /**
102 * Create a Verse from book, chapter and verse numbers, patching up if the
103 * specified verse does not exist.
104 * <p>
105 * The actual value of the boolean is ignored. However for future proofing
106 * you should only use 'true'. Do not use patch_up=false, use
107 * <code>Verse(int, int, int)</code> This so that we can declare this
108 * constructor to not throw an exception. Is there a better way of doing
109 * this?
110 *
111 * @param book
112 * The book number (Genesis = 1)
113 * @param chapter
114 * The chapter number
115 * @param verse
116 * The verse number
117 * @param patch_up
118 * True to trigger reference fixing
119 */
120 public Verse(BibleBook book, int chapter, int verse, boolean patch_up) {
121 if (!patch_up) {
122 throw new IllegalArgumentException(JSOtherMsg.lookupText("Use patch=true."));
123 }
124
125 originalName = null;
126 setAndPatch(book, chapter, verse);
127 }
128
129 /**
130 * Set a Verse using a Verse Ordinal number - WARNING Do not use this method
131 * unless you really know the dangers of doing so. Ordinals are not always
132 * going to be the same. So you should use a Verse or an int[3] in
133 * preference to an int ordinal whenever possible. Ordinal numbers are 1
134 * based and not 0 based.
135 *
136 * @param ordinal
137 * The verse id
138 */
139 public Verse(int ordinal) {
140 originalName = null;
141 set(ordinal);
142 }
143
144 @Override
145 public String toString() {
146 return getName();
147 }
148
149 /* (non-Javadoc)
150 * @see org.crosswire.jsword.passage.Key#getName()
151 */
152 public String getName() {
153 return getName(null);
154 }
155
156 /* (non-Javadoc)
157 * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key)
158 */
159 public String getName(Key base) {
160 if (base != null && !(base instanceof Verse)) {
161 return getName();
162 }
163
164 if (PassageUtil.isPersistentNaming() && originalName != null) {
165 return originalName;
166 }
167
168 String verseName = doGetName((Verse) base);
169 // Only shape it if it can be unshaped.
170 if (shaper.canUnshape()) {
171 return shaper.shape(verseName);
172 }
173
174 return verseName;
175 }
176
177 /* (non-Javadoc)
178 * @see org.crosswire.jsword.passage.Key#getRootName()
179 */
180 public String getRootName() {
181 return book.getShortName();
182 }
183
184 /* (non-Javadoc)
185 * @see org.crosswire.jsword.passage.Key#getOsisRef()
186 */
187 public String getOsisRef() {
188 return book.getOSIS() + Verse.VERSE_OSIS_DELIM + chapter + Verse.VERSE_OSIS_DELIM + verse;
189 }
190
191 /* (non-Javadoc)
192 * @see org.crosswire.jsword.passage.Key#getOsisID()
193 */
194 public String getOsisID() {
195 return getOsisRef();
196 }
197
198 @Override
199 public Verse clone() {
200 Verse copy = null;
201 try {
202 copy = (Verse) super.clone();
203 copy.book = book;
204 copy.chapter = chapter;
205 copy.verse = verse;
206 copy.originalName = originalName;
207 } catch (CloneNotSupportedException e) {
208 assert false : e;
209 }
210
211 return copy;
212 }
213
214 @Override
215 public boolean equals(Object obj) {
216 // Since this can not be null
217 if (obj == null) {
218 return false;
219 }
220
221 // Check that that is the same as this
222 // Don't use instanceOf since that breaks inheritance
223 if (!obj.getClass().equals(this.getClass())) {
224 return false;
225 }
226
227 Verse v = (Verse) obj;
228
229 // The real tests
230 if (v.getBook() != getBook()) {
231 return false;
232 }
233
234 if (v.getChapter() != getChapter()) {
235 return false;
236 }
237
238 if (v.getVerse() != getVerse()) {
239 return false;
240 }
241
242 return true;
243 }
244
245 @Override
246 public int hashCode() {
247 return getOrdinal();
248 }
249
250 /* (non-Javadoc)
251 * @see java.lang.Comparable#compareTo(java.lang.Object)
252 */
253 public int compareTo(Key obj) {
254 Verse that = null;
255 if (obj instanceof Verse) {
256 that = (Verse) obj;
257 } else {
258 that = ((VerseRange) obj).getStart();
259 }
260
261 int thatStart = that.getOrdinal();
262 int thisStart = this.getOrdinal();
263
264 if (thatStart > thisStart) {
265 return -1;
266 }
267
268 if (thatStart < thisStart) {
269 return 1;
270 }
271
272 return 0;
273 }
274
275 /**
276 * Is this verse adjacent to another verse
277 *
278 * @param that
279 * The thing to compare against
280 * @return 1 means he is earlier than me, -1 means he is later ...
281 * @deprecated
282 */
283 @Deprecated
284 public boolean adjacentTo(Verse that) {
285 return Math.abs(that.getOrdinal() - getOrdinal()) == 1;
286 }
287
288 /**
289 * How many verses are there in between the 2 Verses. The answer is -ve if
290 * start is bigger than this (the end). The answer is inclusive of start and exclusive
291 * of this, so that <code>gen12.subtract(gen11) == 1</code>
292 *
293 * @param start
294 * The Verse to compare this to
295 * @return The count of verses between this and that.
296 * @deprecated
297 */
298 @Deprecated
299 public int subtract(Verse start) {
300 return getOrdinal() - start.getOrdinal();
301 }
302
303 /**
304 * Get the verse n down from here this Verse.
305 *
306 * @param n
307 * The number to count down by
308 * @return The new Verse
309 * @deprecated
310 */
311 @Deprecated
312 public Verse subtract(int n) {
313 // AV11N(DMS): deprecate?
314 return Versifications.instance().getDefaultVersification().subtract(this, n);
315 }
316
317 /**
318 * Get the verse that is a few verses on from the one we've got.
319 *
320 * @param n
321 * the number of verses later than the one we're one
322 * @return The new verse
323 * @deprecated
324 */
325 @Deprecated
326 public Verse add(int n) {
327 // AV11N(DMS): deprecate?
328 return Versifications.instance().getDefaultVersification().add(this, n);
329 }
330
331 /**
332 * Return the book that we refer to
333 *
334 * @return The book of the Bible
335 */
336 public BibleBook getBook() {
337 return book;
338 }
339
340 /**
341 * Return the chapter that we refer to
342 *
343 * @return The chapter number
344 */
345 public int getChapter() {
346 return chapter;
347 }
348
349 /**
350 * Return the verse that we refer to
351 *
352 * @return The verse number
353 */
354 public int getVerse() {
355 return verse;
356 }
357
358 /**
359 * Is this verse the first in a chapter
360 *
361 * @return true or false ...
362 * @deprecated
363 */
364 @Deprecated
365 public boolean isStartOfChapter() {
366 return verse == 0;
367 }
368
369 /**
370 * Is this verse the first in a chapter
371 *
372 * @return true or false ...
373 * @deprecated
374 */
375 @Deprecated
376 public boolean isEndOfChapter() {
377 // AV11N(DMS): deprecate?
378 return Versifications.instance().getDefaultVersification().isEndOfChapter(this);
379 }
380
381 /**
382 * Is this verse the first in a chapter
383 *
384 * @return true or false ...
385 * @deprecated
386 */
387 @Deprecated
388 public boolean isStartOfBook() {
389 return verse == 0 && chapter == 0;
390 }
391
392 /**
393 * Is this verse the first in a chapter
394 *
395 * @return true or false ...
396 * @deprecated
397 */
398 @Deprecated
399 public boolean isEndOfBook() {
400 // AV11N(DMS): deprecate?
401 return Versifications.instance().getDefaultVersification().isEndOfBook(this);
402 }
403
404 /**
405 * Is this verse in the same chapter as that one
406 *
407 * @param that
408 * The verse to compare to
409 * @return true or false ...
410 * @deprecated
411 */
412 @Deprecated
413 public boolean isSameChapter(Verse that) {
414 return book == that.book && chapter == that.chapter;
415 }
416
417 /**
418 * Is this verse in the same book as that one
419 *
420 * @param that
421 * The verse to compare to
422 * @return true or false ...
423 * @deprecated
424 */
425 @Deprecated
426 public boolean isSameBook(Verse that) {
427 return book == that.book;
428 }
429
430 /**
431 * Return the verse id that we refer to, where Gen 1:1 = 1, and Rev 22:21 =
432 * 31104
433 *
434 * @return The verse number
435 * @deprecated do not use
436 */
437 @Deprecated
438 public int getOrdinal() {
439 // AV11N(DMS): deprecate?
440 return Versifications.instance().getDefaultVersification().getOrdinal(this);
441 }
442
443 /**
444 * Return the bigger of the 2 verses. If the verses are equal() then return
445 * Verse a
446 *
447 * @param a
448 * The first verse to compare
449 * @param b
450 * The second verse to compare
451 * @return The bigger of the 2 verses
452 * @deprecated do not use
453 */
454 @Deprecated
455 public static Verse max(Verse a, Verse b) {
456 if (a.compareTo(b) == -1) {
457 return b;
458 }
459 return a;
460 }
461
462 /**
463 * Return the smaller of the 2 verses. If the verses are equal() then return
464 * Verse a
465 *
466 * @param a
467 * The first verse to compare
468 * @param b
469 * The second verse to compare
470 * @return The smaller of the 2 verses
471 * @deprecated do not use
472 */
473 @Deprecated
474 public static Verse min(Verse a, Verse b) {
475 if (a.compareTo(b) == 1) {
476 return b;
477 }
478 return a;
479 }
480
481 /**
482 * Create an array of Verses
483 *
484 * @return The array of verses that this makes up
485 */
486 public Verse[] toVerseArray() {
487 return new Verse[] {
488 this
489 };
490 }
491
492 /*
493 * (non-Javadoc)
494 *
495 * @see org.crosswire.jsword.passage.Key#getParent()
496 */
497 public Key getParent() {
498 return parent;
499 }
500
501 /**
502 * Set a parent Key. This allows us to follow the Key interface more
503 * closely, although the concept of a parent for a verse is fairly alien.
504 *
505 * @param parent
506 * The parent Key for this verse
507 */
508 public void setParent(Key parent) {
509 this.parent = parent;
510 }
511
512 /**
513 * Compute the verse representation given the context.
514 *
515 * @param verseBase
516 * the context or null if there is none
517 * @return the verse representation
518 * @deprecated do not use
519 */
520 @Deprecated
521 private String doGetName(Verse verseBase) {
522 // To cope with thing like Jude 2...
523 // AV11N(DMS): move to Versification???
524 if (Versifications.instance().getDefaultVersification().getLastChapter(book) == 1) {
525 if (verseBase == null || verseBase.book != book) {
526 return book.getPreferredName() + Verse.VERSE_PREF_DELIM1 + verse;
527 }
528
529 return String.valueOf(verse);
530 }
531
532 if (verseBase == null || verseBase.book != book) {
533 return book.getPreferredName() + Verse.VERSE_PREF_DELIM1 + chapter + Verse.VERSE_PREF_DELIM2 + verse;
534 }
535
536 if (verseBase.chapter != chapter) {
537 return chapter + Verse.VERSE_PREF_DELIM2 + verse;
538 }
539
540 return String.valueOf(verse);
541 }
542
543 /**
544 * This is simply a convenience function to wrap Integer.parseInt() and give
545 * us a reasonable exception on failure. It is called by VerseRange hence
546 * protected, however I would prefer private
547 *
548 * @param text
549 * The string to be parsed
550 * @return The correctly parsed chapter or verse
551 */
552 protected static int parseInt(String text) throws NoSuchVerseException {
553 try {
554 return Integer.parseInt(shaper.unshape(text));
555 } catch (NumberFormatException ex) {
556 // TRANSLATOR: The chapter or verse number is actually not a number, but something else.
557 // {0} is a placeholder for what the user supplied.
558 throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text));
559 }
560 }
561
562 /**
563 * Mutate into this reference and fix the reference if needed. This must
564 * only be called from a ctor to maintain immutability
565 *
566 * @param book
567 * The book to set (Genesis = 1)
568 * @param chapter
569 * The chapter to set
570 * @param verse
571 * The verse to set
572 * @deprecated do not use
573 */
574 @Deprecated
575 private void setAndPatch(BibleBook book, int chapter, int verse) {
576 // AV11N(DMS): deprecate?
577 Versification v11n = Versifications.instance().getDefaultVersification();
578 Verse patched = v11n.patch(book, chapter, verse);
579
580 this.book = patched.book;
581 this.chapter = patched.chapter;
582 this.verse = patched.verse;
583 }
584
585 /**
586 * Verify and set the references. This must only be called from a ctor to
587 * maintain immutability
588 *
589 * @param book
590 * The book to set (Genesis = 1)
591 * @param chapter
592 * The chapter to set
593 * @param verse
594 * The verse to set
595 */
596 private void set(BibleBook book, int chapter, int verse) {
597 this.book = book;
598 this.chapter = chapter;
599 this.verse = verse;
600 }
601
602 /**
603 * Set the references. This must only be called from a ctor to maintain immutability
604 *
605 * @param ordinal
606 * The ordinal of the verse
607 * @deprecated do not use
608 */
609 @Deprecated
610 private void set(int ordinal) {
611 // AV11N(DMS): deprecate?
612 Versification v11n = Versifications.instance().getDefaultVersification();
613 Verse v = v11n.decodeOrdinal(ordinal);
614
615 book = v.book;
616 chapter = v.chapter;
617 verse = v.verse;
618 }
619
620 /**
621 * Write out the object to the given ObjectOutputStream
622 *
623 * @param out
624 * The stream to write our state to
625 * @throws IOException
626 * if the read fails
627 * @serialData Write the ordinal number of this verse
628 */
629 private void writeObject(ObjectOutputStream out) throws IOException {
630 // Call even if there is no default serializable fields.
631 out.defaultWriteObject();
632
633 // save the ordinal of the verse
634 out.writeInt(getOrdinal());
635
636 // Ignore the original name. Is this wise?
637 // I am expecting that people are not that fussed about it and
638 // it could make everything far more verbose
639 }
640
641 /**
642 * Write out the object to the given ObjectOutputStream
643 *
644 * @param in
645 * The stream to read our state from
646 * @throws IOException
647 * if the read fails
648 * @throws ClassNotFoundException
649 * If the read data is incorrect
650 * @serialData Write the ordinal number of this verse
651 */
652 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
653 // Call even if there is no default serializable fields.
654 in.defaultReadObject();
655
656 set(in.readInt());
657
658 // We are ignoring the originalName. It was set to null in the
659 // default ctor so I will ignore it here.
660 }
661
662 /* (non-Javadoc)
663 * @see org.crosswire.jsword.passage.Key#canHaveChildren()
664 */
665 public boolean canHaveChildren() {
666 return false;
667 }
668
669 /* (non-Javadoc)
670 * @see org.crosswire.jsword.passage.Key#getChildCount()
671 */
672 public int getChildCount() {
673 return 0;
674 }
675
676 /* (non-Javadoc)
677 * @see org.crosswire.jsword.passage.Key#getCardinality()
678 */
679 public int getCardinality() {
680 return 1;
681 }
682
683 /* (non-Javadoc)
684 * @see org.crosswire.jsword.passage.Key#isEmpty()
685 */
686 public boolean isEmpty() {
687 return false;
688 }
689
690 /* (non-Javadoc)
691 * @see org.crosswire.jsword.passage.Key#contains(org.crosswire.jsword.passage.Key)
692 */
693 public boolean contains(Key key) {
694 return this.equals(key);
695 }
696
697 /* (non-Javadoc)
698 * @see java.lang.Iterable#iterator()
699 */
700 public Iterator<Key> iterator() {
701 return new ItemIterator<Key>(this);
702 }
703
704 /* (non-Javadoc)
705 * @see org.crosswire.jsword.passage.Key#addAll(org.crosswire.jsword.passage.Key)
706 */
707 public void addAll(Key key) {
708 throw new UnsupportedOperationException();
709 }
710
711 /* (non-Javadoc)
712 * @see org.crosswire.jsword.passage.Key#removeAll(org.crosswire.jsword.passage.Key)
713 */
714 public void removeAll(Key key) {
715 throw new UnsupportedOperationException();
716 }
717
718 /* (non-Javadoc)
719 * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key)
720 */
721 public void retainAll(Key key) {
722 throw new UnsupportedOperationException();
723 }
724
725 /* (non-Javadoc)
726 * @see org.crosswire.jsword.passage.Key#clear()
727 */
728 public void clear() {
729 }
730
731 /* (non-Javadoc)
732 * @see org.crosswire.jsword.passage.Key#get(int)
733 */
734 public Key get(int index) {
735 if (index == 0) {
736 return this;
737 }
738 return null;
739 }
740
741 /* (non-Javadoc)
742 * @see org.crosswire.jsword.passage.Key#indexOf(org.crosswire.jsword.passage.Key)
743 */
744 public int indexOf(Key that) {
745 if (this.equals(that)) {
746 return 0;
747 }
748 return -1;
749 }
750
751 /* (non-Javadoc)
752 * @see org.crosswire.jsword.passage.Key#blur(int, org.crosswire.jsword.passage.RestrictionType)
753 */
754 public void blur(int by, RestrictionType restrict) {
755 throw new UnsupportedOperationException();
756 }
757
758 /**
759 * To make serialization work across new versions
760 */
761 static final long serialVersionUID = -4033921076023185171L;
762
763 /**
764 * What characters should we use to separate parts of an OSIS verse
765 * reference
766 */
767 public static final String VERSE_OSIS_DELIM = ".";
768
769 /**
770 * What characters should we use to separate the book from the chapter
771 */
772 public static final String VERSE_PREF_DELIM1 = " ";
773
774 /**
775 * What characters should we use to separate the chapter from the verse
776 */
777 public static final String VERSE_PREF_DELIM2 = ":";
778
779 /**
780 * The default verse
781 */
782 public static final Verse DEFAULT = new Verse(BibleBook.GEN, 1, 1);
783
784 /**
785 * Allow the conversion to and from other number representations.
786 */
787 private static NumberShaper shaper = new NumberShaper();
788
789 /**
790 * The parent key. See the key interface for more information.
791 *
792 * NOTE(joe): These keys are not serialized, should we?
793 *
794 * @see Key
795 */
796 private transient Key parent;
797
798 /**
799 * The book of the Bible.
800 */
801 private transient BibleBook book;
802
803 /**
804 * The chapter number
805 */
806 private transient int chapter;
807
808 /**
809 * The verse number
810 */
811 private transient int verse;
812
813 /**
814 * The original string for picky users
815 */
816 private transient String originalName;
817
818 }
819