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