| PassageKeyFactory.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: PassageKeyFactory.java 2226 2012-02-02 19:25:21Z dmsmith $
21 */
22 package org.crosswire.jsword.passage;
23
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.util.Iterator;
27
28 import org.crosswire.jsword.JSOtherMsg;
29 import org.crosswire.jsword.versification.Versification;
30 import org.crosswire.jsword.versification.system.Versifications;
31
32 /**
33 * An implementation of KeyFactory that works for most Bibles that contain all
34 * the verses in the Bible.
35 *
36 * @see gnu.lgpl.License for license details.<br>
37 * The copyright to this program is held by it's authors.
38 * @author Joe Walker [joe at eireneh dot com]
39 * @author DM Smith [dmsmith555 at yahoo dot com]
40 */
41 public final class PassageKeyFactory {
42 /**
43 * This class implements a Singleton pattern. So the ctor is private
44 */
45 private PassageKeyFactory() {
46 }
47
48 public static PassageKeyFactory instance() {
49 return keyf;
50 }
51
52 /* (non-Javadoc)
53 * @see org.crosswire.jsword.passage.KeyFactory#createEmptyKeyList()
54 */
55 public Key createEmptyKeyList(Versification v11n) {
56 return defaultType.createEmptyPassage(v11n);
57 }
58
59 /* (non-Javadoc)
60 * @see org.crosswire.jsword.passage.KeyFactory#getValidKey(java.lang.String)
61 */
62 public Key getValidKey(Versification v11n, String name) {
63 try {
64 return getKey(v11n, name);
65 } catch (NoSuchKeyException e) {
66 return createEmptyKeyList(v11n);
67 }
68 }
69
70 /* (non-Javadoc)
71 * @see org.crosswire.jsword.passage.KeyFactory#getKey(java.lang.String)
72 */
73 public Key getKey(Versification v11n, String name) throws NoSuchKeyException {
74 // since normalization is relatively expensive
75 // don't try it unless it solves a problem.
76 try {
77 return defaultType.createPassage(v11n, name);
78 } catch (Exception e) {
79 try {
80 return defaultType.createPassage(v11n, normalize(name));
81 } catch (Exception e1) {
82 // TODO(DM): Parser should allow valid osis refs!
83 return defaultType.createPassage(v11n, mungOsisRef(name));
84 }
85 }
86 }
87
88 /* (non-Javadoc)
89 * @see org.crosswire.jsword.passage.KeyFactory#getGlobalKeyList()
90 */
91 public Key getGlobalKeyList(Versification v11n) {
92 return whole;
93 }
94
95 /**
96 * Set the default reference type. Must be one of:
97 * <ul>
98 * <li>PassageType.SPEED
99 * <li>PassageType.WRITE_SPEED
100 * <li>PassageType.SIZE
101 * <li>PassageType.MIX
102 * <li>PassageType.TALLY
103 * </ul>
104 *
105 * @param newDefaultType
106 * The new default type.
107 */
108 public static void setDefaultPassage(int newDefaultType) {
109 PassageKeyFactory.defaultType = PassageType.fromInteger(newDefaultType);
110 }
111
112 /**
113 * Get the default reference type.
114 *
115 * @return default_type The new default type.
116 * @see PassageKeyFactory#setDefaultPassage
117 */
118 public static int getDefaultPassage() {
119 return PassageType.toInteger(defaultType);
120 }
121
122 /**
123 * Get a new Passage based on another Passage that synchronizes all access
124 * to its members.
125 *
126 * @param ref
127 * The passage to synchronize
128 * @return A new synchronized passage that proxies requests to the original
129 */
130 public static Passage getSynchronizedPassage(Passage ref) {
131 return new SynchronizedPassage(ref);
132 }
133
134 /**
135 * Get a new Passage based on another Passage that synchronizes all access
136 * to its members.
137 *
138 * @param ref
139 * The passage to synchronize
140 * @param ignore
141 * Do we throw up if someone tries to change us
142 * @return A new synchronized passage that proxies requests to the original
143 */
144 public static Passage getReadOnlyPassage(Passage ref, boolean ignore) {
145 return new ReadOnlyPassage(ref, ignore);
146 }
147
148 /**
149 * Convert us to a binary representation. There are some distinctly
150 * endianist happenings here, but that is OK because we are reading the
151 * stuff we write here just below.
152 *
153 * @param ref
154 * The Passage to convert
155 * @return a byte array
156 */
157 static byte[] toBinaryRepresentation(Passage ref) {
158 Versification v11n = ref.getVersification();
159 int maxOrdinal = v11n.maximumOrdinal();
160 // store these locally we use them so often
161 int verses = ref.countVerses();
162 int ranges = ref.countRanges(RestrictionType.NONE);
163
164 // the size in bytes of teach storage method
165 int bitwise_size = maxOrdinal / 8;
166 int ranged_size = (ranges * 4) + 1;
167 int distinct_size = (verses * 2) + 1;
168
169 // if bitwise is equal smallest
170 if (bitwise_size <= ranged_size && bitwise_size <= distinct_size) {
171 int array_size = binarySize(AbstractPassage.METHOD_COUNT) + (maxOrdinal / 8) + 1;
172 byte[] buffer = new byte[array_size];
173 int index = 0;
174
175 index += toBinary(buffer, index, AbstractPassage.BITWISE, AbstractPassage.METHOD_COUNT);
176
177 for (Key aKey : ref) {
178 Verse verse = (Verse) aKey;
179 int ord = v11n.getOrdinal(verse);
180
181 // Which byte should we be altering
182 int idx0 = (ord / 8) + index;
183
184 // Which bit within that byte (0-7)
185 int bit = (ord % 8) - 1;
186
187 buffer[idx0] |= 1 << bit;
188 }
189
190 return buffer;
191 } else if (distinct_size <= ranged_size) {
192 // if distinct is not bigger than ranged
193 int array_size = binarySize(AbstractPassage.METHOD_COUNT) + binarySize(maxOrdinal)
194 + (verses * binarySize(maxOrdinal));
195 byte[] buffer = new byte[array_size];
196 int index = 0;
197
198 // write the Passage type and the number of verses. There must be
199 // less than 2**16 verses
200 index += toBinary(buffer, index, AbstractPassage.DISTINCT, AbstractPassage.METHOD_COUNT);
201 index += toBinary(buffer, index, verses, maxOrdinal);
202
203 // write the verse ordinals in a loop
204 for (Key aKey : ref) {
205 Verse verse = (Verse) aKey;
206 int ord = v11n.getOrdinal(verse);
207 index += toBinary(buffer, index, ord, maxOrdinal);
208 }
209
210 return buffer;
211 } else {
212 // otherwise use ranges
213 int array_size = binarySize(AbstractPassage.METHOD_COUNT) + binarySize(maxOrdinal / 2)
214 + (2 * ranges * binarySize(maxOrdinal));
215 byte[] buffer = new byte[array_size];
216 int index = 0;
217
218 // write the Passage type and the number of ranges
219 index += toBinary(buffer, index, AbstractPassage.RANGED, AbstractPassage.METHOD_COUNT);
220 index += toBinary(buffer, index, ranges, maxOrdinal / 2);
221
222 // write the verse ordinals in a loop
223 Iterator<Key> it = ref.rangeIterator(RestrictionType.NONE);
224 while (it.hasNext()) {
225 VerseRange range = (VerseRange) it.next();
226 index += toBinary(buffer, index, v11n.getOrdinal(range.getStart()), maxOrdinal);
227 index += toBinary(buffer, index, range.getCardinality(), maxOrdinal);
228 }
229
230 // chop to size
231 return buffer;
232 }
233 }
234
235 /**
236 * Write out the object to the given ObjectOutputStream
237 *
238 * @param buffer
239 * The stream to read our state from
240 * @return The converted Passage
241 * @throws NoSuchVerseException
242 * If the buffer is invalid
243 */
244 static Passage fromBinaryRepresentation(byte[] buffer) throws NoSuchVerseException {
245 // AV11N(DMS): Is this right?
246 Versification rs = Versifications.instance().getDefaultVersification();
247 int maxOrdinal = rs.maximumOrdinal();
248 Passage ref = (Passage) keyf.createEmptyKeyList(rs);
249
250 // Some speedups
251 AbstractPassage aref = null;
252 if (ref instanceof AbstractPassage) {
253 aref = (AbstractPassage) ref;
254 aref.raiseEventSuppresion();
255 aref.raiseNormalizeProtection();
256 }
257
258 int[] index = new int[] {
259 0
260 };
261 int type = fromBinary(buffer, index, AbstractPassage.METHOD_COUNT);
262
263 switch (type) {
264 case AbstractPassage.BITWISE:
265 for (int ord = 1; ord <= maxOrdinal; ord++) {
266 // Which byte should we be viewing
267 int idx0 = (ord / 8) + index[0];
268
269 // Which bit within that byte (0-7)
270 int bit = (ord % 8) - 1;
271
272 if ((buffer[idx0] & (1 << bit)) != 0) {
273 ref.add(rs.decodeOrdinal(ord));
274 }
275 }
276 // index gets left behind here, but we dont care
277 break;
278
279 case AbstractPassage.DISTINCT:
280 int verses = fromBinary(buffer, index, maxOrdinal);
281 for (int i = 0; i < verses; i++) {
282 int ord = fromBinary(buffer, index, maxOrdinal);
283 ref.add(rs.decodeOrdinal(ord));
284 }
285 break;
286
287 case AbstractPassage.RANGED:
288 int ranges = fromBinary(buffer, index, maxOrdinal / 2);
289 for (int i = 0; i < ranges; i++) {
290 int ord = fromBinary(buffer, index, maxOrdinal);
291 int len = fromBinary(buffer, index, maxOrdinal);
292 ref.add(RestrictionType.NONE.toRange(rs, rs.decodeOrdinal(ord), len));
293 }
294 break;
295
296 default:
297 throw new NoSuchVerseException(JSOtherMsg.lookupText("Unknown passage type."));
298 }
299
300 // Some speedups
301 if (aref != null) {
302 aref.lowerEventSuppresionAndTest();
303 aref.lowerNormalizeProtection();
304 }
305
306 return ref;
307 }
308
309 /**
310 * Read a passage from a given stream
311 *
312 * @param in
313 * The stream to read from
314 * @return a newly built Passage
315 * @throws IOException
316 * If there was trouble reading the stream
317 * @throws NoSuchVerseException
318 * if the data was not a valid passage
319 */
320 public static Passage readPassage(Reader in) throws IOException, NoSuchVerseException {
321 // AV11N(DMS): Is this right?
322 Versification rs = Versifications.instance().getDefaultVersification();
323 Passage ref = (Passage) keyf.createEmptyKeyList(rs);
324 ref.readDescription(in);
325 return ref;
326 }
327
328 /**
329 * Write to buffer (starting at index) the given number using a set of bytes
330 * as required by the max possible value for the number
331 *
332 * @param max
333 * The number to write
334 * @return The number of bytes needed
335 */
336 protected static int binarySize(int max) {
337 // 1 byte (2^8)
338 if (max < 256) {
339 return 1;
340 }
341
342 // 2 bytes (2^16)
343 if (max < 65536) {
344 return 2;
345 }
346
347 // 3 bytes (2^24)
348 if (max < 16777216) {
349 return 3;
350 }
351
352 // 4 bytes (2^32)
353 return 4;
354 }
355
356 /**
357 * Write to buffer (starting at index) the given number using a set of bytes
358 * as required by the max possible value for the number
359 *
360 * @param buffer
361 * Where to write to
362 * @param index
363 * The offset to start at
364 * @param number
365 * The number to write
366 * @param max
367 * The max size
368 * @return The number of bytes written
369 */
370 protected static int toBinary(byte[] buffer, int index, int number, int max) {
371 assert number >= 0 : "No -ve output " + number;
372 assert number <= max : "number " + number + " > max " + max;
373
374 // 1 byte (2^8)
375 if (max < 256) {
376 buffer[index] = (byte) number;
377 return 1;
378 }
379
380 // 2 bytes (2^16)
381 if (max < 65536) {
382 buffer[index + 0] = (byte) (number >>> 8);
383 buffer[index + 1] = (byte) (number >>> 0);
384 return 2;
385 }
386
387 // 3 bytes (2^24)
388 if (max < 16777216) {
389 buffer[index + 0] = (byte) (number >>> 16);
390 buffer[index + 1] = (byte) (number >>> 8);
391 buffer[index + 2] = (byte) (number >>> 0);
392 return 3;
393 }
394
395 // 4 bytes (2^32)
396 buffer[index + 0] = (byte) (number >>> 24);
397 buffer[index + 1] = (byte) (number >>> 16);
398 buffer[index + 2] = (byte) (number >>> 8);
399 buffer[index + 3] = (byte) (number >>> 0);
400 return 4;
401 }
402
403 /**
404 * Read and return an int from the buffer (starting at index[0]) using a set
405 * of bytes as required by the max possible value for the number, and
406 * incrementing index[0] by that number of bytes.
407 *
408 * @param buffer
409 * The buffer to read from
410 * @param index
411 * The offset to start at
412 * @param max
413 * The max number of bytes to read
414 * @return The converted number
415 */
416 protected static int fromBinary(byte[] buffer, int[] index, int max) {
417 // Am I naive in thinking that & 0x000000ff turns int -1 into 255?.
418
419 // 1 byte (2^8)
420 int b0 = buffer[index[0]++] & 0x000000ff;
421 if (max < 256) {
422 return b0;
423 }
424
425 // 2 bytes (2^16)
426 int b1 = buffer[index[0]++] & 0x000000ff;
427 if (max < 65536) {
428 return (b0 << 8) + (b1 << 0);
429 }
430
431 // 3 bytes (2^24)
432 int b2 = buffer[index[0]++] & 0x000000ff;
433 if (max < 16777216) {
434 return (b0 << 16) + (b1 << 8) + (b2 << 0);
435 }
436
437 // 4 bytes (2^32)
438 int b3 = buffer[index[0]++] & 0x000000ff;
439 return (b0 << 24) + (b1 << 16) + (b2 << 8) + (b3 << 0);
440 }
441
442 /**
443 * Replace spaces with semi-colons, because the parser expects them.
444 *
445 * @param name
446 * @return the munged value
447 */
448 private String mungOsisRef(String name) {
449 return name.replace(' ', ';');
450 }
451
452 /**
453 * The internals of a Passage require that references are separated with a
454 * reference delimiter. However, people and other systems may not be so
455 * stringent. So we want to allow for
456 * "Ge 1:26 3:22 11:7 20:13 31:7, 53 35:7" (which is from Clarke) This
457 * should become "Ge 1:26, 3:22, 11:7, 20:13, 31:7, 53, 35:7" Basically, the
458 * rule of thumb is that if two numbers are found separated by whitespace
459 * then add a comma between them. One note $, and ff are taken to be
460 * numbers. But it is complicated by Book names that are like 1 Cor And by
461 * verse references like Gen 1.2 Gen.1.2 Gen 1 2 which are all equivalent.
462 * So we use a counter when we see a number, if the counter reaches 2 and
463 * then we see a name or a number we emit a reference delimiter.
464 *
465 * @param name
466 * @return the normalized value
467 */
468 private String normalize(String name) {
469 if (name == null) {
470 return null;
471 }
472
473 // Note this has a lot in common with AccuracyType.tokenize
474 int size = name.length();
475 StringBuilder buf = new StringBuilder(size * 2);
476
477 char curChar = ' ';
478 boolean isNumber = false;
479 boolean wasNumber = false;
480 int i = 0;
481 while (i < size) {
482 curChar = name.charAt(i);
483
484 // Determine whether we are starting a number
485 isNumber = curChar == '$' || Character.isDigit(curChar) || (curChar == 'f' && (i + 1 < size ? name.charAt(i + 1) : ' ') == 'f');
486
487 // If the last thing we saw was a number and the next thing we see
488 // is another number or a word
489 // then we want to put in a ',' or a ' '
490 if (wasNumber) {
491 if (isNumber) {
492 buf.append(AbstractPassage.REF_PREF_DELIM);
493 } else if (Character.isLetter(curChar)) {
494 buf.append(' ');
495 }
496
497 // Having handled the condition, we now set it to false
498 wasNumber = false;
499 }
500
501 if (isNumber) {
502 wasNumber = true;
503 buf.append(curChar);
504 i++;
505
506 // If it started with an 'f' it was also followed by another.
507 if (curChar == 'f') {
508 buf.append('f');
509 i++;
510 } else if (curChar != '$') {
511 // If it wasn't an 'f' or a '$' then it was digits
512 while (i < size) {
513 curChar = name.charAt(i);
514 if (!Character.isDigit(curChar)) {
515 break;
516 }
517 buf.append(curChar);
518 i++;
519 }
520 }
521
522 // skip all following whitespace, it will be added back in as
523 // needed
524 while (i < size && Character.isWhitespace(name.charAt(i))) {
525 i++;
526 }
527 } else {
528 buf.append(curChar);
529 i++;
530 }
531 }
532
533 return buf.toString();
534 }
535
536 /**
537 * The default type
538 */
539 private static PassageType defaultType = PassageType.SPEED;
540
541 /**
542 * How we create Passages
543 */
544 private static PassageKeyFactory keyf = new PassageKeyFactory();
545
546 /**
547 * The cached whole Bible passage
548 */
549 private static Passage whole;
550
551 static {
552 try {
553 // AV11N(DMS): Is this right?
554 whole = new ReadOnlyPassage(defaultType.createPassage(Versifications.instance().getDefaultVersification(), "Gen 1:1-Rev 22:21"), true);
555 } catch (NoSuchKeyException ex) {
556 assert false : ex;
557 whole = defaultType.createEmptyPassage(Versifications.instance().getDefaultVersification());
558 }
559 }
560 }
561