1
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
69 public enum AccuracyType {
70
74 BOOK_VERSE {
75 @Override
76 public boolean isVerse() {
77 return true;
78 }
79
80 @Override
81 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
82 BibleBook book = v11n.getBook(parts[0]);
83 int chapter = 1;
84 int verse = 1;
85 final String subIdentifier = getSubIdentifier(parts);
86 final boolean hasSub = subIdentifier != null;
87
88 if (hasSub && parts.length == 4 || !hasSub && parts.length == 3) {
93 chapter = getChapter(v11n, book, parts[1]);
94 verse = getVerse(v11n, book, chapter, parts[2]);
95 } else {
96 verse = getVerse(v11n, book, chapter, parts[1]);
98 }
99 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 return createStartVerse(v11n, null, endParts);
107 }
108 },
109
110
114 BOOK_CHAPTER {
115 @Override
116 public boolean isChapter() {
117 return true;
118 }
119
120 @Override
121 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
122 BibleBook book = v11n.getBook(parts[0]);
123 int chapter = getChapter(v11n, book, parts[1]);
124 int verse = 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 BibleBook book = v11n.getBook(endParts[0]);
132 int chapter = getChapter(v11n, book, endParts[1]);
133 int verse = v11n.getLastVerse(book, chapter);
134 return new Verse(v11n, book, chapter, verse);
135 }
136 },
137
138
142 BOOK_ONLY {
143 @Override
144 public boolean isBook() {
145 return true;
146 }
147
148 @Override
149 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
150 BibleBook book = v11n.getBook(parts[0]);
151 int chapter = 0; int verse = 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 BibleBook book = v11n.getBook(endParts[0]);
159 int chapter = v11n.getLastChapter(book);
160 int verse = v11n.getLastVerse(book, chapter);
161 return new Verse(v11n, book, chapter, verse);
162 }
163 },
164
165
169 CHAPTER_VERSE {
170 @Override
171 public boolean isVerse() {
172 return true;
173 }
174
175 @Override
176 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
177 if (verseRangeBasis == null) {
178 throw new NoSuchVerseException(JSMsg.gettext("Book is missing"));
180 }
181 BibleBook book = verseRangeBasis.getEnd().getBook();
182 int chapter = getChapter(v11n, book, parts[0]);
183 int verse = getVerse(v11n, book, chapter, parts[1]);
184
185 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 BibleBook book = verseBasis.getBook();
192 int chapter = getChapter(v11n, book, endParts[0]);
193 int verse = getVerse(v11n, book, chapter, endParts[1]);
194 return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts));
195 }
196 },
197
198
201 CHAPTER_ONLY {
202 @Override
203 public boolean isChapter() {
204 return true;
205 }
206
207 @Override
208 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
209 if (verseRangeBasis == null) {
210 throw new NoSuchVerseException(JSMsg.gettext("Book is missing"));
212 }
213 BibleBook book = verseRangeBasis.getEnd().getBook();
214 int chapter = getChapter(v11n, book, parts[0]);
215 int verse = 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 BibleBook book = verseBasis.getBook();
224 int chapter = getChapter(v11n, book, endParts[0]);
225 return new Verse(v11n, book, chapter, v11n.getLastVerse(book, chapter));
226 }
227 },
228
229
232 VERSE_ONLY {
233 @Override
234 public boolean isVerse() {
235 return true;
236 }
237
238 @Override
239 public Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException {
240 if (verseRangeBasis == null) {
241 throw new NoSuchVerseException(JSMsg.gettext("Book and chapter are missing"));
243 }
244 BibleBook book = verseRangeBasis.getEnd().getBook();
245 int chapter = verseRangeBasis.getEnd().getChapter();
246 int verse = getVerse(v11n, book, chapter, parts[0]);
247 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 BibleBook book = verseBasis.getBook();
255 int chapter = verseBasis.getChapter();
256 int verse = getVerse(v11n, book, chapter, endParts[0]);
257 return new Verse(v11n, book, chapter, verse, getSubIdentifier(endParts));
258 }
259 };
260
261
271 public abstract Verse createStartVerse(Versification v11n, VerseRange verseRangeBasis, String[] parts) throws NoSuchVerseException;
272
273
283 public abstract Verse createEndVerse(Versification v11n, Verse verseBasis, String[] endParts) throws NoSuchVerseException;
284
285
289 public boolean isBook() {
290 return false;
291 }
292
293
297 public boolean isChapter() {
298 return false;
299 }
300
301
304 public boolean isVerse() {
305 return false;
306 }
307
308
322 public static final int getChapter(Versification v11n, BibleBook lbook, String chapter) throws NoSuchVerseException {
323 if (isEndMarker(chapter)) {
324 return v11n.getLastChapter(lbook);
325 }
326 return parseInt(chapter);
327 }
328
329
344 public static final int getVerse(Versification v11n, BibleBook lbook, int lchapter, String verse) throws NoSuchVerseException {
345 if (isEndMarker(verse)) {
346 return v11n.getLastVerse(lbook, lchapter);
347 }
348 return parseInt(verse);
349 }
350
351
356 public int toInteger() {
357 return ordinal();
358 }
359
360
371 public static AccuracyType fromText(Versification v11n, String original, String[] parts) throws NoSuchVerseException {
372 return fromText(v11n, original, parts, null, null);
373 }
374
375
384 public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy) throws NoSuchVerseException {
385 return fromText(v11n, original, parts, verseAccuracy, null);
386 }
387
388
397 public static AccuracyType fromText(Versification v11n, String original, String[] parts, VerseRange basis) throws NoSuchVerseException {
398 return fromText(v11n, original, parts, null, basis);
399 }
400
401
422 public static AccuracyType fromText(Versification v11n, String original, String[] parts, AccuracyType verseAccuracy, VerseRange basis) throws NoSuchVerseException {
423 int partsLength = parts.length;
424 String lastPart = parts[partsLength - 1];
425 if (lastPart.length() > 0 && lastPart.charAt(0) == '!') {
426 --partsLength;
427 }
428 switch (partsLength) {
429 case 1:
430 if (v11n.isBook(parts[0])) {
431 return BOOK_ONLY;
432 }
433
434 checkValidChapterOrVerse(parts[0]);
437
438 if (verseAccuracy != null) {
440 if (verseAccuracy.isVerse()) {
441 return VERSE_ONLY;
442 }
443
444 if (verseAccuracy.isChapter()) {
445 return CHAPTER_ONLY;
446 }
447 }
448
449 if (basis != null) {
450 if (basis.isWholeChapter()) {
451 return CHAPTER_ONLY;
452 }
453 return VERSE_ONLY;
454 }
455
456 throw buildVersePartsException(original, parts);
457
458 case 2:
459 BibleBook pbook = v11n.getBook(parts[0]);
461 if (pbook == null) {
462 checkValidChapterOrVerse(parts[0]);
463 checkValidChapterOrVerse(parts[1]);
464 return CHAPTER_VERSE;
465 }
466
467 if (v11n.getLastChapter(pbook) == 1) {
468 return BOOK_VERSE;
469 }
470
471 return BOOK_CHAPTER;
472
473 case 3:
474 if (v11n.getBook(parts[0]) != null) {
475 checkValidChapterOrVerse(parts[1]);
476 checkValidChapterOrVerse(parts[2]);
477 return BOOK_VERSE;
478 }
479
480 throw buildVersePartsException(original, parts);
481
482 default:
483 throw buildVersePartsException(original, parts);
484 }
485 }
486
487 private static NoSuchVerseException buildVersePartsException(String original, String[] parts) {
488 StringBuilder buffer = new StringBuilder(original);
489 for (int i = 0; i < parts.length; i++) {
490 buffer.append(", ").append(parts[i]);
491 }
492 return new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", buffer.toString()));
494 }
495
496
504 private static void checkValidChapterOrVerse(String text) throws NoSuchVerseException {
505 if (!isEndMarker(text)) {
506 parseInt(text);
507 }
508 }
509
510
521 private static int parseInt(String text) throws NoSuchVerseException {
522 try {
523 return Integer.parseInt(new NumberShaper().unshape(text));
524 } catch (NumberFormatException ex) {
525 throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text));
528 }
529 }
530
531
538 private static boolean isEndMarker(String text) {
539 if (text.equals(AccuracyType.VERSE_END_MARK1)) {
540 return true;
541 }
542
543 if (text.equals(AccuracyType.VERSE_END_MARK2)) {
544 return true;
545 }
546
547 return false;
548 }
549
550 private static boolean hasSubIdentifier(String[] parts) {
551 String subIdentifier = parts[parts.length - 1];
552 return subIdentifier != null && subIdentifier.length() > 0 && subIdentifier.charAt(0) == '!';
553 }
554
555 protected static String getSubIdentifier(String[] parts) {
556 String subIdentifier = null;
557 if (hasSubIdentifier(parts)) {
558 subIdentifier = parts[parts.length - 1].substring(1);
559 }
560 return subIdentifier;
561 }
562
563
589 public static String[] tokenize(String input) throws NoSuchVerseException {
590 String[] args = {
592 null, null, null, null, null, null, null, null
593 };
594
595 int length = input.length();
598 char[] normalized = new char[length * 2];
600 char lastChar = '0'; char curChar = ' '; int tokenCount = 0;
604 int normalizedLength = 0;
605 int startIndex = 0;
606 String token = null;
607 boolean foundBoundary = false;
608 boolean foundSubIdentifier = false;
609 for (int i = 0; i < length; i++) {
610 curChar = input.charAt(i);
611 if (curChar == '!') {
612 foundSubIdentifier = true;
613 token = new String(normalized, startIndex, normalizedLength - startIndex);
614 args[tokenCount++] = token;
615 normalizedLength = 0;
616 }
617 if (foundSubIdentifier) {
618 normalized[normalizedLength++] = curChar;
619 continue;
620 }
621 boolean charIsDigit = curChar == '$' || Character.isDigit(curChar)
622 || (curChar == 'f' && (i + 1 < length ? input.charAt(i + 1) : ' ') == 'f' && !Character.isLetter(lastChar));
623 if (charIsDigit || Character.isLetter(curChar)) {
624 foundBoundary = true;
625 boolean charWasDigit = lastChar == '$' || Character.isDigit(lastChar) || (lastChar == 'f' && (i > 2 ? input.charAt(i - 2) : '0') == 'f');
626 if (charWasDigit || Character.isLetter(lastChar)) {
627 foundBoundary = false;
628 if (normalizedLength > 0 && charWasDigit != charIsDigit) {
631 foundBoundary = true;
632 }
633 }
634 if (foundBoundary) {
635 if (charIsDigit) {
639 if (tokenCount >= args.length) {
640 throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input));
642 }
643
644 token = new String(normalized, startIndex, normalizedLength - startIndex);
645 args[tokenCount++] = token;
646 normalizedLength = 0;
647 } else {
648 normalized[normalizedLength++] = ' ';
649 }
650 }
651 normalized[normalizedLength++] = curChar;
652 }
653
654 if (normalizedLength > 0) {
656 lastChar = curChar;
657 }
658 }
659
660 if (tokenCount >= args.length) {
661 throw new NoSuchVerseException(JSMsg.gettext("Too many parts to the Verse. (Parts are separated by any of {0})", input));
663 }
664
665 token = new String(normalized, startIndex, normalizedLength - startIndex);
666 args[tokenCount++] = token;
667
668 String[] results = new String[tokenCount];
669 System.arraycopy(args, 0, results, 0, tokenCount);
670 return results;
671 }
672
673
676 public static final String VERSE_ALLOWED_DELIMS = " :.";
677
678
681 public static final String VERSE_END_MARK1 = "$";
682
683
686 public static final String VERSE_END_MARK2 = "ff";
687 }
688