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