Coverage Report - org.crosswire.jsword.passage.AccuracyType
 
Classes in this File Line Coverage Branch Coverage Complexity
AccuracyType
0%
0/120
0%
0/96
3.081
AccuracyType$1
0%
0/13
0%
0/10
3.081
AccuracyType$2
0%
0/10
N/A
3.081
AccuracyType$3
0%
0/10
N/A
3.081
AccuracyType$4
0%
0/12
0%
0/2
3.081
AccuracyType$5
0%
0/11
0%
0/2
3.081
AccuracyType$6
0%
0/12
0%
0/2
3.081
 
 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  
  * © CrossWire Bible Society, 2005 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.passage;
 21  
 
 22  
 import org.crosswire.common.icu.NumberShaper;
 23  
 import org.crosswire.jsword.JSMsg;
 24  
 import org.crosswire.jsword.versification.BibleBook;
 25  
 import org.crosswire.jsword.versification.Versification;
 26  
 
 27  
 /**
 28  
  * Types of Accuracy for verse references. For example:
 29  
  * <ul>
 30  
  * <li>Gen == BOOK_ONLY;</li>
 31  
  * <li>Gen 1 == BOOK_CHAPTER;</li>
 32  
  * <li>Gen 1:1 == BOOK_VERSE;</li>
 33  
  * <li>Jude 1 == BOOK_VERSE;</li>
 34  
  * <li>Jude 1:1 == BOOK_VERSE;</li>
 35  
  * <li>1:1 == CHAPTER_VERSE;</li>
 36  
  * <li>10 == BOOK_ONLY, CHAPTER_ONLY, or VERSE_ONLY</li>
 37  
  * </ul>
 38  
  * 
 39  
  * <p>
 40  
  * With the last one, you will note that there is a choice. By itself there is
 41  
  * not enough information to determine which one it is. There has to be a
 42  
  * context in which it is used.
 43  
  * </p><p>
 44  
  * It may be found in a verse range like: Gen 1:2 - 10. In this case the context
 45  
  * of 10 is Gen 1:2, which is BOOK_VERSE. So in this case, 10 is VERSE_ONLY.
 46  
  * </p><p>
 47  
  * If it is at the beginning of a range like 10 - 22:3, it has to have more
 48  
  * context. If the context is a prior entry like Gen 2:5, 10 - 22:3, then its
 49  
  * context is Gen 2:5, which is BOOK_VERSE and 10 is VERSE_ONLY.
 50  
  * </p><p>
 51  
  * However if it is Gen 2, 10 - 22:3 then the context is Gen 2, BOOK_CHAPTER so
 52  
  * 10 is understood as BOOK_CHAPTER.
 53  
  * </p><p>
 54  
  * As a special case, if the preceding range is an entire chapter or book then
 55  
  * 10 would understood as CHAPTER_ONLY or BOOK_ONLY (respectively)
 56  
  * </p><p>
 57  
  * If the number has no preceding context, then it is understood as being
 58  
  * BOOK_ONLY.
 59  
  * </p><p>
 60  
  * In all of these examples, the start verse was being interpreted. In the case
 61  
  * of a verse that is the end of a range, it is interpreted in the context of
 62  
  * the range's start.
 63  
  * </p>
 64  
  * 
 65  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 66  
  * @author Joe Walker
 67  
  * @author DM Smith
 68  
  */
 69  0
 public enum AccuracyType {
 70  
     /**
 71  
      * The verse was specified as book, chapter and verse. For example, Gen 1:1,
 72  
      * Jude 3 (which only has one chapter)
 73  
      */
 74  0
     BOOK_VERSE {
 75  
         @Override
 76  
         public boolean isVerse() {
 77  0
             return true;
 78  
         }
 79  
 
 80  
         @Override
 81  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 82  0
             BibleBook book = v11n.getBook(parts[0]);
 83  0
             int chapter = 1;
 84  0
             int verse = 1;
 85  0
             final String subIdentifier = getSubIdentifier(parts);
 86  0
             final boolean hasSub = subIdentifier != null;
 87  
 
 88  
             //can be of form, BCV, BCV!sub, BV, BV!a
 89  
             //we only have a chapter and verse number if
 90  
             // a- BCV (3 parts) or b- BCV!sub (4 parts)
 91  
             // however, we have 3 parts if BV!a
 92  0
             if (hasSub && parts.length == 4 || !hasSub && parts.length == 3) {
 93  0
                 chapter = getChapter(v11n, book, parts[1]);
 94  0
                 verse = getVerse(v11n, book, chapter, parts[2]);
 95  
             } else {
 96  
                 // Some books only have 1 chapter
 97  0
                 verse = getVerse(v11n, book, chapter, parts[1]);
 98  
             }
 99  0
             return new Verse(v11n, book, chapter, verse, subIdentifier);
 100  
         }
 101  
 
 102  
         @Override
 103  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 104  
             // A fully specified verse is the same regardless of whether it is a
 105  
             // start or an end to a range.
 106  0
             return createStartVerse(v11n, null, endParts);
 107  
         }
 108  
     },
 109  
 
 110  
     /**
 111  
      * The passage was specified to a book and chapter (no verse). For example,
 112  
      * Gen 1
 113  
      */
 114  0
     BOOK_CHAPTER {
 115  
         @Override
 116  
         public boolean isChapter() {
 117  0
             return true;
 118  
         }
 119  
 
 120  
         @Override
 121  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 122  0
             BibleBook book = v11n.getBook(parts[0]);
 123  0
             int chapter = getChapter(v11n, book, parts[1]);
 124  0
             int verse = 0; // chapter > 0 ? 1 : 0; // 0 ?
 125  0
             return new Verse(v11n, book, chapter, verse);
 126  
         }
 127  
 
 128  
         @Override
 129  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 130  
             // Very similar to the start verse but we want the end of the chapter
 131  0
             BibleBook book = v11n.getBook(endParts[0]);
 132  0
             int chapter = getChapter(v11n, book, endParts[1]);
 133  0
             int verse = v11n.getLastVerse(book, chapter);
 134  0
             return new Verse(v11n, book, chapter, verse);
 135  
         }
 136  
     },
 137  
 
 138  
     /**
 139  
      * The passage was specified to a book only (no chapter or verse). For
 140  
      * example, Gen
 141  
      */
 142  0
     BOOK_ONLY {
 143  
         @Override
 144  
         public boolean isBook() {
 145  0
             return true;
 146  
         }
 147  
 
 148  
         @Override
 149  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 150  0
             BibleBook book = v11n.getBook(parts[0]);
 151  0
             int chapter = 0; // v11n.getLastChapter(book) > 0 ? 1 : 0; // 0 ?
 152  0
             int verse = 0; // v11n.getLastVerse(book, chapter) > 0 ? 1 : 0; // 0 ?
 153  0
             return new Verse(v11n, book, chapter, verse);
 154  
         }
 155  
 
 156  
         @Override
 157  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 158  0
             BibleBook book = v11n.getBook(endParts[0]);
 159  0
             int chapter = v11n.getLastChapter(book);
 160  0
             int verse = v11n.getLastVerse(book, chapter);
 161  0
             return new Verse(v11n, book, chapter, verse);
 162  
         }
 163  
     },
 164  
 
 165  
     /**
 166  
      * The passage was specified to a chapter and verse (no book). For example,
 167  
      * 1:1
 168  
      */
 169  0
     CHAPTER_VERSE {
 170  
         @Override
 171  
         public boolean isVerse() {
 172  0
             return true;
 173  
         }
 174  
 
 175  
         @Override
 176  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 177  0
             if (verseRangeBasis == null) {
 178  
                 // TRANSLATOR: The user supplied a verse reference but did not give the book of the Bible.
 179  0
                 throw new NoSuchVerseException(JSMsg.gettext("Book is missing"));
 180  
             }
 181  0
             BibleBook book = verseRangeBasis.getEnd().getBook();
 182  0
             int chapter = getChapter(v11n, book, parts[0]);
 183  0
             int verse = getVerse(v11n, book, chapter, parts[1]);
 184  
 
 185  0
             return new Verse(v11n, book, chapter, verse, getSubIdentifier(parts));
 186  
         }
 187  
 
 188  
         @Override
 189  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 190  
             // Very similar to the start verse but use the verse as a basis
 191  0
             BibleBook book = verseBasis.getBook();
 192  0
             int chapter = getChapter(v11n, book, endParts[0]);
 193  0
             int verse = getVerse(v11n, book, chapter, endParts[1]);
 194  0
             return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts));
 195  
         }
 196  
     },
 197  
 
 198  
     /**
 199  
      * There was only a chapter number
 200  
      */
 201  0
     CHAPTER_ONLY {
 202  
         @Override
 203  
         public boolean isChapter() {
 204  0
             return true;
 205  
         }
 206  
 
 207  
         @Override
 208  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 209  0
             if (verseRangeBasis == null) {
 210  
                 // TRANSLATOR: The user supplied a verse reference but did not give the book of the Bible.
 211  0
                 throw new NoSuchVerseException(JSMsg.gettext("Book is missing"));
 212  
             }
 213  0
             BibleBook book = verseRangeBasis.getEnd().getBook();
 214  0
             int chapter = getChapter(v11n, book, parts[0]);
 215  0
             int verse = 0; // chapter > 0 ? 1 : 0; // 0 ?
 216  0
             return new Verse(v11n, book, chapter, verse);
 217  
         }
 218  
 
 219  
         @Override
 220  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 221  
             // Very similar to the start verse but use the verse as a basis
 222  
             // and it gets the end of the chapter
 223  0
             BibleBook book = verseBasis.getBook();
 224  0
             int chapter = getChapter(v11n, book, endParts[0]);
 225  0
             return new Verse(v11n, book, chapter, v11n.getLastVerse(book, chapter));
 226  
         }
 227  
     },
 228  
 
 229  
     /**
 230  
      * There was only a verse number
 231  
      */
 232  0
     VERSE_ONLY {
 233  
         @Override
 234  
         public boolean isVerse() {
 235  0
             return true;
 236  
         }
 237  
 
 238  
         @Override
 239  
         public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
 240  0
             if (verseRangeBasis == null) {
 241  
                 // TRANSLATOR: The user supplied a verse reference but did not give the book or chapter of the Bible.
 242  0
                 throw new NoSuchVerseException(JSMsg.gettext("Book and chapter are missing"));
 243  
             }
 244  0
             BibleBook book = verseRangeBasis.getEnd().getBook();
 245  0
             int chapter = verseRangeBasis.getEnd().getChapter();
 246  0
             int verse = getVerse(v11n, book, chapter, parts[0]);
 247  0
             return new Verse(v11n, book, chapter, verse, getSubIdentifier(parts));
 248  
         }
 249  
 
 250  
         @Override
 251  
         public Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException {
 252  
             // Very similar to the start verse but use the verse as a basis
 253  
             // and it gets the end of the chapter
 254  0
             BibleBook book = verseBasis.getBook();
 255  0
             int chapter = verseBasis.getChapter();
 256  0
             int verse = getVerse(v11n, book, chapter, endParts[0]);
 257  0
             return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts));
 258  
         }
 259  
     };
 260  
 
 261  
     /**
 262  
      * @param v11n
 263  
      *            the versification to which this reference pertains
 264  
      * @param verseRangeBasis
 265  
      *            the range that stood before the string reference
 266  
      * @param parts
 267  
      *            a tokenized version of the original
 268  
      * @return a <code>Verse</code> for the original
 269  
      * @throws NoSuchVerseException
 270  
      */
 271  
     public abstract Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException;
 272  
 
 273  
     /**
 274  
      * @param v11n
 275  
      *            the versification to which this reference pertains
 276  
      * @param verseBasis
 277  
      *            the verse at the beginning of the range
 278  
      * @param endParts
 279  
      *            a tokenized version of the original
 280  
      * @return a <code>Verse</code> for the original
 281  
      * @throws NoSuchVerseException
 282  
      */
 283  
     public abstract Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException;
 284  
 
 285  
     /**
 286  
      * @return true if this AccuracyType specifies down to the book but not
 287  
      *         chapter or verse.
 288  
      */
 289  
     public boolean isBook() {
 290  0
         return false;
 291  
     }
 292  
 
 293  
     /**
 294  
      * @return true if this AccuracyType specifies down to the chapter but not
 295  
      *         the verse.
 296  
      */
 297  
     public boolean isChapter() {
 298  0
         return false;
 299  
     }
 300  
 
 301  
     /**
 302  
      * @return true if this AccuracyType specifies down to the verse.
 303  
      */
 304  
     public boolean isVerse() {
 305  0
         return false;
 306  
     }
 307  
 
 308  
     /**
 309  
      * Interprets the chapter value, which is either a number or "ff" or "$"
 310  
      * (meaning "what follows")
 311  
      * 
 312  
      * @param v11n
 313  
      *            the versification to which this reference pertains
 314  
      * @param lbook
 315  
      *            the book
 316  
      * @param chapter
 317  
      *            a string representation of the chapter. May be "ff" or "$" for
 318  
      *            "what follows".
 319  
      * @return the number of the chapter
 320  
      * @throws NoSuchVerseException
 321  
      */
 322  
     public static final int getChapter(Versification v11n, BibleBook lbook, String chapter) throws NoSuchVerseException {
 323  0
         if (isEndMarker(chapter)) {
 324  0
             return v11n.getLastChapter(lbook);
 325  
         }
 326  0
         return parseInt(chapter);
 327  
     }
 328  
 
 329  
     /**
 330  
      * Interprets the verse value, which is either a number or "ff" or "$"
 331  
      * (meaning "what follows")
 332  
      * 
 333  
      * @param v11n
 334  
      *            the versification to which this reference pertains
 335  
      * @param lbook
 336  
      *            the integer representation of the book
 337  
      * @param lchapter
 338  
      *            the integer representation of the chapter
 339  
      * @param verse
 340  
      *            the string representation of the verse
 341  
      * @return the number of the verse
 342  
      * @throws NoSuchVerseException
 343  
      */
 344  
     public static final int getVerse(Versification v11n, BibleBook lbook, int lchapter, String verse) throws NoSuchVerseException {
 345  0
         if (isEndMarker(verse)) {
 346  0
             return v11n.getLastVerse(lbook, lchapter);
 347  
         }
 348  0
         return parseInt(verse);
 349  
     }
 350  
 
 351  
     /**
 352  
      * Get an integer representation for this AccuracyType
 353  
      * 
 354  
      * @return the ordinal representation
 355  
      */
 356  
     public int toInteger() {
 357  0
         return ordinal();
 358  
     }
 359  
 
 360  
     /**
 361  
      * Determine how closely the string defines a verse.
 362  
      * 
 363  
      * @param v11n
 364  
      *            the versification to which this reference pertains
 365  
      * @param original
 366  
      * @param parts
 367  
      *            is a reference split into parts
 368  
      * @return what is the kind of accuracy of the string reference.
 369  
      * @throws NoSuchVerseException
 370  
      */
 371  
     public static AccuracyType fromText(Versification v11n, String original, String[] parts) throws NoSuchVerseException {
 372  0
         return fromText(v11n, original, parts, null, null);
 373  
     }
 374  
 
 375  
     /**
 376  
      * @param v11n
 377  
      *            the versification to which this reference pertains
 378  
      * @param original
 379  
      * @param parts
 380  
      * @param verseAccuracy
 381  
      * @return the accuracy of the parts
 382  
      * @throws NoSuchVerseException
 383  
      */
 384  
     public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy) throws NoSuchVerseException {
 385  0
         return fromText(v11n, original, parts, verseAccuracy, null);
 386  
     }
 387  
 
 388  
     /**
 389  
      * @param v11n
 390  
      *            the versification to which this reference pertains
 391  
      * @param original
 392  
      * @param parts
 393  
      * @param basis
 394  
      * @return the accuracy of the parts
 395  
      * @throws NoSuchVerseException
 396  
      */
 397  
     public static AccuracyType fromText(Versification v11n, String original, String[] parts, VerseRange basis) throws NoSuchVerseException {
 398  0
         return fromText(v11n, original, parts, null, basis);
 399  
     }
 400  
 
 401  
     /**
 402  
      * Does this string exactly define a Verse. For example:
 403  
      * <ul>
 404  
      * <li>fromText("Gen") == AccuracyType.BOOK_ONLY;</li>
 405  
      * <li>fromText("Gen 1:1") == AccuracyType.BOOK_VERSE;</li>
 406  
      * <li>fromText("Gen 1") == AccuracyType.BOOK_CHAPTER;</li>
 407  
      * <li>fromText("Jude 1") == AccuracyType.BOOK_VERSE;</li>
 408  
      * <li>fromText("Jude 1:1") == AccuracyType.BOOK_VERSE;</li>
 409  
      * <li>fromText("1:1") == AccuracyType.CHAPTER_VERSE;</li>
 410  
      * <li>fromText("1") == AccuracyType.VERSE_ONLY;</li>
 411  
      * </ul>
 412  
      * 
 413  
      * @param v11n
 414  
      *            the versification to which this reference pertains
 415  
      * @param original 
 416  
      * @param parts
 417  
      * @param verseAccuracy
 418  
      * @param basis
 419  
      * @return the accuracy of the parts
 420  
      * @throws NoSuchVerseException
 421  
      */
 422  
     public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy, VerseRange basis) throws NoSuchVerseException {
 423  0
         int partsLength = parts.length;
 424  0
         String lastPart = parts[partsLength - 1];
 425  0
         if (lastPart.length() > 0 && lastPart.charAt(0) == '!') {
 426  0
             --partsLength;
 427  
         }
 428  0
         switch (partsLength) {
 429  
         case 1:
 430  0
             if (v11n.isBook(parts[0])) {
 431  0
                 return BOOK_ONLY;
 432  
             }
 433  
 
 434  
             // At this point we should have a number.
 435  
             // But double check it
 436  0
             checkValidChapterOrVerse(parts[0]);
 437  
 
 438  
             // What it is depends upon what stands before it.
 439  0
             if (verseAccuracy != null) {
 440  0
                 if (verseAccuracy.isVerse()) {
 441  0
                     return VERSE_ONLY;
 442  
                 }
 443  
 
 444  0
                 if (verseAccuracy.isChapter()) {
 445  0
                     return CHAPTER_ONLY;
 446  
                 }
 447  
             }
 448  
 
 449  0
             if (basis != null) {
 450  0
                 if (basis.isWholeChapter()) {
 451  0
                     return CHAPTER_ONLY;
 452  
                 }
 453  0
                 return VERSE_ONLY;
 454  
             }
 455  
 
 456  0
             throw buildVersePartsException(original, parts);
 457  
 
 458  
         case 2:
 459  
             // Does it start with a book?
 460  0
             BibleBook pbook = v11n.getBook(parts[0]);
 461  0
             if (pbook == null) {
 462  0
                 checkValidChapterOrVerse(parts[0]);
 463  0
                 checkValidChapterOrVerse(parts[1]);
 464  0
                 return CHAPTER_VERSE;
 465  
             }
 466  
 
 467  0
             if (v11n.getLastChapter(pbook) == 1) {
 468  0
                 return BOOK_VERSE;
 469  
             }
 470  
 
 471  0
             return BOOK_CHAPTER;
 472  
 
 473  
         case 3:
 474  0
             if (v11n.getBook(parts[0]) != null) {
 475  0
                 checkValidChapterOrVerse(parts[1]);
 476  0
                 checkValidChapterOrVerse(parts[2]);
 477  0
                 return BOOK_VERSE;
 478  
             }
 479  
 
 480  0
             throw buildVersePartsException(original, parts);
 481  
 
 482  
         default:
 483  0
             throw buildVersePartsException(original, parts);
 484  
         }
 485  
     }
 486  
 
 487  
     private static NoSuchVerseException buildVersePartsException(String original, String[] parts) {
 488  0
         StringBuilder buffer = new StringBuilder(original);
 489  0
         for (int i = 0; i < parts.length; i++) {
 490  0
             buffer.append(", ").append(parts[i]);
 491  
         }
 492  
         // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators.
 493  0
         return new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", buffer.toString()));
 494  
     }
 495  
 
 496  
     /**
 497  
      * Is this text valid in a chapter/verse context
 498  
      * 
 499  
      * @param text
 500  
      *            The string to test for validity
 501  
      * @throws NoSuchVerseException
 502  
      *             If the text is invalid
 503  
      */
 504  
     private static void checkValidChapterOrVerse(String text) throws NoSuchVerseException {
 505  0
         if (!isEndMarker(text)) {
 506  0
             parseInt(text);
 507  
         }
 508  0
     }
 509  
 
 510  
     /**
 511  
      * This is simply a convenience function to wrap Integer.parseInt() and give
 512  
      * us a reasonable exception on failure. It is called by VerseRange hence
 513  
      * protected, however I would prefer private
 514  
      * 
 515  
      * @param text
 516  
      *            The string to be parsed
 517  
      * @return The correctly parsed chapter or verse
 518  
      * @exception NoSuchVerseException
 519  
      *                If the reference is illegal
 520  
      */
 521  
     private static int parseInt(String text) throws NoSuchVerseException {
 522  
         try {
 523  0
             return Integer.parseInt(new NumberShaper().unshape(text));
 524  0
         } catch (NumberFormatException ex) {
 525  
             // TRANSLATOR: The chapter or verse number is actually not a number, but something else.
 526  
             // {0} is a placeholder for what the user supplied.
 527  0
             throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text));
 528  
         }
 529  
     }
 530  
 
 531  
     /**
 532  
      * Is this string a legal marker for 'to the end of the chapter'
 533  
      * 
 534  
      * @param text
 535  
      *            The string to be checked
 536  
      * @return true if this is a legal marker
 537  
      */
 538  
     private static boolean isEndMarker(String text) {
 539  0
         if (text.equals(AccuracyType.VERSE_END_MARK1)) {
 540  0
             return true;
 541  
         }
 542  
 
 543  0
         if (text.equals(AccuracyType.VERSE_END_MARK2)) {
 544  0
             return true;
 545  
         }
 546  
 
 547  0
         return false;
 548  
     }
 549  
 
 550  
     private static boolean hasSubIdentifier(String[] parts) {
 551  0
         String subIdentifier = parts[parts.length - 1];
 552  0
         return subIdentifier != null && subIdentifier.length() > 0 && subIdentifier.charAt(0) == '!';
 553  
     }
 554  
 
 555  
     protected static String getSubIdentifier(String[] parts) {
 556  0
         String subIdentifier = null;
 557  0
         if (hasSubIdentifier(parts)) {
 558  0
             subIdentifier = parts[parts.length - 1].substring(1);
 559  
         }
 560  0
         return subIdentifier;
 561  
     }
 562  
 
 563  
     /**
 564  
      * Take a string representation of a verse and parse it into an Array of
 565  
      * Strings where each part is likely to be a verse part. The goal is to
 566  
      * allow the greatest possible variations in user input.
 567  
      * <p>
 568  
      * Parts can be separated by pretty much anything. No distinction is made
 569  
      * between them. While chapter and verse need to be separated, a separator
 570  
      * is assumed between digits and non-digits. Adjacent words, (i.e. sequences
 571  
      * of non-digits) are understood to be a book reference. If a number runs up
 572  
      * against a book name, it is considered to be either part of the book name
 573  
      * (i.e. it is before it) or a chapter number (i.e. it stands after it.)
 574  
      * </p>
 575  
      * <p>
 576  
      * Note: ff and $ are considered to be digits.
 577  
      * </p>
 578  
      * <p>
 579  
      * Note: it is not necessary for this to be a BCV (book, chapter, verse), it
 580  
      * may just be BC, B, C, V or CV. No distinction is needed here for a number
 581  
      * that stands alone.
 582  
      * </p>
 583  
      * 
 584  
      * @param input
 585  
      *            The string to parse.
 586  
      * @return The string array
 587  
      * @throws NoSuchVerseException
 588  
      */
 589  
     public static String[] tokenize(String input) throws NoSuchVerseException {
 590  
         // The results are expected to be no more than 3 parts
 591  0
         String[] args = {
 592  
                 null, null, null, null, null, null, null, null
 593  
         };
 594  
 
 595  
         // Normalize the input by replacing non-digits and non-letters with
 596  
         // spaces.
 597  0
         int length = input.length();
 598  
         // Create an output array big enough to add ' ' where needed
 599  0
         char[] normalized = new char[length * 2];
 600  0
         char lastChar = '0'; // start with a digit so normalized won't start
 601  
                              // with a space
 602  0
         char curChar = ' '; // can be anything
 603  0
         int tokenCount = 0;
 604  0
         int normalizedLength = 0;
 605  0
         int startIndex = 0;
 606  0
         String token = null;
 607  0
         boolean foundBoundary = false;
 608  0
         boolean foundSubIdentifier = false;
 609  0
         for (int i = 0; i < length; i++) {
 610  0
             curChar = input.charAt(i);
 611  0
             if (curChar == '!') {
 612  0
                 foundSubIdentifier = true;
 613  0
                 token = new String(normalized, startIndex, normalizedLength - startIndex);
 614  0
                 args[tokenCount++] = token;
 615  0
                 normalizedLength = 0;
 616  
             }
 617  0
             if (foundSubIdentifier) {
 618  0
                 normalized[normalizedLength++] = curChar;
 619  0
                 continue;
 620  
             }
 621  0
             boolean charIsDigit = curChar == '$' || Character.isDigit(curChar)
 622  
                     || (curChar == 'f' && (i + 1 < length ? input.charAt(i + 1) : ' ') == 'f' && !Character.isLetter(lastChar));
 623  0
             if (charIsDigit || Character.isLetter(curChar)) {
 624  0
                 foundBoundary = true;
 625  0
                 boolean charWasDigit = lastChar == '$' || Character.isDigit(lastChar) || (lastChar == 'f' && (i > 2 ? input.charAt(i - 2) : '0') == 'f');
 626  0
                 if (charWasDigit || Character.isLetter(lastChar)) {
 627  0
                     foundBoundary = false;
 628  
                     // Replace transitions between digits and letters with
 629  
                     // spaces.
 630  0
                     if (normalizedLength > 0 && charWasDigit != charIsDigit) {
 631  0
                         foundBoundary = true;
 632  
                     }
 633  
                 }
 634  0
                 if (foundBoundary) {
 635  
                     // On a boundary:
 636  
                     // Digits always start a new token
 637  
                     // Letters always continue a previous token
 638  0
                     if (charIsDigit) {
 639  0
                         if (tokenCount >= args.length) {
 640  
                             // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators.
 641  0
                             throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input));
 642  
                         }
 643  
 
 644  0
                         token = new String(normalized, startIndex, normalizedLength - startIndex);
 645  0
                         args[tokenCount++] = token;
 646  0
                         normalizedLength = 0;
 647  
                     } else {
 648  0
                         normalized[normalizedLength++] = ' ';
 649  
                     }
 650  
                 }
 651  0
                 normalized[normalizedLength++] = curChar;
 652  
             }
 653  
 
 654  
             // Until the first character is copied, there is no last char
 655  0
             if (normalizedLength > 0) {
 656  0
                 lastChar = curChar;
 657  
             }
 658  
         }
 659  
 
 660  0
         if (tokenCount >= args.length) {
 661  
             // TRANSLATOR: The user specified a verse with too many separators. {0} is a placeholder for the allowable separators.
 662  0
             throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input));
 663  
         }
 664  
 
 665  0
         token = new String(normalized, startIndex, normalizedLength - startIndex);
 666  0
         args[tokenCount++] = token;
 667  
 
 668  0
         String[] results = new String[tokenCount];
 669  0
         System.arraycopy(args, 0, results, 0, tokenCount);
 670  0
         return results;
 671  
     }
 672  
 
 673  
     /**
 674  
      * What characters can we use to separate parts to a verse
 675  
      */
 676  
     public static final String VERSE_ALLOWED_DELIMS = " :.";
 677  
 
 678  
     /**
 679  
      * Characters that are used to indicate end of verse/chapter, part 1
 680  
      */
 681  
     public static final String VERSE_END_MARK1 = "$";
 682  
 
 683  
     /**
 684  
      * Characters that are used to indicate end of verse/chapter, part 2
 685  
      */
 686  
     public static final String VERSE_END_MARK2 = "ff";
 687  
 }