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