Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
BitwisePassage |
|
| 2.4347826086956523;2.435 | ||||
BitwisePassage$VerseIterator |
|
| 2.4347826086956523;2.435 |
1 | /** | |
2 | * Distribution License: | |
3 | * JSword is free software; you can redistribute it and/or modify it under | |
4 | * the terms of the GNU Lesser General Public License, version 2.1 or later | |
5 | * as published by the Free Software Foundation. This program is distributed | |
6 | * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | |
7 | * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
8 | * See the GNU Lesser General Public License for more details. | |
9 | * | |
10 | * The License is available on the internet at: | |
11 | * http://www.gnu.org/copyleft/lgpl.html | |
12 | * or by writing to: | |
13 | * Free Software Foundation, Inc. | |
14 | * 59 Temple Place - Suite 330 | |
15 | * Boston, MA 02111-1307, USA | |
16 | * | |
17 | * © CrossWire Bible Society, 2005 - 2016 | |
18 | * | |
19 | */ | |
20 | package org.crosswire.jsword.passage; | |
21 | ||
22 | import java.io.IOException; | |
23 | import java.io.ObjectInputStream; | |
24 | import java.io.ObjectOutputStream; | |
25 | import java.util.BitSet; | |
26 | import java.util.Iterator; | |
27 | import java.util.NoSuchElementException; | |
28 | ||
29 | import org.crosswire.jsword.versification.Versification; | |
30 | import org.crosswire.jsword.versification.system.Versifications; | |
31 | ||
32 | /** | |
33 | * A Passage that is implemented using a BitSet - one for each verse. The | |
34 | * attributes of the style are: | |
35 | * <ul> | |
36 | * <li>Fairly fast manipulation | |
37 | * <li>Fairly getName() | |
38 | * <li>Static size, poor for small Passages, good for large Passages | |
39 | * </ul> | |
40 | * | |
41 | * <p> | |
42 | * The BitSet has one more bit than the number of verses in the Bible. This | |
43 | * would waste 1 bit per BitSet but since this doesn't cause BitSet to need an | |
44 | * extra long it doesn't, and it saves us some maths. | |
45 | * </p> | |
46 | * | |
47 | * @see gnu.lgpl.License The GNU Lesser General Public License for details. | |
48 | * @author Joe Walker | |
49 | * @author DM Smith | |
50 | */ | |
51 | 0 | public class BitwisePassage extends AbstractPassage { |
52 | /** | |
53 | * Create an empty BitwisePassage. There are no ctors from either Verse or | |
54 | * VerseRange so you need to do new <code>DistinctPassage().add(...);</code> | |
55 | * | |
56 | * @param v11n | |
57 | * The Versification to which this Passage belongs. | |
58 | */ | |
59 | public BitwisePassage(Versification v11n) { | |
60 | 0 | super(v11n); |
61 | 0 | store = new BitSet(v11n.maximumOrdinal() + 1); |
62 | 0 | } |
63 | ||
64 | /** | |
65 | * Create a Verse from a human readable string. The opposite of toString(), | |
66 | * Given any BitwisePassage v1, and the following | |
67 | * <code>DistinctPassage v2 = new BitwisePassage(v1.toString());</code> Then | |
68 | * <code>v1.equals(v2);</code> Theoretically, since there are many ways of | |
69 | * representing a BitwisePassage as text string comparison along the lines | |
70 | * of: <code>v1.toString().equals(v2.toString())</code> could be false. | |
71 | * Practically since toString() is standardized this will be true however. | |
72 | * We don't need to worry about thread safety in a ctor since we don't exist | |
73 | * yet. | |
74 | * | |
75 | * @param v11n | |
76 | * The Versification to which this Passage belongs. | |
77 | * @param refs | |
78 | * A String containing the text of the BitwisePassage | |
79 | * @param basis | |
80 | * The basis by which to interpret refs | |
81 | * @throws NoSuchVerseException | |
82 | * If the string is not parsable | |
83 | */ | |
84 | protected BitwisePassage(Versification v11n, String refs, Key basis) throws NoSuchVerseException { | |
85 | 0 | super(v11n, refs); |
86 | 0 | store = new BitSet(v11n.maximumOrdinal() + 1); |
87 | 0 | addVerses(refs, basis); |
88 | 0 | } |
89 | ||
90 | /** | |
91 | * Create a Verse from a human readable string. The opposite of toString(), | |
92 | * Given any BitwisePassage v1, and the following | |
93 | * <code>DistinctPassage v2 = new BitwisePassage(v1.toString());</code> Then | |
94 | * <code>v1.equals(v2);</code> Theoretically, since there are many ways of | |
95 | * representing a BitwisePassage as text string comparison along the lines | |
96 | * of: <code>v1.toString().equals(v2.toString())</code> could be false. | |
97 | * Practically since toString() is standardized this will be true however. | |
98 | * We don't need to worry about thread safety in a ctor since we don't exist | |
99 | * yet. | |
100 | * | |
101 | * @param v11n | |
102 | * The Versification to which this Passage belongs. | |
103 | * @param refs | |
104 | * A String containing the text of the BitwisePassage | |
105 | * @throws NoSuchVerseException | |
106 | * If the string is not parsable | |
107 | */ | |
108 | protected BitwisePassage(Versification v11n, String refs) throws NoSuchVerseException { | |
109 | 0 | this(v11n, refs, null); |
110 | 0 | } |
111 | ||
112 | @Override | |
113 | public BitwisePassage clone() { | |
114 | // This gets us a shallow copy | |
115 | 0 | BitwisePassage copy = (BitwisePassage) super.clone(); |
116 | ||
117 | 0 | copy.store = (BitSet) store.clone(); |
118 | ||
119 | 0 | return copy; |
120 | } | |
121 | ||
122 | @Override | |
123 | public int countVerses() { | |
124 | 0 | return store.cardinality(); |
125 | } | |
126 | ||
127 | @Override | |
128 | public boolean isEmpty() { | |
129 | 0 | return store.isEmpty(); |
130 | } | |
131 | ||
132 | /* (non-Javadoc) | |
133 | * @see java.lang.Iterable#iterator() | |
134 | */ | |
135 | public Iterator<Key> iterator() { | |
136 | 0 | return new VerseIterator(); |
137 | } | |
138 | ||
139 | @Override | |
140 | public boolean contains(Key obj) { | |
141 | 0 | for (Key aKey : obj) { |
142 | 0 | Verse verse = (Verse) aKey; |
143 | 0 | if (!store.get(verse.getOrdinal())) { |
144 | 0 | return false; |
145 | } | |
146 | 0 | } |
147 | ||
148 | 0 | return true; |
149 | } | |
150 | ||
151 | /* (non-Javadoc) | |
152 | * @see org.crosswire.jsword.passage.Passage#add(org.crosswire.jsword.passage.Key) | |
153 | */ | |
154 | public void add(Key obj) { | |
155 | 0 | optimizeWrites(); |
156 | ||
157 | 0 | Verse firstVerse = null; |
158 | 0 | Verse lastVerse = null; |
159 | 0 | for (Key aKey : obj) { |
160 | 0 | lastVerse = (Verse) aKey; |
161 | 0 | if (firstVerse == null) { |
162 | 0 | firstVerse = lastVerse; |
163 | } | |
164 | 0 | store.set(lastVerse.getOrdinal()); |
165 | } | |
166 | ||
167 | // we do an extra check here because the cost of calculating the | |
168 | // params is non-zero and may be wasted | |
169 | 0 | if (suppressEvents == 0) { |
170 | 0 | fireIntervalAdded(this, firstVerse, lastVerse); |
171 | } | |
172 | 0 | } |
173 | ||
174 | /** | |
175 | * A shortcut to adding a key, by ordinal. The ordinal needs to be taken | |
176 | * from the same versification as the passage being created. | |
177 | * | |
178 | * @param ordinal | |
179 | * the ordinal | |
180 | */ | |
181 | public void addVersifiedOrdinal(int ordinal) { | |
182 | 0 | optimizeWrites(); |
183 | 0 | store.set(ordinal); |
184 | ||
185 | // we do an extra check here because the cost of calculating the | |
186 | // params is non-zero and may be wasted | |
187 | 0 | if (suppressEvents == 0) { |
188 | 0 | Verse verse = getVersification().decodeOrdinal(ordinal); |
189 | 0 | fireIntervalAdded(this, verse, verse); |
190 | } | |
191 | 0 | } |
192 | ||
193 | /* (non-Javadoc) | |
194 | * @see org.crosswire.jsword.passage.Passage#remove(org.crosswire.jsword.passage.Key) | |
195 | */ | |
196 | public void remove(Key obj) { | |
197 | 0 | optimizeWrites(); |
198 | ||
199 | 0 | Verse firstVerse = null; |
200 | 0 | Verse lastVerse = null; |
201 | 0 | for (Key aKey : obj) { |
202 | 0 | lastVerse = (Verse) aKey; |
203 | 0 | if (firstVerse == null) { |
204 | 0 | firstVerse = lastVerse; |
205 | } | |
206 | 0 | store.clear(lastVerse.getOrdinal()); |
207 | } | |
208 | ||
209 | // we do an extra check here because the cost of calculating the | |
210 | // params is non-zero and may be wasted | |
211 | 0 | if (suppressEvents == 0) { |
212 | 0 | fireIntervalAdded(this, firstVerse, lastVerse); |
213 | } | |
214 | 0 | } |
215 | ||
216 | @Override | |
217 | public void addAll(Key key) { | |
218 | //check for key empty. This avoids the AIOBounds with that.getVerseAt, during event firing | |
219 | 0 | if (key.isEmpty()) { |
220 | //nothing to add | |
221 | 0 | return; |
222 | } | |
223 | ||
224 | 0 | optimizeWrites(); |
225 | ||
226 | 0 | if (key instanceof BitwisePassage) { |
227 | 0 | BitwisePassage thatRef = (BitwisePassage) key; |
228 | 0 | store.or(thatRef.store); |
229 | 0 | } else { |
230 | 0 | super.addAll(key); |
231 | } | |
232 | ||
233 | // we do an extra check here because the cost of calculating the | |
234 | // params is non-zero and may be wasted | |
235 | 0 | if (suppressEvents == 0 && !key.isEmpty()) { |
236 | 0 | if (key instanceof Passage) { |
237 | 0 | Passage that = (Passage) key; |
238 | 0 | fireIntervalAdded(this, that.getVerseAt(0), that.getVerseAt(that.countVerses() - 1)); |
239 | 0 | } else if (key instanceof VerseRange) { |
240 | 0 | VerseRange that = (VerseRange) key; |
241 | 0 | fireIntervalAdded(this, that.getStart(), that.getEnd()); |
242 | 0 | } else if (key instanceof Verse) { |
243 | 0 | Verse that = (Verse) key; |
244 | 0 | fireIntervalAdded(this, that, that); |
245 | } | |
246 | } | |
247 | 0 | } |
248 | ||
249 | @Override | |
250 | public void removeAll(Key key) { | |
251 | 0 | optimizeWrites(); |
252 | ||
253 | 0 | if (key instanceof BitwisePassage) { |
254 | 0 | BitwisePassage thatRef = (BitwisePassage) key; |
255 | ||
256 | 0 | store.andNot(thatRef.store); |
257 | 0 | } else { |
258 | 0 | super.removeAll(key); |
259 | } | |
260 | ||
261 | // we do an extra check here because the cost of calculating the | |
262 | // params is non-zero and may be wasted | |
263 | 0 | if (suppressEvents == 0 && !key.isEmpty()) { |
264 | 0 | if (key instanceof Passage) { |
265 | 0 | Passage that = (Passage) key; |
266 | 0 | fireIntervalRemoved(this, that.getVerseAt(0), that.getVerseAt(that.countVerses() - 1)); |
267 | 0 | } else if (key instanceof VerseRange) { |
268 | 0 | VerseRange that = (VerseRange) key; |
269 | 0 | fireIntervalRemoved(this, that.getStart(), that.getEnd()); |
270 | 0 | } else if (key instanceof Verse) { |
271 | 0 | Verse that = (Verse) key; |
272 | 0 | fireIntervalRemoved(this, that, that); |
273 | } | |
274 | } | |
275 | 0 | } |
276 | ||
277 | @Override | |
278 | public void retainAll(Key key) { | |
279 | 0 | optimizeWrites(); |
280 | ||
281 | 0 | BitSet thatStore = null; |
282 | 0 | if (key instanceof BitwisePassage) { |
283 | 0 | thatStore = ((BitwisePassage) key).store; |
284 | } else { | |
285 | 0 | Versification v11n = getVersification(); |
286 | 0 | thatStore = new BitSet(v11n.maximumOrdinal() + 1); |
287 | ||
288 | 0 | for (Key aKey : key) { |
289 | 0 | int ord = ((Verse) aKey).getOrdinal(); |
290 | 0 | if (store.get(ord)) { |
291 | 0 | thatStore.set(ord); |
292 | } | |
293 | 0 | } |
294 | } | |
295 | 0 | store.and(thatStore); |
296 | ||
297 | 0 | fireIntervalRemoved(this, null, null); |
298 | 0 | } |
299 | ||
300 | @Override | |
301 | public void clear() { | |
302 | 0 | optimizeWrites(); |
303 | ||
304 | 0 | store.clear(); |
305 | ||
306 | 0 | fireIntervalRemoved(this, null, null); |
307 | 0 | } |
308 | ||
309 | @Override | |
310 | public void blur(int verses, RestrictionType restrict) { | |
311 | 0 | assert verses >= 0; |
312 | 0 | optimizeWrites(); |
313 | 0 | raiseNormalizeProtection(); |
314 | ||
315 | 0 | if (!restrict.equals(RestrictionType.NONE)) { |
316 | 0 | super.blur(verses, restrict); |
317 | } else { | |
318 | 0 | optimizeWrites(); |
319 | 0 | raiseEventSuppresion(); |
320 | 0 | raiseNormalizeProtection(); |
321 | ||
322 | 0 | int maximumOrdinal = getVersification().maximumOrdinal(); |
323 | 0 | BitSet newStore = new BitSet(maximumOrdinal + 1); |
324 | ||
325 | 0 | for (int i = store.nextSetBit(0); i >= 0; i = store.nextSetBit(i + 1)) { |
326 | 0 | int start = Math.max(1, i - verses); |
327 | 0 | int end = Math.min(maximumOrdinal, i + verses); |
328 | ||
329 | 0 | for (int j = start; j <= end; j++) { |
330 | 0 | newStore.set(j); |
331 | } | |
332 | } | |
333 | ||
334 | 0 | store = newStore; |
335 | ||
336 | 0 | lowerNormalizeProtection(); |
337 | 0 | if (lowerEventSuppressionAndTest()) { |
338 | 0 | fireIntervalAdded(this, null, null); |
339 | } | |
340 | } | |
341 | 0 | } |
342 | ||
343 | /** | |
344 | * Iterate over the Verses | |
345 | * | |
346 | * @author Joe Walker | |
347 | * @author DM Smith | |
348 | */ | |
349 | 0 | private final class VerseIterator implements Iterator<Key> { |
350 | /** | |
351 | * Find the first unused verse | |
352 | */ | |
353 | 0 | VerseIterator() { |
354 | 0 | next = -1; |
355 | 0 | calculateNext(); |
356 | 0 | } |
357 | ||
358 | /* (non-Javadoc) | |
359 | * @see java.util.Iterator#hasNext() | |
360 | */ | |
361 | public boolean hasNext() { | |
362 | 0 | return next >= 0; |
363 | } | |
364 | ||
365 | /* (non-Javadoc) | |
366 | * @see java.util.Iterator#next() | |
367 | */ | |
368 | public Key next() throws NoSuchElementException { | |
369 | 0 | if (!hasNext()) { |
370 | 0 | throw new NoSuchElementException(); |
371 | } | |
372 | ||
373 | 0 | Key retcode = getVersification().decodeOrdinal(next); |
374 | 0 | calculateNext(); |
375 | ||
376 | 0 | return retcode; |
377 | } | |
378 | ||
379 | /* (non-Javadoc) | |
380 | * @see java.util.Iterator#remove() | |
381 | */ | |
382 | public void remove() throws UnsupportedOperationException { | |
383 | 0 | store.clear(next); |
384 | 0 | } |
385 | ||
386 | /** | |
387 | * Find the next bit | |
388 | */ | |
389 | private void calculateNext() { | |
390 | 0 | next = store.nextSetBit(next + 1); |
391 | 0 | } |
392 | ||
393 | /** | |
394 | * What is the next Verse to be considered | |
395 | */ | |
396 | private int next; | |
397 | } | |
398 | ||
399 | /** | |
400 | * Call the support mechanism in AbstractPassage | |
401 | * | |
402 | * @param out | |
403 | * The stream to write our state to | |
404 | * @serialData Write the ordinal number of this verse | |
405 | * @see AbstractPassage#writeObjectSupport(ObjectOutputStream) | |
406 | * @throws IOException | |
407 | * if the read fails | |
408 | */ | |
409 | private void writeObject(ObjectOutputStream out) throws IOException { | |
410 | 0 | out.defaultWriteObject(); |
411 | ||
412 | // Save off the versification by name | |
413 | 0 | out.writeUTF(getVersification().getName()); |
414 | ||
415 | 0 | writeObjectSupport(out); |
416 | 0 | } |
417 | ||
418 | /** | |
419 | * Call the support mechanism in AbstractPassage | |
420 | * | |
421 | * @param in | |
422 | * The stream to read our state from | |
423 | * @throws IOException | |
424 | * if the read fails | |
425 | * @throws ClassNotFoundException | |
426 | * If the read data is incorrect | |
427 | * @serialData Write the ordinal number of this verse | |
428 | * @see AbstractPassage#readObjectSupport(ObjectInputStream) | |
429 | */ | |
430 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | |
431 | 0 | optimizeWrites(); |
432 | ||
433 | 0 | in.defaultReadObject(); |
434 | ||
435 | // Read the versification by name | |
436 | 0 | String v11nName = in.readUTF(); |
437 | 0 | Versification v11n = Versifications.instance().getVersification(v11nName); |
438 | ||
439 | 0 | store = new BitSet(v11n.maximumOrdinal() + 1); |
440 | ||
441 | 0 | readObjectSupport(in); |
442 | 0 | } |
443 | ||
444 | /** | |
445 | * To make serialization work across new versions | |
446 | */ | |
447 | private static final long serialVersionUID = -5931560451407396276L; | |
448 | ||
449 | /** | |
450 | * The place the real data is stored | |
451 | */ | |
452 | protected transient BitSet store; | |
453 | } |