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