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 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the 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-2013
18   *     The copyright to this program is held by its authors.
19   *
20   */
21  package org.crosswire.jsword.passage;
22  
23  import java.io.IOException;
24  import java.io.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.util.Iterator;
27  
28  import org.crosswire.common.icu.NumberShaper;
29  import org.crosswire.common.util.ItemIterator;
30  import org.crosswire.jsword.JSMsg;
31  import org.crosswire.jsword.JSOtherMsg;
32  import org.crosswire.jsword.versification.BibleBook;
33  import org.crosswire.jsword.versification.BibleNames;
34  import org.crosswire.jsword.versification.Versification;
35  import org.crosswire.jsword.versification.system.Versifications;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * A Verse is a pointer to a single verse. Externally its unique identifier is
41   * a String of the form "Gen 1:1" Internally we use
42   * <code>( v11n, book, chapter, verse )</code>
43   * 
44   * <p>
45   * A Verse is designed to be immutable. This is a necessary from a collections
46   * point of view. A Verse should always be valid, although some versions may not
47   * return any text for verses that they consider to be untranslated in some
48   * way.
49   * </p>
50   * 
51   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
52   * @author Joe Walker
53   * @author DM Smith
54   */
55  public final class Verse implements VerseKey<Verse> {
56      /**
57       * Create a Verse from book, chapter and verse numbers, throwing up if the
58       * specified Verse does not exist.
59       * 
60       * @param v11n
61       *            The versification to which this verse belongs
62       * @param book
63       *            The book number (Genesis = 1)
64       * @param chapter
65       *            The chapter number
66       * @param verse
67       *            The verse number
68       */
69      public Verse(Versification v11n, BibleBook book, int chapter, int verse) {
70          this(v11n, book, chapter, verse, null);
71      }
72  
73      /**
74       * Create a Verse from book, chapter and verse numbers, throwing up if the
75       * specified Verse does not exist.
76       * 
77       * @param v11n
78       *            The versification to which this verse belongs
79       * @param book
80       *            The book number (Genesis = 1)
81       * @param chapter
82       *            The chapter number
83       * @param verse
84       *            The verse number
85       * @param subIdentifier
86       *            The optional sub identifier
87       */
88      public Verse(Versification v11n, BibleBook book, int chapter, int verse, String subIdentifier) {
89          this.v11n = v11n;
90          this.book = book;
91          this.chapter = chapter;
92          this.verse = verse;
93          this.subIdentifier = subIdentifier;
94          this.ordinal = v11n.getOrdinal(this);
95      }
96  
97      /**
98       * Create a Verse from book, chapter and verse numbers, patching up if the
99       * specified verse does not exist.
100      * <p>
101      * The actual value of the boolean is ignored. However for future proofing
102      * you should only use 'true'. Do not use patch_up=false, use
103      * <code>Verse(int, int, int)</code> This so that we can declare this
104      * constructor to not throw an exception. Is there a better way of doing
105      * this?
106      * 
107      * @param v11n
108      *            The versification to which this verse belongs
109      * @param book
110      *            The book number (Genesis = 1)
111      * @param chapter
112      *            The chapter number
113      * @param verse
114      *            The verse number
115      * @param patch_up
116      *            True to trigger reference fixing
117      */
118     public Verse(Versification v11n, BibleBook book, int chapter, int verse, boolean patch_up) {
119         if (!patch_up) {
120             throw new IllegalArgumentException(JSOtherMsg.lookupText("Use patch=true."));
121         }
122 
123         this.v11n = v11n;
124         Verse patched = this.v11n.patch(book, chapter, verse);
125         this.book = patched.book;
126         this.chapter = patched.chapter;
127         this.verse = patched.verse;
128         this.ordinal = patched.ordinal;
129     }
130 
131     /**
132      * Set a Verse using a verse ordinal number - WARNING Do not use this method
133      * unless you really know the dangers of doing so. Ordinals are not always
134      * going to be the same. So you should use Versification, Book, Chapter and Verse
135      * in preference to an int ordinal whenever possible. Ordinal numbers are 1
136      * based and not 0 based.
137      * 
138      * @param v11n
139      *            The versification to which this verse belongs
140      * @param ordinal
141      *            The verse id
142      */
143     public Verse(Versification v11n, int ordinal) {
144         Verse decoded = v11n.decodeOrdinal(ordinal);
145         this.v11n = v11n;
146         this.book = decoded.book;
147         this.chapter = decoded.chapter;
148         this.verse = decoded.verse;
149         this.ordinal = decoded.ordinal;
150     }
151 
152     /* (non-Javadoc)
153      * @see org.crosswire.jsword.passage.VerseKey#isWhole()
154      */
155     public boolean isWhole() {
156         return subIdentifier == null || subIdentifier.length() == 0;
157     }
158 
159     /* (non-Javadoc)
160      * @see org.crosswire.jsword.passage.VerseKey#getWhole()
161      */
162     public Verse getWhole() {
163         if (isWhole()) {
164             return this;
165         }
166         return new Verse(v11n, book, chapter, verse);
167     }
168 
169     @Override
170     public String toString() {
171         return getName();
172     }
173 
174     /* (non-Javadoc)
175      * @see org.crosswire.jsword.passage.Key#getName()
176      */
177     public String getName() {
178         return getName(null);
179     }
180 
181     /* (non-Javadoc)
182      * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key)
183      */
184     public String getName(Key base) {
185         if (base != null && !(base instanceof Verse)) {
186             return getName();
187         }
188 
189         String verseName = doGetName((Verse) base);
190         // Only shape it if it can be unshaped.
191         if (shaper.canUnshape()) {
192             return shaper.shape(verseName);
193         }
194 
195         return verseName;
196     }
197 
198     /* (non-Javadoc)
199      * @see org.crosswire.jsword.passage.Key#getRootName()
200      */
201     public String getRootName() {
202         return BibleNames.instance().getShortName(book);
203     }
204 
205     /* (non-Javadoc)
206      * @see org.crosswire.jsword.passage.Key#getOsisRef()
207      */
208     public String getOsisRef() {
209         return getOsisID();
210     }
211 
212     /* (non-Javadoc)
213      * @see org.crosswire.jsword.passage.Key#getOsisID()
214      */
215     public String getOsisID() {
216         final StringBuilder buf = getVerseIdentifier();
217         if (subIdentifier != null && subIdentifier.length() > 0) {
218             buf.append(VERSE_OSIS_SUB_PREFIX);
219             buf.append(subIdentifier);
220         }
221         return buf.toString();
222     }
223 
224     /* (non-Javadoc)
225      * @see org.crosswire.jsword.passage.Key#getOsisID()
226      */
227     public String getOsisIDNoSubIdentifier() {
228         return getVerseIdentifier().toString();
229     }
230 
231     /**
232      * Gets the common name of the verse, excluding any !abc sub-identifier
233      * @return the verse OSIS-ID, excluding the sub-identifier
234      */
235     private StringBuilder getVerseIdentifier() {
236         StringBuilder buf = new StringBuilder();
237         buf.append(book.getOSIS());
238         buf.append(Verse.VERSE_OSIS_DELIM);
239         buf.append(chapter);
240         buf.append(Verse.VERSE_OSIS_DELIM);
241         buf.append(verse);
242         return buf;
243     }
244 
245     @Override
246     public Verse clone() {
247         Verse copy = null;
248         try {
249             copy = (Verse) super.clone();
250             copy.v11n = this.v11n;
251             copy.book = this.book;
252             copy.chapter = this.chapter;
253             copy.verse = this.verse;
254             copy.ordinal = this.ordinal;
255             copy.subIdentifier = this.subIdentifier;
256         } catch (CloneNotSupportedException e) {
257             assert false : e;
258         }
259 
260         return copy;
261     }
262 
263     @Override
264     public boolean equals(Object obj) {
265         // Since this can not be null
266         if (!(obj instanceof Verse)) {
267             return false;
268         }
269 
270         Verse that = (Verse) obj;
271 
272         // The real tests
273         return this.ordinal == that.ordinal
274                 && this.v11n.equals(that.v11n)
275                 && bothNullOrEqual(this.subIdentifier, that.subIdentifier);
276     }
277 
278     @Override
279     public int hashCode() {
280         int result = 31 + ordinal;
281         result = 31 * result + ((v11n == null) ? 0 : v11n.hashCode());
282         return 31 * result + ((subIdentifier == null) ? 0 : subIdentifier.hashCode());
283     }
284 
285     /* (non-Javadoc)
286      * @see java.lang.Comparable#compareTo(java.lang.Object)
287      */
288     public int compareTo(Key obj) {
289         return this.ordinal - ((Verse) obj).ordinal;
290     }
291 
292     /* (non-Javadoc)
293      * @see org.crosswire.jsword.passage.VerseKey#getVersification()
294      */
295     public Versification getVersification() {
296         return v11n;
297     }
298 
299     /* (non-Javadoc)
300      * @see org.crosswire.jsword.passage.Passage#reversify(org.crosswire.jsword.versification.Versification)
301      */
302     public Verse reversify(Versification newVersification) {
303         if (v11n.equals(newVersification)) {
304             return this;
305         }
306 
307         try {
308             //check the v11n supports this key, otherwise this leads to all sorts of issues
309             if (newVersification.validate(book, chapter, verse, true)) {
310                 return new Verse(newVersification, book, chapter, verse);
311             }
312         } catch (NoSuchVerseException ex) {
313             // will never happen
314             log.error("Contract for validate was changed to thrown an exception when silent mode is true", ex);
315         }
316         return null;
317     }
318 
319     /**
320      * Return the book that we refer to
321      * 
322      * @return The book of the Bible
323      */
324     public BibleBook getBook() {
325         return book;
326     }
327 
328     /**
329      * Return the chapter that we refer to
330      * 
331      * @return The chapter number
332      */
333     public int getChapter() {
334         return chapter;
335     }
336 
337     /**
338      * Return the verse that we refer to
339      * 
340      * @return The verse number
341      */
342     public int getVerse() {
343         return verse;
344     }
345 
346     /**
347      * Return the sub identifier if any
348      * @return The optional OSIS sub identifier
349      */
350     public String getSubIdentifier() {
351         return subIdentifier;
352     }
353 
354     /**
355      * Return the ordinal value of the verse in its versification.
356      * 
357      * @return The verse number
358      */
359     public int getOrdinal() {
360         return ordinal;
361     }
362 
363     /**
364      * Create an array of Verses
365      * 
366      * @return The array of verses that this makes up
367      */
368     public Verse[] toVerseArray() {
369         return new Verse[] {
370             this
371         };
372     }
373 
374     /* (non-Javadoc)
375      * @see org.crosswire.jsword.passage.Key#getParent()
376      */
377     public Key getParent() {
378         return null;
379     }
380 
381     /**
382      * Determine whether two objects are equal, allowing nulls
383      * @param x
384      * @param y
385      * @return true if both are null or the two are equal
386      */
387     public static boolean bothNullOrEqual(Object x, Object y) {
388         return x == y || (x != null && x.equals(y));
389     }
390 
391     /**
392      * Compute the verse representation given the context.
393      * 
394      * @param verseBase
395      *            the context or null if there is none
396      * @return the verse representation
397      */
398     private String doGetName(Verse verseBase) {
399         StringBuilder buf = new StringBuilder();
400         // To cope with thing like Jude 2...
401         if (book.isShortBook()) {
402             if (verseBase == null || verseBase.book != book) {
403                 buf.append(BibleNames.instance().getPreferredName(book));
404                 buf.append(Verse.VERSE_PREF_DELIM1);
405                 buf.append(verse);
406                 return buf.toString();
407             }
408 
409             return Integer.toString(verse);
410         }
411 
412         if (verseBase == null || verseBase.book != book) {
413             buf.append(BibleNames.instance().getPreferredName(book));
414             buf.append(Verse.VERSE_PREF_DELIM1);
415             buf.append(chapter);
416             buf.append(Verse.VERSE_PREF_DELIM2);
417             buf.append(verse);
418             return buf.toString();
419         }
420 
421         if (verseBase.chapter != chapter) {
422             buf.append(chapter);
423             buf.append(Verse.VERSE_PREF_DELIM2);
424             buf.append(verse);
425             return buf.toString();
426         }
427 
428         return Integer.toString(verse);
429     }
430 
431     /**
432      * This is simply a convenience function to wrap Integer.parseInt() and give
433      * us a reasonable exception on failure. It is called by VerseRange hence
434      * protected, however I would prefer private
435      * 
436      * @param text
437      *            The string to be parsed
438      * @return The correctly parsed chapter or verse
439      */
440     protected static int parseInt(String text) throws NoSuchVerseException {
441         try {
442             return Integer.parseInt(shaper.unshape(text));
443         } catch (NumberFormatException ex) {
444             // TRANSLATOR: The chapter or verse number is actually not a number, but something else.
445             // {0} is a placeholder for what the user supplied.
446             throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text));
447         }
448     }
449 
450     /**
451      * Write out the object to the given ObjectOutputStream
452      * 
453      * @param out
454      *            The stream to write our state to
455      * @throws IOException
456      *             if the read fails
457      * @serialData Write the ordinal number of this verse
458      */
459     private void writeObject(ObjectOutputStream out) throws IOException {
460         out.defaultWriteObject();
461         out.writeUTF(v11n.getName());
462     }
463 
464     /**
465      * Write out the object to the given ObjectOutputStream
466      * 
467      * @param in
468      *            The stream to read our state from
469      * @throws IOException
470      *             if the read fails
471      * @throws ClassNotFoundException
472      *             If the read data is incorrect
473      * @serialData Write the ordinal number of this verse
474      */
475     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
476         in.defaultReadObject();
477         String v11nName = in.readUTF();
478         v11n = Versifications.instance().getVersification(v11nName);
479         Verse decoded = v11n.decodeOrdinal(ordinal);
480 
481         this.book = decoded.book;
482         this.chapter = decoded.chapter;
483         this.verse = decoded.verse;
484     }
485 
486     /* (non-Javadoc)
487      * @see org.crosswire.jsword.passage.Key#canHaveChildren()
488      */
489     public boolean canHaveChildren() {
490         return false;
491     }
492 
493     /* (non-Javadoc)
494      * @see org.crosswire.jsword.passage.Key#getChildCount()
495      */
496     public int getChildCount() {
497         return 0;
498     }
499 
500     /* (non-Javadoc)
501      * @see org.crosswire.jsword.passage.Key#getCardinality()
502      */
503     public int getCardinality() {
504         return 1;
505     }
506 
507     /* (non-Javadoc)
508      * @see org.crosswire.jsword.passage.Key#isEmpty()
509      */
510     public boolean isEmpty() {
511         return false;
512     }
513 
514     /* (non-Javadoc)
515      * @see org.crosswire.jsword.passage.Key#contains(org.crosswire.jsword.passage.Key)
516      */
517     public boolean contains(Key key) {
518         return this.equals(key);
519     }
520 
521     /* (non-Javadoc)
522      * @see java.lang.Iterable#iterator()
523      */
524     public Iterator<Key> iterator() {
525         return new ItemIterator<Key>(this);
526     }
527 
528     /* (non-Javadoc)
529      * @see org.crosswire.jsword.passage.Key#addAll(org.crosswire.jsword.passage.Key)
530      */
531     public void addAll(Key key) {
532         throw new UnsupportedOperationException();
533     }
534 
535     /* (non-Javadoc)
536      * @see org.crosswire.jsword.passage.Key#removeAll(org.crosswire.jsword.passage.Key)
537      */
538     public void removeAll(Key key) {
539         throw new UnsupportedOperationException();
540     }
541 
542     /* (non-Javadoc)
543      * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key)
544      */
545     public void retainAll(Key key) {
546         throw new UnsupportedOperationException();
547     }
548 
549     /* (non-Javadoc)
550      * @see org.crosswire.jsword.passage.Key#clear()
551      */
552     public void clear() {
553         // do nothing
554     }
555 
556     /* (non-Javadoc)
557      * @see org.crosswire.jsword.passage.Key#get(int)
558      */
559     public Key get(int index) {
560         if (index == 0) {
561             return this;
562         }
563         return null;
564     }
565 
566     /* (non-Javadoc)
567      * @see org.crosswire.jsword.passage.Key#indexOf(org.crosswire.jsword.passage.Key)
568      */
569     public int indexOf(Key that) {
570         if (this.equals(that)) {
571             return 0;
572         }
573         return -1;
574     }
575 
576     /* (non-Javadoc)
577      * @see org.crosswire.jsword.passage.Key#blur(int, org.crosswire.jsword.passage.RestrictionType)
578      */
579     public void blur(int by, RestrictionType restrict) {
580         throw new UnsupportedOperationException();
581     }
582 
583     /**
584      * What characters should we use to separate parts of an OSIS verse
585      * reference
586      */
587     public static final char VERSE_OSIS_DELIM = '.';
588 
589     /**
590      * What characters should we use to start an OSIS sub identifier
591      */
592     public static final char VERSE_OSIS_SUB_PREFIX = '!';
593 
594     /**
595      * What characters should we use to separate the book from the chapter
596      */
597     public static final char VERSE_PREF_DELIM1 = ' ';
598 
599     /**
600      * What characters should we use to separate the chapter from the verse
601      */
602     public static final char VERSE_PREF_DELIM2 = ':';
603 
604     /**
605      * The default verse
606      */
607     public static final Verse DEFAULT = new Verse(Versifications.instance().getVersification("KJV"), BibleBook.GEN, 1, 1);
608 
609     /**
610      * Allow the conversion to and from other number representations.
611      */
612     private static NumberShaper shaper = new NumberShaper();
613 
614     /**
615      * The versification for this verse.
616      */
617     private transient Versification v11n;
618 
619     /**
620      * The ordinal value for this verse within its versification.
621      */
622     private int ordinal;
623 
624     /**
625      * The book of the Bible.
626      */
627     private transient BibleBook book;
628 
629     /**
630      * The chapter number
631      */
632     private transient int chapter;
633 
634     /**
635      * The verse number
636      */
637     private transient int verse;
638 
639     /**
640      * The OSIS Sub-identifier if present.
641      * This should be a string that allows for the likes of:
642      * a.xy.asdf.qr
643      */
644     private String subIdentifier;
645 
646     private static final Logger log = LoggerFactory.getLogger(Verse.class);
647 
648     /**
649      * To make serialization work across new versions
650      */
651     private static final long serialVersionUID = -4033921076023185171L;
652 }
653