1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
32
33
34
35
36
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
47
48
49
50
51
52
53
54
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
60
61
62 static final int VARIABLE_UNIT_WIDTH = 0;
63
64
65
66 static final byte SHORT_WEIGHT_EXTRA_PLACEHOLDER = (byte)0x00;
67
68
69 private static final byte[] NO_REPEATED_WEIGHT = {(byte)0xFF, (byte)0xFF};
70
71
72
73
74 static final byte INTERNATIONAL_EXTRA_PLACEHOLDER = (byte)0x02;
75
76
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
81
82
83
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
90
91 static final byte LONG_REPEATED_WEIGHT_PREFIX = (byte)0x05;
92
93
94
95 static final byte[] CJK_WEIGHT_LEAD = {(byte)0xFD, (byte)0xFF};
96
97
98 static final int WEIGHT_ENTRIES_PER_CHAR = 3;
99
100
101 static final int TERTIARY_WEIGHTS_PER_CHAR = 3;
102
103
104 static final byte DEFAULT_TERTIARY_WEIGHT = (byte)0x01;
105
106
107
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
114 static final String GEN_LEG_FAMILY = "genleg";
115
116
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
124
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
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
174
175
176 PROLONGED("V") {
177 @Override public CharHandler parseCodes(String[] codeStrings) {
178
179
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
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
213 private static final byte KANA_CODE_PREFIX = (byte)0x7F;
214
215
216
217
218
219
220
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
231
232
233 private static final byte[][] KANA_VOWEL_WEIGHTS = createKanaVowelWeights();
234
235
236
237
238
239 abstract static class CharHandler {
240
241 private int _numCharUnits = 1;
242
243 private int _numPrimaryUnits = 1;
244
245 private byte _extraPlaceholder = INTERNATIONAL_EXTRA_PLACEHOLDER;
246
247 public abstract Type getType();
248
249
250
251
252
253 public boolean isProlongedSound() {
254 return false;
255 }
256 public byte[] getInlineBytes(char c) {
257 return null;
258 }
259
260
261
262
263
264 public int getNumCharUnits() {
265 return _numCharUnits;
266 }
267
268
269
270
271
272
273
274 public int getNumPrimaryUnits() {
275 return _numPrimaryUnits;
276 }
277
278
279
280
281
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
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
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
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
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
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
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
420
421
422
423
424
425 static final class Contraction
426 {
427 private final String _seq;
428 private final CharHandler[] _handlers;
429
430
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
453
454
455
456
457
458
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
478 return true;
479 }
480
481
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
501
502
503
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
513
514
515
516
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
529
530
531
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
547 static final CharHandler IGNORED_CHAR_HANDLER = new CharHandler() {
548 @Override public Type getType() {
549 return Type.IGNORED;
550 }
551 };
552
553
554
555 private static final ThreadLocal<byte[]> SURROGATE_CHAR_BUF =
556 ThreadLocal.withInitial(() -> new byte[2]);
557
558
559
560
561
562
563 interface SurrogateCharHandlers {
564 CharHandler get(char c);
565 }
566
567
568
569 static final SurrogateCharHandlers IGNORED_SURROGATES =
570 c -> IGNORED_CHAR_HANDLER;
571
572
573
574
575
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
605
606 private static final CharHandler[] _values = loadCodes(
607 CODES_FILE, FIRST_CHAR, LAST_CHAR);
608 }
609
610 private static final class ExtCodes
611 {
612
613
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
627
628
629 int getPrimaryUnitWidth() {
630 return VARIABLE_UNIT_WIDTH;
631 }
632
633
634
635
636
637
638
639
640
641 byte[] getTertiaryWeights(char c) {
642 return null;
643 }
644
645
646
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
660
661
662
663 Contraction getContraction(String str, int idx)
664 {
665 return null;
666 }
667
668
669
670
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
681
682
683
684
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
697
698
699
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
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
744
745
746
747
748
749
750
751
752
753
754
755
756
757
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
767
768
769 addOneByteCodes(leadBytes, rest);
770 }
771 }
772
773 for(CharHandler ch : values) {
774 countCharUnits(ch, leadBytes, primaryWidth);
775 }
776 }
777
778
779
780
781
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
792
793
794
795
796
797
798 static void countCharUnits(GeneralLegacyIndexCodes base,
799 CharHandler[] handlers) {
800
801
802
803
804
805
806
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
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
833
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
844
845
846
847 int numUnits = numEntries;
848 if(hasCjkLead(bytes)) {
849
850
851 numUnits = 1;
852 }
853
854 byte[] extraBytes = ch.getExtraBytes();
855 if(extraBytes != null) {
856
857
858
859
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
868
869
870
871 ch._extraPlaceholder = SHORT_WEIGHT_EXTRA_PLACEHOLDER;
872 }
873 }
874
875
876
877
878
879
880
881
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
897
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
907
908
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
919
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
933
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
949
950
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
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
988
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
1002
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
1015
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
1033
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
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
1063
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
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
1088
1089
1090 static int asUnsignedChar(char c)
1091 {
1092 return c & 0xFFFF;
1093 }
1094
1095
1096
1097
1098
1099
1100 void writeNonNullIndexTextValue(
1101 Object value, ByteStream bout, boolean isAscending)
1102 throws IOException
1103 {
1104
1105 String str = toIndexCharSequence(value);
1106
1107
1108
1109 int prevLength = bout.getLength();
1110
1111
1112 ExtraCodesStream extraCodes = null;
1113 ByteStream unprintableCodes = null;
1114 ByteStream crazyCodes = null;
1115 ByteStream crazyCopyCodes = null;
1116
1117
1118
1119 int[] crazyUnits = null;
1120 int tertiaryRoom = (TERTIARY_WEIGHTS_PER_CHAR * str.length());
1121
1122
1123
1124 ByteStream overrunWeights = null;
1125 int charOffset = 0;
1126
1127
1128
1129
1130
1131 int unprintOffset = 0;
1132
1133
1134
1135
1136
1137 byte[] lastWeight = null;
1138
1139
1140 byte lastCrazyFlag = 0;
1141
1142
1143
1144
1145 char lastTertiaryChar = 0;
1146 boolean hasLastTertiaryChar = false;
1147
1148
1149
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
1157
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
1173
1174
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
1181
1182
1183
1184
1185
1186
1187 repeatedNothing = (lastWeight == null);
1188 bytes = (repeatedNothing ? NO_REPEATED_WEIGHT :
1189 (ch.isProlongedSound() ? prolongedSoundBytes(lastWeight) :
1190 lastWeight));
1191 numCharUnits = (repeatedNothing ? 0 : 1);
1192
1193
1194
1195 numPrimaryUnits = 1;
1196 }
1197 if(bytes != null) {
1198
1199 bout.write(bytes);
1200
1201
1202
1203
1204
1205 charOffset += numCharUnits;
1206 unprintOffset += numPrimaryUnits;
1207
1208 if(charOffset > tertiaryRoom) {
1209 if(overrunWeights == null) {
1210 overrunWeights = new ByteStream();
1211 }
1212
1213
1214
1215
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
1233
1234
1235
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
1249
1250
1251
1252
1253 if(!ch.isProlongedSound()) {
1254 lastWeight = ((numCharUnits == 1) ?
1255 repeatableWeight(bytes, numPrimaryUnits) : null);
1256 }
1257 if(ch.getType() != Type.REPEAT) {
1258
1259
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
1269
1270 continue;
1271 }
1272
1273 if(repeatedNothing) {
1274
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
1283
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
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
1303 writeUnprintableCodes(curUnprintOffset, bytes, unprintableCodes);
1304
1305
1306
1307
1308
1309 lastWeight = ByteUtil.concat(new byte[]{UNPRINTABLE_MIDFIX}, bytes);
1310 lastCrazyFlag = 0;
1311
1312
1313
1314 hasLastTertiaryChar = false;
1315 }
1316
1317
1318
1319
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
1330
1331
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
1341
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
1352
1353
1354 int numOverrunCodes = 0;
1355 if(overrunWeights != null) {
1356 numOverrunCodes = writeTertiaryOverrun(crazyCodes, crazyUnits,
1357 overrunWeights, tertiaryRoom);
1358 }
1359
1360
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
1377 if(hasExtraCodes) {
1378 extraCodes.writeTo(bout);
1379 }
1380
1381 if(hasCrazyCodes || hasUnprintableCodes) {
1382
1383
1384 bout.write(END_TEXT);
1385 bout.write(END_TEXT);
1386
1387
1388 if(hasCrazyCodes) {
1389
1390 writeCrazyCodes(crazyCodes, crazyCopyCodes, numOverrunCodes,
1391 bout);
1392
1393
1394
1395 if(hasUnprintableCodes) {
1396 bout.write(CRAZY_CODES_UNPRINT_SUFFIX);
1397 }
1398 }
1399
1400
1401 if(hasUnprintableCodes) {
1402
1403
1404 bout.write(END_TEXT);
1405
1406 unprintableCodes.writeTo(bout);
1407 }
1408 }
1409 }
1410
1411
1412 if(!isAscending) {
1413
1414
1415
1416 bout.write(END_EXTRA_TEXT);
1417
1418
1419 IndexData.flipBytes(bout.getBytes(), prevLength,
1420 (bout.getLength() - prevLength));
1421 }
1422
1423
1424 bout.write(END_EXTRA_TEXT);
1425 }
1426
1427 protected static String toIndexCharSequence(Object value)
1428 throws IOException {
1429
1430
1431 String str = ColumnImpl.toCharSequence(value).toString();
1432
1433
1434
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
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
1455
1456
1457
1458
1459
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
1470
1471 private static void writeExtraCodes(
1472 int charOffset, byte[] bytes, byte extraCodeModifier,
1473 ExtraCodesStream extraCodes)
1474 {
1475
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
1486
1487
1488
1489
1490
1491
1492 extraCodes.write(bytes);
1493 extraCodes.incrementNumChars(bytes.length);
1494
1495 } else {
1496
1497
1498
1499 int lastIdx = extraCodes.getLength() - 1;
1500 if(lastIdx >= 0) {
1501
1502
1503 byte lastByte = extraCodes.get(lastIdx);
1504 lastByte += extraCodeModifier;
1505 extraCodes.set(lastIdx, lastByte);
1506
1507 } else {
1508
1509
1510
1511
1512
1513
1514 extraCodes.write(extraCodeModifier);
1515 }
1516 }
1517 }
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535 private static ExtraCodesStream writeWeightOverrun(
1536 ByteStream bout, ByteStream paddedWeights, ExtraCodesStream extraCodes,
1537 int charOffset, int numEntries, int numChars)
1538 {
1539
1540
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
1549
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
1562
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
1577
1578
1579 boolean isDiacriticsReversed() {
1580 return false;
1581 }
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593 private static void reverseExtraCodes(ExtraCodesStream extraCodes,
1594 int numChars)
1595 {
1596
1597
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
1614
1615
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
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
1637 return (extraCodes.getLength() > 0);
1638 }
1639
1640
1641
1642
1643 private static void writeUnprintableCodes(
1644 int unprintOffset, byte[] bytes, ByteStream unprintableCodes)
1645 {
1646
1647
1648
1649
1650 int offset =
1651 (UNPRINTABLE_COUNT_START +
1652 (UNPRINTABLE_COUNT_MULTIPLIER * unprintOffset))
1653 | UNPRINTABLE_OFFSET_FLAGS;
1654
1655
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
1665
1666
1667
1668
1669
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
1706
1707
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
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
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
1751
1752 private static void writeCrazyCodes(ByteStream crazyCodes,
1753 ByteStream crazyCopyCodes,
1754 int numOverrunCodes,
1755 ByteStream bout)
1756 {
1757
1758
1759 int numCrazyChars = crazyCodes.getLength();
1760
1761
1762 trimExtraCodes(crazyCodes, CRAZY_CODE_2, CRAZY_CODE_2, numOverrunCodes);
1763 packCrazyCodes(crazyCodes, bout);
1764
1765
1766
1767 writeCrazyCodesSuffix(numCrazyChars, crazyCopyCodes, bout);
1768 }
1769
1770
1771
1772
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
1790 bout.write(curByte);
1791 curByte = CRAZY_CODE_START;
1792 idx = 0;
1793 }
1794 }
1795
1796
1797 if(idx > 0) {
1798 bout.write(curByte);
1799 }
1800 }
1801
1802
1803
1804
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
1816
1817
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
1836
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 }