Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
VerseRange |
|
| 2.8448275862068964;2.845 | ||||
VerseRange$VerseIterator |
|
| 2.8448275862068964;2.845 |
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 java.io.IOException; | |
23 | import java.io.ObjectInputStream; | |
24 | import java.io.ObjectOutputStream; | |
25 | import java.util.Iterator; | |
26 | import java.util.NoSuchElementException; | |
27 | ||
28 | import org.crosswire.common.icu.NumberShaper; | |
29 | import org.crosswire.jsword.versification.BibleBook; | |
30 | import org.crosswire.jsword.versification.Versification; | |
31 | ||
32 | /** | |
33 | * A VerseRange is one step between a Verse and a Passage - it is a Verse plus a | |
34 | * verseCount. Every VerseRange has a start, a verseCount and an end. A | |
35 | * VerseRange is designed to be immutable. This is a necessary from a | |
36 | * collections point of view. A VerseRange should always be valid, although some | |
37 | * versions may not return any text for verses that they consider to be | |
38 | * miss-translated in some way. | |
39 | * | |
40 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
41 | * @author Joe Walker | |
42 | * @author DM Smith | |
43 | */ | |
44 | 0 | public final class VerseRange implements VerseKey<VerseRange> { |
45 | /** | |
46 | * The default VerseRange is a single verse - Genesis 1:1. I didn't want to | |
47 | * provide this constructor however, you are supposed to provide a default | |
48 | * ctor for all beans. For this reason I suggest you don't use it. | |
49 | * | |
50 | * @param v11n | |
51 | * The versification for the range | |
52 | */ | |
53 | public VerseRange(Versification v11n) { | |
54 | 0 | this(v11n, Verse.DEFAULT, Verse.DEFAULT); |
55 | 0 | } |
56 | ||
57 | /** | |
58 | * Construct a VerseRange from a Verse. The resultant VerseRange will be 1 | |
59 | * verse in verseCount. | |
60 | * | |
61 | * @param v11n | |
62 | * The versification for the range | |
63 | * @param start | |
64 | * The verse to start from | |
65 | */ | |
66 | public VerseRange(Versification v11n, Verse start) { | |
67 | 0 | this(v11n, start, start); |
68 | 0 | } |
69 | ||
70 | 0 | public VerseRange(Versification v11n, Verse start, Verse end) { |
71 | 0 | assert v11n != null; |
72 | 0 | assert start != null; |
73 | 0 | assert end != null; |
74 | ||
75 | 0 | this.v11n = v11n; |
76 | 0 | shaper = new NumberShaper(); |
77 | ||
78 | 0 | int distance = v11n.distance(start, end); |
79 | ||
80 | 0 | if (distance < 0) { |
81 | 0 | this.start = end; |
82 | 0 | this.end = start; |
83 | 0 | this.verseCount = calcVerseCount(); |
84 | 0 | } else if (distance == 0) { |
85 | 0 | this.start = start; |
86 | 0 | this.end = start; |
87 | 0 | this.verseCount = 1; |
88 | } else { | |
89 | 0 | this.start = start; |
90 | 0 | this.end = end; |
91 | 0 | this.verseCount = calcVerseCount(); |
92 | } | |
93 | 0 | } |
94 | ||
95 | /* (non-Javadoc) | |
96 | * @see org.crosswire.jsword.passage.VerseKey#getVersification() | |
97 | */ | |
98 | public Versification getVersification() { | |
99 | 0 | return v11n; |
100 | } | |
101 | ||
102 | /* (non-Javadoc) | |
103 | * @see org.crosswire.jsword.passage.VerseKey#reversify(org.crosswire.jsword.versification.Versification) | |
104 | */ | |
105 | public VerseRange reversify(Versification newVersification) { | |
106 | 0 | if (v11n.equals(newVersification)) { |
107 | 0 | return this; |
108 | } | |
109 | 0 | Verse newStart = start.reversify(newVersification); |
110 | 0 | if (newStart == null) { |
111 | 0 | return null; |
112 | } | |
113 | 0 | Verse newEnd = end.reversify(newVersification); |
114 | 0 | if (newEnd == null) { |
115 | 0 | return null; |
116 | } | |
117 | 0 | return new VerseRange(newVersification, newStart, newEnd); |
118 | } | |
119 | ||
120 | /* (non-Javadoc) | |
121 | * @see org.crosswire.jsword.passage.VerseKey#isWhole() | |
122 | */ | |
123 | public boolean isWhole() { | |
124 | 0 | return start.isWhole() && end.isWhole(); |
125 | } | |
126 | ||
127 | /* (non-Javadoc) | |
128 | * @see org.crosswire.jsword.passage.VerseKey#getWhole() | |
129 | */ | |
130 | public VerseRange getWhole() { | |
131 | 0 | if (isWhole()) { |
132 | 0 | return this; |
133 | } | |
134 | 0 | return new VerseRange(v11n, start.getWhole(), end.getWhole()); |
135 | } | |
136 | ||
137 | /** | |
138 | * Merge 2 VerseRanges together. The resulting range will encompass | |
139 | * Everything in-between the extremities of the 2 ranges. | |
140 | * | |
141 | * @param a | |
142 | * The first verse range to be merged | |
143 | * @param b | |
144 | * The second verse range to be merged | |
145 | */ | |
146 | 0 | public VerseRange(VerseRange a, VerseRange b) { |
147 | 0 | v11n = a.v11n; |
148 | 0 | shaper = new NumberShaper(); |
149 | 0 | start = v11n.min(a.getStart(), b.getStart()); |
150 | 0 | end = v11n.max(a.getEnd(), b.getEnd()); |
151 | 0 | verseCount = calcVerseCount(); |
152 | 0 | } |
153 | ||
154 | /* (non-Javadoc) | |
155 | * @see org.crosswire.jsword.passage.Key#getName() | |
156 | */ | |
157 | public String getName() { | |
158 | 0 | return getName(null); |
159 | } | |
160 | ||
161 | /* (non-Javadoc) | |
162 | * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key) | |
163 | */ | |
164 | public String getName(Key base) { | |
165 | 0 | if (PassageUtil.isPersistentNaming() && originalName != null) { |
166 | 0 | return originalName; |
167 | } | |
168 | ||
169 | 0 | String rangeName = doGetName(base); |
170 | // Only shape it if it can be unshaped. | |
171 | 0 | if (shaper.canUnshape()) { |
172 | 0 | return shaper.shape(rangeName); |
173 | } | |
174 | ||
175 | 0 | return rangeName; |
176 | } | |
177 | ||
178 | /* (non-Javadoc) | |
179 | * @see org.crosswire.jsword.passage.Key#getRootName() | |
180 | */ | |
181 | public String getRootName() { | |
182 | 0 | return start.getRootName(); |
183 | } | |
184 | ||
185 | /* (non-Javadoc) | |
186 | * @see org.crosswire.jsword.passage.Key#getOsisRef() | |
187 | */ | |
188 | public String getOsisRef() { | |
189 | 0 | BibleBook startBook = start.getBook(); |
190 | 0 | BibleBook endBook = end.getBook(); |
191 | 0 | int startChapter = start.getChapter(); |
192 | 0 | int endChapter = end.getChapter(); |
193 | ||
194 | // If this is in 2 separate books | |
195 | 0 | if (startBook != endBook) { |
196 | 0 | StringBuilder buf = new StringBuilder(); |
197 | 0 | if (v11n.isStartOfBook(start)) { |
198 | 0 | buf.append(startBook.getOSIS()); |
199 | 0 | } else if (v11n.isStartOfChapter(start)) { |
200 | 0 | buf.append(startBook.getOSIS()); |
201 | 0 | buf.append(Verse.VERSE_OSIS_DELIM); |
202 | 0 | buf.append(startChapter); |
203 | } else { | |
204 | 0 | buf.append(start.getOsisRef()); |
205 | } | |
206 | ||
207 | 0 | buf.append(VerseRange.RANGE_PREF_DELIM); |
208 | ||
209 | 0 | if (v11n.isEndOfBook(end)) { |
210 | 0 | buf.append(endBook.getOSIS()); |
211 | 0 | } else if (v11n.isEndOfChapter(end)) { |
212 | 0 | buf.append(endBook.getOSIS()); |
213 | 0 | buf.append(Verse.VERSE_OSIS_DELIM); |
214 | 0 | buf.append(endChapter); |
215 | } else { | |
216 | 0 | buf.append(end.getOsisRef()); |
217 | } | |
218 | ||
219 | 0 | return buf.toString(); |
220 | } | |
221 | ||
222 | // This range is exactly a whole book | |
223 | 0 | if (isWholeBook()) { |
224 | // Just report the name of the book, we don't need to worry | |
225 | // about the | |
226 | // base since we start at the start of a book, and should have | |
227 | // been | |
228 | // recently normalized() | |
229 | 0 | return startBook.getOSIS(); |
230 | } | |
231 | ||
232 | // If this is 2 separate chapters in the same book | |
233 | 0 | if (startChapter != endChapter) { |
234 | 0 | StringBuilder buf = new StringBuilder(); |
235 | 0 | if (v11n.isStartOfChapter(start)) { |
236 | 0 | buf.append(startBook.getOSIS()); |
237 | 0 | buf.append(Verse.VERSE_OSIS_DELIM); |
238 | 0 | buf.append(startChapter); |
239 | } else { | |
240 | 0 | buf.append(start.getOsisRef()); |
241 | } | |
242 | ||
243 | 0 | buf.append(VerseRange.RANGE_PREF_DELIM); |
244 | ||
245 | 0 | if (v11n.isEndOfChapter(end)) { |
246 | 0 | buf.append(endBook.getOSIS()); |
247 | 0 | buf.append(Verse.VERSE_OSIS_DELIM); |
248 | 0 | buf.append(endChapter); |
249 | } else { | |
250 | 0 | buf.append(end.getOsisRef()); |
251 | } | |
252 | ||
253 | 0 | return buf.toString(); |
254 | } | |
255 | ||
256 | // If this range is exactly a whole chapter | |
257 | 0 | if (isWholeChapter()) { |
258 | // Just report the name of the book and the chapter | |
259 | 0 | StringBuilder buf = new StringBuilder(); |
260 | 0 | buf.append(startBook.getOSIS()); |
261 | 0 | buf.append(Verse.VERSE_OSIS_DELIM); |
262 | 0 | buf.append(startChapter); |
263 | 0 | return buf.toString(); |
264 | } | |
265 | ||
266 | // If this is 2 separate verses | |
267 | 0 | if (start.getVerse() != end.getVerse()) { |
268 | 0 | StringBuilder buf = new StringBuilder(); |
269 | 0 | buf.append(start.getOsisRef()); |
270 | 0 | buf.append(VerseRange.RANGE_PREF_DELIM); |
271 | 0 | buf.append(end.getOsisRef()); |
272 | 0 | return buf.toString(); |
273 | } | |
274 | ||
275 | // The range is a single verse | |
276 | 0 | return start.getOsisRef(); |
277 | } | |
278 | ||
279 | /* (non-Javadoc) | |
280 | * @see org.crosswire.jsword.passage.Key#getOsisID() | |
281 | */ | |
282 | public String getOsisID() { | |
283 | ||
284 | // This range is exactly a whole book | |
285 | 0 | if (isWholeBook()) { |
286 | // Just report the name of the book, we don't need to worry | |
287 | // about the base since we start at the start of a book, and | |
288 | // should have been recently normalized() | |
289 | 0 | return start.getBook().getOSIS(); |
290 | } | |
291 | ||
292 | // If this range is exactly a whole chapter | |
293 | 0 | if (isWholeChapter()) { |
294 | // Just report the name of the book and the chapter | |
295 | 0 | return start.getBook().getOSIS() + Verse.VERSE_OSIS_DELIM + start.getChapter(); |
296 | } | |
297 | ||
298 | 0 | int startOrdinal = start.getOrdinal(); |
299 | 0 | int endOrdinal = end.getOrdinal(); |
300 | ||
301 | // to see if it is wholly contained in the range and output it if it is. | |
302 | ||
303 | // Estimate the size of the buffer: book.dd.dd (where book is 3-5, 3 typical) | |
304 | 0 | StringBuilder buf = new StringBuilder((endOrdinal - startOrdinal + 1) * 10); |
305 | 0 | buf.append(start.getOsisID()); |
306 | 0 | for (int i = startOrdinal + 1; i < endOrdinal; i++) { |
307 | 0 | buf.append(AbstractPassage.REF_OSIS_DELIM); |
308 | 0 | buf.append(v11n.decodeOrdinal(i).getOsisID()); |
309 | } | |
310 | ||
311 | // It just might be a single verse range! | |
312 | 0 | if (startOrdinal != endOrdinal) { |
313 | 0 | buf.append(AbstractPassage.REF_OSIS_DELIM); |
314 | 0 | buf.append(end.getOsisID()); |
315 | } | |
316 | ||
317 | 0 | return buf.toString(); |
318 | } | |
319 | ||
320 | @Override | |
321 | public String toString() { | |
322 | 0 | return getName(); |
323 | } | |
324 | ||
325 | /** | |
326 | * Fetch the first verse in this range. | |
327 | * | |
328 | * @return The first verse in the range | |
329 | */ | |
330 | public Verse getStart() { | |
331 | 0 | return start; |
332 | } | |
333 | ||
334 | /** | |
335 | * Fetch the last verse in this range. | |
336 | * | |
337 | * @return The last verse in the range | |
338 | */ | |
339 | public Verse getEnd() { | |
340 | 0 | return end; |
341 | } | |
342 | ||
343 | @Override | |
344 | public VerseRange clone() { | |
345 | // This gets us a shallow copy | |
346 | 0 | VerseRange copy = null; |
347 | try { | |
348 | 0 | copy = (VerseRange) super.clone(); |
349 | 0 | copy.start = start; |
350 | 0 | copy.end = end; |
351 | 0 | copy.verseCount = verseCount; |
352 | 0 | copy.originalName = originalName; |
353 | 0 | copy.shaper = new NumberShaper(); |
354 | 0 | copy.v11n = v11n; |
355 | 0 | } catch (CloneNotSupportedException e) { |
356 | 0 | assert false : e; |
357 | 0 | } |
358 | ||
359 | 0 | return copy; |
360 | } | |
361 | ||
362 | @Override | |
363 | public boolean equals(Object obj) { | |
364 | 0 | if (!(obj instanceof VerseRange)) { |
365 | 0 | return false; |
366 | } | |
367 | 0 | VerseRange vr = (VerseRange) obj; |
368 | 0 | return verseCount == vr.verseCount && start.equals(vr.start) && v11n.equals(vr.v11n); |
369 | } | |
370 | ||
371 | @Override | |
372 | public int hashCode() { | |
373 | 0 | int result = start.hashCode(); |
374 | 0 | result = 31 * result + verseCount; |
375 | 0 | return 31 * result + ((v11n == null) ? 0 : v11n.hashCode()); |
376 | } | |
377 | ||
378 | /* (non-Javadoc) | |
379 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
380 | */ | |
381 | public int compareTo(Key obj) { | |
382 | 0 | VerseRange that = (VerseRange) obj; |
383 | ||
384 | 0 | int result = start.compareTo(that.start); |
385 | 0 | return result == 0 ? this.verseCount - that.verseCount : result; |
386 | } | |
387 | ||
388 | /** | |
389 | * Are the 2 VerseRanges in question contiguous. that is - could they be | |
390 | * represented by a single VerseRange. Note that one range could be entirely | |
391 | * contained within the other and they would be considered adjacentTo() For | |
392 | * example Gen 1:1-2 is adjacent to Gen 1:1-5 and Gen 1:3-4 but not to Gen | |
393 | * 1:4-10. Also Gen 1:29-30 is adjacent to Gen 2:1-10 | |
394 | * | |
395 | * @param that | |
396 | * The VerseRange to compare to | |
397 | * @return true if the ranges are adjacent | |
398 | */ | |
399 | public boolean adjacentTo(VerseRange that) { | |
400 | 0 | int thatStart = that.getStart().getOrdinal(); |
401 | 0 | int thatEnd = that.getEnd().getOrdinal(); |
402 | 0 | int thisStart = getStart().getOrdinal(); |
403 | 0 | int thisEnd = getEnd().getOrdinal(); |
404 | ||
405 | // if that starts inside or is next to this we are adjacent. | |
406 | 0 | if (thatStart >= thisStart - 1 && thatStart <= thisEnd + 1) { |
407 | 0 | return true; |
408 | } | |
409 | ||
410 | // if this starts inside or is next to that we are adjacent. | |
411 | 0 | if (thisStart >= thatStart - 1 && thisStart <= thatEnd + 1) { |
412 | 0 | return true; |
413 | } | |
414 | ||
415 | // otherwise we're not adjacent | |
416 | 0 | return false; |
417 | } | |
418 | ||
419 | /** | |
420 | * Do the 2 VerseRanges in question actually overlap. This is slightly more | |
421 | * restrictive than the adjacentTo() test which could be satisfied by ranges | |
422 | * like Gen 1:1-2 and Gen 1:3-4. overlaps() however would return false given | |
423 | * these ranges. For example Gen 1:1-2 is adjacent to Gen 1:1-5 but not to | |
424 | * Gen 1:3-4 not to Gen 1:4-10. Also Gen 1:29-30 does not overlap Gen 2:1-10 | |
425 | * | |
426 | * @param that | |
427 | * The VerseRange to compare to | |
428 | * @return true if the ranges are adjacent | |
429 | */ | |
430 | public boolean overlaps(VerseRange that) { | |
431 | 0 | int thatStart = that.getStart().getOrdinal(); |
432 | 0 | int thatEnd = that.getEnd().getOrdinal(); |
433 | 0 | int thisStart = getStart().getOrdinal(); |
434 | 0 | int thisEnd = getEnd().getOrdinal(); |
435 | ||
436 | // if that starts inside this we are adjacent. | |
437 | 0 | if (thatStart >= thisStart && thatStart <= thisEnd) { |
438 | 0 | return true; |
439 | } | |
440 | ||
441 | // if this starts inside that we are adjacent. | |
442 | 0 | if (thisStart >= thatStart && thisStart <= thatEnd) { |
443 | 0 | return true; |
444 | } | |
445 | ||
446 | // otherwise we're not adjacent | |
447 | 0 | return false; |
448 | } | |
449 | ||
450 | /** | |
451 | * Is the given verse entirely within our range. For example if this = | |
452 | * "Gen 1:1-31" then: <tt>contains(Verse("Gen 1:3")) == true</tt> | |
453 | * <tt>contains(Verse("Gen 2:1")) == false</tt> | |
454 | * | |
455 | * @param that | |
456 | * The Verse to compare to | |
457 | * @return true if we contain it. | |
458 | */ | |
459 | public boolean contains(Verse that) { | |
460 | 0 | return v11n.distance(start, that) >= 0 && v11n.distance(that, end) >= 0; |
461 | } | |
462 | ||
463 | /** | |
464 | * Is the given range within our range. For example if this = "Gen 1:1-31" | |
465 | * then: <tt>this.contains(Verse("Gen 1:3-10")) == true</tt> | |
466 | * <tt>this.contains(Verse("Gen 2:1-1")) == false</tt> | |
467 | * | |
468 | * @param that | |
469 | * The Verse to compare to | |
470 | * @return true if we contain it. | |
471 | */ | |
472 | public boolean contains(VerseRange that) { | |
473 | 0 | return v11n.distance(start, that.getStart()) >= 0 && v11n.distance(that.getEnd(), end) >= 0; |
474 | } | |
475 | ||
476 | /* (non-Javadoc) | |
477 | * @see org.crosswire.jsword.passage.Key#contains(org.crosswire.jsword.passage.Key) | |
478 | */ | |
479 | public boolean contains(Key key) { | |
480 | 0 | if (key instanceof VerseRange) { |
481 | 0 | return contains((VerseRange) key); |
482 | } | |
483 | 0 | if (key instanceof Verse) { |
484 | 0 | return contains((Verse) key); |
485 | } | |
486 | 0 | return false; |
487 | } | |
488 | ||
489 | /** | |
490 | * Does this range represent exactly one chapter, no more or less. | |
491 | * | |
492 | * @return true if we are exactly one chapter. | |
493 | */ | |
494 | public boolean isWholeChapter() { | |
495 | 0 | return v11n.isSameChapter(start, end) && isWholeChapters(); |
496 | } | |
497 | ||
498 | /** | |
499 | * Does this range represent a number of whole chapters | |
500 | * | |
501 | * @return true if we are a whole number of chapters. | |
502 | */ | |
503 | public boolean isWholeChapters() { | |
504 | 0 | return v11n.isStartOfChapter(start) && v11n.isEndOfChapter(end); |
505 | } | |
506 | ||
507 | /** | |
508 | * Does this range represent exactly one book, no more or less. | |
509 | * | |
510 | * @return true if we are exactly one book. | |
511 | */ | |
512 | public boolean isWholeBook() { | |
513 | 0 | return v11n.isSameBook(start, end) && isWholeBooks(); |
514 | } | |
515 | ||
516 | /** | |
517 | * Does this range represent a whole number of books. | |
518 | * | |
519 | * @return true if we are a whole number of books. | |
520 | */ | |
521 | public boolean isWholeBooks() { | |
522 | 0 | return v11n.isStartOfBook(start) && v11n.isEndOfBook(end); |
523 | } | |
524 | ||
525 | /** | |
526 | * Does this range occupy more than one book; | |
527 | * | |
528 | * @return true if we occupy 2 or more books | |
529 | */ | |
530 | public boolean isMultipleBooks() { | |
531 | 0 | return start.getBook() != end.getBook(); |
532 | } | |
533 | ||
534 | /** | |
535 | * Create an array of Verses | |
536 | * | |
537 | * @return The array of verses that this makes up | |
538 | */ | |
539 | public Verse[] toVerseArray() { | |
540 | 0 | Verse[] retcode = new Verse[verseCount]; |
541 | 0 | int ord = start.getOrdinal(); |
542 | 0 | for (int i = 0; i < verseCount; i++) { |
543 | 0 | retcode[i] = v11n.decodeOrdinal(ord + i); |
544 | } | |
545 | ||
546 | 0 | return retcode; |
547 | } | |
548 | ||
549 | /** | |
550 | * Enumerate the subranges in this range | |
551 | * | |
552 | * @param restrict | |
553 | * @return a range iterator | |
554 | */ | |
555 | public Iterator<VerseRange> rangeIterator(RestrictionType restrict) { | |
556 | 0 | return new AbstractPassage.VerseRangeIterator(v11n, iterator(), restrict); |
557 | } | |
558 | ||
559 | /* (non-Javadoc) | |
560 | * @see org.crosswire.jsword.passage.Key#getParent() | |
561 | */ | |
562 | public Key getParent() { | |
563 | 0 | return parent; |
564 | } | |
565 | ||
566 | /** | |
567 | * Set a parent Key. This allows us to follow the Key interface more | |
568 | * closely, although the concept of a parent for a verse is fairly alien. | |
569 | * | |
570 | * @param parent | |
571 | * The parent Key for this verse | |
572 | */ | |
573 | public void setParent(Key parent) { | |
574 | 0 | this.parent = parent; |
575 | 0 | } |
576 | ||
577 | /** | |
578 | * Create a VerseRange that is the stuff left of VerseRange a when you | |
579 | * remove the stuff in VerseRange b. | |
580 | * | |
581 | * @param a | |
582 | * Verses at the start or end of b | |
583 | * @param b | |
584 | * All the verses | |
585 | * @return A list of the Verses outstanding | |
586 | */ | |
587 | public static VerseRange[] remainder(VerseRange a, VerseRange b) { | |
588 | 0 | VerseRange rstart = null; |
589 | 0 | VerseRange rend = null; |
590 | ||
591 | 0 | Versification v11n = a.getVersification(); |
592 | ||
593 | // If a starts before b get the Range of the prequel | |
594 | 0 | if (v11n.distance(a.getStart(), b.getStart()) > 0) { |
595 | 0 | rstart = new VerseRange(v11n, a.getStart(), v11n.subtract(b.getEnd(), 1)); |
596 | } | |
597 | ||
598 | // If a ends after b get the Range of the sequel | |
599 | 0 | if (v11n.distance(a.getEnd(), b.getEnd()) < 0) { |
600 | 0 | rend = new VerseRange(v11n, v11n.add(b.getEnd(), 1), a.getEnd()); |
601 | } | |
602 | ||
603 | 0 | if (rstart == null) { |
604 | 0 | if (rend == null) { |
605 | 0 | return new VerseRange[] {}; |
606 | } | |
607 | 0 | return new VerseRange[] { |
608 | rend | |
609 | }; | |
610 | } | |
611 | ||
612 | 0 | if (rend == null) { |
613 | 0 | return new VerseRange[] { |
614 | rstart | |
615 | }; | |
616 | } | |
617 | 0 | return new VerseRange[] { |
618 | rstart, rend | |
619 | }; | |
620 | } | |
621 | ||
622 | /** | |
623 | * Create a VerseRange that is the stuff in VerseRange a that is also | |
624 | * in VerseRange b. | |
625 | * | |
626 | * @param a | |
627 | * The verses that you might want | |
628 | * @param b | |
629 | * The verses that you definitely don't | |
630 | * @return A list of the Verses outstanding | |
631 | */ | |
632 | public static VerseRange intersection(VerseRange a, VerseRange b) { | |
633 | 0 | Versification v11n = a.getVersification(); |
634 | 0 | Verse newStart = v11n.max(a.getStart(), b.getStart()); |
635 | 0 | Verse newEnd = v11n.min(a.getEnd(), b.getEnd()); |
636 | ||
637 | 0 | if (v11n.distance(newStart, newEnd) >= 0) { |
638 | 0 | return new VerseRange(a.getVersification(), newStart, newEnd); |
639 | } | |
640 | ||
641 | 0 | return null; |
642 | } | |
643 | ||
644 | private String doGetName(Key base) { | |
645 | // Cache these we're going to be using them a lot. | |
646 | 0 | BibleBook startBook = start.getBook(); |
647 | 0 | int startChapter = start.getChapter(); |
648 | 0 | int startVerse = start.getVerse(); |
649 | 0 | BibleBook endBook = end.getBook(); |
650 | 0 | int endChapter = end.getChapter(); |
651 | 0 | int endVerse = end.getVerse(); |
652 | ||
653 | // If this is in 2 separate books | |
654 | 0 | if (startBook != endBook) { |
655 | // This range is exactly a whole book | |
656 | 0 | if (isWholeBooks()) { |
657 | // Just report the name of the book, we don't need to worry | |
658 | // about the base since we start at the start of a book, | |
659 | // and should have been recently normalized() | |
660 | 0 | return v11n.getPreferredName(startBook) + VerseRange.RANGE_PREF_DELIM + v11n.getPreferredName(endBook); |
661 | } | |
662 | ||
663 | // If this range is exactly a whole chapter | |
664 | 0 | if (isWholeChapters()) { |
665 | // Just report book and chapter names | |
666 | 0 | return v11n.getPreferredName(startBook) + Verse.VERSE_PREF_DELIM1 + startChapter + VerseRange.RANGE_PREF_DELIM |
667 | + v11n.getPreferredName(endBook) + Verse.VERSE_PREF_DELIM1 + endChapter; | |
668 | } | |
669 | ||
670 | 0 | if (v11n.isChapterIntro(start)) { |
671 | 0 | return v11n.getPreferredName(startBook) + Verse.VERSE_PREF_DELIM1 + startChapter + VerseRange.RANGE_PREF_DELIM + end.getName(base); |
672 | } | |
673 | 0 | if (v11n.isBookIntro(start)) { |
674 | 0 | return v11n.getPreferredName(startBook) + VerseRange.RANGE_PREF_DELIM + end.getName(base); |
675 | } | |
676 | 0 | return start.getName(base) + VerseRange.RANGE_PREF_DELIM + end.getName(base); |
677 | } | |
678 | ||
679 | // This range is exactly a whole book | |
680 | 0 | if (isWholeBook()) { |
681 | // Just report the name of the book, we don't need to worry about | |
682 | // the | |
683 | // base since we start at the start of a book, and should have been | |
684 | // recently normalized() | |
685 | 0 | return v11n.getPreferredName(startBook); |
686 | } | |
687 | ||
688 | // If this is 2 separate chapters | |
689 | 0 | if (startChapter != endChapter) { |
690 | // If this range is a whole number of chapters | |
691 | 0 | if (isWholeChapters()) { |
692 | // Just report the name of the book and the chapters | |
693 | 0 | return v11n.getPreferredName(startBook) + Verse.VERSE_PREF_DELIM1 + startChapter + VerseRange.RANGE_PREF_DELIM + endChapter; |
694 | } | |
695 | ||
696 | 0 | return start.getName(base) + VerseRange.RANGE_PREF_DELIM + endChapter + Verse.VERSE_PREF_DELIM2 + endVerse; |
697 | } | |
698 | ||
699 | // If this range is exactly a whole chapter | |
700 | 0 | if (isWholeChapter()) { |
701 | // Just report the name of the book and the chapter | |
702 | 0 | return v11n.getPreferredName(startBook) + Verse.VERSE_PREF_DELIM1 + startChapter; |
703 | } | |
704 | ||
705 | // If this is 2 separate verses | |
706 | 0 | if (startVerse != endVerse) { |
707 | 0 | return start.getName(base) + VerseRange.RANGE_PREF_DELIM + endVerse; |
708 | } | |
709 | ||
710 | // The range is a single verse | |
711 | 0 | return start.getName(base); |
712 | } | |
713 | ||
714 | /** | |
715 | * Calculate the last verse in this range. | |
716 | * | |
717 | * @return The last verse in the range | |
718 | */ | |
719 | private Verse calcEnd() { | |
720 | 0 | if (verseCount == 1) { |
721 | 0 | return start; |
722 | } | |
723 | 0 | return v11n.add(start, verseCount - 1); |
724 | } | |
725 | ||
726 | /** | |
727 | * Calculate how many verses in this range | |
728 | * | |
729 | * @return The number of verses. Always>= 1. | |
730 | */ | |
731 | private int calcVerseCount() { | |
732 | 0 | return v11n.distance(start, end) + 1; |
733 | } | |
734 | ||
735 | /** | |
736 | * Check to see that everything is ok with the Data | |
737 | */ | |
738 | private void verifyData() { | |
739 | 0 | assert verseCount == calcVerseCount() : "start=" + start + ", end=" + end + ", verseCount=" + verseCount; |
740 | 0 | } |
741 | ||
742 | /** | |
743 | * Write out the object to the given ObjectOutputStream | |
744 | * | |
745 | * @param out | |
746 | * The stream to write our state to | |
747 | * @throws IOException | |
748 | * If the write fails | |
749 | * @serialData Write the ordinal number of this verse | |
750 | */ | |
751 | private void writeObject(ObjectOutputStream out) throws IOException { | |
752 | 0 | out.defaultWriteObject(); |
753 | ||
754 | // Ignore the original name. Is this wise? | |
755 | // I am expecting that people are not that fussed about it and | |
756 | // it could make everything far more verbose | |
757 | 0 | } |
758 | ||
759 | /** | |
760 | * Write out the object to the given ObjectOutputStream | |
761 | * | |
762 | * @param in | |
763 | * The stream to read our state from | |
764 | * @throws IOException | |
765 | * If the write fails | |
766 | * @throws ClassNotFoundException | |
767 | * If the read data is incorrect | |
768 | * @serialData Write the ordinal number of this verse | |
769 | */ | |
770 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | |
771 | 0 | in.defaultReadObject(); |
772 | ||
773 | 0 | end = calcEnd(); |
774 | 0 | shaper = new NumberShaper(); |
775 | ||
776 | 0 | verifyData(); |
777 | ||
778 | // We are ignoring the originalName and parent. | |
779 | 0 | } |
780 | ||
781 | /** | |
782 | * Iterate over the Verses in the VerseRange | |
783 | */ | |
784 | 0 | private static final class VerseIterator implements Iterator<Key> { |
785 | /** | |
786 | * Ctor | |
787 | */ | |
788 | 0 | protected VerseIterator(VerseRange range) { |
789 | 0 | v11n = range.getVersification(); |
790 | 0 | nextVerse = range.getStart(); |
791 | 0 | total = range.getCardinality(); |
792 | 0 | count = 0; |
793 | 0 | } |
794 | ||
795 | /* (non-Javadoc) | |
796 | * @see java.util.Iterator#hasNext() | |
797 | */ | |
798 | public boolean hasNext() { | |
799 | 0 | return nextVerse != null; |
800 | } | |
801 | ||
802 | /* (non-Javadoc) | |
803 | * @see java.util.Iterator#next() | |
804 | */ | |
805 | public Key next() throws NoSuchElementException { | |
806 | 0 | if (nextVerse == null) { |
807 | 0 | throw new NoSuchElementException(); |
808 | } | |
809 | 0 | Verse currentVerse = nextVerse; |
810 | 0 | nextVerse = ++count < total ? v11n.next(nextVerse) : null; |
811 | 0 | return currentVerse; |
812 | } | |
813 | ||
814 | /* (non-Javadoc) | |
815 | * @see java.util.Iterator#remove() | |
816 | */ | |
817 | public void remove() throws UnsupportedOperationException { | |
818 | 0 | throw new UnsupportedOperationException(); |
819 | } | |
820 | ||
821 | private Versification v11n; | |
822 | private Verse nextVerse; | |
823 | private int count; | |
824 | private int total; | |
825 | } | |
826 | ||
827 | /* (non-Javadoc) | |
828 | * @see org.crosswire.jsword.passage.Key#canHaveChildren() | |
829 | */ | |
830 | public boolean canHaveChildren() { | |
831 | 0 | return false; |
832 | } | |
833 | ||
834 | /* (non-Javadoc) | |
835 | * @see org.crosswire.jsword.passage.Key#getChildCount() | |
836 | */ | |
837 | public int getChildCount() { | |
838 | 0 | return 0; |
839 | } | |
840 | ||
841 | /* (non-Javadoc) | |
842 | * @see org.crosswire.jsword.passage.Key#getCardinality() | |
843 | */ | |
844 | public int getCardinality() { | |
845 | 0 | return verseCount; |
846 | } | |
847 | ||
848 | /* (non-Javadoc) | |
849 | * @see org.crosswire.jsword.passage.Key#isEmpty() | |
850 | */ | |
851 | public boolean isEmpty() { | |
852 | 0 | return verseCount == 0; |
853 | } | |
854 | ||
855 | /* | |
856 | * (non-Javadoc) | |
857 | * | |
858 | * @see org.crosswire.jsword.passage.Key#iterator() | |
859 | */ | |
860 | public Iterator<Key> iterator() { | |
861 | 0 | return new VerseIterator(this); |
862 | } | |
863 | ||
864 | /* (non-Javadoc) | |
865 | * @see org.crosswire.jsword.passage.Key#addAll(org.crosswire.jsword.passage.Key) | |
866 | */ | |
867 | public void addAll(Key key) { | |
868 | 0 | throw new UnsupportedOperationException(); |
869 | } | |
870 | ||
871 | /* (non-Javadoc) | |
872 | * @see org.crosswire.jsword.passage.Key#removeAll(org.crosswire.jsword.passage.Key) | |
873 | */ | |
874 | public void removeAll(Key key) { | |
875 | 0 | throw new UnsupportedOperationException(); |
876 | } | |
877 | ||
878 | /* (non-Javadoc) | |
879 | * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key) | |
880 | */ | |
881 | public void retainAll(Key key) { | |
882 | 0 | throw new UnsupportedOperationException(); |
883 | } | |
884 | ||
885 | /* (non-Javadoc) | |
886 | * @see org.crosswire.jsword.passage.Key#clear() | |
887 | */ | |
888 | public void clear() { | |
889 | 0 | } |
890 | ||
891 | /* (non-Javadoc) | |
892 | * @see org.crosswire.jsword.passage.Key#get(int) | |
893 | */ | |
894 | public Key get(int index) { | |
895 | 0 | return null; |
896 | } | |
897 | ||
898 | /* (non-Javadoc) | |
899 | * @see org.crosswire.jsword.passage.Key#indexOf(org.crosswire.jsword.passage.Key) | |
900 | */ | |
901 | public int indexOf(Key that) { | |
902 | 0 | return -1; |
903 | } | |
904 | ||
905 | /* (non-Javadoc) | |
906 | * @see org.crosswire.jsword.passage.Key#blur(int, org.crosswire.jsword.passage.RestrictionType) | |
907 | */ | |
908 | public void blur(int by, RestrictionType restrict) { | |
909 | 0 | VerseRange newRange = restrict.blur(v11n, this, by, by); |
910 | 0 | start = newRange.start; |
911 | 0 | end = newRange.end; |
912 | 0 | verseCount = newRange.verseCount; |
913 | 0 | } |
914 | ||
915 | /** | |
916 | * What characters can we use to separate the 2 parts to a VerseRanges | |
917 | */ | |
918 | public static final char RANGE_OSIS_DELIM = '-'; | |
919 | ||
920 | /** | |
921 | * What characters should we use to separate VerseRange parts on output | |
922 | */ | |
923 | public static final char RANGE_PREF_DELIM = RANGE_OSIS_DELIM; | |
924 | ||
925 | /** | |
926 | * The Versification with which this range is defined. | |
927 | */ | |
928 | private transient Versification v11n; | |
929 | ||
930 | /** | |
931 | * The start of the range | |
932 | */ | |
933 | private Verse start; | |
934 | ||
935 | /** | |
936 | * The number of verses in the range | |
937 | */ | |
938 | private int verseCount; | |
939 | ||
940 | /** | |
941 | * The last verse. Not actually needed, since it can be computed. | |
942 | */ | |
943 | private transient Verse end; | |
944 | ||
945 | /** | |
946 | * Allow the conversion to and from other number representations. | |
947 | */ | |
948 | private transient NumberShaper shaper; | |
949 | ||
950 | /** | |
951 | * The parent key. | |
952 | */ | |
953 | private transient Key parent; | |
954 | ||
955 | /** | |
956 | * The original string for picky users | |
957 | */ | |
958 | private transient String originalName; | |
959 | ||
960 | /** | |
961 | * Serialization ID | |
962 | */ | |
963 | static final long serialVersionUID = 8307795549869653580L; | |
964 | } |