View Javadoc
1   /*
2   Copyright (c) 2008 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStreamReader;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import static com.healthmarketscience.jackcess.impl.ByteUtil.ByteStream;
29  
30  /**
31   * The "general legacy" (access 2000-2007) weight table, and the encoder which
32   * turns a text value into an index entry.  Every other collation extends this
33   * one: a family replaces the weight table, and a tailoring moves a few
34   * characters against the table of its family.
35   *
36   * @author James Ahlborn
37   */
38  public class GeneralLegacyIndexCodes {
39  
40    static final int MAX_TEXT_INDEX_CHAR_LENGTH =
41      (JetFormat.TEXT_FIELD_MAX_LENGTH / JetFormat.TEXT_FIELD_UNIT_SIZE);
42  
43    static final byte END_TEXT = (byte)0x01;
44    static final byte END_EXTRA_TEXT = (byte)0x00;
45  
46    // unprintable char is removed from normal text.
47    // pattern for unprintable chars in the extra bytes:
48    // 01 01 01 <pos> 06  <code> )
49    // <pos> = 7 + (4 * char_pos) | 0x8000 (as short)
50    // <code> = char code
51    // in ms access, char_pos counts the two byte primary units written so far,
52    // a position in the primary output rather than a count of extra codes
53    // bytes.  the 06 is the type byte of the char's weight table entry and
54    // <code> is that entry's low byte
55    static final int UNPRINTABLE_COUNT_START = 7;
56    static final int UNPRINTABLE_COUNT_MULTIPLIER = 4;
57    static final int UNPRINTABLE_OFFSET_FLAGS = 0x8000;
58    static final byte UNPRINTABLE_MIDFIX = (byte)0x06;
59    /** a weight table whose entries are not all the same number of bytes wide,
60        which the general legacy table is: its latin codes take one byte and its
61        arabic codes two */
62    static final int VARIABLE_UNIT_WIDTH = 0;
63    /** what stands in for a character whose weight is shorter than a whole
64        entry of its table, where every other character takes
65        INTERNATIONAL_EXTRA_PLACEHOLDER */
66    static final byte SHORT_WEIGHT_EXTRA_PLACEHOLDER = (byte)0x00;
67  
68    /** what a repeat mark writes when no weight has been written before it */
69    private static final byte[] NO_REPEATED_WEIGHT = {(byte)0xFF, (byte)0xFF};
70  
71    // international char is replaced with ascii char.
72    // pattern for international chars in the extra bytes:
73    // [ 02 (for each normal char) ] [ <symbol_code> (for each inat char) ]
74    static final byte INTERNATIONAL_EXTRA_PLACEHOLDER = (byte)0x02;
75  
76    // see writeCrazyCodes
77    static final byte CRAZY_CODE_START = (byte)0x80;
78    static final byte CRAZY_CODE_1 = (byte)0x02;
79    static final byte CRAZY_CODE_2 = (byte)0x03;
80    // the suffix which follows the crazy codes is
81    // ff 02 [80 x numRepeats] ff [80 x numRepeats], where numRepeats grows with
82    // the number of chars on the crazy path, one repeat per (partial) group of
83    // CRAZY_CODES_PER_SUFFIX_REPEAT chars
84    static final byte CRAZY_CODES_SUFFIX_START = (byte)0xFF;
85    static final byte CRAZY_CODES_SUFFIX_MIDFIX = (byte)0x02;
86    static final byte CRAZY_CODES_SUFFIX_SEPARATOR = (byte)0xFF;
87    static final byte CRAZY_CODES_SUFFIX_REPEAT = (byte)0x80;
88    static final int CRAZY_CODES_PER_SUFFIX_REPEAT = 7;
89    /** what a repeat mark writes before the weight it copies, where that weight
90        is more than one entry */
91    static final byte LONG_REPEATED_WEIGHT_PREFIX = (byte)0x05;
92    /** the entry which leads the weight of a cjk ideograph, where the entry
93        after it is the ideograph's own.  Such a weight is two entries and stands
94        for one character, which every other weight of two entries does not */
95    static final byte[] CJK_WEIGHT_LEAD = {(byte)0xFD, (byte)0xFF};
96    /** how many weight entries ms access keeps room for in each character of a
97        value, which a character of four entries writes past */
98    static final int WEIGHT_ENTRIES_PER_CHAR = 3;
99    /** how many tertiary weights ms access keeps room for in each character of
100       a value, which a value of more character units writes past */
101   static final int TERTIARY_WEIGHTS_PER_CHAR = 3;
102   /** the tertiary weight of every character the tertiary weights file does
103       not name */
104   static final byte DEFAULT_TERTIARY_WEIGHT = (byte)0x01;
105   /** a second run of crazy codes says, for each character which took a crazy
106       code, whether it is a character of its own, a repeat mark standing for
107       the one before it, or the prolonged sound mark */
108   static final byte CRAZY_COPY_CODE_CHAR = (byte)0x01;
109   static final byte CRAZY_COPY_CODE_REPEAT = (byte)0x02;
110   static final byte CRAZY_COPY_CODE_PROLONGED = (byte)0x03;
111   static final byte CRAZY_CODES_UNPRINT_SUFFIX = (byte)0xFF;
112 
113   /** the name this family carries in the name of a codes file */
114   static final String GEN_LEG_FAMILY = "genleg";
115 
116   // the weight table, one handler per code point
117   private static final String CODES_FILE =
118     DatabaseImpl.RESOURCE_PATH + "index_codes_" + GEN_LEG_FAMILY + ".txt";
119   private static final String EXT_CODES_FILE =
120     DatabaseImpl.RESOURCE_PATH + "index_codes_ext_" + GEN_LEG_FAMILY + ".txt";
121 
122   /**
123    * Enum which classifies the types of char encoding strategies used when
124    * creating text index entries.
125    */
126   enum Type {
127     SIMPLE("S") {
128       @Override public CharHandler parseCodes(String[] codeStrings) {
129         return parseSimpleCodes(codeStrings);
130       }
131     },
132     INTERNATIONAL("I") {
133       @Override public CharHandler parseCodes(String[] codeStrings) {
134         return parseInternationalCodes(codeStrings);
135       }
136     },
137     UNPRINTABLE("U") {
138       @Override public CharHandler parseCodes(String[] codeStrings) {
139         return parseUnprintableCodes(codeStrings);
140       }
141     },
142     UNPRINTABLE_EXT("P") {
143       @Override public CharHandler parseCodes(String[] codeStrings) {
144         return parseUnprintableExtCodes(codeStrings);
145       }
146     },
147     INTERNATIONAL_EXT("Z") {
148       @Override public CharHandler parseCodes(String[] codeStrings) {
149         return parseInternationalExtCodes(codeStrings);
150       }
151     },
152     SIGNIFICANT("G") {
153       @Override public CharHandler parseCodes(String[] codeStrings) {
154         return parseSignificantCodes(codeStrings);
155       }
156     },
157     SURROGATE("Q") {
158       @Override public CharHandler parseCodes(String[] codeStrings) {
159         // these are not parsed from the codes files
160         throw new UnsupportedOperationException();
161       }
162     },
163     IGNORED("X") {
164       @Override public CharHandler parseCodes(String[] codeStrings) {
165         return IGNORED_CHAR_HANDLER;
166       }
167     },
168     REPEAT("R") {
169       @Override public CharHandler parseCodes(String[] codeStrings) {
170         return parseRepeatCodes(codeStrings);
171       }
172     },
173     // this type only reads the prefix of the prolonged sound mark.  its handler
174     // reports Type.REPEAT, so the writer handles it as a repeat mark, and
175     // getType never returns this type
176     PROLONGED("V") {
177       @Override public CharHandler parseCodes(String[] codeStrings) {
178         // the mark has no codes of its own, so a line which gives it any
179         // fails the load
180         if((codeStrings.length != 1) || !codeStrings[0].isEmpty()) {
181           throw new IllegalStateException("Unexpected code strings " +
182                                           Arrays.asList(codeStrings));
183         }
184         return PROLONGED_SOUND_CHAR_HANDLER;
185       }
186     };
187 
188     private final String _prefixCode;
189 
190     private Type(String prefixCode) {
191       _prefixCode = prefixCode;
192     }
193 
194     public String getPrefixCode() {
195       return _prefixCode;
196     }
197 
198     public abstract CharHandler parseCodes(String[] codeStrings);
199   }
200 
201   /** the Types by the prefix code which names each one in a codes file */
202   private static final Map<String,Type> PREFIX_MAP = createPrefixMap();
203 
204   private static Map<String,Type> createPrefixMap() {
205     Map<String,Type> prefixMap = new HashMap<>();
206     for(Type type : Type.values()) {
207       prefixMap.put(type.getPrefixCode(), type);
208     }
209     return prefixMap;
210   }
211 
212   /** the first byte of the two byte inline code of a kana */
213   private static final byte KANA_CODE_PREFIX = (byte)0x7F;
214   /** the inline code of the vowel a kana ends on, keyed by the second byte of
215       the kana's own inline code, with 0 where the mark copies instead.  the
216       vowel cannot be read off the character: the rows of the syllabary are
217       not contiguous, and a voiced kana, a small kana, a circled kana and a
218       halfwidth kana all carry the code of the plain kana they sort as.  the
219       vowel follows that code, so a character the two families weigh
220       differently takes a different vowel in each */
221   private static final byte[] KANA_VOWEL_CODES = {
222     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03,
223     0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
224     0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x02, 0x03,
225     0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
226     0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00,
227     0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00,
228     0x00, 0x00, 0x02, 0x03, 0x00, 0x05, 0x06,
229   };
230   /** the weight a prolonged sound mark writes for each vowel, indexed by the
231       vowel's code.  the writer only reads a weight, so one array for each
232       vowel serves every mark */
233   private static final byte[][] KANA_VOWEL_WEIGHTS = createKanaVowelWeights();
234 
235   /**
236    * Base class for the handlers which hold the text index character encoding
237    * information.
238    */
239   abstract static class CharHandler {
240     /** set once the whole table is read, see countCharUnits */
241     private int _numCharUnits = 1;
242     /** set once the whole table is read, see countCharUnits */
243     private int _numPrimaryUnits = 1;
244     /** set once the whole table is read, see countCharUnits */
245     private byte _extraPlaceholder = INTERNATIONAL_EXTRA_PLACEHOLDER;
246 
247     public abstract Type getType();
248     /**
249      * @return whether this is the prolonged sound mark, which takes the place
250      *         of the kana before it like a repeat mark but writes the code of
251      *         that kana's vowel rather than a copy of the whole kana
252      */
253     public boolean isProlongedSound() {
254       return false;
255     }
256     public byte[] getInlineBytes(char c) {
257       return null;
258     }
259     /**
260      * @return how many characters this one counts as in the offsets which the
261      *         extra, unprintable and crazy codes carry.  A character weighed
262      *         as two, such as the sharp s of "ss", counts as two
263      */
264     public int getNumCharUnits() {
265       return _numCharUnits;
266     }
267     /**
268      * @return how many places this character takes in the offset which an
269      *         unprintable weight carries, which counts the weight entries
270      *         written rather than the characters they stand for.  The general
271      *         table writes every entry in two bytes, so a four byte weight is
272      *         two entries there although it carries one diacritic weight
273      */
274     public int getNumPrimaryUnits() {
275       return _numPrimaryUnits;
276     }
277     /**
278      * @return the byte which stands in for this character in the diacritic
279      *         weights where it has none of its own.  A character whose weight
280      *         is shorter than a whole entry of its table takes {@code 00}
281      *         rather than the usual placeholder
282      */
283     public byte getExtraPlaceholder() {
284       return _extraPlaceholder;
285     }
286     public byte[] getExtraBytes() {
287       return null;
288     }
289     public byte[] getUnprintableBytes() {
290       return null;
291     }
292     public byte getExtraByteModifier() {
293       return 0;
294     }
295     public byte getCrazyFlag() {
296       return 0;
297     }
298     public boolean isSignificantChar() {
299       return false;
300     }
301   }
302 
303   /**
304    * CharHandler for Type.SIMPLE
305    */
306   private static final class SimpleCharHandler extends CharHandler {
307     private final byte[] _bytes;
308     private SimpleCharHandler(byte[] bytes) {
309       _bytes = bytes;
310     }
311     @Override public Type getType() {
312       return Type.SIMPLE;
313     }
314     @Override public byte[] getInlineBytes(char c) {
315       return _bytes;
316     }
317   }
318 
319   /**
320    * CharHandler for Type.INTERNATIONAL
321    */
322   private static final class InternationalCharHandler extends CharHandler {
323     private final byte[] _bytes;
324     private final byte[] _extraBytes;
325     private InternationalCharHandler(byte[] bytes, byte[] extraBytes) {
326       _bytes = bytes;
327       _extraBytes = extraBytes;
328     }
329     @Override public Type getType() {
330       return Type.INTERNATIONAL;
331     }
332     @Override public byte[] getInlineBytes(char c) {
333       return _bytes;
334     }
335     @Override public byte[] getExtraBytes() {
336       return _extraBytes;
337     }
338   }
339 
340   /**
341    * CharHandler for Type.UNPRINTABLE
342    */
343   private static final class UnprintableCharHandler extends CharHandler {
344     private final byte[] _unprintBytes;
345     private UnprintableCharHandler(byte[] unprintBytes) {
346       _unprintBytes = unprintBytes;
347     }
348     @Override public Type getType() {
349       return Type.UNPRINTABLE;
350     }
351     @Override public byte[] getUnprintableBytes() {
352       return _unprintBytes;
353     }
354   }
355 
356   /**
357    * CharHandler for Type.UNPRINTABLE_EXT
358    */
359   private static final class UnprintableExtCharHandler extends CharHandler {
360     private final byte _extraByteMod;
361     private UnprintableExtCharHandler(Byte extraByteMod) {
362       _extraByteMod = extraByteMod;
363     }
364     @Override public Type getType() {
365       return Type.UNPRINTABLE_EXT;
366     }
367     @Override public byte getExtraByteModifier() {
368       return _extraByteMod;
369     }
370   }
371 
372   /**
373    * CharHandler for Type.INTERNATIONAL_EXT
374    */
375   private static final class InternationalExtCharHandler extends CharHandler {
376     private final byte[] _bytes;
377     private final byte[] _extraBytes;
378     private final byte _crazyFlag;
379     private InternationalExtCharHandler(byte[] bytes, byte[] extraBytes,
380                                         byte crazyFlag) {
381       _bytes = bytes;
382       _extraBytes = extraBytes;
383       _crazyFlag = crazyFlag;
384     }
385     @Override public Type getType() {
386       return Type.INTERNATIONAL_EXT;
387     }
388     @Override public byte[] getInlineBytes(char c) {
389       return _bytes;
390     }
391     @Override public byte[] getExtraBytes() {
392       return _extraBytes;
393     }
394     @Override public byte getCrazyFlag() {
395       return _crazyFlag;
396     }
397   }
398 
399   /**
400    * CharHandler for Type.SIGNIFICANT
401    */
402   private static final class SignificantCharHandler extends CharHandler {
403     private final byte[] _bytes;
404     private SignificantCharHandler(byte[] bytes) {
405       _bytes = bytes;
406     }
407     @Override public Type getType() {
408       return Type.SIGNIFICANT;
409     }
410     @Override public byte[] getInlineBytes(char c) {
411       return _bytes;
412     }
413     @Override public boolean isSignificantChar() {
414       return true;
415     }
416   }
417 
418   /**
419    * A sequence of characters which a collation weighs as a unit, such as
420    * {@code ch} in czech, which is one weight of its own between h and i.  It
421    * writes what a character writes, so it holds CharHandlers, and hungarian
422    * needs more than one of them: it writes a doubled digraph such as
423    * {@code ccs} as the weight of {@code cs} twice.
424    */
425   static final class Contraction
426   {
427     private final String _seq;
428     private final CharHandler[] _handlers;
429     /** where the case rule starts, which is 1 for a doubled digraph and 0
430         for every other sequence */
431     private final int _caseStart;
432 
433     Contraction(String seq, CharHandler[] handlers, int caseStart) {
434       _seq = seq;
435       _handlers = handlers;
436       _caseStart = caseStart;
437     }
438 
439     public int getLength() {
440       return _seq.length();
441     }
442 
443     public int getNumUnits() {
444       return _handlers.length;
445     }
446 
447     public CharHandler getHandler(int idx) {
448       return _handlers[idx];
449     }
450 
451     /**
452      * Whether this sequence is the one at the given position of the value.
453      * Ms access weighs a sequence as a unit only when it is written all in
454      * lower case, all in upper case, or in title case, so {@code cha}
455      * contracts and {@code cHa} is a {@code c} and an {@code h}.  A doubled
456      * digraph such as the hungarian {@code ccs} is the leading letter and
457      * then the digraph, and the rule holds for the digraph alone, so
458      * {@code cCSa} contracts.
459      */
460     public boolean matches(String str, int idx) {
461       return (str.regionMatches(true, idx, _seq, 0, _seq.length()) &&
462               casesMatch(str, idx + _caseStart, idx + _seq.length()));
463     }
464 
465     private static boolean casesMatch(String str, int start, int end) {
466       boolean sawUpper = false;
467       boolean sawLower = false;
468       for(int i = start; i < end; ++i) {
469         char c = str.charAt(i);
470         if(Character.isUpperCase(c)) {
471           sawUpper = true;
472         } else if(Character.isLowerCase(c)) {
473           sawLower = true;
474         }
475       }
476       if(!sawUpper || !sawLower) {
477         // all of one case, a character with no case counting as either
478         return true;
479       }
480       // mixed, so only title case is left: the first character upper and no
481       // other one upper
482       if(!Character.isUpperCase(str.charAt(start))) {
483         return false;
484       }
485       for(int i = start + 1; i < end; ++i) {
486         if(Character.isUpperCase(str.charAt(i))) {
487           return false;
488         }
489       }
490       return true;
491     }
492 
493     @Override
494     public String toString() {
495       return _seq;
496     }
497   }
498 
499   /**
500    * CharHandler for Type.REPEAT, a mark which stands for the character before
501    * it, such as the arabic shadda or a japanese iteration mark.  It writes a
502    * copy of the weight written before it, which the handler cannot know, so
503    * writeNonNullIndexTextValue supplies the bytes.
504    */
505   private static final CharHandler REPEAT_CHAR_HANDLER = new CharHandler() {
506     @Override public Type getType() {
507       return Type.REPEAT;
508     }
509   };
510 
511   /**
512    * CharHandler for the prolonged sound mark, which lengthens the kana before
513    * it.  The mark writes the code of the vowel that kana ends on rather than a
514    * copy of the whole kana, and after anything which is not a kana it copies.
515    * Its type is Type.REPEAT, because it takes the place of the character
516    * before it the same way a repeat mark does.
517    */
518   static final CharHandler PROLONGED_SOUND_CHAR_HANDLER = new CharHandler() {
519     @Override public Type getType() {
520       return Type.REPEAT;
521     }
522     @Override public boolean isProlongedSound() {
523       return true;
524     }
525   };
526 
527   /**
528    * CharHandler for a Type.REPEAT mark which has a secondary weight of its
529    * own, such as the voiced japanese iteration marks.  It copies the weight
530    * before it like any other repeat mark and adds its own weight to the extra
531    * codes.
532    */
533   private static final class RepeatExtraCharHandler extends CharHandler {
534     private final byte[] _extraBytes;
535     private RepeatExtraCharHandler(byte[] extraBytes) {
536       _extraBytes = extraBytes;
537     }
538     @Override public Type getType() {
539       return Type.REPEAT;
540     }
541     @Override public byte[] getExtraBytes() {
542       return _extraBytes;
543     }
544   }
545 
546   /** shared CharHandler instance for Type.IGNORED */
547   static final CharHandler IGNORED_CHAR_HANDLER = new CharHandler() {
548     @Override public Type getType() {
549       return Type.IGNORED;
550     }
551   };
552 
553   /** the surrogate char bufs are computed on the fly.  re-use a buffer for
554       those */
555   private static final ThreadLocal<byte[]> SURROGATE_CHAR_BUF =
556     ThreadLocal.withInitial(() -> new byte[2]);
557 
558   /**
559    * Supplies the handler for a surrogate char.  The surrogates are not in the
560    * codes files, and which handler a char takes depends on where it falls in
561    * the weight table, so the collations differ here.
562    */
563   interface SurrogateCharHandlers {
564     CharHandler get(char c);
565   }
566 
567   /** the general legacy collation gives a surrogate no weight at all, so both
568       halves of a pair are ignored */
569   static final SurrogateCharHandlers IGNORED_SURROGATES =
570     c -> IGNORED_CHAR_HANDLER;
571 
572   /**
573    * Base for the handlers of the surrogate chars, which are computed rather
574    * than read from the codes files.  Only the general collation weights them,
575    * so the general legacy collation has no subclass of this.
576    */
577   static abstract class SurrogateCharHandler extends CharHandler {
578     private final byte[] _extraBytes;
579 
580     protected SurrogateCharHandler(byte extraByte) {
581       _extraBytes = new byte[]{extraByte};
582     }
583     @Override public Type getType() {
584       return Type.SURROGATE;
585     }
586     @Override public byte[] getExtraBytes() {
587       return _extraBytes;
588     }
589     protected static byte[] toInlineBytes(int idxC) {
590       byte[] bytes = SURROGATE_CHAR_BUF.get();
591       bytes[0] = (byte)((idxC >>> 8) & 0xFF);
592       bytes[1] = (byte)(idxC & 0xFF);
593       return bytes;
594     }
595   }
596 
597   static final char FIRST_CHAR = (char)0x0000;
598   static final char LAST_CHAR = (char)0x00FF;
599   static final char FIRST_EXT_CHAR = LAST_CHAR + 1;
600   static final char LAST_EXT_CHAR = (char)0xFFFF;
601 
602   private static final class Codes
603   {
604     /** handlers for the first 256 chars.  use nested class to lazy load the
605         handlers */
606     private static final CharHandler[] _values = loadCodes(
607         CODES_FILE, FIRST_CHAR, LAST_CHAR);
608   }
609 
610   private static final class ExtCodes
611   {
612     /** handlers for the rest of the chars in BMP 0.  use nested class to
613         lazy load the handlers */
614     private static final CharHandler[] _values = loadCodes(
615         EXT_CODES_FILE, FIRST_EXT_CHAR, LAST_EXT_CHAR, IGNORED_SURROGATES,
616         Codes._values);
617   }
618 
619   static final GeneralLegacyIndexCodes GEN_LEG_INSTANCE =
620     new GeneralLegacyIndexCodes();
621 
622   GeneralLegacyIndexCodes() {
623   }
624 
625   /**
626    * @return how many bytes a weight entry of this table takes, or
627    *         {@link #VARIABLE_UNIT_WIDTH} where the widths are mixed
628    */
629   int getPrimaryUnitWidth() {
630     return VARIABLE_UNIT_WIDTH;
631   }
632 
633   /**
634    * @return the tertiary weight of each unit of the given character, or
635    *         {@code null} where every one of them is
636    *         {@link #DEFAULT_TERTIARY_WEIGHT}.  The general legacy table gives
637    *         no character more than three units, so a value can never write
638    *         past the room the tertiary weights have and nothing here is ever
639    *         read
640    */
641   byte[] getTertiaryWeights(char c) {
642     return null;
643   }
644 
645   /**
646    * Returns the CharHandler for the given character.
647    */
648   CharHandler getCharHandler(char c)
649   {
650     if(c <= LAST_CHAR) {
651       return Codes._values[c];
652     }
653 
654     int extOffset = asUnsignedChar(c) - asUnsignedChar(FIRST_EXT_CHAR);
655     return ExtCodes._values[extOffset];
656   }
657 
658   /**
659    * Returns the sequence of characters which this collation weighs as a unit
660    * at the given position of the value, or {@code null} where it weighs none.
661    * Most collations have no such sequence anywhere.
662    */
663   Contraction getContraction(String str, int idx)
664   {
665     return null;
666   }
667 
668   /**
669    * Loads the CharHandlers for the given range of characters from the
670    * resource file with the given name.
671    */
672   static CharHandler[] loadCodes(String codesFilePath,
673                                  char firstChar, char lastChar)
674   {
675     return loadCodes(codesFilePath, firstChar, lastChar, IGNORED_SURROGATES,
676                      null);
677   }
678 
679   /**
680    * Loads the CharHandlers for the given range of characters from the
681    * resource file with the given name, taking the handlers for the surrogate
682    * chars from the given source.  The handlers of the rest of the weight
683    * table, where the file holds only a part of it, make up the rest of the
684    * codes which the character units are counted against.
685    */
686   static CharHandler[] loadCodes(String codesFilePath,
687                                  char firstChar, char lastChar,
688                                  SurrogateCharHandlers surrogates,
689                                  CharHandler[] rest)
690   {
691     return loadCodes(codesFilePath, firstChar, lastChar, surrogates, rest,
692                      VARIABLE_UNIT_WIDTH);
693   }
694 
695   /**
696    * Loads the CharHandlers as above, for a weight table whose entries are all
697    * the given number of bytes wide.  A width of {@link #VARIABLE_UNIT_WIDTH}
698    * means the table mixes widths, and then an entry is read against the one
699    * byte codes the table holds.
700    */
701   static CharHandler[] loadCodes(String codesFilePath,
702                                  char firstChar, char lastChar,
703                                  SurrogateCharHandlers surrogates,
704                                  CharHandler[] rest, int primaryWidth)
705   {
706     int numCodes = (asUnsignedChar(lastChar) - asUnsignedChar(firstChar)) + 1;
707     CharHandler[] values = new CharHandler[numCodes];
708 
709     BufferedReader reader = null;
710     try {
711 
712       reader = new BufferedReader(
713           new InputStreamReader(
714               DatabaseImpl.getResourceAsStream(codesFilePath), "US-ASCII"));
715 
716       int start = asUnsignedChar(firstChar);
717       int end = asUnsignedChar(lastChar);
718       for(int i = start; i <= end; ++i) {
719         char c = (char)i;
720         CharHandler ch = null;
721         if(Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
722           // surrogate chars are not included in the codes files
723           ch = surrogates.get(c);
724         } else {
725           ch = parseCodes(reader.readLine());
726         }
727         values[(i - start)] = ch;
728       }
729 
730     } catch(IOException e) {
731       throw new RuntimeException("failed loading index codes file " +
732                                  codesFilePath, e);
733     } finally {
734       ByteUtil.closeQuietly(reader);
735     }
736 
737     countCharUnits(values, rest, primaryWidth);
738 
739     return values;
740   }
741 
742   /**
743    * Works out which characters of a codes table are weighed as more than one
744    * character, and tells each one, so the offsets which the extra and
745    * unprintable codes carry come out where ms access puts them.
746    *
747    * A few characters stand for two, such as the sharp s for "ss" and the ash
748    * for "ae", and hold the whole code of each character they stand for.  A
749    * character which simply has a long code, such as "^" or "1/4", starts with
750    * a prefix byte which is never a code on its own, so splitting its code into
751    * equal parts never gives parts which are all codes.  That difference is the
752    * test, and it needs no list of its own.
753    *
754    * The code width is not fixed.  The latin characters of the general legacy
755    * table take one byte and the same table gives the arabic letters two, so a
756    * code of mixed widths is read entry by entry against the bytes which are a
757    * whole code on their own.
758    */
759   private static void countCharUnits(CharHandler[] values,
760                                      CharHandler[] rest, int primaryWidth) {
761     Set<Integer> leadBytes = null;
762     if(primaryWidth == VARIABLE_UNIT_WIDTH) {
763       leadBytes = new HashSet<Integer>();
764       addOneByteCodes(leadBytes, values);
765       if(rest != null) {
766         // the parts of a code which stands for more than one character are
767         // codes of the whole weight table, which a file holding only a part
768         // of it does not have on its own
769         addOneByteCodes(leadBytes, rest);
770       }
771     }
772 
773     for(CharHandler ch : values) {
774       countCharUnits(ch, leadBytes, primaryWidth);
775     }
776   }
777 
778   /**
779    * Adds the codes of the given handlers which are one byte wide.  A byte
780    * which is not one of these starts a code two bytes wide, which is how a
781    * code of mixed widths is read.
782    */
783   private static void addOneByteCodes(Set<Integer> leadBytes,
784                                       CharHandler[] handlers) {
785     for(CharHandler ch : handlers) {
786       addOneByteCode(leadBytes, ch);
787     }
788   }
789 
790   /**
791    * Works out the character units of the handlers which a tailored collation
792    * moves, against the codes of the weight table it tailors, since the parts
793    * of a code which stands for two characters are codes of that table.  The
794    * german phone book collation, for one, moves the a, o and u umlauts to the
795    * codes of "ae", "oe" and "ue".  See
796    * {@link #countCharUnits(CharHandler[],CharHandler[],int)}.
797    */
798   static void countCharUnits(GeneralLegacyIndexCodes base,
799                              CharHandler[] handlers) {
800     // a code which stands for two characters is made of the codes of two
801     // letters, and a letter is either in the first 256 characters or is one
802     // the collation moves itself.  the croatian d and z-caron is the one
803     // which needs both: the collation moves the digraph to the codes of the
804     // two letters and moves the z-caron as well.  so the rest of the weight
805     // table is never read, which keeps it unloaded for a database which holds
806     // no character out of the first 256
807     int primaryWidth = base.getPrimaryUnitWidth();
808     Set<Integer> leadBytes = null;
809     if(primaryWidth == VARIABLE_UNIT_WIDTH) {
810       leadBytes = new HashSet<Integer>();
811       for(int i = asUnsignedChar(FIRST_CHAR); i <= asUnsignedChar(LAST_CHAR);
812           ++i) {
813         addOneByteCode(leadBytes, base.getCharHandler((char)i));
814       }
815       addOneByteCodes(leadBytes, handlers);
816     }
817 
818     for(CharHandler ch : handlers) {
819       countCharUnits(ch, leadBytes, primaryWidth);
820     }
821   }
822 
823   /** adds the code of the given handler, where it is one byte wide */
824   private static void addOneByteCode(Set<Integer> leadBytes, CharHandler ch) {
825     byte[] bytes = inlineBytes(ch);
826     if((bytes != null) && (bytes.length == 1)) {
827       leadBytes.add(bytes[0] & 0xFF);
828     }
829   }
830 
831   /**
832    * Tells the given handler how many characters it is weighed as, which is
833    * how many weight entries its code holds.
834    */
835   private static void countCharUnits(CharHandler ch, Set<Integer> leadBytes,
836                                      int primaryWidth) {
837     byte[] bytes = inlineBytes(ch);
838     if(bytes == null) {
839       return;
840     }
841 
842     int numEntries = countEntries(bytes, leadBytes, primaryWidth);
843     // a weight entry is one character, in both tables.  the st ligature needs
844     // the entry by entry read: its weight is a two byte entry and then a one
845     // byte one, so it splits into no equal parts although it stands for two
846     // characters
847     int numUnits = numEntries;
848     if(hasCjkLead(bytes)) {
849       // a cjk ideograph is the lead entry and then its own, two entries which
850       // stand for one character
851       numUnits = 1;
852     }
853 
854     byte[] extraBytes = ch.getExtraBytes();
855     if(extraBytes != null) {
856       // a character carries at most one diacritic weight for each character
857       // it stands for, so two of them mean it stands for at least two.  the
858       // greek dialytika tonos needs this, because its code does not split
859       // while it carries the weights of the diaeresis and the tonos
860       numUnits = Math.max(numUnits, extraBytes.length);
861     }
862 
863     ch._numCharUnits = numUnits;
864     ch._numPrimaryUnits = numEntries;
865     if((primaryWidth != VARIABLE_UNIT_WIDTH) &&
866        (bytes.length < primaryWidth)) {
867       // the 23 lao vowels and tone marks from U+0EB0 to U+0ECB are the only
868       // characters whose weight is shorter than an entry of their table, and
869       // ms access gives them 00 where every other character takes the
870       // placeholder
871       ch._extraPlaceholder = SHORT_WEIGHT_EXTRA_PLACEHOLDER;
872     }
873   }
874 
875   /**
876    * @return how many weight entries the given code holds.  Where the table
877    *         writes every entry in the same number of bytes the count follows
878    *         from the length, and a code one byte short of a whole entry still
879    *         fills one.  Where the widths are mixed, a byte which is a whole
880    *         code on its own is one entry and any other byte starts an entry of
881    *         two
882    */
883   private static int countEntries(byte[] bytes, Set<Integer> leadBytes,
884                                   int primaryWidth) {
885     if(primaryWidth != VARIABLE_UNIT_WIDTH) {
886       return ((bytes.length + (primaryWidth - 1)) / primaryWidth);
887     }
888     int numEntries = 0;
889     for(int i = 0; i < bytes.length; ++numEntries) {
890       i += (leadBytes.contains(bytes[i] & 0xFF) ? 1 : 2);
891     }
892     return numEntries;
893   }
894 
895   /**
896    * @return whether the given code is a cjk ideograph's, which is the lead
897    *         entry and then the ideograph's own
898    */
899   private static boolean hasCjkLead(byte[] bytes) {
900     return ((bytes.length == (CJK_WEIGHT_LEAD.length * 2)) &&
901             (bytes[0] == CJK_WEIGHT_LEAD[0]) &&
902             (bytes[1] == CJK_WEIGHT_LEAD[1]));
903   }
904 
905   /**
906    * @return the inline codes of a handler which has them, else {@code null}.
907    *         a surrogate handler makes its codes out of the character it is
908    *         given rather than holding them, so it carries no code of its own
909    */
910   private static byte[] inlineBytes(CharHandler ch) {
911     if((ch == null) || (ch.getType() == Type.SURROGATE)) {
912       return null;
913     }
914     return ch.getInlineBytes('\0');
915   }
916 
917   /**
918    * Returns a CharHandler parsed from the given line from an index codes
919    * file.
920    */
921   static CharHandler parseCodes(String codeLine)
922   {
923     if(codeLine == null) {
924       throw new IllegalStateException("Invalid codes file");
925     }
926     String prefix = codeLine.substring(0, 1);
927     String suffix = ((codeLine.length() > 1) ? codeLine.substring(1) : "");
928     return PREFIX_MAP.get(prefix).parseCodes(suffix.split(",", -1));
929   }
930 
931   /**
932    * Returns the handler for a repeat mark, which carries a secondary weight
933    * only when the codes file gives it one.
934    */
935   private static CharHandler parseRepeatCodes(String[] codeStrings)
936   {
937     if(codeStrings.length != 1) {
938       throw new IllegalStateException("Unexpected code strings " +
939                                       Arrays.asList(codeStrings));
940     }
941     if(codeStrings[0].isEmpty()) {
942       return REPEAT_CHAR_HANDLER;
943     }
944     return new RepeatExtraCharHandler(codesToBytes(codeStrings[0], true));
945   }
946 
947   /**
948    * Returns the weight a prolonged sound mark writes after the given weight:
949    * the weight of the vowel where the given weight is a kana's, and the given
950    * weight itself where it is not a kana or the kana's vowel is its own code.
951    */
952   private static byte[] prolongedSoundBytes(byte[] lastWeight)
953   {
954     if((lastWeight.length != 2) || (lastWeight[0] != KANA_CODE_PREFIX)) {
955       return lastWeight;
956     }
957     int code = lastWeight[1] & 0xFF;
958     byte vowel = ((code < KANA_VOWEL_CODES.length) ?
959                   KANA_VOWEL_CODES[code] : 0);
960     return ((vowel == 0) ? lastWeight : KANA_VOWEL_WEIGHTS[vowel & 0xFF]);
961   }
962 
963   private static byte[][] createKanaVowelWeights()
964   {
965     byte[][] weights = new byte[256][];
966     for(byte vowel : KANA_VOWEL_CODES) {
967       if((vowel != 0) && (weights[vowel & 0xFF] == null)) {
968         weights[vowel & 0xFF] = new byte[]{KANA_CODE_PREFIX, vowel};
969       }
970     }
971     return weights;
972   }
973 
974   /**
975    * Returns a SimpleCharHandler parsed from the given index code strings.
976    */
977   private static CharHandler parseSimpleCodes(String[] codeStrings)
978   {
979     if(codeStrings.length != 1) {
980       throw new IllegalStateException("Unexpected code strings " +
981                                       Arrays.asList(codeStrings));
982     }
983     return new SimpleCharHandler(codesToBytes(codeStrings[0], true));
984   }
985 
986   /**
987    * Returns an InternationalCharHandler parsed from the given index code
988    * strings.
989    */
990   private static CharHandler parseInternationalCodes(String[] codeStrings)
991   {
992     if(codeStrings.length != 2) {
993       throw new IllegalStateException("Unexpected code strings " +
994                                       Arrays.asList(codeStrings));
995     }
996     return new InternationalCharHandler(codesToBytes(codeStrings[0], true),
997                                         codesToBytes(codeStrings[1], true));
998   }
999 
1000   /**
1001    * Returns a UnprintableCharHandler parsed from the given index code
1002    * strings.
1003    */
1004   private static CharHandler parseUnprintableCodes(String[] codeStrings)
1005   {
1006     if(codeStrings.length != 1) {
1007       throw new IllegalStateException("Unexpected code strings " +
1008                                       Arrays.asList(codeStrings));
1009     }
1010     return new UnprintableCharHandler(codesToBytes(codeStrings[0], true));
1011   }
1012 
1013   /**
1014    * Returns a UnprintableExtCharHandler parsed from the given index code
1015    * strings.
1016    */
1017   private static CharHandler parseUnprintableExtCodes(String[] codeStrings)
1018   {
1019     if(codeStrings.length != 1) {
1020       throw new IllegalStateException("Unexpected code strings " +
1021                                       Arrays.asList(codeStrings));
1022     }
1023     byte[] bytes = codesToBytes(codeStrings[0], true);
1024     if(bytes.length != 1) {
1025       throw new IllegalStateException("Unexpected code strings " +
1026                                       Arrays.asList(codeStrings));
1027     }
1028     return new UnprintableExtCharHandler(bytes[0]);
1029   }
1030 
1031   /**
1032    * Returns a InternationalExtCharHandler parsed from the given index code
1033    * strings.
1034    */
1035   private static CharHandler parseInternationalExtCodes(String[] codeStrings)
1036   {
1037     if(codeStrings.length != 3) {
1038       throw new IllegalStateException("Unexpected code strings " +
1039                                       Arrays.asList(codeStrings));
1040     }
1041 
1042     byte crazyFlag = ("1".equals(codeStrings[2]) ?
1043                       CRAZY_CODE_1 : CRAZY_CODE_2);
1044     return new InternationalExtCharHandler(codesToBytes(codeStrings[0], true),
1045                                            codesToBytes(codeStrings[1], false),
1046                                            crazyFlag);
1047   }
1048 
1049   /**
1050    * Returns a SignificantCharHandler parsed from the given index code strings.
1051    */
1052   private static CharHandler parseSignificantCodes(String[] codeStrings)
1053   {
1054     if(codeStrings.length != 1) {
1055       throw new IllegalStateException("Unexpected code strings " +
1056                                       Arrays.asList(codeStrings));
1057     }
1058     return new SignificantCharHandler(codesToBytes(codeStrings[0], true));
1059   }
1060 
1061   /**
1062    * Converts a string of hex encoded bytes to a byte[], optionally throwing
1063    * an exception if no codes are given.
1064    */
1065   private static byte[] codesToBytes(String codes, boolean required)
1066   {
1067     if(codes.length() == 0) {
1068       if(required) {
1069         throw new IllegalStateException("empty code bytes");
1070       }
1071       return null;
1072     }
1073     if((codes.length() % 2) != 0) {
1074       // stripped a leading 0
1075       codes = "0" + codes;
1076     }
1077     byte[] bytes = new byte[codes.length() / 2];
1078     for(int i = 0; i < bytes.length; ++i) {
1079       int charIdx = i*2;
1080       bytes[i] = (byte)(Integer.parseInt(codes.substring(charIdx, charIdx + 2),
1081                                          16));
1082     }
1083     return bytes;
1084   }
1085 
1086   /**
1087    * Returns the char as an unsigned int, so that it can be compared and
1088    * offset without the sign of a widened char getting in the way.
1089    */
1090   static int asUnsignedChar(char c)
1091   {
1092     return c & 0xFFFF;
1093   }
1094 
1095   /**
1096    * Writes the index entry of a text value: the inline weights of its
1097    * characters, then the extra codes, the unprintable codes and the crazy
1098    * flags which the characters put in the tail.
1099    */
1100   void writeNonNullIndexTextValue(
1101       Object value, ByteStream bout, boolean isAscending)
1102     throws IOException
1103   {
1104     // convert to string
1105     String str = toIndexCharSequence(value);
1106 
1107     // record previous entry length so we can do any post-processing
1108     // necessary for this entry (handling descending)
1109     int prevLength = bout.getLength();
1110 
1111     // now, convert each character to a "code" of one or more bytes
1112     ExtraCodesStream extraCodes = null;
1113     ByteStream unprintableCodes = null;
1114     ByteStream crazyCodes = null;
1115     ByteStream crazyCopyCodes = null;
1116     // the first character unit of each character which wrote a crazy code,
1117     // which says whether that code was written before or after the tertiary
1118     // weight which lands on top of it
1119     int[] crazyUnits = null;
1120     int tertiaryRoom = (TERTIARY_WEIGHTS_PER_CHAR * str.length());
1121     // the tertiary weight of every unit from tertiaryRoom on, which are the
1122     // weights written past the room they have.  only a value long enough to
1123     // write one needs it, so it stays null until one turns up
1124     ByteStream overrunWeights = null;
1125     int charOffset = 0;
1126     // the offset an unprintable weight carries counts the weight entries
1127     // written rather than the characters they stand for, which is a count of
1128     // its own: a repeat mark which stands for no character still writes an
1129     // entry, and the general table writes a four byte weight as two entries
1130     // although it carries one diacritic weight
1131     int unprintOffset = 0;
1132     // what a repeat mark copies: the last weight written, whether it went
1133     // inline or into the unprintable codes.  A diacritic weight does not
1134     // count, so "a" with an acute and then a repeat mark copies the "a", and
1135     // nor does the weight of a character weighed as more than one, which a
1136     // mark cannot stand for, or the vowel a prolonged sound mark writes
1137     byte[] lastWeight = null;
1138     // a repeat mark stands for the character before it in every part of the
1139     // entry, so it copies that character's crazy flag along with its weight
1140     byte lastCrazyFlag = 0;
1141     // and that character's tertiary weight, which is held as the character
1142     // rather than the weight because only a value which writes past the room
1143     // the tertiary weights have reads it, and looking it up here would do so
1144     // for every character of every value
1145     char lastTertiaryChar = 0;
1146     boolean hasLastTertiaryChar = false;
1147     // the weights as ms access holds them, where a weight shorter than a whole
1148     // entry is padded out with zeroes.  only a value which holds such a weight
1149     // needs it, so it stays null until one turns up
1150     ByteStream paddedWeights = null;
1151     int primaryWidth = getPrimaryUnitWidth();
1152     for(int i = 0; i < str.length(); ++i) {
1153 
1154       char c = str.charAt(i);
1155 
1156       // a collation can weigh a sequence of characters as a unit, and such a
1157       // sequence writes what one or more characters write
1158       Contraction contraction = getContraction(str, i);
1159       int numUnits = 1;
1160       if(contraction != null) {
1161         numUnits = contraction.getNumUnits();
1162         i += (contraction.getLength() - 1);
1163       }
1164 
1165       for(int u = 0; u < numUnits; ++u) {
1166 
1167         CharHandler ch = ((contraction != null) ? contraction.getHandler(u) :
1168                           getCharHandler(c));
1169 
1170         int curCharOffset = charOffset;
1171         int curUnprintOffset = unprintOffset;
1172         // a unit of a sequence stands for no one character, and the handlers
1173         // a sequence holds read their bytes from the weight table rather than
1174         // from the char they are given
1175         byte[] bytes = ch.getInlineBytes((contraction != null) ? 0 : c);
1176         int numCharUnits = ch.getNumCharUnits();
1177         int numPrimaryUnits = ch.getNumPrimaryUnits();
1178         boolean repeatedNothing = false;
1179         if(ch.getType() == Type.REPEAT) {
1180           // a repeat mark stands for the character before it, so it writes
1181           // that character's weight again, and a prolonged sound mark writes
1182           // the weight of the vowel instead.  with nothing to copy either one
1183           // writes NO_REPEATED_WEIGHT, which stands for no character: it takes
1184           // no place in the offsets the diacritic weights carry, it gives no
1185           // diacritic weight of its own, and a mark after it has nothing to
1186           // copy either
1187           repeatedNothing = (lastWeight == null);
1188           bytes = (repeatedNothing ? NO_REPEATED_WEIGHT :
1189                    (ch.isProlongedSound() ? prolongedSoundBytes(lastWeight) :
1190                     lastWeight));
1191           numCharUnits = (repeatedNothing ? 0 : 1);
1192           // a mark writes one weight entry either way, so an unprintable
1193           // weight after it carries one more place even where the mark stands
1194           // for no character
1195           numPrimaryUnits = 1;
1196         }
1197         if(bytes != null) {
1198           // write the "inline" codes immediately
1199           bout.write(bytes);
1200 
1201           // only increment the charOffset for chars with inline codes, by
1202           // the number of characters this one is weighed as.  ms access
1203           // counts a char weighed as two, such as the sharp s of "ss", twice
1204           // here, so a later diacritic lands one place on
1205           charOffset += numCharUnits;
1206           unprintOffset += numPrimaryUnits;
1207 
1208           if(charOffset > tertiaryRoom) {
1209             if(overrunWeights == null) {
1210               overrunWeights = new ByteStream();
1211             }
1212             // a repeat mark takes the weight of the character it stands
1213             // for.  a contraction weighs a sequence of characters together
1214             // and no one character of it carries the weight, so a sequence
1215             // takes the default
1216             byte[] tertiary = null;
1217             if(ch.getType() == Type.REPEAT) {
1218               tertiary = (hasLastTertiaryChar ?
1219                           getTertiaryWeights(lastTertiaryChar) : null);
1220             } else if(contraction == null) {
1221               tertiary = getTertiaryWeights(c);
1222             }
1223             for(int t = Math.max(0, (tertiaryRoom - curCharOffset));
1224                 t < numCharUnits; ++t) {
1225               overrunWeights.write(tertiaryWeight(tertiary, t));
1226             }
1227           }
1228 
1229           int paddedLen = (numPrimaryUnits * primaryWidth);
1230           if(paddedWeights == null) {
1231             if(paddedLen > bytes.length) {
1232               // the first weight which does not fill its entries.  everything
1233               // before it needed no padding, so bout holds it as ms access
1234               // does.  it starts at prevLength, since an index on more than
1235               // one column has the columns before this one in bout as well
1236               paddedWeights = new ByteStream(bout.getLength() + paddedLen);
1237               paddedWeights.write(
1238                   bout.getBytes(), prevLength,
1239                   (bout.getLength() - prevLength - bytes.length));
1240             }
1241           }
1242           if(paddedWeights != null) {
1243             paddedWeights.write(bytes);
1244             paddedWeights.writeFill(
1245                 Math.max(0, (paddedLen - bytes.length)), (byte)0);
1246           }
1247 
1248           // only a character weighed as one leaves a weight a repeat mark can
1249           // copy, which also leaves nothing after NO_REPEATED_WEIGHT.  a
1250           // prolonged sound mark leaves the weight of the kana it lengthens
1251           // and not the vowel it wrote, so a repeat mark after it copies the
1252           // kana
1253           if(!ch.isProlongedSound()) {
1254             lastWeight = ((numCharUnits == 1) ?
1255                           repeatableWeight(bytes, numPrimaryUnits) : null);
1256           }
1257           if(ch.getType() != Type.REPEAT) {
1258             // a mark leaves these alone, so a mark after it takes the flag
1259             // and the tertiary weight of the character it stands for
1260             lastCrazyFlag = ((numCharUnits == 1) ? ch.getCrazyFlag() : 0);
1261             lastTertiaryChar = c;
1262             hasLastTertiaryChar = (contraction == null);
1263           }
1264         }
1265 
1266         if((ch.getType() == Type.SIMPLE) &&
1267            (ch.getExtraPlaceholder() == INTERNATIONAL_EXTRA_PLACEHOLDER)) {
1268           // common case, skip further code handling.  a character which does
1269           // not take the usual placeholder still has to write its own
1270           continue;
1271         }
1272 
1273         if(repeatedNothing) {
1274           // a mark with no weight to repeat gives no secondary weight either
1275           continue;
1276         }
1277 
1278         bytes = ch.getExtraBytes();
1279         byte extraCodeModifier = ch.getExtraByteModifier();
1280         if((bytes == null) && (extraCodeModifier == 0) &&
1281            (ch.getExtraPlaceholder() != INTERNATIONAL_EXTRA_PLACEHOLDER)) {
1282           // the placeholder of this character is not the one the gaps are
1283           // filled with, so it is written where it stands
1284           bytes = new byte[numCharUnits];
1285           Arrays.fill(bytes, ch.getExtraPlaceholder());
1286         }
1287         if((bytes != null) || (extraCodeModifier != 0)) {
1288           if(extraCodes == null) {
1289             extraCodes = new ExtraCodesStream(str.length());
1290           }
1291 
1292           // keep track of the extra codes for later
1293           writeExtraCodes(curCharOffset, bytes, extraCodeModifier, extraCodes);
1294         }
1295 
1296         bytes = ch.getUnprintableBytes();
1297         if(bytes != null) {
1298           if(unprintableCodes == null) {
1299             unprintableCodes = new ByteStream();
1300           }
1301 
1302           // keep track of the unprintable codes for later
1303           writeUnprintableCodes(curUnprintOffset, bytes, unprintableCodes);
1304 
1305           // a repeat mark after one of these copies it in the shape it takes
1306           // in the unprintable codes, the midfix and then the code.  an
1307           // unprintable character takes no crazy code, so a mark standing for
1308           // one takes none either
1309           lastWeight = ByteUtil.concat(new byte[]{UNPRINTABLE_MIDFIX}, bytes);
1310           lastCrazyFlag = 0;
1311           // an unprintable character writes no tertiary weight of its own,
1312           // so a mark standing for one has none to copy and takes the
1313           // default
1314           hasLastTertiaryChar = false;
1315         }
1316 
1317         // a repeat mark takes the flag of the character it stands for, which
1318         // the inline block leaves alone and the unprintable one clears, since
1319         // an unprintable character takes no flag
1320         byte crazyFlag = ((ch.getType() == Type.REPEAT) ? lastCrazyFlag :
1321                           ch.getCrazyFlag());
1322         if(crazyFlag != 0) {
1323           if(crazyCodes == null) {
1324             crazyCodes = new ByteStream();
1325             crazyCopyCodes = new ByteStream();
1326             crazyUnits = new int[str.length()];
1327           }
1328 
1329           // keep track of the crazy flags for later.  a contraction can write
1330           // more units than it has characters, so there can be more codes than
1331           // the value has characters
1332           int numCrazyCodes = crazyCodes.getLength();
1333           if(numCrazyCodes == crazyUnits.length) {
1334             crazyUnits = Arrays.copyOf(crazyUnits, (numCrazyCodes * 2));
1335           }
1336           crazyUnits[numCrazyCodes] = curCharOffset;
1337           crazyCodes.write(crazyFlag);
1338           byte copyCode = CRAZY_COPY_CODE_CHAR;
1339           if(ch.isProlongedSound()) {
1340             // the mark carries its own code even where the vowel it writes is
1341             // the whole of the kana before it, as it is after the moraic nasal
1342             copyCode = CRAZY_COPY_CODE_PROLONGED;
1343           } else if(ch.getType() == Type.REPEAT) {
1344             copyCode = CRAZY_COPY_CODE_REPEAT;
1345           }
1346           crazyCopyCodes.write(copyCode);
1347         }
1348       }
1349     }
1350 
1351     // a crazy code which the tertiary overrun wrote is a weight rather than a
1352     // flag, and ms access trims only a trailing flag, so these codes stay
1353     // however they come out
1354     int numOverrunCodes = 0;
1355     if(overrunWeights != null) {
1356       numOverrunCodes = writeTertiaryOverrun(crazyCodes, crazyUnits,
1357                                              overrunWeights, tertiaryRoom);
1358     }
1359 
1360     // write end text flag
1361     bout.write(END_TEXT);
1362 
1363     extraCodes = writeWeightOverrun(bout, paddedWeights, extraCodes,
1364                                     charOffset, unprintOffset, str.length());
1365 
1366     if(isDiacriticsReversed() && (extraCodes != null)) {
1367       reverseExtraCodes(extraCodes, charOffset);
1368     }
1369 
1370     boolean hasExtraCodes = trimExtraCodes(
1371         extraCodes, (byte)0, INTERNATIONAL_EXTRA_PLACEHOLDER);
1372     boolean hasUnprintableCodes = (unprintableCodes != null);
1373     boolean hasCrazyCodes = (crazyCodes != null);
1374     if(hasExtraCodes || hasUnprintableCodes || hasCrazyCodes) {
1375 
1376       // we write all the international extra bytes first
1377       if(hasExtraCodes) {
1378         extraCodes.writeTo(bout);
1379       }
1380 
1381       if(hasCrazyCodes || hasUnprintableCodes) {
1382 
1383         // write 2 more end flags
1384         bout.write(END_TEXT);
1385         bout.write(END_TEXT);
1386 
1387         // next come the crazy flags
1388         if(hasCrazyCodes) {
1389 
1390           writeCrazyCodes(crazyCodes, crazyCopyCodes, numOverrunCodes,
1391                           bout);
1392 
1393           // if we are writing unprintable codes after this, tack on another
1394           // code
1395           if(hasUnprintableCodes) {
1396             bout.write(CRAZY_CODES_UNPRINT_SUFFIX);
1397           }
1398         }
1399 
1400         // then we write all the unprintable extra bytes
1401         if(hasUnprintableCodes) {
1402 
1403           // write another end flag
1404           bout.write(END_TEXT);
1405 
1406           unprintableCodes.writeTo(bout);
1407         }
1408       }
1409     }
1410 
1411     // handle descending order by inverting the bytes
1412     if(!isAscending) {
1413 
1414       // we actually write the end byte before flipping the bytes, and write
1415       // another one after flipping
1416       bout.write(END_EXTRA_TEXT);
1417 
1418       // flip the bytes that we have written thus far for this text value
1419       IndexData.flipBytes(bout.getBytes(), prevLength,
1420                           (bout.getLength() - prevLength));
1421     }
1422 
1423     // write end extra text
1424     bout.write(END_EXTRA_TEXT);
1425   }
1426 
1427   protected static String toIndexCharSequence(Object value)
1428       throws IOException {
1429 
1430     // first, convert to string
1431     String str = ColumnImpl.toCharSequence(value).toString();
1432 
1433     // all text columns (including memos) are only indexed up to the max
1434     // number of chars in a VARCHAR column
1435     int len = str.length();
1436     if(len > MAX_TEXT_INDEX_CHAR_LENGTH) {
1437       str = str.substring(0, MAX_TEXT_INDEX_CHAR_LENGTH);
1438       len = MAX_TEXT_INDEX_CHAR_LENGTH;
1439     }
1440 
1441     // trailing spaces are ignored for text index entries
1442     if((len > 0) && (str.charAt(len - 1) == ' ')) {
1443       do {
1444         --len;
1445       } while((len > 0) && (str.charAt(len - 1) == ' '));
1446 
1447       str = str.substring(0, len);
1448     }
1449 
1450     return str;
1451   }
1452 
1453   /**
1454    * @return the form a repeat mark writes to stand for a character whose
1455    *         weight is the given bytes.  A weight of one entry is copied as it
1456    *         is, and one of more entries is written short, as
1457    *         LONG_REPEATED_WEIGHT_PREFIX and the first byte of the last entry.
1458    *         Measured over fourteen cjk ideographs, whose weights are all the
1459    *         lead entry {@code fdff} and then one which differs
1460    */
1461   private static byte[] repeatableWeight(byte[] bytes, int numPrimaryUnits) {
1462     if(numPrimaryUnits <= 1) {
1463       return bytes;
1464     }
1465     return new byte[]{LONG_REPEATED_WEIGHT_PREFIX, bytes[bytes.length - 2]};
1466   }
1467 
1468   /**
1469    * Encodes the given extra code info in the given stream.
1470    */
1471   private static void writeExtraCodes(
1472       int charOffset, byte[] bytes, byte extraCodeModifier,
1473       ExtraCodesStream extraCodes)
1474   {
1475     // we fill in a placeholder value for any chars w/out extra codes
1476     int numChars = extraCodes.getNumChars();
1477     if(numChars < charOffset) {
1478       int fillChars = charOffset - numChars;
1479       extraCodes.writeFill(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
1480       extraCodes.incrementNumChars(fillChars);
1481     }
1482 
1483     if(bytes != null) {
1484 
1485       // write the actual extra codes and move the count on by what was
1486       // written, since the count is the place the stream has reached rather
1487       // than a tally of characters.  a character weighed as four which writes
1488       // one weight leaves the other three places to be filled later, and one
1489       // weighed as two which writes a weight for each of them fills both
1490       // itself.  the lone modifier below is the one write which takes a byte
1491       // without moving the count
1492       extraCodes.write(bytes);
1493       extraCodes.incrementNumChars(bytes.length);
1494 
1495     } else {
1496 
1497       // extra code modifiers modify the existing extra code bytes and do not
1498       // count as additional extra code chars
1499       int lastIdx = extraCodes.getLength() - 1;
1500       if(lastIdx >= 0) {
1501 
1502         // the extra code modifier is added to the last extra code written
1503         byte lastByte = extraCodes.get(lastIdx);
1504         lastByte += extraCodeModifier;
1505         extraCodes.set(lastIdx, lastByte);
1506 
1507       } else {
1508 
1509         // there is no previous extra code, so the modifier is the code.  it
1510         // takes a byte of the stream without taking a character place, so from
1511         // here on the stream is one byte longer than the count.  measured: a
1512         // modifier, a "k" and an a-umlaut give 01 ff 02 13, where the umlaut's
1513         // weight sits after the modifier and the placeholder for the "k"
1514         extraCodes.write(extraCodeModifier);
1515       }
1516     }
1517   }
1518 
1519   /**
1520    * Writes the end of the weights over the start of the diacritic weights,
1521    * which ms access does where the weights run long.
1522    * <p>
1523    * Ms access keeps room for three weight entries for each character of the
1524    * value, so a character of four entries writes two bytes more than its
1525    * share, and they land on the diacritic weights which follow.  Only U+06D6,
1526    * U+06D7, U+FD24 and U+FD6F have four entries, and three of them in a row is
1527    * the shortest value which runs past the room.  What lands in the diacritic
1528    * weights is the weight of whichever character ends the value, so this is ms
1529    * access reading past the end of its own weights rather than a rule about
1530    * those marks, but the entries ms access writes are what jackcess has to
1531    * match.
1532    *
1533    * @return the diacritic weights, which this makes where the value had none
1534    */
1535   private static ExtraCodesStream writeWeightOverrun(
1536       ByteStream bout, ByteStream paddedWeights, ExtraCodesStream extraCodes,
1537       int charOffset, int numEntries, int numChars)
1538   {
1539     // the room is counted over the characters of the value as it is given,
1540     // which is why a character writing no weight at all still takes its share
1541     int overrunLen = (2 * (numEntries - (WEIGHT_ENTRIES_PER_CHAR * numChars)));
1542     if(overrunLen <= 0) {
1543       return extraCodes;
1544     }
1545 
1546     ExtraCodesStream codes =
1547       ((extraCodes != null) ? extraCodes : new ExtraCodesStream(numChars));
1548     // the weights land on places which may carry no diacritic weight yet, so
1549     // the placeholders they stand on have to be written first
1550     int fillChars = charOffset - codes.getNumChars();
1551     if(fillChars > 0) {
1552       codes.writeFill(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
1553       codes.incrementNumChars(fillChars);
1554     }
1555 
1556     overrunLen = Math.min(overrunLen, codes.getLength());
1557     if(overrunLen <= 0) {
1558       return extraCodes;
1559     }
1560 
1561     // the END_TEXT byte is written by now, and the weights are what comes
1562     // before it
1563     byte[] weights = ((paddedWeights != null) ? paddedWeights.getBytes() :
1564                       bout.getBytes());
1565     int weightsLen = ((paddedWeights != null) ? paddedWeights.getLength() :
1566                       (bout.getLength() - 1));
1567     overrunLen = Math.min(overrunLen, weightsLen);
1568     int from = (weightsLen - overrunLen);
1569     for(int i = 0; i < overrunLen; ++i) {
1570       codes.set(i, weights[from + i]);
1571     }
1572     return codes;
1573   }
1574 
1575   /**
1576    * Whether the extra codes are written from the end of a value rather than
1577    * the start, which only the french collation does.
1578    */
1579   boolean isDiacriticsReversed() {
1580     return false;
1581   }
1582 
1583   /**
1584    * Reverses the extra codes, which orders the diacritics of a value from its
1585    * end.
1586    * <p>
1587    * The reversal is over the bytes rather than over per character groups, even
1588    * though a character can occupy more than one place in the list.  U+01C6
1589    * writes the two inline weights of d and z and two extra codes with them,
1590    * and ms access writes {@code 01 14 00} for it in french against
1591    * {@code 01 02 14 00} in general legacy, which splits the pair.
1592    */
1593   private static void reverseExtraCodes(ExtraCodesStream extraCodes,
1594                                         int numChars)
1595   {
1596     // the codes stop at the last char which had one, so the chars after it
1597     // need their placeholders before there is a list to reverse
1598     int fillChars = numChars - extraCodes.getNumChars();
1599     if(fillChars > 0) {
1600       extraCodes.writeFill(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
1601       extraCodes.incrementNumChars(fillChars);
1602     }
1603 
1604     byte[] bytes = extraCodes.getBytes();
1605     for(int i = 0, j = extraCodes.getLength() - 1; i < j; ++i, --j) {
1606       byte b = bytes[i];
1607       bytes[i] = bytes[j];
1608       bytes[j] = b;
1609     }
1610   }
1611 
1612   /**
1613    * Trims any bytes in the given range off of the end of the given stream,
1614    * returning whether or not there are any bytes left in the given stream
1615    * after trimming.
1616    */
1617   private static boolean trimExtraCodes(ByteStream extraCodes,
1618                                         byte minTrimCode, byte maxTrimCode)
1619   {
1620     return trimExtraCodes(extraCodes, minTrimCode, maxTrimCode, 0);
1621   }
1622 
1623   /**
1624    * Trims as the three argument form does, but never below the given length.
1625    */
1626   private static boolean trimExtraCodes(ByteStream extraCodes,
1627                                         byte minTrimCode, byte maxTrimCode,
1628                                         int minLength)
1629   {
1630     if(extraCodes == null) {
1631       return false;
1632     }
1633 
1634     extraCodes.trimTrailing(minTrimCode, maxTrimCode, minLength);
1635 
1636     // anything left?
1637     return (extraCodes.getLength() > 0);
1638   }
1639 
1640   /**
1641    * Encodes the given unprintable char codes in the given stream.
1642    */
1643   private static void writeUnprintableCodes(
1644       int unprintOffset, byte[] bytes, ByteStream unprintableCodes)
1645   {
1646     // the offset counts the weight entries written before this character.
1647     // measured with the hyphen after characters weighed as one, two, three
1648     // and four: the sharp s, which writes two entries, moves the hyphen from
1649     // place 2 to place 3
1650     int offset =
1651       (UNPRINTABLE_COUNT_START +
1652        (UNPRINTABLE_COUNT_MULTIPLIER * unprintOffset))
1653       | UNPRINTABLE_OFFSET_FLAGS;
1654 
1655     // write offset as big-endian short
1656     unprintableCodes.write((offset >> 8) & 0xFF);
1657     unprintableCodes.write(offset & 0xFF);
1658 
1659     unprintableCodes.write(UNPRINTABLE_MIDFIX);
1660     unprintableCodes.write(bytes);
1661   }
1662 
1663   /**
1664    * Reads a tertiary weights file, which names only the characters whose
1665    * weights are not all {@link #DEFAULT_TERTIARY_WEIGHT}.  A line is a code
1666    * point in hex, a comma, and one digit for each unit of that character:
1667    *
1668    * <pre>   00AA,3
1669    *   0385,10</pre>
1670    */
1671   static Map<Character,byte[]> loadTertiaryWeights(String weightsFilePath)
1672   {
1673     Map<Character,byte[]> weights = new HashMap<Character,byte[]>();
1674 
1675     BufferedReader reader = null;
1676     try {
1677 
1678       reader = new BufferedReader(
1679           new InputStreamReader(
1680               DatabaseImpl.getResourceAsStream(weightsFilePath), "US-ASCII"));
1681 
1682       String line = null;
1683       while((line = reader.readLine()) != null) {
1684         int sep = line.indexOf(',');
1685         char c = (char)Integer.parseInt(line.substring(0, sep), 16);
1686         String digits = line.substring(sep + 1);
1687         byte[] units = new byte[digits.length()];
1688         for(int i = 0; i < units.length; ++i) {
1689           units[i] = (byte)(digits.charAt(i) - '0');
1690         }
1691         weights.put(c, units);
1692       }
1693 
1694     } catch(IOException e) {
1695       throw new RuntimeException("failed loading tertiary weights file " +
1696                                  weightsFilePath, e);
1697     } finally {
1698       ByteUtil.closeQuietly(reader);
1699     }
1700 
1701     return weights;
1702   }
1703 
1704   /**
1705    * @return the tertiary weight of the given unit of a character, where
1706    *         {@code null} stands for a character every one of whose units
1707    *         takes the default
1708    */
1709   private static byte tertiaryWeight(byte[] weights, int unit) {
1710     return (((weights != null) && (unit < weights.length)) ? weights[unit] :
1711             DEFAULT_TERTIARY_WEIGHT);
1712   }
1713 
1714   /**
1715    * Writes the tertiary weights which went past their room over the crazy
1716    * codes, as ms access does.
1717    *
1718    * Ms access holds the tertiary weights in a run of
1719    * {@link #TERTIARY_WEIGHTS_PER_CHAR} for each character of the value and
1720    * the crazy codes in the memory straight after it, so the weight of unit
1721    * {@code (TERTIARY_WEIGHTS_PER_CHAR * length) + j} lands on the crazy code
1722    * of the jth character which took one.  Whichever of the two is written
1723    * last is the one which stays, and a character writes its crazy code before
1724    * its own tertiary weight, so one which lands on its own code loses it.
1725    *
1726    * Only a character of four units can take a value past the room, and only
1727    * the general table has one.
1728    */
1729   private static int writeTertiaryOverrun(ByteStream crazyCodes,
1730                                           int[] crazyUnits,
1731                                           ByteStream overrunWeights,
1732                                           int tertiaryRoom)
1733   {
1734     if(crazyCodes == null) {
1735       return 0;
1736     }
1737     int numCodes = Math.min(overrunWeights.getLength(),
1738                             crazyCodes.getLength());
1739     int numWritten = 0;
1740     for(int j = 0; j < numCodes; ++j) {
1741       if((tertiaryRoom + j) >= crazyUnits[j]) {
1742         crazyCodes.set(j, overrunWeights.get(j));
1743         numWritten = (j + 1);
1744       }
1745     }
1746     return numWritten;
1747   }
1748 
1749   /**
1750    * Encode the given crazy code bytes into the given byte stream.
1751    */
1752   private static void writeCrazyCodes(ByteStream crazyCodes,
1753                                       ByteStream crazyCopyCodes,
1754                                       int numOverrunCodes,
1755                                       ByteStream bout)
1756   {
1757     // the suffix length depends on how many chars took the crazy path, which
1758     // is the code count before any trimming
1759     int numCrazyChars = crazyCodes.getLength();
1760 
1761     // CRAZY_CODE_2 flags at the end are ignored, so ditch them
1762     trimExtraCodes(crazyCodes, CRAZY_CODE_2, CRAZY_CODE_2, numOverrunCodes);
1763     packCrazyCodes(crazyCodes, bout);
1764 
1765     // write crazy code suffix (note, we write this even if all the codes are
1766     // trimmed)
1767     writeCrazyCodesSuffix(numCrazyChars, crazyCopyCodes, bout);
1768   }
1769 
1770   /**
1771    * Packs the given crazy codes into the stream, 2 bits for each code and 3
1772    * codes to a byte under a common prefix.
1773    */
1774   private static void packCrazyCodes(ByteStream crazyCodes, ByteStream bout)
1775   {
1776     if(crazyCodes.getLength() == 0) {
1777       return;
1778     }
1779 
1780     byte curByte = CRAZY_CODE_START;
1781     int idx = 0;
1782     for(int i = 0; i < crazyCodes.getLength(); ++i) {
1783       byte nextByte = crazyCodes.get(i);
1784       nextByte <<= ((2 - idx) * 2);
1785       curByte |= nextByte;
1786 
1787       ++idx;
1788       if(idx == 3) {
1789         // write current byte and reset
1790         bout.write(curByte);
1791         curByte = CRAZY_CODE_START;
1792         idx = 0;
1793       }
1794     }
1795 
1796     // write last byte
1797     if(idx > 0) {
1798       bout.write(curByte);
1799     }
1800   }
1801 
1802   /**
1803    * Encode the suffix which follows the crazy codes into the given byte
1804    * stream.
1805    */
1806   private static void writeCrazyCodesSuffix(int numCrazyChars,
1807                                             ByteStream crazyCopyCodes,
1808                                             ByteStream bout)
1809   {
1810     int numRepeats =
1811       ((numCrazyChars + (CRAZY_CODES_PER_SUFFIX_REPEAT - 1)) /
1812        CRAZY_CODES_PER_SUFFIX_REPEAT);
1813 
1814     bout.write(CRAZY_CODES_SUFFIX_START);
1815     // the second run says which of the crazy codes a repeat mark wrote.  a
1816     // run which names no mark is left out, the same way the flags drop their
1817     // trailing CRAZY_CODE_2
1818     trimExtraCodes(crazyCopyCodes, CRAZY_COPY_CODE_CHAR, CRAZY_COPY_CODE_CHAR);
1819     packCrazyCodes(crazyCopyCodes, bout);
1820     bout.write(CRAZY_CODES_SUFFIX_MIDFIX);
1821     writeCrazyCodesSuffixRepeats(numRepeats, bout);
1822     bout.write(CRAZY_CODES_SUFFIX_SEPARATOR);
1823     writeCrazyCodesSuffixRepeats(numRepeats, bout);
1824   }
1825 
1826   private static void writeCrazyCodesSuffixRepeats(int numRepeats,
1827                                                    ByteStream bout)
1828   {
1829     for(int i = 0; i < numRepeats; ++i) {
1830       bout.write(CRAZY_CODES_SUFFIX_REPEAT);
1831     }
1832   }
1833 
1834   /**
1835    * Extension of ByteStream which keeps track of how far along the characters
1836    * of the value the stream has reached.
1837    */
1838   private static final class ExtraCodesStream extends ByteStream
1839   {
1840     private int _numChars;
1841 
1842     private ExtraCodesStream(int length) {
1843       super(length);
1844     }
1845 
1846     public int getNumChars() {
1847       return _numChars;
1848     }
1849 
1850     public void incrementNumChars(int inc) {
1851       _numChars += inc;
1852     }
1853   }
1854 
1855 }