Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PassageTally |
|
| 2.816666666666667;2.817 | ||||
PassageTally$Order |
|
| 2.816666666666667;2.817 | ||||
PassageTally$OrderedVerseIterator |
|
| 2.816666666666667;2.817 | ||||
PassageTally$OrderedVerseRangeIterator |
|
| 2.816666666666667;2.817 | ||||
PassageTally$TalliedVerse |
|
| 2.816666666666667;2.817 | ||||
PassageTally$TalliedVerseRange |
|
| 2.816666666666667;2.817 | ||||
PassageTally$VerseIterator |
|
| 2.816666666666667;2.817 |
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 | * The copyright to this program is held by its authors. | |
19 | * | |
20 | */ | |
21 | package org.crosswire.jsword.passage; | |
22 | ||
23 | import java.io.IOException; | |
24 | import java.io.ObjectInputStream; | |
25 | import java.io.ObjectOutputStream; | |
26 | import java.util.Iterator; | |
27 | import java.util.NoSuchElementException; | |
28 | import java.util.Set; | |
29 | import java.util.TreeSet; | |
30 | ||
31 | import org.crosswire.jsword.JSOtherMsg; | |
32 | import org.crosswire.jsword.versification.Versification; | |
33 | import org.slf4j.Logger; | |
34 | import org.slf4j.LoggerFactory; | |
35 | ||
36 | /** | |
37 | * Similar to a Passage, but that stores a ranking for each of the Verses that | |
38 | * it contains. | |
39 | * | |
40 | * <p> | |
41 | * Currently there is no well defined spec for what the rank of a verse means - | |
42 | * it is just an int. Since this number is exposed in 2 places | |
43 | * (getNameAndTally() and getTallyFor()) we should specify what the numbers | |
44 | * mean. Trouble is most tallies come from searches where the numbers only have | |
45 | * relative meaning. | |
46 | * </p> | |
47 | * | |
48 | * <p> | |
49 | * This class exactly implements the Passage interface when the ordering is set | |
50 | * to Order.BIBLICAL, however an additional setting of Order.TALLY sorts the | |
51 | * verses by the rank in this tally. | |
52 | * | |
53 | * <p> | |
54 | * Calling <code>tally.add(Gen 1:1); tally.add(Gen 1:1);</code> is redundant for | |
55 | * a Passage however a PassageTally will increase the rank of Gen 1:1, there are | |
56 | * additional methods <code>unAdd()</code> and <code>unAddAll()</code> that do | |
57 | * the reverse, of decreasing the rank of the specified verse(s). | |
58 | * </p> | |
59 | * | |
60 | * <p> | |
61 | * The point is to allow a search for | |
62 | * "God loves us, and gave Jesus to die to save us" to correctly identify John | |
63 | * 3:16. So we are using fuzzy matching big style, but I think this will be very | |
64 | * useful. | |
65 | * </p> | |
66 | * | |
67 | * <p> | |
68 | * How should we rank VerseRanges? We could use a sum of the ranks of the verses | |
69 | * in a range or the maximum value of a range. The former would seem to be more | |
70 | * mathematically correct, but I think that the latter is better because: the | |
71 | * concept of max value is preserved, because a wide blurred match is generally | |
72 | * not as good as a sharply defined one. | |
73 | * </p> | |
74 | * | |
75 | * <p> | |
76 | * Should we be going for a PassageTallyFactory type approach? Of the 3 | |
77 | * implementations of Passage, The RangedPassage does not make sense here, and a | |
78 | * PassageTally will not have the range of uses that a Passage has, so I think | |
79 | * there is more likely to be a correct answer. So right now the answer is no. | |
80 | * </p> | |
81 | * | |
82 | * <p> | |
83 | * Memory considerations: The BitSet approach will always use a | |
84 | * <code>int[31000]</code> = 128k of memory.<br> | |
85 | * The Distinct approach will be n * int[4] where n is the number of verses | |
86 | * stored. I expect most searches to have at least n=1000. Also 128k<br> | |
87 | * Given this, (A Distinct style PassageTally will usually use more memory than | |
88 | * a BitSet style PassageTally) And the intuitive result that the BitSet will be | |
89 | * faster, I'm going to start by implementing the latter only. | |
90 | * </p> | |
91 | * | |
92 | * <p> | |
93 | * To think about - I've upped the MAX_TALLY to 20000 to help the new mapper | |
94 | * program. I'm not sure why it was originally 100? | |
95 | * | |
96 | * <p> | |
97 | * LATER(joe): Specify how passage ranks work. | |
98 | * | |
99 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
100 | * @author Joe Walker | |
101 | */ | |
102 | 0 | public class PassageTally extends AbstractPassage { |
103 | /** | |
104 | * Create an empty PassageTally | |
105 | * | |
106 | * @param v11n | |
107 | * The Versification to which this Passage belongs. | |
108 | */ | |
109 | public PassageTally(Versification v11n) { | |
110 | 0 | super(v11n); |
111 | 0 | board = new int[v11n.maximumOrdinal() + 1]; |
112 | 0 | } |
113 | ||
114 | /** | |
115 | * Create a Verse from a human readable string. The opposite of toString() | |
116 | * | |
117 | * @param v11n | |
118 | * The Versification to which this Passage belongs. | |
119 | * @param refs | |
120 | * The text to interpret | |
121 | * @param basis | |
122 | * The basis by which to interpret refs | |
123 | * @throws NoSuchVerseException | |
124 | * If refs is invalid | |
125 | */ | |
126 | protected PassageTally(Versification v11n, String refs, Key basis) throws NoSuchVerseException { | |
127 | 0 | super(v11n, refs); |
128 | 0 | board = new int[v11n.maximumOrdinal() + 1]; |
129 | 0 | addVerses(refs, basis); |
130 | 0 | } |
131 | ||
132 | protected PassageTally(Versification v11n, String refs) throws NoSuchVerseException { | |
133 | 0 | this(v11n, refs, null); |
134 | 0 | } |
135 | ||
136 | @Override | |
137 | public boolean isEmpty() { | |
138 | 0 | return size == 0; |
139 | } | |
140 | ||
141 | @Override | |
142 | public int countVerses() { | |
143 | 0 | return size; |
144 | } | |
145 | ||
146 | /** | |
147 | * Set how we sort the verses we output. The options are: | |
148 | * <ul> | |
149 | * <li>Order.BIBLICAL: Natural Biblical order</li> | |
150 | * <li>Order.TALLY: In an order specified by this class</li> | |
151 | * </ul> | |
152 | * | |
153 | * @param order | |
154 | * the sort order | |
155 | */ | |
156 | public void setOrdering(Order order) { | |
157 | 0 | this.order = order; |
158 | 0 | } |
159 | ||
160 | /** | |
161 | * Get how we sort the verses we output. | |
162 | * | |
163 | * @return the sort order | |
164 | */ | |
165 | public Order getOrdering() { | |
166 | 0 | return order; |
167 | } | |
168 | ||
169 | /** | |
170 | * @return the total | |
171 | */ | |
172 | public int getTotal() { | |
173 | 0 | return total; |
174 | } | |
175 | ||
176 | /** | |
177 | * @param total | |
178 | * the total to set | |
179 | */ | |
180 | public void setTotal(int total) { | |
181 | 0 | this.total = total; |
182 | 0 | } |
183 | ||
184 | @Override | |
185 | public PassageTally clone() { | |
186 | // This gets us a shallow copy | |
187 | 0 | PassageTally copy = (PassageTally) super.clone(); |
188 | ||
189 | 0 | copy.board = board.clone(); |
190 | ||
191 | 0 | return copy; |
192 | } | |
193 | ||
194 | @Override | |
195 | public String toString() { | |
196 | 0 | return getName(0); |
197 | } | |
198 | ||
199 | @Override | |
200 | public String getName() { | |
201 | 0 | return getName(0); |
202 | } | |
203 | ||
204 | /** | |
205 | * A Human readable version of the verse list. Uses short books names, and | |
206 | * the shortest possible rendering eg "Mat 3:1-4, 6" | |
207 | * | |
208 | * @param cnt | |
209 | * The number of matches to return, 0 gives all matches | |
210 | * @return a String containing a description of the verses | |
211 | */ | |
212 | public String getName(int cnt) { | |
213 | 0 | int maxCount = cnt; |
214 | 0 | if (PassageUtil.isPersistentNaming() && originalName != null) { |
215 | 0 | return originalName; |
216 | } | |
217 | ||
218 | 0 | StringBuilder retcode = new StringBuilder(); |
219 | ||
220 | 0 | if (order == Order.BIBLICAL) { |
221 | 0 | Iterator<VerseRange> it = rangeIterator(RestrictionType.NONE); |
222 | 0 | Verse current = null; |
223 | 0 | while (it.hasNext()) { |
224 | 0 | VerseRange range = it.next(); |
225 | 0 | retcode.append(range.getName(current)); |
226 | ||
227 | 0 | if (it.hasNext()) { |
228 | 0 | retcode.append(AbstractPassage.REF_PREF_DELIM); |
229 | } | |
230 | ||
231 | 0 | current = range.getStart(); |
232 | 0 | } |
233 | 0 | } else { |
234 | 0 | if (maxCount == 0) { |
235 | 0 | maxCount = Integer.MAX_VALUE; |
236 | } | |
237 | ||
238 | 0 | Iterator<Key> it = new OrderedVerseIterator(getVersification(), board); |
239 | 0 | Key current = null; |
240 | 0 | int count = 0; |
241 | ||
242 | 0 | while (it.hasNext() && count < maxCount) { |
243 | 0 | Key verse = it.next(); |
244 | 0 | retcode.append(verse.getName(current)); |
245 | ||
246 | 0 | current = verse; |
247 | 0 | count++; |
248 | ||
249 | 0 | if (it.hasNext() && count < maxCount) { |
250 | 0 | retcode.append(AbstractPassage.REF_PREF_DELIM); |
251 | } | |
252 | 0 | } |
253 | } | |
254 | ||
255 | 0 | return retcode.toString(); |
256 | } | |
257 | ||
258 | /** | |
259 | * A Human readable version of the PassageTally. Uses short books names, and | |
260 | * the shortest possible rendering eg "Mat 3:1-4" | |
261 | * | |
262 | * @return a String containing a description of the verses | |
263 | */ | |
264 | public String getNameAndTally() { | |
265 | 0 | return getNameAndTally(0); |
266 | } | |
267 | ||
268 | /** | |
269 | * A Human readable version of the PassageTally. Uses short books names, and | |
270 | * the shortest possible rendering eg "Mat 3:1-4" | |
271 | * | |
272 | * @param cnt | |
273 | * The number of matches to return, 0 gives all matches | |
274 | * @return a String containing a description of the verses | |
275 | */ | |
276 | public String getNameAndTally(int cnt) { | |
277 | 0 | int maxCount = cnt; |
278 | 0 | StringBuilder retcode = new StringBuilder(); |
279 | 0 | if (maxCount == 0) { |
280 | 0 | maxCount = Integer.MAX_VALUE; |
281 | } | |
282 | ||
283 | 0 | OrderedVerseIterator it = new OrderedVerseIterator(getVersification(), board); |
284 | 0 | int count = 0; |
285 | ||
286 | 0 | while (it.hasNext() && count < maxCount) { |
287 | 0 | Key verse = it.next(); |
288 | 0 | retcode.append(verse.getName()); |
289 | 0 | retcode.append(" ("); |
290 | 0 | retcode.append(100 * it.lastRank() / max); |
291 | 0 | retcode.append("%)"); |
292 | ||
293 | 0 | count++; |
294 | ||
295 | 0 | if (it.hasNext() && count < maxCount) { |
296 | 0 | retcode.append(AbstractPassage.REF_PREF_DELIM); |
297 | } | |
298 | 0 | } |
299 | ||
300 | 0 | return retcode.toString(); |
301 | } | |
302 | ||
303 | /** | |
304 | * Iterate through the verse elements in the current sort order | |
305 | * | |
306 | * @return A verse Iterator | |
307 | */ | |
308 | public Iterator<Key> iterator() { | |
309 | 0 | if (order == Order.BIBLICAL) { |
310 | 0 | return new VerseIterator(); |
311 | } | |
312 | 0 | return new OrderedVerseIterator(getVersification(), board); |
313 | } | |
314 | ||
315 | @Override | |
316 | public Iterator<VerseRange> rangeIterator(RestrictionType restrict) { | |
317 | 0 | if (order == Order.BIBLICAL) { |
318 | 0 | return new VerseRangeIterator(getVersification(), iterator(), restrict); |
319 | } | |
320 | 0 | return new OrderedVerseRangeIterator(getVersification(), iterator(), board); |
321 | } | |
322 | ||
323 | /** | |
324 | * Does this tally contain all the specified verses? | |
325 | * | |
326 | * @param that | |
327 | * The verses to test for | |
328 | * @return true if all the verses exist in this tally | |
329 | */ | |
330 | @Override | |
331 | public boolean contains(Key that) { | |
332 | 0 | for (Key aKey : that) { |
333 | 0 | Verse verse = (Verse) aKey; |
334 | 0 | if (board[verse.getOrdinal()] == 0) { |
335 | 0 | return false; |
336 | } | |
337 | 0 | } |
338 | ||
339 | 0 | return true; |
340 | } | |
341 | ||
342 | /** | |
343 | * The ranking given to a specific verse | |
344 | * | |
345 | * @param verse | |
346 | * The verse to get the ranking of | |
347 | * @return The rank of the verse in question | |
348 | */ | |
349 | public int getTallyOf(Verse verse) { | |
350 | 0 | return board[verse.getOrdinal()]; |
351 | } | |
352 | ||
353 | /** | |
354 | * What is the index of the give verse in the current ordering scheme | |
355 | * | |
356 | * @param verse | |
357 | * The verse to get the index of | |
358 | * @return The index of the verse or -1 if the verse was not found | |
359 | */ | |
360 | public int getIndexOf(Verse verse) { | |
361 | 0 | int pos = verse.getOrdinal(); |
362 | 0 | int tally = board[pos]; |
363 | 0 | return tally > 0 ? pos : -1; |
364 | } | |
365 | ||
366 | /** | |
367 | * Add/Increment this verses in the rankings | |
368 | * | |
369 | * @param that | |
370 | * The verses to add/increment | |
371 | */ | |
372 | public void add(Key that) { | |
373 | 0 | optimizeWrites(); |
374 | ||
375 | 0 | alterVerseBase(that, 1); |
376 | 0 | fireIntervalAdded(this, null, null); |
377 | 0 | } |
378 | ||
379 | /** | |
380 | * DONT USE THIS. It makes public something of the ratings scheme which is | |
381 | * not generally recommended. This method is likely to be removed at a | |
382 | * moments notice, and it only here to keep Mapper happy. Add/Increment this | |
383 | * verses in the rankings | |
384 | * | |
385 | * @param that | |
386 | * The verses to add/increment | |
387 | * @param count | |
388 | * The amount to increment by | |
389 | */ | |
390 | public void add(Key that, int count) { | |
391 | 0 | optimizeWrites(); |
392 | ||
393 | 0 | alterVerseBase(that, count); |
394 | 0 | fireIntervalAdded(this, null, null); |
395 | 0 | } |
396 | ||
397 | /** | |
398 | * Remove/Decrement this verses in the rankings | |
399 | * | |
400 | * @param that | |
401 | * The verses to remove/decrement | |
402 | */ | |
403 | public void unAdd(Key that) { | |
404 | 0 | optimizeWrites(); |
405 | ||
406 | 0 | alterVerseBase(that, -1); |
407 | 0 | fireIntervalRemoved(this, null, null); |
408 | 0 | } |
409 | ||
410 | /** | |
411 | * Remove these verses from the rankings, ie, set their rank to zero. | |
412 | * | |
413 | * @param that | |
414 | * The verses to remove/decrement | |
415 | */ | |
416 | public void remove(Key that) { | |
417 | 0 | optimizeWrites(); |
418 | ||
419 | 0 | for (Key aKey : that) { |
420 | 0 | Verse verse = (Verse) aKey; |
421 | 0 | kill(verse.getOrdinal()); |
422 | 0 | } |
423 | ||
424 | 0 | fireIntervalRemoved(this, null, null); |
425 | 0 | } |
426 | ||
427 | @Override | |
428 | public void addAll(Key that) { | |
429 | 0 | optimizeWrites(); |
430 | ||
431 | 0 | if (that instanceof PassageTally) { |
432 | 0 | PassageTally tally = (PassageTally) that; |
433 | ||
434 | 0 | int vib = getVersification().maximumOrdinal(); |
435 | 0 | for (int i = 0; i <= vib; i++) { |
436 | 0 | increment(i, tally.board[i]); |
437 | } | |
438 | ||
439 | 0 | incrementMax(tally.max); |
440 | 0 | } else { |
441 | 0 | for (Key aKey : that) { |
442 | 0 | Verse verse = (Verse) aKey; |
443 | 0 | increment(verse.getOrdinal(), 1); |
444 | 0 | } |
445 | ||
446 | 0 | incrementMax(1); |
447 | } | |
448 | ||
449 | 0 | fireIntervalAdded(this, null, null); |
450 | 0 | } |
451 | ||
452 | /** | |
453 | * Remove/Decrement these verses in the rankings | |
454 | * | |
455 | * @param that | |
456 | * The verses to remove/decrement | |
457 | */ | |
458 | public void unAddAll(Passage that) { | |
459 | 0 | optimizeWrites(); |
460 | ||
461 | 0 | if (that instanceof PassageTally) { |
462 | 0 | PassageTally tally = (PassageTally) that; |
463 | ||
464 | 0 | int vib = getVersification().maximumOrdinal(); |
465 | 0 | for (int i = 0; i <= vib; i++) { |
466 | 0 | increment(i, -tally.board[i]); |
467 | } | |
468 | 0 | } else { |
469 | 0 | for (Key aKey : that) { |
470 | 0 | Verse verse = (Verse) aKey; |
471 | 0 | increment(verse.getOrdinal(), -1); |
472 | 0 | } |
473 | } | |
474 | ||
475 | 0 | fireIntervalRemoved(this, null, null); |
476 | ||
477 | // Just because we've decremented some doesn't | |
478 | // change the max. So we don't need to do: | |
479 | // incrementMax(-1); | |
480 | 0 | } |
481 | ||
482 | @Override | |
483 | public void removeAll(Key key) { | |
484 | 0 | optimizeWrites(); |
485 | ||
486 | 0 | if (key instanceof PassageTally) { |
487 | 0 | PassageTally tally = (PassageTally) key; |
488 | ||
489 | 0 | int vib = getVersification().maximumOrdinal(); |
490 | 0 | for (int i = 0; i <= vib; i++) { |
491 | 0 | if (tally.board[i] != 0) { |
492 | 0 | kill(i); |
493 | } | |
494 | } | |
495 | 0 | } else { |
496 | 0 | for (Key aKey : key) { |
497 | 0 | Verse verse = (Verse) aKey; |
498 | 0 | kill(verse.getOrdinal()); |
499 | 0 | } |
500 | } | |
501 | ||
502 | 0 | fireIntervalRemoved(this, null, null); |
503 | ||
504 | // Just because we've decremented some doesn't | |
505 | // change the max. So we don't need to do: | |
506 | // incrementMax(-1); | |
507 | 0 | } |
508 | ||
509 | @Override | |
510 | public void clear() { | |
511 | 0 | optimizeWrites(); |
512 | ||
513 | 0 | for (int i = 0; i < board.length; i++) { |
514 | 0 | board[i] = 0; |
515 | } | |
516 | ||
517 | 0 | size = 0; |
518 | ||
519 | 0 | fireIntervalRemoved(this, null, null); |
520 | 0 | } |
521 | ||
522 | /** | |
523 | * Ensures that there are a maximum of <code>count</code> Verses in this | |
524 | * Passage. If there were more than <code>count</code> Verses then a new | |
525 | * Passage is created containing the Verses from <code>count</code> + 1 | |
526 | * onwards. If there was not greater than <code>count</code> in the Passage, | |
527 | * then the passage remains unchanged, and null is returned. | |
528 | * | |
529 | * @param count | |
530 | * The maximum number of Verses to allow in this collection | |
531 | * @return A new Passage containing the remaining verses or null | |
532 | * @see Verse | |
533 | */ | |
534 | @Override | |
535 | public Passage trimVerses(int count) { | |
536 | 0 | optimizeWrites(); |
537 | ||
538 | 0 | int i = 0; |
539 | 0 | boolean overflow = false; |
540 | ||
541 | 0 | Passage remainder = this.clone(); |
542 | ||
543 | 0 | for (Key verse : this) { |
544 | 0 | i++; |
545 | 0 | if (i > count) { |
546 | 0 | remove(verse); |
547 | 0 | overflow = true; |
548 | } else { | |
549 | 0 | remainder.remove(verse); |
550 | } | |
551 | ||
552 | } | |
553 | ||
554 | 0 | if (overflow) { |
555 | 0 | return remainder; |
556 | } | |
557 | 0 | return null; |
558 | } | |
559 | ||
560 | /** | |
561 | * Take the verses in the tally and give them all and equal rank of 1. After | |
562 | * this method has executed then both sorting methods for a. | |
563 | */ | |
564 | public void flatten() { | |
565 | 0 | optimizeWrites(); |
566 | ||
567 | 0 | for (int i = 0; i < board.length; i++) { |
568 | 0 | if (board[i] != 0) { |
569 | 0 | board[i] = 1; |
570 | } | |
571 | } | |
572 | ||
573 | 0 | max = 1; |
574 | 0 | } |
575 | ||
576 | @Override | |
577 | public void blur(int verses, RestrictionType restrict) { | |
578 | 0 | assert verses >= 0; |
579 | ||
580 | 0 | optimizeWrites(); |
581 | 0 | raiseEventSuppresion(); |
582 | 0 | raiseNormalizeProtection(); |
583 | ||
584 | 0 | if (!restrict.equals(RestrictionType.NONE)) { |
585 | 0 | log.warn("Restrict={} is not properly supported.", restrict); |
586 | ||
587 | // This is a bit of a cheat, but there is no way I'm going | |
588 | // to do the math to speed up the restricted version | |
589 | 0 | PassageTally temp = this.clone(); |
590 | 0 | Iterator<VerseRange> it = temp.rangeIterator(RestrictionType.NONE); |
591 | ||
592 | 0 | while (it.hasNext()) { |
593 | 0 | VerseRange range = it.next(); |
594 | 0 | for (int i = 0; i <= verses; i++) { |
595 | 0 | add(restrict.blur(getVersification(), range, i, i)); |
596 | } | |
597 | 0 | } |
598 | 0 | } else { |
599 | 0 | int[] newBoard = new int[board.length]; |
600 | ||
601 | 0 | for (int i = 0; i < board.length; i++) { |
602 | 0 | if (board[i] != 0) { |
603 | // This could be re-written more simply: | |
604 | // for (int j = -verses; j <= verses; j++) { | |
605 | // int k = i + j; | |
606 | // if (k >= 0 && k <= BibleInfo.maximumOrdinal()) { | |
607 | // new_board[k] += board[i] + verses - mod(j); | |
608 | // } | |
609 | // } | |
610 | // However splitting the loop in 2 will speed it up quite a bit. | |
611 | ||
612 | 0 | for (int j = -verses; j < 0; j++) { |
613 | 0 | int k = i + j; |
614 | 0 | if (k >= 0) { |
615 | 0 | newBoard[k] += board[i] + verses + j; |
616 | } | |
617 | } | |
618 | ||
619 | 0 | newBoard[i] += board[i] + verses; |
620 | ||
621 | 0 | for (int j = 1; j <= verses; j++) { |
622 | 0 | int k = i + j; |
623 | 0 | if (k < board.length - 1) { |
624 | 0 | newBoard[k] += board[i] + verses - j; |
625 | } | |
626 | } | |
627 | } | |
628 | } | |
629 | ||
630 | 0 | board = newBoard; |
631 | } | |
632 | ||
633 | 0 | resetMax(); |
634 | ||
635 | 0 | lowerNormalizeProtection(); |
636 | 0 | if (lowerEventSuppressionAndTest()) { |
637 | 0 | fireIntervalAdded(this, null, null); |
638 | } | |
639 | 0 | } |
640 | ||
641 | /** | |
642 | * Sometimes we end up not knowing what the max is - this makes sure we know | |
643 | * accurately. Same with size. | |
644 | */ | |
645 | private void resetMax() { | |
646 | 0 | optimizeWrites(); |
647 | ||
648 | 0 | max = 0; |
649 | 0 | size = 0; |
650 | 0 | for (int i = 0; i < board.length; i++) { |
651 | 0 | if (board[i] > 0) { |
652 | 0 | size++; |
653 | } | |
654 | 0 | if (board[i] > max) { |
655 | 0 | max = board[i]; |
656 | } | |
657 | } | |
658 | 0 | } |
659 | ||
660 | /** | |
661 | * Increment/Decrement this verses in the rankings | |
662 | * | |
663 | * @param that | |
664 | * The verses to add/increment | |
665 | * @param tally | |
666 | * The amount to increment/decrement by | |
667 | */ | |
668 | private void alterVerseBase(Key that, int tally) { | |
669 | 0 | for (Key aKey : that) { |
670 | 0 | Verse verse = (Verse) aKey; |
671 | 0 | increment(verse.getOrdinal(), tally); |
672 | 0 | } |
673 | ||
674 | 0 | if (tally > 0) { |
675 | 0 | incrementMax(tally); |
676 | } | |
677 | 0 | } |
678 | ||
679 | /** | |
680 | * Increment a verse by an amount | |
681 | * | |
682 | * @param ord | |
683 | * The verse to increment | |
684 | * @param tally | |
685 | * The amount to increase by | |
686 | */ | |
687 | private void increment(int ord, int tally) { | |
688 | 0 | boolean exists = board[ord] > 0; |
689 | 0 | board[ord] += tally; |
690 | 0 | if (board[ord] > MAX_TALLY) { |
691 | 0 | board[ord] = MAX_TALLY; |
692 | } | |
693 | 0 | if (board[ord] < 0) { |
694 | 0 | board[ord] = 0; |
695 | } | |
696 | ||
697 | // Recompute the size | |
698 | 0 | if (exists && board[ord] == 0) { |
699 | 0 | size--; |
700 | 0 | } else if (!exists && board[ord] > 0) { |
701 | 0 | size++; |
702 | } | |
703 | 0 | } |
704 | ||
705 | /** | |
706 | * Increment a verse by an amount | |
707 | * | |
708 | * @param tally | |
709 | * The amount to increase by | |
710 | */ | |
711 | private void incrementMax(int tally) { | |
712 | 0 | max += tally; |
713 | 0 | if (max > MAX_TALLY) { |
714 | 0 | max = MAX_TALLY; |
715 | } | |
716 | 0 | if (max < 0) { |
717 | 0 | max = 0; |
718 | } | |
719 | 0 | } |
720 | ||
721 | /** | |
722 | * Wipe the rank of the given verse to zero | |
723 | * | |
724 | * @param ord | |
725 | * The verse to increment | |
726 | */ | |
727 | private void kill(int ord) { | |
728 | 0 | if (board[ord] > 0) { |
729 | 0 | size--; |
730 | } | |
731 | ||
732 | 0 | board[ord] = 0; |
733 | 0 | } |
734 | ||
735 | /** | |
736 | * Call the support mechanism in AbstractPassage | |
737 | * | |
738 | * @param out | |
739 | * The stream to write our state to | |
740 | * @serialData Write the ordinal number of this verse | |
741 | * @see AbstractPassage#writeObjectSupport(ObjectOutputStream) | |
742 | * @throws IOException | |
743 | * if the read fails | |
744 | */ | |
745 | private void writeObject(ObjectOutputStream out) throws IOException { | |
746 | 0 | out.defaultWriteObject(); |
747 | ||
748 | 0 | writeObjectSupport(out); |
749 | 0 | } |
750 | ||
751 | /** | |
752 | * Call the support mechanism in AbstractPassage | |
753 | * | |
754 | * @param in | |
755 | * The stream to read our state from | |
756 | * @throws IOException | |
757 | * if the read fails | |
758 | * @throws ClassNotFoundException | |
759 | * If the read data is incorrect | |
760 | * @serialData Write the ordinal number of this verse | |
761 | * @see AbstractPassage#readObjectSupport(ObjectInputStream) | |
762 | */ | |
763 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | |
764 | 0 | optimizeWrites(); |
765 | ||
766 | 0 | in.defaultReadObject(); |
767 | ||
768 | 0 | readObjectSupport(in); |
769 | 0 | } |
770 | ||
771 | /** | |
772 | * Indicates how this PassageTally is to order it's Verses. | |
773 | */ | |
774 | 0 | public enum Order { |
775 | /** | |
776 | * Sort in Biblical order | |
777 | */ | |
778 | 0 | BIBLICAL, |
779 | ||
780 | /** | |
781 | * Sort in tally rank order | |
782 | */ | |
783 | 0 | TALLY |
784 | } | |
785 | ||
786 | /** | |
787 | * The highest tally possible | |
788 | */ | |
789 | public static final int MAX_TALLY = 20000; | |
790 | ||
791 | /* | |
792 | * The number of verses in the tally. | |
793 | */ | |
794 | private int size; | |
795 | ||
796 | /* | |
797 | * The total number of verses that this tally represents. | |
798 | */ | |
799 | private int total; | |
800 | ||
801 | /** | |
802 | * The tally board itself | |
803 | */ | |
804 | protected int[] board; | |
805 | ||
806 | /** | |
807 | * The maximum tally possible | |
808 | */ | |
809 | private int max; | |
810 | ||
811 | /** | |
812 | * The maximum tally possible | |
813 | */ | |
814 | 0 | private Order order = Order.BIBLICAL; |
815 | ||
816 | /** | |
817 | * The log stream | |
818 | */ | |
819 | 0 | private static final Logger log = LoggerFactory.getLogger(PassageTally.class); |
820 | ||
821 | /** | |
822 | * Serialization ID | |
823 | */ | |
824 | private static final long serialVersionUID = 3761128240928274229L; | |
825 | ||
826 | /** | |
827 | * Iterate over the Verses in normal verse order | |
828 | * | |
829 | * @author Joe Walker | |
830 | */ | |
831 | 0 | private final class VerseIterator implements Iterator<Key> { |
832 | /** | |
833 | * Find the first unused verse | |
834 | */ | |
835 | 0 | protected VerseIterator() { |
836 | 0 | calculateNext(); |
837 | 0 | } |
838 | ||
839 | /* (non-Javadoc) | |
840 | * @see java.util.Iterator#hasNext() | |
841 | */ | |
842 | public boolean hasNext() { | |
843 | 0 | return next <= board.length - 1; |
844 | } | |
845 | ||
846 | /* (non-Javadoc) | |
847 | * @see java.util.Iterator#next() | |
848 | */ | |
849 | public Key next() throws NoSuchElementException { | |
850 | 0 | if (next >= board.length) { |
851 | 0 | throw new NoSuchElementException(); |
852 | } | |
853 | ||
854 | 0 | Key retcode = getVersification().decodeOrdinal(next); |
855 | 0 | calculateNext(); |
856 | ||
857 | 0 | return retcode; |
858 | } | |
859 | ||
860 | /* (non-Javadoc) | |
861 | * @see java.util.Iterator#remove() | |
862 | */ | |
863 | public void remove() throws UnsupportedOperationException { | |
864 | 0 | throw new UnsupportedOperationException(); |
865 | } | |
866 | ||
867 | /** | |
868 | * Find the next bit | |
869 | */ | |
870 | private void calculateNext() { | |
871 | do { | |
872 | 0 | next++; |
873 | 0 | } while (next < board.length && board[next] == 0); |
874 | 0 | } |
875 | ||
876 | /** What is the next Verse to be considered */ | |
877 | private int next; | |
878 | } | |
879 | ||
880 | /** | |
881 | * Iterate over the Verses in order of their rank in the tally | |
882 | * | |
883 | * @author Joe Walker | |
884 | */ | |
885 | 0 | private static final class OrderedVerseIterator implements Iterator<Key> { |
886 | /** | |
887 | * Find the first unused verse | |
888 | */ | |
889 | 0 | protected OrderedVerseIterator(Versification v11n, int[] board) { |
890 | 0 | referenceSystem = v11n; |
891 | 0 | TreeSet<TalliedVerse> output = new TreeSet<TalliedVerse>(); |
892 | ||
893 | 0 | int vib = board.length - 1; |
894 | 0 | for (int i = 0; i <= vib; i++) { |
895 | 0 | if (board[i] != 0) { |
896 | 0 | output.add(new TalliedVerse(i, board[i])); |
897 | } | |
898 | } | |
899 | ||
900 | 0 | it = output.iterator(); |
901 | 0 | last = null; |
902 | 0 | } |
903 | ||
904 | /* (non-Javadoc) | |
905 | * @see java.util.Iterator#hasNext() | |
906 | */ | |
907 | public boolean hasNext() { | |
908 | 0 | return it.hasNext(); |
909 | } | |
910 | ||
911 | /* (non-Javadoc) | |
912 | * @see java.util.Iterator#next() | |
913 | */ | |
914 | public Key next() throws NoSuchElementException { | |
915 | 0 | last = it.next(); |
916 | 0 | return referenceSystem.decodeOrdinal(last.ord); |
917 | } | |
918 | ||
919 | /* (non-Javadoc) | |
920 | * @see java.util.Iterator#remove() | |
921 | */ | |
922 | public void remove() throws UnsupportedOperationException { | |
923 | 0 | throw new UnsupportedOperationException(); |
924 | } | |
925 | ||
926 | /** | |
927 | * @return the next Verse in the iteration | |
928 | * @throws NoSuchElementException | |
929 | * if hasNext() == false | |
930 | */ | |
931 | public int lastRank() throws NoSuchElementException { | |
932 | 0 | if (last != null) { |
933 | 0 | return last.tally; |
934 | } | |
935 | 0 | throw new NoSuchElementException(JSOtherMsg.lookupText("nextElement() has not been called yet.")); |
936 | } | |
937 | ||
938 | /** | |
939 | * The Versification is needed to decode board positions. | |
940 | */ | |
941 | private Versification referenceSystem; | |
942 | /** | |
943 | * So that we can get at the ranking of the given verse | |
944 | */ | |
945 | private TalliedVerse last; | |
946 | ||
947 | /** | |
948 | * The Iterator we are converting | |
949 | */ | |
950 | private Iterator<TalliedVerse> it; | |
951 | } | |
952 | ||
953 | /** | |
954 | * Hack to make this work with J2SE 1.1 as well as J2SE 1.2 This compared 2 | |
955 | * Integers | |
956 | */ | |
957 | 0 | private static class TalliedVerse implements Comparable<TalliedVerse> { |
958 | /** | |
959 | * Convenience ctor to set the public variables | |
960 | * | |
961 | * @param ord | |
962 | * the verse id | |
963 | * @param tally | |
964 | * the rank of the verse | |
965 | */ | |
966 | 0 | protected TalliedVerse(int ord, int tally) { |
967 | 0 | this.ord = ord; |
968 | 0 | this.tally = tally; |
969 | 0 | } |
970 | ||
971 | @Override | |
972 | public int hashCode() { | |
973 | 0 | int result = 31 + ord; |
974 | 0 | return 31 * result + tally; |
975 | } | |
976 | ||
977 | @Override | |
978 | public boolean equals(Object obj) { | |
979 | 0 | if (this == obj) { |
980 | 0 | return true; |
981 | } | |
982 | 0 | if (obj == null) { |
983 | 0 | return false; |
984 | } | |
985 | 0 | if (getClass() != obj.getClass()) { |
986 | 0 | return false; |
987 | } | |
988 | 0 | final TalliedVerse other = (TalliedVerse) obj; |
989 | 0 | if (tally != other.tally) { |
990 | 0 | return false; |
991 | } | |
992 | 0 | if (ord != other.ord) { |
993 | 0 | return false; |
994 | } | |
995 | 0 | return true; |
996 | } | |
997 | ||
998 | /* (non-Javadoc) | |
999 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
1000 | */ | |
1001 | public int compareTo(TalliedVerse that) { | |
1002 | 0 | if (that.tally == this.tally) { |
1003 | 0 | return this.ord - that.ord; |
1004 | } | |
1005 | ||
1006 | 0 | return that.tally - this.tally; |
1007 | } | |
1008 | ||
1009 | /** | |
1010 | * The verse id | |
1011 | */ | |
1012 | protected int ord; | |
1013 | ||
1014 | /** | |
1015 | * The rank of the verse | |
1016 | */ | |
1017 | protected int tally; | |
1018 | } | |
1019 | ||
1020 | /** | |
1021 | * Iterate over the Ranges in order of their rank in the tally | |
1022 | * | |
1023 | * @author Joe Walker | |
1024 | */ | |
1025 | 0 | private static final class OrderedVerseRangeIterator implements Iterator<VerseRange> { |
1026 | /** | |
1027 | * Find the first unused verse | |
1028 | * | |
1029 | * @param v11n | |
1030 | * the versification to which this reference pertains | |
1031 | * @param vit | |
1032 | * @param board | |
1033 | */ | |
1034 | 0 | protected OrderedVerseRangeIterator(Versification v11n, Iterator<Key> vit, int[] board) { |
1035 | 0 | Set<TalliedVerseRange> output = new TreeSet<TalliedVerseRange>(); |
1036 | ||
1037 | 0 | Iterator<VerseRange> rit = new VerseRangeIterator(v11n, vit, RestrictionType.NONE); |
1038 | 0 | while (rit.hasNext()) { |
1039 | 0 | VerseRange range = rit.next(); |
1040 | ||
1041 | // Calculate the maximum rank for a verse | |
1042 | 0 | int rank = 0; |
1043 | 0 | Iterator<Key> iter = range.iterator(); |
1044 | 0 | while (iter.hasNext()) { |
1045 | 0 | Verse verse = (Verse) iter.next(); |
1046 | 0 | int temp = board[verse.getOrdinal()]; |
1047 | 0 | if (temp > rank) { |
1048 | 0 | rank = temp; |
1049 | } | |
1050 | 0 | } |
1051 | ||
1052 | 0 | output.add(new TalliedVerseRange(range, rank)); |
1053 | 0 | } |
1054 | ||
1055 | 0 | this.it = output.iterator(); |
1056 | 0 | last = null; |
1057 | 0 | } |
1058 | ||
1059 | /* (non-Javadoc) | |
1060 | * @see java.util.Iterator#hasNext() | |
1061 | */ | |
1062 | public boolean hasNext() { | |
1063 | 0 | return it.hasNext(); |
1064 | } | |
1065 | ||
1066 | /* (non-Javadoc) | |
1067 | * @see java.util.Iterator#next() | |
1068 | */ | |
1069 | public VerseRange next() throws NoSuchElementException { | |
1070 | 0 | last = it.next(); |
1071 | 0 | return last.range; |
1072 | } | |
1073 | ||
1074 | /* (non-Javadoc) | |
1075 | * @see java.util.Iterator#remove() | |
1076 | */ | |
1077 | public void remove() throws UnsupportedOperationException { | |
1078 | 0 | throw new UnsupportedOperationException(); |
1079 | } | |
1080 | ||
1081 | /** | |
1082 | * So that we can get at the ranking of the given verse | |
1083 | */ | |
1084 | private TalliedVerseRange last; | |
1085 | ||
1086 | /** | |
1087 | * The Iterator we are converting | |
1088 | */ | |
1089 | private Iterator<TalliedVerseRange> it; | |
1090 | } | |
1091 | ||
1092 | /** | |
1093 | * Hack to make this work with JDK1.1 as well as JDK1.2 This compared 2 | |
1094 | * Integers | |
1095 | */ | |
1096 | 0 | private static class TalliedVerseRange implements Comparable<TalliedVerseRange> { |
1097 | /** | |
1098 | * Convenience ctor to set the public variables | |
1099 | * | |
1100 | * @param range | |
1101 | * The verse range | |
1102 | * @param tally | |
1103 | * The rank of the verse | |
1104 | */ | |
1105 | 0 | protected TalliedVerseRange(VerseRange range, int tally) { |
1106 | 0 | this.range = range; |
1107 | 0 | this.tally = tally; |
1108 | 0 | } |
1109 | ||
1110 | @Override | |
1111 | public int hashCode() { | |
1112 | 0 | int result = 31 + tally; |
1113 | 0 | return 31 * result + ((range == null) ? 0 : range.hashCode()); |
1114 | } | |
1115 | ||
1116 | @Override | |
1117 | public boolean equals(Object obj) { | |
1118 | 0 | if (this == obj) { |
1119 | 0 | return true; |
1120 | } | |
1121 | 0 | if (obj == null) { |
1122 | 0 | return false; |
1123 | } | |
1124 | 0 | if (getClass() != obj.getClass()) { |
1125 | 0 | return false; |
1126 | } | |
1127 | 0 | final TalliedVerseRange other = (TalliedVerseRange) obj; |
1128 | 0 | if (tally != other.tally) { |
1129 | 0 | return false; |
1130 | } | |
1131 | 0 | if (range == null) { |
1132 | 0 | if (other.range != null) { |
1133 | 0 | return false; |
1134 | } | |
1135 | 0 | } else if (!range.equals(other.range)) { |
1136 | 0 | return false; |
1137 | } | |
1138 | 0 | return true; |
1139 | } | |
1140 | ||
1141 | /* (non-Javadoc) | |
1142 | * @see java.lang.Comparable#compareTo(java.lang.Object) | |
1143 | */ | |
1144 | public int compareTo(TalliedVerseRange that) { | |
1145 | 0 | if (that.tally == this.tally) { |
1146 | 0 | return this.range.compareTo(that.range); |
1147 | } | |
1148 | ||
1149 | 0 | return that.tally - this.tally; |
1150 | } | |
1151 | ||
1152 | /** | |
1153 | * The verse range | |
1154 | */ | |
1155 | protected VerseRange range; | |
1156 | ||
1157 | /** | |
1158 | * The rank of the verse | |
1159 | */ | |
1160 | protected int tally; | |
1161 | } | |
1162 | } |