1
20 package org.crosswire.jsword.passage;
21
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.util.Iterator;
26
27 import org.crosswire.common.icu.NumberShaper;
28 import org.crosswire.common.util.ItemIterator;
29 import org.crosswire.jsword.JSMsg;
30 import org.crosswire.jsword.JSOtherMsg;
31 import org.crosswire.jsword.versification.BibleBook;
32 import org.crosswire.jsword.versification.BibleNames;
33 import org.crosswire.jsword.versification.Versification;
34 import org.crosswire.jsword.versification.system.Versifications;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38
54 public final class Verse implements VerseKey<Verse> {
55
68 public Verse(Versification v11n, BibleBook book, int chapter, int verse) {
69 this(v11n, book, chapter, verse, null);
70 }
71
72
87 public Verse(Versification v11n, BibleBook book, int chapter, int verse, String subIdentifier) {
88 this.v11n = v11n;
89 this.book = book;
90 this.chapter = chapter;
91 this.verse = verse;
92 this.subIdentifier = subIdentifier;
93 this.ordinal = v11n.getOrdinal(this);
94 }
95
96
117 public Verse(Versification v11n, BibleBook book, int chapter, int verse, boolean patchUp) {
118 if (!patchUp) {
119 throw new IllegalArgumentException(JSOtherMsg.lookupText("Use patchUp=true."));
120 }
121
122 this.v11n = v11n;
123 Verse patched = this.v11n.patch(book, chapter, verse);
124 this.book = patched.book;
125 this.chapter = patched.chapter;
126 this.verse = patched.verse;
127 this.ordinal = patched.ordinal;
128 }
129
130
142 public Verse(Versification v11n, int ordinal) {
143 Verse decoded = v11n.decodeOrdinal(ordinal);
144 this.v11n = v11n;
145 this.book = decoded.book;
146 this.chapter = decoded.chapter;
147 this.verse = decoded.verse;
148 this.ordinal = decoded.ordinal;
149 }
150
151
154 public boolean isWhole() {
155 return subIdentifier == null || subIdentifier.length() == 0;
156 }
157
158
161 public Verse getWhole() {
162 if (isWhole()) {
163 return this;
164 }
165 return new Verse(v11n, book, chapter, verse);
166 }
167
168 @Override
169 public String toString() {
170 return getName();
171 }
172
173
176 public String getName() {
177 return getName(null);
178 }
179
180
183 public String getName(Key base) {
184 if (base != null && !(base instanceof Verse)) {
185 return getName();
186 }
187
188 String verseName = doGetName((Verse) base);
189 if (shaper.canUnshape()) {
191 return shaper.shape(verseName);
192 }
193
194 return verseName;
195 }
196
197
200 public String getRootName() {
201 return BibleNames.instance().getShortName(book);
202 }
203
204
207 public String getOsisRef() {
208 return getOsisID();
209 }
210
211
214 public String getOsisID() {
215 final StringBuilder buf = getVerseIdentifier();
216 if (subIdentifier != null && subIdentifier.length() > 0) {
217 buf.append(VERSE_OSIS_SUB_PREFIX);
218 buf.append(subIdentifier);
219 }
220 return buf.toString();
221 }
222
223
226 public String getOsisIDNoSubIdentifier() {
227 return getVerseIdentifier().toString();
228 }
229
230
234 private StringBuilder getVerseIdentifier() {
235 StringBuilder buf = new StringBuilder();
236 buf.append(book.getOSIS());
237 buf.append(Verse.VERSE_OSIS_DELIM);
238 buf.append(chapter);
239 buf.append(Verse.VERSE_OSIS_DELIM);
240 buf.append(verse);
241 return buf;
242 }
243
244 @Override
245 public Verse clone() {
246 Verse copy = null;
247 try {
248 copy = (Verse) super.clone();
249 copy.v11n = this.v11n;
250 copy.book = this.book;
251 copy.chapter = this.chapter;
252 copy.verse = this.verse;
253 copy.ordinal = this.ordinal;
254 copy.subIdentifier = this.subIdentifier;
255 } catch (CloneNotSupportedException e) {
256 assert false : e;
257 }
258
259 return copy;
260 }
261
262 @Override
263 public boolean equals(Object obj) {
264 if (!(obj instanceof Verse)) {
266 return false;
267 }
268
269 Verse that = (Verse) obj;
270
271 return this.ordinal == that.ordinal
273 && this.v11n.equals(that.v11n)
274 && bothNullOrEqual(this.subIdentifier, that.subIdentifier);
275 }
276
277 @Override
278 public int hashCode() {
279 int result = 31 + ordinal;
280 result = 31 * result + ((v11n == null) ? 0 : v11n.hashCode());
281 return 31 * result + ((subIdentifier == null) ? 0 : subIdentifier.hashCode());
282 }
283
284
287 public int compareTo(Key obj) {
288 return this.ordinal - ((Verse) obj).ordinal;
289 }
290
291
294 public Versification getVersification() {
295 return v11n;
296 }
297
298
301 public Verse reversify(Versification newVersification) {
302 if (v11n.equals(newVersification)) {
303 return this;
304 }
305
306 try {
307 if (newVersification.validate(book, chapter, verse, true)) {
309 return new Verse(newVersification, book, chapter, verse);
310 }
311 } catch (NoSuchVerseException ex) {
312 log.error("Contract for validate was changed to thrown an exception when silent mode is true", ex);
314 }
315 return null;
316 }
317
318
323 public BibleBook getBook() {
324 return book;
325 }
326
327
332 public int getChapter() {
333 return chapter;
334 }
335
336
341 public int getVerse() {
342 return verse;
343 }
344
345
349 public String getSubIdentifier() {
350 return subIdentifier;
351 }
352
353
358 public int getOrdinal() {
359 return ordinal;
360 }
361
362
367 public Verse[] toVerseArray() {
368 return new Verse[] {
369 this
370 };
371 }
372
373
376 public Key getParent() {
377 return null;
378 }
379
380
386 public static boolean bothNullOrEqual(Object x, Object y) {
387 return x == y || (x != null && x.equals(y));
388 }
389
390
397 private String doGetName(Verse verseBase) {
398 StringBuilder buf = new StringBuilder();
399 if (book.isShortBook()) {
401 if (verseBase == null || verseBase.book != book) {
402 buf.append(BibleNames.instance().getPreferredName(book));
403 buf.append(Verse.VERSE_PREF_DELIM1);
404 buf.append(verse);
405 return buf.toString();
406 }
407
408 return Integer.toString(verse);
409 }
410
411 if (verseBase == null || verseBase.book != book) {
412 buf.append(BibleNames.instance().getPreferredName(book));
413 buf.append(Verse.VERSE_PREF_DELIM1);
414 buf.append(chapter);
415 buf.append(Verse.VERSE_PREF_DELIM2);
416 buf.append(verse);
417 return buf.toString();
418 }
419
420 if (verseBase.chapter != chapter) {
421 buf.append(chapter);
422 buf.append(Verse.VERSE_PREF_DELIM2);
423 buf.append(verse);
424 return buf.toString();
425 }
426
427 return Integer.toString(verse);
428 }
429
430
439 protected static int parseInt(String text) throws NoSuchVerseException {
440 try {
441 return Integer.parseInt(shaper.unshape(text));
442 } catch (NumberFormatException ex) {
443 throw new NoSuchVerseException(JSMsg.gettext("Cannot understand {0} as a chapter or verse.", text));
446 }
447 }
448
449
458 private void writeObject(ObjectOutputStream out) throws IOException {
459 out.defaultWriteObject();
460 out.writeUTF(v11n.getName());
461 }
462
463
474 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
475 in.defaultReadObject();
476 String v11nName = in.readUTF();
477 v11n = Versifications.instance().getVersification(v11nName);
478 Verse decoded = v11n.decodeOrdinal(ordinal);
479
480 this.book = decoded.book;
481 this.chapter = decoded.chapter;
482 this.verse = decoded.verse;
483 }
484
485
488 public boolean canHaveChildren() {
489 return false;
490 }
491
492
495 public int getChildCount() {
496 return 0;
497 }
498
499
502 public int getCardinality() {
503 return 1;
504 }
505
506
509 public boolean isEmpty() {
510 return false;
511 }
512
513
516 public boolean contains(Key key) {
517 return this.equals(key);
518 }
519
520
523 public Iterator<Key> iterator() {
524 return new ItemIterator<Key>(this);
525 }
526
527
530 public void addAll(Key key) {
531 throw new UnsupportedOperationException();
532 }
533
534
537 public void removeAll(Key key) {
538 throw new UnsupportedOperationException();
539 }
540
541
544 public void retainAll(Key key) {
545 throw new UnsupportedOperationException();
546 }
547
548
551 public void clear() {
552 }
554
555
558 public Key get(int index) {
559 if (index == 0) {
560 return this;
561 }
562 return null;
563 }
564
565
568 public int indexOf(Key that) {
569 if (this.equals(that)) {
570 return 0;
571 }
572 return -1;
573 }
574
575
578 public void blur(int by, RestrictionType restrict) {
579 throw new UnsupportedOperationException();
580 }
581
582
586 public static final char VERSE_OSIS_DELIM = '.';
587
588
591 public static final char VERSE_OSIS_SUB_PREFIX = '!';
592
593
596 public static final char VERSE_PREF_DELIM1 = ' ';
597
598
601 public static final char VERSE_PREF_DELIM2 = ':';
602
603
606 public static final Verse DEFAULT = new Verse(Versifications.instance().getVersification("KJV"), BibleBook.GEN, 1, 1);
607
608
611 private static NumberShaper shaper = new NumberShaper();
612
613
616 private transient Versification v11n;
617
618
621 private int ordinal;
622
623
626 private transient BibleBook book;
627
628
631 private transient int chapter;
632
633
636 private transient int verse;
637
638
643 private String subIdentifier;
644
645 private static final Logger log = LoggerFactory.getLogger(Verse.class);
646
647
650 private static final long serialVersionUID = -4033921076023185171L;
651 }
652