Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractPassage |
|
| 3.0;3 | ||||
AbstractPassage$VerseRangeIterator |
|
| 3.0;3 |
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.BufferedReader; | |
23 | import java.io.BufferedWriter; | |
24 | import java.io.IOException; | |
25 | import java.io.ObjectInputStream; | |
26 | import java.io.ObjectOutputStream; | |
27 | import java.io.Reader; | |
28 | import java.io.Writer; | |
29 | import java.util.ArrayList; | |
30 | import java.util.BitSet; | |
31 | import java.util.Iterator; | |
32 | import java.util.List; | |
33 | import java.util.NoSuchElementException; | |
34 | ||
35 | import org.crosswire.common.util.StringUtil; | |
36 | import org.crosswire.jsword.JSMsg; | |
37 | import org.crosswire.jsword.JSOtherMsg; | |
38 | import org.crosswire.jsword.versification.BibleBook; | |
39 | import org.crosswire.jsword.versification.Versification; | |
40 | import org.crosswire.jsword.versification.system.Versifications; | |
41 | import org.slf4j.Logger; | |
42 | import org.slf4j.LoggerFactory; | |
43 | ||
44 | /** | |
45 | * This is a base class to help with some of the common implementation details | |
46 | * of being a Passage. | |
47 | * <p> | |
48 | * Importantly, this class takes care of Serialization in a general yet | |
49 | * optimized way. I think I am going to have a look at replacement here. | |
50 | * | |
51 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
52 | * @author Joe Walker | |
53 | * @author DM Smith | |
54 | */ | |
55 | 0 | public abstract class AbstractPassage implements Passage { |
56 | /** | |
57 | * Setup that leaves original name being null | |
58 | * | |
59 | * @param v11n | |
60 | * The Versification to which this Passage belongs. | |
61 | */ | |
62 | protected AbstractPassage(Versification v11n) { | |
63 | 0 | this(v11n, null); |
64 | 0 | } |
65 | ||
66 | /** | |
67 | * Setup the original name of this reference | |
68 | * | |
69 | * @param v11n | |
70 | * The Versification to which this Passage belongs. | |
71 | * @param passageName | |
72 | * The text originally used to create this Passage. | |
73 | */ | |
74 | 0 | protected AbstractPassage(Versification v11n, String passageName) { |
75 | 0 | this.v11n = v11n; |
76 | 0 | this.originalName = passageName; |
77 | 0 | this.listeners = new ArrayList<PassageListener>(); |
78 | 0 | } |
79 | ||
80 | /* (non-Javadoc) | |
81 | * @see org.crosswire.jsword.passage.Passage#getVersification() | |
82 | */ | |
83 | public Versification getVersification() { | |
84 | 0 | return v11n; |
85 | } | |
86 | ||
87 | /* (non-Javadoc) | |
88 | * @see org.crosswire.jsword.passage.VerseKey#reversify(org.crosswire.jsword.versification.Versification) | |
89 | */ | |
90 | public Passage reversify(Versification newVersification) { | |
91 | 0 | if (v11n.equals(newVersification)) { |
92 | 0 | return this; |
93 | } | |
94 | 0 | throw new UnsupportedOperationException(); |
95 | } | |
96 | ||
97 | /* (non-Javadoc) | |
98 | * @see org.crosswire.jsword.passage.VerseKey#isWhole() | |
99 | */ | |
100 | public boolean isWhole() { | |
101 | 0 | throw new UnsupportedOperationException(); |
102 | } | |
103 | ||
104 | /* (non-Javadoc) | |
105 | * @see org.crosswire.jsword.passage.VerseKey#getWhole() | |
106 | */ | |
107 | public Passage getWhole() { | |
108 | 0 | throw new UnsupportedOperationException(); |
109 | } | |
110 | ||
111 | /* (non-Javadoc) | |
112 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
113 | */ | |
114 | public int compareTo(Key obj) { | |
115 | 0 | Passage thatref = (Passage) obj; |
116 | 0 | if (thatref.countVerses() == 0) { |
117 | 0 | if (countVerses() == 0) { |
118 | 0 | return 0; |
119 | } | |
120 | // that is empty so he should come before me | |
121 | 0 | return -1; |
122 | } | |
123 | ||
124 | 0 | if (countVerses() == 0) { |
125 | // we are empty be he isn't so we are first | |
126 | 0 | return 1; |
127 | } | |
128 | ||
129 | 0 | Verse thatfirst = thatref.getVerseAt(0); |
130 | 0 | Verse thisfirst = getVerseAt(0); |
131 | ||
132 | 0 | return getVersification().distance(thatfirst, thisfirst); |
133 | } | |
134 | ||
135 | @Override | |
136 | public AbstractPassage clone() { | |
137 | // This gets us a shallow copy | |
138 | 0 | AbstractPassage copy = null; |
139 | ||
140 | try { | |
141 | 0 | copy = (AbstractPassage) super.clone(); |
142 | 0 | copy.listeners = new ArrayList<PassageListener>(); |
143 | 0 | copy.listeners.addAll(listeners); |
144 | ||
145 | 0 | copy.originalName = originalName; |
146 | 0 | } catch (CloneNotSupportedException e) { |
147 | 0 | assert false : e; |
148 | 0 | } |
149 | ||
150 | 0 | return copy; |
151 | } | |
152 | ||
153 | @Override | |
154 | public boolean equals(Object obj) { | |
155 | // This is cheating because I am supposed to say: | |
156 | // <code>!obj.getClass().equals(this.getClass())</code> | |
157 | // However I think it is entirely valid for a RangedPassage | |
158 | // to equal a DistinctPassage since the point of the Factory | |
159 | // is that the user does not need to know the actual type of the | |
160 | // Object he is using. | |
161 | 0 | if (!(obj instanceof Passage)) { |
162 | 0 | return false; |
163 | } | |
164 | 0 | Passage that = (Passage) obj; |
165 | // The real test | |
166 | // FIXME: this is not really true since the versification any longer. | |
167 | 0 | return that.getOsisRef().equals(getOsisRef()); |
168 | } | |
169 | ||
170 | @Override | |
171 | public int hashCode() { | |
172 | 0 | return getOsisRef().hashCode(); |
173 | } | |
174 | ||
175 | /* (non-Javadoc) | |
176 | * @see org.crosswire.jsword.passage.Key#getName() | |
177 | */ | |
178 | public String getName() { | |
179 | 0 | if (PassageUtil.isPersistentNaming() && originalName != null) { |
180 | 0 | return originalName; |
181 | } | |
182 | ||
183 | 0 | StringBuilder retcode = new StringBuilder(); |
184 | ||
185 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
186 | 0 | Verse current = null; |
187 | 0 | while (it.hasNext()) { |
188 | 0 | VerseRange range = it.next(); |
189 | 0 | retcode.append(range.getName(current)); |
190 | ||
191 | // FIXME: Potential bug. According to iterator contract hasNext and | |
192 | // next must be paired. | |
193 | 0 | if (it.hasNext()) { |
194 | 0 | retcode.append(AbstractPassage.REF_PREF_DELIM); |
195 | } | |
196 | ||
197 | 0 | current = range.getStart(); |
198 | 0 | } |
199 | ||
200 | 0 | return retcode.toString(); |
201 | } | |
202 | ||
203 | /* (non-Javadoc) | |
204 | * @see org.crosswire.jsword.passage.Key#getName(org.crosswire.jsword.passage.Key) | |
205 | */ | |
206 | public String getName(Key base) { | |
207 | 0 | return getName(); |
208 | } | |
209 | ||
210 | /* (non-Javadoc) | |
211 | * @see org.crosswire.jsword.passage.Key#getRootName() | |
212 | */ | |
213 | public String getRootName() { | |
214 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
215 | 0 | while (it.hasNext()) { |
216 | 0 | VerseRange range = it.next(); |
217 | 0 | return range.getRootName(); |
218 | } | |
219 | ||
220 | 0 | return getName(); |
221 | } | |
222 | ||
223 | /* (non-Javadoc) | |
224 | * @see org.crosswire.jsword.passage.Key#getOsisRef() | |
225 | */ | |
226 | public String getOsisRef() { | |
227 | 0 | StringBuilder retcode = new StringBuilder(); |
228 | ||
229 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
230 | 0 | boolean hasNext = it.hasNext(); |
231 | 0 | while (hasNext) { |
232 | 0 | Key range = it.next(); |
233 | 0 | retcode.append(range.getOsisRef()); |
234 | ||
235 | 0 | hasNext = it.hasNext(); |
236 | 0 | if (hasNext) { |
237 | 0 | retcode.append(AbstractPassage.REF_OSIS_DELIM); |
238 | } | |
239 | 0 | } |
240 | ||
241 | 0 | return retcode.toString(); |
242 | } | |
243 | ||
244 | /* (non-Javadoc) | |
245 | * @see org.crosswire.jsword.passage.Key#getOsisID() | |
246 | */ | |
247 | public String getOsisID() { | |
248 | 0 | StringBuilder retcode = new StringBuilder(); |
249 | ||
250 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
251 | 0 | boolean hasNext = it.hasNext(); |
252 | 0 | while (hasNext) { |
253 | 0 | Key range = it.next(); |
254 | 0 | retcode.append(range.getOsisID()); |
255 | ||
256 | 0 | hasNext = it.hasNext(); |
257 | 0 | if (hasNext) { |
258 | 0 | retcode.append(AbstractPassage.REF_OSIS_DELIM); |
259 | } | |
260 | 0 | } |
261 | ||
262 | 0 | return retcode.toString(); |
263 | } | |
264 | ||
265 | @Override | |
266 | public String toString() { | |
267 | 0 | return getName(); |
268 | } | |
269 | ||
270 | /* (non-Javadoc) | |
271 | * @see org.crosswire.jsword.passage.Passage#getOverview() | |
272 | */ | |
273 | public String getOverview() { | |
274 | // TRANSLATOR: This provides an overview of the verses in one or more books. The placeholders here deserve extra comment. | |
275 | // {0,number,integer} is a placeholder for the count of verses. It will be displayed as an integer using the number system of the user's locale. | |
276 | // {0,choice,0#verses|1#verse|1<verses} uses the value of the number of verses to display the correct singular or plural form for the word "verse" | |
277 | // Choices are separated by |. And each choice consists of a number, a comparison and the value to use when the comparison is met. | |
278 | // Choices are ordered from smallest to largest. The numbers represent boundaries that determine when a choice is used. | |
279 | // The comparison # means to match exactly. | |
280 | // The comparison < means that the number on the left is less than the number being evaluated. | |
281 | // Here, 0 is the first boundary specified by a #. So every number less than or equal to 0 get the first choice. | |
282 | // In this situation, we are dealing with counting numbers, so we'll never have negative numbers. | |
283 | // Next choice is 1 with a boundary specified by #. So all numbers greater than 0 (the first choice) but less than or equal to 1 get the second choice. | |
284 | // In this situation, the only number that will match is 1. | |
285 | // The final choice is 1<. This means that every number greater than 1 will get this choice. | |
286 | // Putting the first two placeholders together we get "0 verses", "1 verse" or "n verses" (where n is 2 or more) | |
287 | // The reason to go into this is that this pattern works for English. Other languages might have different ways of representing singular and plurals. | |
288 | // {1,number,integer} is a placeholder for the count of Bible books. It works the same way as the count of verses. | |
289 | // {1,choice,0#books|1#book|1<books} is the placeholder for the singular or plural of "book" | |
290 | 0 | return JSMsg.gettext("{0,number,integer} {0,choice,0#verses|1#verse|1<verses} in {1,number,integer} {1,choice,0#books|1#book|1<books}", |
291 | Integer.valueOf(countVerses()), Integer.valueOf(booksInPassage() | |
292 | )); | |
293 | } | |
294 | ||
295 | /* (non-Javadoc) | |
296 | * @see org.crosswire.jsword.passage.Key#isEmpty() | |
297 | */ | |
298 | public boolean isEmpty() { | |
299 | // Is there any content? | |
300 | 0 | return !iterator().hasNext(); |
301 | } | |
302 | ||
303 | /* (non-Javadoc) | |
304 | * @see org.crosswire.jsword.passage.Passage#countVerses() | |
305 | */ | |
306 | public int countVerses() { | |
307 | 0 | int count = 0; |
308 | ||
309 | 0 | for (Iterator<?> iter = iterator(); iter.hasNext(); iter.next()) { |
310 | 0 | count++; |
311 | } | |
312 | ||
313 | 0 | return count; |
314 | } | |
315 | ||
316 | /* (non-Javadoc) | |
317 | * @see org.crosswire.jsword.passage.Passage#hasRanges(org.crosswire.jsword.passage.RestrictionType) | |
318 | */ | |
319 | public boolean hasRanges(RestrictionType restrict) { | |
320 | 0 | int count = 0; |
321 | ||
322 | 0 | Iterator<VerseRange> it = rangeIterator(restrict); |
323 | 0 | while (it.hasNext()) { |
324 | 0 | it.next(); |
325 | 0 | count++; |
326 | 0 | if (count == 2) { |
327 | 0 | return true; |
328 | } | |
329 | } | |
330 | ||
331 | 0 | return false; |
332 | } | |
333 | ||
334 | /* (non-Javadoc) | |
335 | * @see org.crosswire.jsword.passage.Passage#countRanges(org.crosswire.jsword.passage.RestrictionType) | |
336 | */ | |
337 | public int countRanges(RestrictionType restrict) { | |
338 | 0 | int count = 0; |
339 | ||
340 | 0 | Iterator<VerseRange> it = rangeIterator(restrict); |
341 | 0 | while (it.hasNext()) { |
342 | 0 | it.next(); |
343 | 0 | count++; |
344 | } | |
345 | ||
346 | 0 | return count; |
347 | } | |
348 | ||
349 | /* (non-Javadoc) | |
350 | * @see org.crosswire.jsword.passage.Passage#booksInPassage() | |
351 | */ | |
352 | public int booksInPassage() { | |
353 | // FIXME(DMS): a passage does not have to be ordered, for example PassageTally. | |
354 | 0 | BibleBook currentBook = null; |
355 | 0 | int bookCount = 0; |
356 | ||
357 | 0 | for (Key aKey : this) { |
358 | 0 | Verse verse = (Verse) aKey; |
359 | 0 | if (currentBook != verse.getBook()) { |
360 | 0 | currentBook = verse.getBook(); |
361 | 0 | bookCount++; |
362 | } | |
363 | 0 | } |
364 | ||
365 | 0 | return bookCount; |
366 | } | |
367 | ||
368 | /* (non-Javadoc) | |
369 | * @see org.crosswire.jsword.passage.Passage#getVerseAt(int) | |
370 | */ | |
371 | public Verse getVerseAt(int offset) throws ArrayIndexOutOfBoundsException { | |
372 | 0 | Iterator<Key> it = iterator(); |
373 | 0 | Object retcode = null; |
374 | ||
375 | 0 | for (int i = 0; i <= offset; i++) { |
376 | 0 | if (!it.hasNext()) { |
377 | 0 | throw new ArrayIndexOutOfBoundsException(JSOtherMsg.lookupText("Index out of range (Given {0,number,integer}, Max {1,number,integer}).", Integer.valueOf(offset), Integer.valueOf(countVerses()))); |
378 | } | |
379 | ||
380 | 0 | retcode = it.next(); |
381 | } | |
382 | ||
383 | 0 | return (Verse) retcode; |
384 | } | |
385 | ||
386 | /* (non-Javadoc) | |
387 | * @see org.crosswire.jsword.passage.Passage#getRangeAt(int, org.crosswire.jsword.passage.RestrictionType) | |
388 | */ | |
389 | public VerseRange getRangeAt(int offset, RestrictionType restrict) throws ArrayIndexOutOfBoundsException { | |
390 | 0 | Iterator<VerseRange> it = rangeIterator(restrict); |
391 | 0 | Object retcode = null; |
392 | ||
393 | 0 | for (int i = 0; i <= offset; i++) { |
394 | 0 | if (!it.hasNext()) { |
395 | 0 | throw new ArrayIndexOutOfBoundsException(JSOtherMsg.lookupText("Index out of range (Given {0,number,integer}, Max {1,number,integer}).", Integer.valueOf(offset), Integer.valueOf(countVerses()))); |
396 | } | |
397 | ||
398 | 0 | retcode = it.next(); |
399 | } | |
400 | ||
401 | 0 | return (VerseRange) retcode; |
402 | } | |
403 | ||
404 | /* (non-Javadoc) | |
405 | * @see org.crosswire.jsword.passage.Passage#rangeIterator(org.crosswire.jsword.passage.RestrictionType) | |
406 | */ | |
407 | public Iterator<VerseRange> rangeIterator(RestrictionType restrict) { | |
408 | 0 | return new VerseRangeIterator(getVersification(), iterator(), restrict); |
409 | } | |
410 | ||
411 | /* (non-Javadoc) | |
412 | * @see org.crosswire.jsword.passage.Passage#containsAll(org.crosswire.jsword.passage.Passage) | |
413 | */ | |
414 | public boolean containsAll(Passage that) { | |
415 | 0 | if (that instanceof RangedPassage) { |
416 | 0 | Iterator<VerseRange> iter = null; |
417 | ||
418 | 0 | iter = ((RangedPassage) that).rangeIterator(RestrictionType.NONE); |
419 | 0 | while (iter.hasNext()) { |
420 | 0 | if (!contains(iter.next())) { |
421 | 0 | return false; |
422 | } | |
423 | } | |
424 | 0 | } else { |
425 | 0 | Iterator<Key> iter = that.iterator(); |
426 | 0 | while (iter.hasNext()) { |
427 | 0 | if (!contains(iter.next())) { |
428 | 0 | return false; |
429 | } | |
430 | } | |
431 | ||
432 | } | |
433 | ||
434 | 0 | return true; |
435 | } | |
436 | ||
437 | /* (non-Javadoc) | |
438 | * @see org.crosswire.jsword.passage.Passage#trimVerses(int) | |
439 | */ | |
440 | public Passage trimVerses(int count) { | |
441 | 0 | optimizeWrites(); |
442 | 0 | raiseNormalizeProtection(); |
443 | ||
444 | 0 | int i = 0; |
445 | 0 | boolean overflow = false; |
446 | ||
447 | 0 | Passage remainder = this.clone(); |
448 | ||
449 | 0 | for (Key verse : this) { |
450 | 0 | i++; |
451 | 0 | if (i > count) { |
452 | 0 | remove(verse); |
453 | 0 | overflow = true; |
454 | } else { | |
455 | 0 | remainder.remove(verse); |
456 | } | |
457 | } | |
458 | ||
459 | 0 | lowerNormalizeProtection(); |
460 | // The event notification is done by the remove above | |
461 | ||
462 | 0 | if (overflow) { |
463 | 0 | return remainder; |
464 | } | |
465 | 0 | return null; |
466 | } | |
467 | ||
468 | /* (non-Javadoc) | |
469 | * @see org.crosswire.jsword.passage.Passage#trimRanges(int, org.crosswire.jsword.passage.RestrictionType) | |
470 | */ | |
471 | public Passage trimRanges(int count, RestrictionType restrict) { | |
472 | 0 | optimizeWrites(); |
473 | 0 | raiseNormalizeProtection(); |
474 | ||
475 | 0 | int i = 0; |
476 | 0 | boolean overflow = false; |
477 | ||
478 | 0 | Passage remainder = this.clone(); |
479 | ||
480 | 0 | Iterator<VerseRange> it = rangeIterator(restrict); |
481 | 0 | while (it.hasNext()) { |
482 | 0 | i++; |
483 | 0 | Key range = it.next(); |
484 | ||
485 | 0 | if (i > count) { |
486 | 0 | remove(range); |
487 | 0 | overflow = true; |
488 | } else { | |
489 | 0 | remainder.remove(range); |
490 | } | |
491 | 0 | } |
492 | ||
493 | 0 | lowerNormalizeProtection(); |
494 | // The event notification is done by the remove above | |
495 | ||
496 | 0 | if (overflow) { |
497 | 0 | return remainder; |
498 | } | |
499 | 0 | return null; |
500 | } | |
501 | ||
502 | /* Now supports adding keys from different versifications. | |
503 | * (non-Javadoc) | |
504 | * @see org.crosswire.jsword.passage.Key#addAll(org.crosswire.jsword.passage.Key) | |
505 | */ | |
506 | public void addAll(Key key) { | |
507 | //check for key empty. This avoids the AIOBounds with that.getVerseAt, during event firing | |
508 | 0 | if (key.isEmpty()) { |
509 | //nothing to add | |
510 | 0 | return; |
511 | } | |
512 | ||
513 | 0 | optimizeWrites(); |
514 | 0 | raiseEventSuppresion(); |
515 | 0 | raiseNormalizeProtection(); |
516 | ||
517 | ||
518 | 0 | if (key instanceof RangedPassage) { |
519 | 0 | Iterator<VerseRange> it = ((RangedPassage) key).rangeIterator(RestrictionType.NONE); |
520 | 0 | while (it.hasNext()) { |
521 | // Avoid touching store to make thread safety easier. | |
522 | 0 | add(it.next()); |
523 | } | |
524 | 0 | } else { |
525 | 0 | for (Key subkey : key) { |
526 | 0 | add(subkey); |
527 | } | |
528 | } | |
529 | ||
530 | 0 | lowerNormalizeProtection(); |
531 | 0 | if (lowerEventSuppressionAndTest()) { |
532 | 0 | if (key instanceof Passage) { |
533 | 0 | Passage that = (Passage) key; |
534 | 0 | fireIntervalAdded(this, that.getVerseAt(0), that.getVerseAt(that.countVerses() - 1)); |
535 | 0 | } else if (key instanceof VerseRange) { |
536 | 0 | VerseRange that = (VerseRange) key; |
537 | 0 | fireIntervalAdded(this, that.getStart(), that.getEnd()); |
538 | 0 | } else if (key instanceof Verse) { |
539 | 0 | Verse that = (Verse) key; |
540 | 0 | fireIntervalAdded(this, that, that); |
541 | } | |
542 | } | |
543 | 0 | } |
544 | ||
545 | /* (non-Javadoc) | |
546 | * @see org.crosswire.jsword.passage.Key#removeAll(org.crosswire.jsword.passage.Key) | |
547 | */ | |
548 | public void removeAll(Key key) { | |
549 | 0 | optimizeWrites(); |
550 | 0 | raiseEventSuppresion(); |
551 | 0 | raiseNormalizeProtection(); |
552 | ||
553 | 0 | if (key instanceof RangedPassage) { |
554 | 0 | Iterator<VerseRange> it = ((RangedPassage) key).rangeIterator(RestrictionType.NONE); |
555 | 0 | while (it.hasNext()) { |
556 | // Avoid touching store to make thread safety easier. | |
557 | 0 | remove(it.next()); |
558 | } | |
559 | 0 | } else { |
560 | 0 | Iterator<Key> it = key.iterator(); |
561 | 0 | while (it.hasNext()) { |
562 | // Avoid touching store to make thread safety easier. | |
563 | 0 | remove(it.next()); |
564 | } | |
565 | } | |
566 | ||
567 | 0 | lowerNormalizeProtection(); |
568 | 0 | if (lowerEventSuppressionAndTest()) { |
569 | 0 | if (key instanceof Passage) { |
570 | 0 | Passage that = (Passage) key; |
571 | 0 | fireIntervalRemoved(this, that.getVerseAt(0), that.getVerseAt(that.countVerses() - 1)); |
572 | 0 | } else if (key instanceof VerseRange) { |
573 | 0 | VerseRange that = (VerseRange) key; |
574 | 0 | fireIntervalRemoved(this, that.getStart(), that.getEnd()); |
575 | 0 | } else if (key instanceof Verse) { |
576 | 0 | Verse that = (Verse) key; |
577 | 0 | fireIntervalRemoved(this, that, that); |
578 | } | |
579 | } | |
580 | 0 | } |
581 | ||
582 | /* (non-Javadoc) | |
583 | * @see org.crosswire.jsword.passage.Key#retainAll(org.crosswire.jsword.passage.Key) | |
584 | */ | |
585 | public void retainAll(Key key) { | |
586 | 0 | optimizeWrites(); |
587 | 0 | raiseEventSuppresion(); |
588 | 0 | raiseNormalizeProtection(); |
589 | ||
590 | 0 | Passage temp = this.clone(); |
591 | 0 | for (Key verse : temp) { |
592 | 0 | if (!key.contains(verse)) { |
593 | 0 | remove(verse); |
594 | } | |
595 | } | |
596 | ||
597 | 0 | lowerNormalizeProtection(); |
598 | 0 | if (lowerEventSuppressionAndTest()) { |
599 | 0 | fireIntervalRemoved(this, null, null); |
600 | } | |
601 | 0 | } |
602 | ||
603 | /* (non-Javadoc) | |
604 | * @see org.crosswire.jsword.passage.Key#clear() | |
605 | */ | |
606 | public void clear() { | |
607 | 0 | optimizeWrites(); |
608 | 0 | raiseNormalizeProtection(); |
609 | ||
610 | 0 | remove(getVersification().getAllVerses()); |
611 | ||
612 | 0 | if (lowerEventSuppressionAndTest()) { |
613 | 0 | fireIntervalRemoved(this, null, null); |
614 | } | |
615 | 0 | } |
616 | ||
617 | /* (non-Javadoc) | |
618 | * @see org.crosswire.jsword.passage.Key#blur(int, org.crosswire.jsword.passage.RestrictionType) | |
619 | */ | |
620 | public void blur(int verses, RestrictionType restrict) { | |
621 | 0 | optimizeWrites(); |
622 | 0 | raiseEventSuppresion(); |
623 | 0 | raiseNormalizeProtection(); |
624 | ||
625 | 0 | Passage temp = this.clone(); |
626 | 0 | Iterator<VerseRange> it = temp.rangeIterator(RestrictionType.NONE); |
627 | ||
628 | 0 | while (it.hasNext()) { |
629 | 0 | VerseRange range = restrict.blur(getVersification(), it.next(), verses, verses); |
630 | 0 | add(range); |
631 | 0 | } |
632 | ||
633 | 0 | lowerNormalizeProtection(); |
634 | 0 | if (lowerEventSuppressionAndTest()) { |
635 | 0 | fireIntervalAdded(this, null, null); |
636 | } | |
637 | 0 | } |
638 | ||
639 | /* (non-Javadoc) | |
640 | * @see org.crosswire.jsword.passage.Passage#writeDescription(java.io.Writer) | |
641 | */ | |
642 | public void writeDescription(Writer out) throws IOException { | |
643 | 0 | BufferedWriter bout = new BufferedWriter(out); |
644 | 0 | bout.write(v11n.getName()); |
645 | 0 | bout.newLine(); |
646 | ||
647 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
648 | ||
649 | 0 | while (it.hasNext()) { |
650 | 0 | Key range = it.next(); |
651 | 0 | bout.write(range.getName()); |
652 | 0 | bout.newLine(); |
653 | 0 | } |
654 | ||
655 | 0 | bout.flush(); |
656 | 0 | } |
657 | ||
658 | /* (non-Javadoc) | |
659 | * @see org.crosswire.jsword.passage.Passage#readDescription(java.io.Reader) | |
660 | */ | |
661 | public void readDescription(Reader in) throws IOException, NoSuchVerseException { | |
662 | 0 | raiseEventSuppresion(); |
663 | 0 | raiseNormalizeProtection(); |
664 | ||
665 | 0 | int count = 0; // number of lines read |
666 | // Quiet Android from complaining about using the default BufferReader buffer size. | |
667 | // The actual buffer size is undocumented. So this is a good idea any way. | |
668 | 0 | BufferedReader bin = new BufferedReader(in, 8192); |
669 | ||
670 | 0 | String v11nName = bin.readLine(); |
671 | 0 | v11n = Versifications.instance().getVersification(v11nName); |
672 | ||
673 | while (true) { | |
674 | 0 | String line = bin.readLine(); |
675 | 0 | if (line == null) { |
676 | 0 | break; |
677 | } | |
678 | ||
679 | 0 | count++; |
680 | 0 | addVerses(line, null); |
681 | 0 | } |
682 | ||
683 | // If the file was empty then there is nothing to do | |
684 | 0 | if (count == 0) { |
685 | 0 | return; |
686 | } | |
687 | ||
688 | 0 | lowerNormalizeProtection(); |
689 | 0 | if (lowerEventSuppressionAndTest()) { |
690 | 0 | fireIntervalAdded(this, getVerseAt(0), getVerseAt(countVerses() - 1)); |
691 | } | |
692 | 0 | } |
693 | ||
694 | /* (non-Javadoc) | |
695 | * @see org.crosswire.jsword.passage.Passage#optimizeReads() | |
696 | */ | |
697 | public void optimizeReads() { | |
698 | 0 | } |
699 | ||
700 | /** | |
701 | * Simple method to instruct children to stop caching results | |
702 | */ | |
703 | protected void optimizeWrites() { | |
704 | 0 | } |
705 | ||
706 | /* (non-Javadoc) | |
707 | * @see org.crosswire.jsword.passage.Passage#addPassageListener(org.crosswire.jsword.passage.PassageListener) | |
708 | */ | |
709 | public void addPassageListener(PassageListener li) { | |
710 | 0 | synchronized (listeners) { |
711 | 0 | listeners.add(li); |
712 | 0 | } |
713 | 0 | } |
714 | ||
715 | /* (non-Javadoc) | |
716 | * @see org.crosswire.jsword.passage.Passage#removePassageListener(org.crosswire.jsword.passage.PassageListener) | |
717 | */ | |
718 | public void removePassageListener(PassageListener li) { | |
719 | 0 | synchronized (listeners) { |
720 | 0 | listeners.remove(li); |
721 | 0 | } |
722 | 0 | } |
723 | ||
724 | /* (non-Javadoc) | |
725 | * @see org.crosswire.jsword.passage.Passage#contains(org.crosswire.jsword.passage.Key) | |
726 | */ | |
727 | public boolean contains(Key key) { | |
728 | 0 | Passage ref = KeyUtil.getPassage(key); |
729 | 0 | return containsAll(ref); |
730 | } | |
731 | ||
732 | /* (non-Javadoc) | |
733 | * @see org.crosswire.jsword.passage.Key#getCardinality() | |
734 | */ | |
735 | public int getCardinality() { | |
736 | 0 | return countVerses(); |
737 | } | |
738 | ||
739 | /* (non-Javadoc) | |
740 | * @see org.crosswire.jsword.passage.Key#indexOf(org.crosswire.jsword.passage.Key) | |
741 | */ | |
742 | public int indexOf(Key that) { | |
743 | 0 | int index = 0; |
744 | 0 | for (Key key : this) { |
745 | 0 | if (key.equals(that)) { |
746 | 0 | return index; |
747 | } | |
748 | ||
749 | 0 | index++; |
750 | } | |
751 | ||
752 | 0 | return -1; |
753 | } | |
754 | ||
755 | /* (non-Javadoc) | |
756 | * @see org.crosswire.jsword.passage.Key#canHaveChildren() | |
757 | */ | |
758 | public boolean canHaveChildren() { | |
759 | 0 | return false; |
760 | } | |
761 | ||
762 | /* (non-Javadoc) | |
763 | * @see org.crosswire.jsword.passage.Key#getChildCount() | |
764 | */ | |
765 | public int getChildCount() { | |
766 | 0 | return 0; |
767 | } | |
768 | ||
769 | /* (non-Javadoc) | |
770 | * @see org.crosswire.jsword.passage.Key#get(int) | |
771 | */ | |
772 | public Key get(int index) { | |
773 | 0 | return getVerseAt(index); |
774 | } | |
775 | ||
776 | /* (non-Javadoc) | |
777 | * @see org.crosswire.jsword.passage.Key#getParent() | |
778 | */ | |
779 | public Key getParent() { | |
780 | 0 | return parent; |
781 | } | |
782 | ||
783 | /** | |
784 | * Set a parent Key. This allows us to follow the Key interface more | |
785 | * closely, although the concept of a parent for a verse is fairly alien. | |
786 | * | |
787 | * @param parent | |
788 | * The parent Key for this verse | |
789 | */ | |
790 | public void setParent(Key parent) { | |
791 | 0 | this.parent = parent; |
792 | 0 | } |
793 | ||
794 | /** | |
795 | * AbstractPassage subclasses must call this method <b>after</b> one or more | |
796 | * elements of the list are added. The changed elements are specified by a | |
797 | * closed interval from start to end. | |
798 | * | |
799 | * @param source | |
800 | * The thing that changed, typically "this". | |
801 | * @param start | |
802 | * One end of the new interval. | |
803 | * @param end | |
804 | * The other end of the new interval. | |
805 | * @see PassageListener | |
806 | */ | |
807 | protected void fireIntervalAdded(Object source, Verse start, Verse end) { | |
808 | 0 | if (suppressEvents != 0) { |
809 | 0 | return; |
810 | } | |
811 | ||
812 | // Create Event | |
813 | 0 | PassageEvent ev = new PassageEvent(source, PassageEvent.EventType.ADDED, start, end); |
814 | ||
815 | // Copy listener vector so it won't change while firing | |
816 | List<PassageListener> temp; | |
817 | 0 | synchronized (listeners) { |
818 | 0 | temp = new ArrayList<PassageListener>(); |
819 | 0 | temp.addAll(listeners); |
820 | 0 | } |
821 | ||
822 | // And run through the list shouting | |
823 | 0 | for (int i = 0; i < temp.size(); i++) { |
824 | 0 | PassageListener rl = temp.get(i); |
825 | 0 | rl.versesAdded(ev); |
826 | } | |
827 | 0 | } |
828 | ||
829 | /** | |
830 | * AbstractPassage subclasses must call this method <b>before</b> one or | |
831 | * more elements of the list are added. The changed elements are specified | |
832 | * by a closed interval from start to end. | |
833 | * | |
834 | * @param source | |
835 | * The thing that changed, typically "this". | |
836 | * @param start | |
837 | * One end of the new interval. | |
838 | * @param end | |
839 | * The other end of the new interval. | |
840 | * @see PassageListener | |
841 | */ | |
842 | protected void fireIntervalRemoved(Object source, Verse start, Verse end) { | |
843 | 0 | if (suppressEvents != 0) { |
844 | 0 | return; |
845 | } | |
846 | ||
847 | // Create Event | |
848 | 0 | PassageEvent ev = new PassageEvent(source, PassageEvent.EventType.REMOVED, start, end); |
849 | ||
850 | // Copy listener vector so it won't change while firing | |
851 | List<PassageListener> temp; | |
852 | 0 | synchronized (listeners) { |
853 | 0 | temp = new ArrayList<PassageListener>(); |
854 | 0 | temp.addAll(listeners); |
855 | 0 | } |
856 | ||
857 | // And run through the list shouting | |
858 | 0 | for (int i = 0; i < temp.size(); i++) { |
859 | 0 | PassageListener rl = temp.get(i); |
860 | 0 | rl.versesRemoved(ev); |
861 | } | |
862 | 0 | } |
863 | ||
864 | /** | |
865 | * AbstractPassage subclasses must call this method <b>before</b> one or | |
866 | * more elements of the list are added. The changed elements are specified | |
867 | * by a closed interval from start to end. | |
868 | * | |
869 | * @param source | |
870 | * The thing that changed, typically "this". | |
871 | * @param start | |
872 | * One end of the new interval. | |
873 | * @param end | |
874 | * The other end of the new interval. | |
875 | * @see PassageListener | |
876 | */ | |
877 | protected void fireContentsChanged(Object source, Verse start, Verse end) { | |
878 | 0 | if (suppressEvents != 0) { |
879 | 0 | return; |
880 | } | |
881 | ||
882 | // Create Event | |
883 | 0 | PassageEvent ev = new PassageEvent(source, PassageEvent.EventType.CHANGED, start, end); |
884 | ||
885 | // Copy listener vector so it won't change while firing | |
886 | List<PassageListener> temp; | |
887 | 0 | synchronized (listeners) { |
888 | 0 | temp = new ArrayList<PassageListener>(); |
889 | 0 | temp.addAll(listeners); |
890 | 0 | } |
891 | ||
892 | // And run through the list shouting | |
893 | 0 | for (int i = 0; i < temp.size(); i++) { |
894 | 0 | PassageListener rl = temp.get(i); |
895 | 0 | rl.versesChanged(ev); |
896 | } | |
897 | 0 | } |
898 | ||
899 | /** | |
900 | * Create a Passage from a human readable string. The opposite of | |
901 | * <code>toString()</code>. Since this method is not public it leaves | |
902 | * control of <code>suppress_events</code> up to the people | |
903 | * that call it. | |
904 | * | |
905 | * @param refs | |
906 | * A String containing the text of the RangedPassage | |
907 | * @param basis | |
908 | * The basis for understanding refs | |
909 | * @throws NoSuchVerseException | |
910 | * if the string is invalid | |
911 | */ | |
912 | protected void addVerses(String refs, Key basis) throws NoSuchVerseException { | |
913 | 0 | optimizeWrites(); |
914 | ||
915 | 0 | String[] parts = StringUtil.split(refs, AbstractPassage.REF_ALLOWED_DELIMS); |
916 | 0 | if (parts.length == 0) { |
917 | 0 | return; |
918 | } | |
919 | ||
920 | 0 | int start = 0; |
921 | 0 | VerseRange vrBasis = null; |
922 | 0 | if (basis instanceof Verse) { |
923 | 0 | vrBasis = new VerseRange(v11n, (Verse) basis); |
924 | 0 | } else if (basis instanceof VerseRange) { |
925 | 0 | vrBasis = (VerseRange) basis; |
926 | } else { | |
927 | // If we are not passed a useful basis, | |
928 | // then we treat the first as a special case because there is | |
929 | // nothing to sensibly base this reference on | |
930 | 0 | vrBasis = VerseRangeFactory.fromString(v11n, parts[0].trim()); |
931 | // We add it because it was part of the given input | |
932 | 0 | add(vrBasis); |
933 | 0 | start = 1; |
934 | } | |
935 | ||
936 | // Loop for the other verses, interpreting each on the | |
937 | // basis of the one before. | |
938 | 0 | for (int i = start; i < parts.length; i++) { |
939 | 0 | VerseRange next = VerseRangeFactory.fromString(v11n, parts[i].trim(), vrBasis); |
940 | 0 | add(next); |
941 | 0 | vrBasis = next; |
942 | } | |
943 | 0 | } |
944 | ||
945 | /** | |
946 | * We sometimes need to sort ourselves out ... I don't think we need to be | |
947 | * synchronized since we are private and we could check that all public | |
948 | * calling of normalize() are synchronized, however this is safe, and I | |
949 | * don't think there is a cost associated with a double synchronize. (?) | |
950 | */ | |
951 | /* protected */void normalize() { | |
952 | // before doing any normalization we should be checking that | |
953 | // skip_normalization == 0, and just returning if so. | |
954 | 0 | } |
955 | ||
956 | /** | |
957 | * If things want to prevent normalization because they are doing a set of | |
958 | * changes that should be normalized in one go, this is what to call. Be | |
959 | * sure to call lowerNormalizeProtection() when you are done. | |
960 | */ | |
961 | public void raiseNormalizeProtection() { | |
962 | 0 | skipNormalization++; |
963 | ||
964 | 0 | if (skipNormalization > 10) { |
965 | // This is a bit drastic and does not give us much | |
966 | // chance to fix the error | |
967 | // throw new LogicError(); | |
968 | ||
969 | 0 | log.warn("skip_normalization={}", Integer.toString(skipNormalization)); |
970 | } | |
971 | 0 | } |
972 | ||
973 | /** | |
974 | * If things want to prevent normalization because they are doing a set of | |
975 | * changes that should be normalized in one go, they should call | |
976 | * raiseNormalizeProtection() and when done call this. This also calls | |
977 | * normalize() if the count reaches zero. | |
978 | */ | |
979 | public void lowerNormalizeProtection() { | |
980 | 0 | skipNormalization--; |
981 | ||
982 | 0 | if (skipNormalization == 0) { |
983 | 0 | normalize(); |
984 | } | |
985 | ||
986 | 0 | assert skipNormalization >= 0; |
987 | 0 | } |
988 | ||
989 | /** | |
990 | * If things want to prevent event firing because they are doing a set of | |
991 | * changes that should be notified in one go, this is what to call. Be sure | |
992 | * to call lowerEventSuppression() when you are done. | |
993 | */ | |
994 | public void raiseEventSuppresion() { | |
995 | 0 | suppressEvents++; |
996 | ||
997 | 0 | if (suppressEvents > 10) { |
998 | // This is a bit drastic and does not give us much | |
999 | // chance to fix the error | |
1000 | // throw new LogicError(); | |
1001 | ||
1002 | 0 | log.warn("suppress_events={}", Integer.toString(suppressEvents)); |
1003 | } | |
1004 | 0 | } |
1005 | ||
1006 | /** | |
1007 | * If things want to prevent event firing because they are doing a set of | |
1008 | * changes that should be notified in one go, they should call | |
1009 | * raiseEventSuppression() and when done call this. | |
1010 | * | |
1011 | * @return true if it is then safe to fire an event. | |
1012 | */ | |
1013 | public boolean lowerEventSuppressionAndTest() { | |
1014 | 0 | suppressEvents--; |
1015 | 0 | assert suppressEvents >= 0; |
1016 | ||
1017 | 0 | return suppressEvents == 0; |
1018 | } | |
1019 | ||
1020 | /** | |
1021 | * Convert the Object to a VerseRange. If base is a Verse then return a | |
1022 | * VerseRange of zero length. | |
1023 | * | |
1024 | * @param base | |
1025 | * The object to be cast | |
1026 | * @return The VerseRange | |
1027 | * @exception java.lang.ClassCastException | |
1028 | * If this is not a Verse or a VerseRange | |
1029 | */ | |
1030 | protected static VerseRange toVerseRange(Versification v11n, Object base) throws ClassCastException { | |
1031 | 0 | assert base != null; |
1032 | ||
1033 | 0 | if (base instanceof VerseRange) { |
1034 | 0 | return (VerseRange) base; |
1035 | 0 | } else if (base instanceof Verse) { |
1036 | 0 | return new VerseRange(v11n, (Verse) base); |
1037 | } | |
1038 | ||
1039 | 0 | throw new ClassCastException(JSOtherMsg.lookupText("Can only use Verses and VerseRanges in this Collection")); |
1040 | } | |
1041 | ||
1042 | /** | |
1043 | * Skip over verses that are part of a range | |
1044 | */ | |
1045 | 0 | protected static final class VerseRangeIterator implements Iterator<VerseRange> { |
1046 | /** | |
1047 | * iterate, amalgamating Verses into VerseRanges | |
1048 | */ | |
1049 | 0 | protected VerseRangeIterator(Versification v11n, Iterator<Key> it, RestrictionType restrict) { |
1050 | 0 | this.v11n = v11n; |
1051 | 0 | this.it = it; |
1052 | 0 | this.restrict = restrict; |
1053 | ||
1054 | 0 | if (it.hasNext()) { |
1055 | 0 | nextVerse = (Verse) it.next(); |
1056 | } | |
1057 | ||
1058 | 0 | calculateNext(); |
1059 | 0 | } |
1060 | ||
1061 | /* (non-Javadoc) | |
1062 | * @see java.util.Iterator#hasNext() | |
1063 | */ | |
1064 | public boolean hasNext() { | |
1065 | 0 | return nextRange != null; |
1066 | } | |
1067 | ||
1068 | /* (non-Javadoc) | |
1069 | * @see java.util.Iterator#next() | |
1070 | */ | |
1071 | public VerseRange next() throws NoSuchElementException { | |
1072 | 0 | VerseRange retcode = nextRange; |
1073 | ||
1074 | 0 | if (retcode == null) { |
1075 | 0 | throw new NoSuchElementException(); |
1076 | } | |
1077 | ||
1078 | 0 | calculateNext(); |
1079 | 0 | return retcode; |
1080 | } | |
1081 | ||
1082 | /* (non-Javadoc) | |
1083 | * @see java.util.Iterator#remove() | |
1084 | */ | |
1085 | public void remove() throws UnsupportedOperationException { | |
1086 | 0 | throw new UnsupportedOperationException(); |
1087 | } | |
1088 | ||
1089 | /** | |
1090 | * Find the next VerseRange | |
1091 | */ | |
1092 | private void calculateNext() { | |
1093 | 0 | if (nextVerse == null) { |
1094 | 0 | nextRange = null; |
1095 | 0 | return; |
1096 | } | |
1097 | ||
1098 | 0 | Verse start = nextVerse; |
1099 | 0 | Verse end = nextVerse; |
1100 | ||
1101 | findnext: while (true) { | |
1102 | 0 | if (!it.hasNext()) { |
1103 | 0 | nextVerse = null; |
1104 | 0 | break; |
1105 | } | |
1106 | ||
1107 | 0 | nextVerse = (Verse) it.next(); |
1108 | ||
1109 | // If the next verse adjacent | |
1110 | 0 | if (!v11n.isAdjacentVerse(end, nextVerse)) { |
1111 | 0 | break; |
1112 | } | |
1113 | ||
1114 | // Even if the next verse is adjacent we might want to break | |
1115 | // if we have moved into a new chapter/book | |
1116 | 0 | if (!restrict.isSameScope(v11n, end, nextVerse)) { |
1117 | 0 | break findnext; |
1118 | } | |
1119 | ||
1120 | 0 | end = nextVerse; |
1121 | } | |
1122 | ||
1123 | 0 | nextRange = new VerseRange(v11n, start, end); |
1124 | 0 | } |
1125 | ||
1126 | /** | |
1127 | * The Versification to which these verses belong. | |
1128 | */ | |
1129 | private Versification v11n; | |
1130 | ||
1131 | /** | |
1132 | * The Iterator that we are proxying to | |
1133 | */ | |
1134 | private Iterator<Key> it; | |
1135 | ||
1136 | /** | |
1137 | * What is the next VerseRange to be considered | |
1138 | */ | |
1139 | private VerseRange nextRange; | |
1140 | ||
1141 | /** | |
1142 | * What is the next Verse to be considered | |
1143 | */ | |
1144 | private Verse nextVerse; | |
1145 | ||
1146 | /** | |
1147 | * Do we restrict ranges to not crossing chapter boundaries | |
1148 | */ | |
1149 | private RestrictionType restrict; | |
1150 | } | |
1151 | ||
1152 | /** | |
1153 | * Write out the object to the given ObjectOutputStream. There are 3 ways of | |
1154 | * doing this - according to the 3 implementations of Passage. | |
1155 | * <ul> | |
1156 | * <li>Distinct: If we write out a list if verse ordinals then the space | |
1157 | * used is 4 bytes per verse. | |
1158 | * <li>Bitwise: If we write out a bitmap then the space used is something | |
1159 | * like 31104/8 = 4k bytes. | |
1160 | * <li>Ranged: The we write a list of start/end pairs then the space used is | |
1161 | * 8 bytes per range. | |
1162 | * </ul> | |
1163 | * Since we can take our time about this section, we calculate the optimal | |
1164 | * storage method before we do the saving. If some methods come out equal | |
1165 | * first then bitwise is preferred, then distinct, then ranged, because I | |
1166 | * imagine that for speed of deserialization this is the sensible order. | |
1167 | * I've not tested it though. | |
1168 | * | |
1169 | * @param out | |
1170 | * The stream to write our state to | |
1171 | * @throws IOException | |
1172 | * if the read fails | |
1173 | */ | |
1174 | protected void writeObjectSupport(ObjectOutputStream out) throws IOException { | |
1175 | // Save off the versification by name | |
1176 | 0 | out.writeUTF(v11n.getName()); |
1177 | ||
1178 | // the size in bits of teach storage method | |
1179 | 0 | int bitwiseSize = v11n.maximumOrdinal(); |
1180 | 0 | int rangedSize = 8 * countRanges(RestrictionType.NONE); |
1181 | 0 | int distinctSize = 4 * countVerses(); |
1182 | ||
1183 | // if bitwise is equal smallest | |
1184 | 0 | if (bitwiseSize <= rangedSize && bitwiseSize <= distinctSize) { |
1185 | 0 | out.writeInt(BITWISE); |
1186 | ||
1187 | 0 | BitSet store = new BitSet(bitwiseSize); |
1188 | 0 | Iterator<Key> iter = iterator(); |
1189 | 0 | while (iter.hasNext()) { |
1190 | 0 | Verse verse = (Verse) iter.next(); |
1191 | 0 | store.set(verse.getOrdinal()); |
1192 | 0 | } |
1193 | ||
1194 | 0 | out.writeObject(store); |
1195 | 0 | } else if (distinctSize <= rangedSize) { |
1196 | // if distinct is not bigger than ranged | |
1197 | // write the Passage type and the number of verses | |
1198 | 0 | out.writeInt(DISTINCT); |
1199 | 0 | out.writeInt(countVerses()); |
1200 | ||
1201 | // write the verse ordinals in a loop | |
1202 | 0 | for (Key aKey : this) { |
1203 | 0 | Verse verse = (Verse) aKey; |
1204 | 0 | out.writeInt(verse.getOrdinal()); |
1205 | 0 | } |
1206 | } else { | |
1207 | // otherwise use ranges | |
1208 | // write the Passage type and the number of ranges | |
1209 | 0 | out.writeInt(RANGED); |
1210 | 0 | out.writeInt(countRanges(RestrictionType.NONE)); |
1211 | ||
1212 | // write the verse ordinals in a loop | |
1213 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
1214 | 0 | while (it.hasNext()) { |
1215 | 0 | VerseRange range = it.next(); |
1216 | 0 | out.writeInt(range.getStart().getOrdinal()); |
1217 | 0 | out.writeInt(range.getCardinality()); |
1218 | 0 | } |
1219 | } | |
1220 | ||
1221 | // Ignore the original name. Is this wise? | |
1222 | // I am expecting that people are not that fussed about it and | |
1223 | // it could make everything far more verbose | |
1224 | 0 | } |
1225 | ||
1226 | /** | |
1227 | * Serialization support. | |
1228 | * | |
1229 | * @param is | |
1230 | * @throws IOException | |
1231 | * @throws ClassNotFoundException | |
1232 | */ | |
1233 | private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { | |
1234 | 0 | listeners = new ArrayList<PassageListener>(); |
1235 | 0 | originalName = null; |
1236 | 0 | parent = null; |
1237 | 0 | skipNormalization = 0; |
1238 | 0 | suppressEvents = 0; |
1239 | ||
1240 | 0 | is.defaultReadObject(); |
1241 | 0 | } |
1242 | ||
1243 | /** | |
1244 | * Write out the object to the given ObjectOutputStream | |
1245 | * | |
1246 | * @param is | |
1247 | * The stream to read our state from | |
1248 | * @throws IOException | |
1249 | * if the read fails | |
1250 | * @throws ClassNotFoundException | |
1251 | * If the read data is incorrect | |
1252 | */ | |
1253 | protected void readObjectSupport(ObjectInputStream is) throws IOException, ClassNotFoundException { | |
1254 | 0 | raiseEventSuppresion(); |
1255 | 0 | raiseNormalizeProtection(); |
1256 | ||
1257 | // Read the versification by name | |
1258 | 0 | String v11nName = is.readUTF(); |
1259 | 0 | v11n = Versifications.instance().getVersification(v11nName); |
1260 | ||
1261 | 0 | int type = is.readInt(); |
1262 | 0 | switch (type) { |
1263 | case BITWISE: | |
1264 | 0 | BitSet store = (BitSet) is.readObject(); |
1265 | 0 | for (int i = 0; i < v11n.maximumOrdinal(); i++) { |
1266 | 0 | if (store.get(i)) { |
1267 | 0 | add(v11n.decodeOrdinal(i)); |
1268 | } | |
1269 | } | |
1270 | 0 | break; |
1271 | ||
1272 | case DISTINCT: | |
1273 | 0 | int verses = is.readInt(); |
1274 | 0 | for (int i = 0; i < verses; i++) { |
1275 | 0 | int ord = is.readInt(); |
1276 | 0 | add(v11n.decodeOrdinal(ord)); |
1277 | } | |
1278 | 0 | break; |
1279 | ||
1280 | case RANGED: | |
1281 | 0 | int ranges = is.readInt(); |
1282 | 0 | for (int i = 0; i < ranges; i++) { |
1283 | 0 | int ord = is.readInt(); |
1284 | 0 | int count = is.readInt(); |
1285 | 0 | add(RestrictionType.NONE.toRange(getVersification(), v11n.decodeOrdinal(ord), count)); |
1286 | } | |
1287 | 0 | break; |
1288 | ||
1289 | default: | |
1290 | 0 | throw new ClassCastException(JSOtherMsg.lookupText("Can only use Verses and VerseRanges in this Collection")); |
1291 | } | |
1292 | ||
1293 | // We are ignoring the originalName. It was set to null in the | |
1294 | // default ctor so I will ignore it here. | |
1295 | ||
1296 | // We don't bother to call fireContentsChanged(...) because | |
1297 | // nothing can have registered at this point | |
1298 | 0 | lowerEventSuppressionAndTest(); |
1299 | 0 | lowerNormalizeProtection(); |
1300 | 0 | } |
1301 | ||
1302 | /** | |
1303 | * The log stream | |
1304 | */ | |
1305 | 0 | private static final Logger log = LoggerFactory.getLogger(AbstractPassage.class); |
1306 | ||
1307 | /** | |
1308 | * Serialization type constant for a BitWise layout | |
1309 | */ | |
1310 | protected static final int BITWISE = 0; | |
1311 | ||
1312 | /** | |
1313 | * Serialization type constant for a Distinct layout | |
1314 | */ | |
1315 | protected static final int DISTINCT = 1; | |
1316 | ||
1317 | /** | |
1318 | * Serialization type constant for a Ranged layout | |
1319 | */ | |
1320 | protected static final int RANGED = 2; | |
1321 | ||
1322 | /** | |
1323 | * Count of serializations methods | |
1324 | */ | |
1325 | protected static final int METHOD_COUNT = 3; | |
1326 | ||
1327 | /** | |
1328 | * The Versification to which this passage belongs. | |
1329 | */ | |
1330 | private transient Versification v11n; | |
1331 | ||
1332 | /** | |
1333 | * The parent key. See the key interface for more information. NOTE(joe): | |
1334 | * These keys are not serialized, should we? | |
1335 | * | |
1336 | * @see Key | |
1337 | */ | |
1338 | private transient Key parent; | |
1339 | ||
1340 | /** | |
1341 | * Support for change notification | |
1342 | */ | |
1343 | protected transient List<PassageListener> listeners; | |
1344 | ||
1345 | /** | |
1346 | * The original string for picky users | |
1347 | */ | |
1348 | protected transient String originalName; | |
1349 | ||
1350 | /** | |
1351 | * If we have several changes to make then we increment this and then | |
1352 | * decrement it when done (and fire an event off). If the cost of | |
1353 | * calculating the parameters to the fire is high then we can check that | |
1354 | * this is 0 before doing the calculation. | |
1355 | */ | |
1356 | protected transient int suppressEvents; | |
1357 | ||
1358 | /** | |
1359 | * Do we skip normalization for now - if we want to skip then we increment | |
1360 | * this, and the decrement it when done. | |
1361 | */ | |
1362 | protected transient int skipNormalization; | |
1363 | ||
1364 | /** | |
1365 | * What characters can we use to separate VerseRanges in a Passage | |
1366 | */ | |
1367 | public static final String REF_ALLOWED_DELIMS = ",;\n\r\t"; | |
1368 | ||
1369 | /** | |
1370 | * What characters should we use to separate VerseRanges in a Passage | |
1371 | */ | |
1372 | public static final String REF_PREF_DELIM = ", "; | |
1373 | ||
1374 | /** | |
1375 | * What characters should we use to separate VerseRanges in a Passage | |
1376 | */ | |
1377 | public static final String REF_OSIS_DELIM = " "; | |
1378 | ||
1379 | /** | |
1380 | * Serialization ID | |
1381 | */ | |
1382 | private static final long serialVersionUID = -5931560451407396276L; | |
1383 | } |