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.Map;
25
26 import static com.healthmarketscience.jackcess.impl.ByteUtil.ByteStream;
27
28
29
30
31
32
33
34 public class GeneralLegacyIndexCodes {
35
36 static final int MAX_TEXT_INDEX_CHAR_LENGTH =
37 (JetFormat.TEXT_FIELD_MAX_LENGTH / JetFormat.TEXT_FIELD_UNIT_SIZE);
38
39 static final byte END_TEXT = (byte)0x01;
40 static final byte END_EXTRA_TEXT = (byte)0x00;
41
42
43
44
45
46
47
48
49
50
51 static final int UNPRINTABLE_COUNT_START = 7;
52 static final int UNPRINTABLE_COUNT_MULTIPLIER = 4;
53 static final int UNPRINTABLE_OFFSET_FLAGS = 0x8000;
54 static final byte UNPRINTABLE_MIDFIX = (byte)0x06;
55
56
57
58
59 static final byte INTERNATIONAL_EXTRA_PLACEHOLDER = (byte)0x02;
60
61
62 static final byte CRAZY_CODE_START = (byte)0x80;
63 static final byte CRAZY_CODE_1 = (byte)0x02;
64 static final byte CRAZY_CODE_2 = (byte)0x03;
65
66
67
68
69 static final byte CRAZY_CODES_SUFFIX_START = (byte)0xFF;
70 static final byte CRAZY_CODES_SUFFIX_MIDFIX = (byte)0x02;
71 static final byte CRAZY_CODES_SUFFIX_SEPARATOR = (byte)0xFF;
72 static final byte CRAZY_CODES_SUFFIX_REPEAT = (byte)0x80;
73 static final int CRAZY_CODES_PER_SUFFIX_REPEAT = 7;
74 static final byte CRAZY_CODES_UNPRINT_SUFFIX = (byte)0xFF;
75
76
77 private static final String CODES_FILE =
78 DatabaseImpl.RESOURCE_PATH + "index_codes_genleg.txt";
79 private static final String EXT_CODES_FILE =
80 DatabaseImpl.RESOURCE_PATH + "index_codes_ext_genleg.txt";
81
82
83
84
85
86 enum Type {
87 SIMPLE("S") {
88 @Override public CharHandler parseCodes(String[] codeStrings) {
89 return parseSimpleCodes(codeStrings);
90 }
91 },
92 INTERNATIONAL("I") {
93 @Override public CharHandler parseCodes(String[] codeStrings) {
94 return parseInternationalCodes(codeStrings);
95 }
96 },
97 UNPRINTABLE("U") {
98 @Override public CharHandler parseCodes(String[] codeStrings) {
99 return parseUnprintableCodes(codeStrings);
100 }
101 },
102 UNPRINTABLE_EXT("P") {
103 @Override public CharHandler parseCodes(String[] codeStrings) {
104 return parseUnprintableExtCodes(codeStrings);
105 }
106 },
107 INTERNATIONAL_EXT("Z") {
108 @Override public CharHandler parseCodes(String[] codeStrings) {
109 return parseInternationalExtCodes(codeStrings);
110 }
111 },
112 SIGNIFICANT("G") {
113 @Override public CharHandler parseCodes(String[] codeStrings) {
114 return parseSignificantCodes(codeStrings);
115 }
116 },
117 SURROGATE("Q") {
118 @Override public CharHandler parseCodes(String[] codeStrings) {
119
120 throw new UnsupportedOperationException();
121 }
122 },
123 IGNORED("X") {
124 @Override public CharHandler parseCodes(String[] codeStrings) {
125 return IGNORED_CHAR_HANDLER;
126 }
127 };
128
129 private final String _prefixCode;
130
131 private Type(String prefixCode) {
132 _prefixCode = prefixCode;
133 }
134
135 public String getPrefixCode() {
136 return _prefixCode;
137 }
138
139 public abstract CharHandler parseCodes(String[] codeStrings);
140 }
141
142
143
144
145
146 abstract static class CharHandler {
147 public abstract Type getType();
148 public byte[] getInlineBytes(char c) {
149 return null;
150 }
151 public byte[] getExtraBytes() {
152 return null;
153 }
154 public byte[] getUnprintableBytes() {
155 return null;
156 }
157 public byte getExtraByteModifier() {
158 return 0;
159 }
160 public byte getCrazyFlag() {
161 return 0;
162 }
163 public boolean isSignificantChar() {
164 return false;
165 }
166 }
167
168
169
170
171 private static final class SimpleCharHandler extends CharHandler {
172 private final byte[] _bytes;
173 private SimpleCharHandler(byte[] bytes) {
174 _bytes = bytes;
175 }
176 @Override public Type getType() {
177 return Type.SIMPLE;
178 }
179 @Override public byte[] getInlineBytes(char c) {
180 return _bytes;
181 }
182 }
183
184
185
186
187 private static final class InternationalCharHandler extends CharHandler {
188 private final byte[] _bytes;
189 private final byte[] _extraBytes;
190 private InternationalCharHandler(byte[] bytes, byte[] extraBytes) {
191 _bytes = bytes;
192 _extraBytes = extraBytes;
193 }
194 @Override public Type getType() {
195 return Type.INTERNATIONAL;
196 }
197 @Override public byte[] getInlineBytes(char c) {
198 return _bytes;
199 }
200 @Override public byte[] getExtraBytes() {
201 return _extraBytes;
202 }
203 }
204
205
206
207
208 private static final class UnprintableCharHandler extends CharHandler {
209 private final byte[] _unprintBytes;
210 private UnprintableCharHandler(byte[] unprintBytes) {
211 _unprintBytes = unprintBytes;
212 }
213 @Override public Type getType() {
214 return Type.UNPRINTABLE;
215 }
216 @Override public byte[] getUnprintableBytes() {
217 return _unprintBytes;
218 }
219 }
220
221
222
223
224 private static final class UnprintableExtCharHandler extends CharHandler {
225 private final byte _extraByteMod;
226 private UnprintableExtCharHandler(Byte extraByteMod) {
227 _extraByteMod = extraByteMod;
228 }
229 @Override public Type getType() {
230 return Type.UNPRINTABLE_EXT;
231 }
232 @Override public byte getExtraByteModifier() {
233 return _extraByteMod;
234 }
235 }
236
237
238
239
240 private static final class InternationalExtCharHandler extends CharHandler {
241 private final byte[] _bytes;
242 private final byte[] _extraBytes;
243 private final byte _crazyFlag;
244 private InternationalExtCharHandler(byte[] bytes, byte[] extraBytes,
245 byte crazyFlag) {
246 _bytes = bytes;
247 _extraBytes = extraBytes;
248 _crazyFlag = crazyFlag;
249 }
250 @Override public Type getType() {
251 return Type.INTERNATIONAL_EXT;
252 }
253 @Override public byte[] getInlineBytes(char c) {
254 return _bytes;
255 }
256 @Override public byte[] getExtraBytes() {
257 return _extraBytes;
258 }
259 @Override public byte getCrazyFlag() {
260 return _crazyFlag;
261 }
262 }
263
264
265
266
267 private static final class SignificantCharHandler extends CharHandler {
268 private final byte[] _bytes;
269 private SignificantCharHandler(byte[] bytes) {
270 _bytes = bytes;
271 }
272 @Override public Type getType() {
273 return Type.SIGNIFICANT;
274 }
275 @Override public byte[] getInlineBytes(char c) {
276 return _bytes;
277 }
278 @Override public boolean isSignificantChar() {
279 return true;
280 }
281 }
282
283
284 static final CharHandler IGNORED_CHAR_HANDLER = new CharHandler() {
285 @Override public Type getType() {
286 return Type.IGNORED;
287 }
288 };
289
290
291
292 private static final ThreadLocal<byte[]> SURROGATE_CHAR_BUF =
293 ThreadLocal.withInitial(() -> new byte[2]);
294
295
296
297
298
299
300 interface SurrogateCharHandlers {
301 CharHandler get(char c);
302 }
303
304
305
306 static final SurrogateCharHandlers IGNORED_SURROGATES =
307 c -> IGNORED_CHAR_HANDLER;
308
309
310
311
312
313
314 static abstract class SurrogateCharHandler extends CharHandler {
315 private final byte[] _extraBytes;
316
317 protected SurrogateCharHandler(byte extraByte) {
318 _extraBytes = new byte[]{extraByte};
319 }
320 @Override public Type getType() {
321 return Type.SURROGATE;
322 }
323 @Override public byte[] getExtraBytes() {
324 return _extraBytes;
325 }
326 protected static byte[] toInlineBytes(int idxC) {
327 byte[] bytes = SURROGATE_CHAR_BUF.get();
328 bytes[0] = (byte)((idxC >>> 8) & 0xFF);
329 bytes[1] = (byte)(idxC & 0xFF);
330 return bytes;
331 }
332 }
333
334 static final char FIRST_CHAR = (char)0x0000;
335 static final char LAST_CHAR = (char)0x00FF;
336 static final char FIRST_EXT_CHAR = LAST_CHAR + 1;
337 static final char LAST_EXT_CHAR = (char)0xFFFF;
338
339 private static final class Codes
340 {
341
342
343 private static final CharHandler[] _values = loadCodes(
344 CODES_FILE, FIRST_CHAR, LAST_CHAR);
345 }
346
347 private static final class ExtCodes
348 {
349
350
351 private static final CharHandler[] _values = loadCodes(
352 EXT_CODES_FILE, FIRST_EXT_CHAR, LAST_EXT_CHAR);
353 }
354
355 static final GeneralLegacyIndexCodes GEN_LEG_INSTANCE =
356 new GeneralLegacyIndexCodes();
357
358 GeneralLegacyIndexCodes() {
359 }
360
361
362
363
364 CharHandler getCharHandler(char c)
365 {
366 if(c <= LAST_CHAR) {
367 return Codes._values[c];
368 }
369
370 int extOffset = asUnsignedChar(c) - asUnsignedChar(FIRST_EXT_CHAR);
371 return ExtCodes._values[extOffset];
372 }
373
374
375
376
377
378 static CharHandler[] loadCodes(String codesFilePath,
379 char firstChar, char lastChar)
380 {
381 return loadCodes(codesFilePath, firstChar, lastChar, IGNORED_SURROGATES);
382 }
383
384
385
386
387
388
389 static CharHandler[] loadCodes(String codesFilePath,
390 char firstChar, char lastChar,
391 SurrogateCharHandlers surrogates)
392 {
393 int numCodes = (asUnsignedChar(lastChar) - asUnsignedChar(firstChar)) + 1;
394 CharHandler[] values = new CharHandler[numCodes];
395
396 Map<String,Type> prefixMap = new HashMap<>();
397 for(Type type : Type.values()) {
398 prefixMap.put(type.getPrefixCode(), type);
399 }
400
401 BufferedReader reader = null;
402 try {
403
404 reader = new BufferedReader(
405 new InputStreamReader(
406 DatabaseImpl.getResourceAsStream(codesFilePath), "US-ASCII"));
407
408 int start = asUnsignedChar(firstChar);
409 int end = asUnsignedChar(lastChar);
410 for(int i = start; i <= end; ++i) {
411 char c = (char)i;
412 CharHandler ch = null;
413 if(Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
414
415 ch = surrogates.get(c);
416 } else {
417 ch = parseCodes(prefixMap, reader.readLine());
418 }
419 values[(i - start)] = ch;
420 }
421
422 } catch(IOException e) {
423 throw new RuntimeException("failed loading index codes file " +
424 codesFilePath, e);
425 } finally {
426 ByteUtil.closeQuietly(reader);
427 }
428
429 return values;
430 }
431
432
433
434
435
436 private static CharHandler parseCodes(Map<String,Type> prefixMap,
437 String codeLine)
438 {
439 if(codeLine == null) {
440 throw new IllegalStateException("Invalid codes file");
441 }
442 String prefix = codeLine.substring(0, 1);
443 String suffix = ((codeLine.length() > 1) ? codeLine.substring(1) : "");
444 return prefixMap.get(prefix).parseCodes(suffix.split(",", -1));
445 }
446
447
448
449
450 private static CharHandler parseSimpleCodes(String[] codeStrings)
451 {
452 if(codeStrings.length != 1) {
453 throw new IllegalStateException("Unexpected code strings " +
454 Arrays.asList(codeStrings));
455 }
456 return new SimpleCharHandler(codesToBytes(codeStrings[0], true));
457 }
458
459
460
461
462
463 private static CharHandler parseInternationalCodes(String[] codeStrings)
464 {
465 if(codeStrings.length != 2) {
466 throw new IllegalStateException("Unexpected code strings " +
467 Arrays.asList(codeStrings));
468 }
469 return new InternationalCharHandler(codesToBytes(codeStrings[0], true),
470 codesToBytes(codeStrings[1], true));
471 }
472
473
474
475
476
477 private static CharHandler parseUnprintableCodes(String[] codeStrings)
478 {
479 if(codeStrings.length != 1) {
480 throw new IllegalStateException("Unexpected code strings " +
481 Arrays.asList(codeStrings));
482 }
483 return new UnprintableCharHandler(codesToBytes(codeStrings[0], true));
484 }
485
486
487
488
489
490 private static CharHandler parseUnprintableExtCodes(String[] codeStrings)
491 {
492 if(codeStrings.length != 1) {
493 throw new IllegalStateException("Unexpected code strings " +
494 Arrays.asList(codeStrings));
495 }
496 byte[] bytes = codesToBytes(codeStrings[0], true);
497 if(bytes.length != 1) {
498 throw new IllegalStateException("Unexpected code strings " +
499 Arrays.asList(codeStrings));
500 }
501 return new UnprintableExtCharHandler(bytes[0]);
502 }
503
504
505
506
507
508 private static CharHandler parseInternationalExtCodes(String[] codeStrings)
509 {
510 if(codeStrings.length != 3) {
511 throw new IllegalStateException("Unexpected code strings " +
512 Arrays.asList(codeStrings));
513 }
514
515 byte crazyFlag = ("1".equals(codeStrings[2]) ?
516 CRAZY_CODE_1 : CRAZY_CODE_2);
517 return new InternationalExtCharHandler(codesToBytes(codeStrings[0], true),
518 codesToBytes(codeStrings[1], false),
519 crazyFlag);
520 }
521
522
523
524
525 private static CharHandler parseSignificantCodes(String[] codeStrings)
526 {
527 if(codeStrings.length != 1) {
528 throw new IllegalStateException("Unexpected code strings " +
529 Arrays.asList(codeStrings));
530 }
531 return new SignificantCharHandler(codesToBytes(codeStrings[0], true));
532 }
533
534
535
536
537
538 private static byte[] codesToBytes(String codes, boolean required)
539 {
540 if(codes.length() == 0) {
541 if(required) {
542 throw new IllegalStateException("empty code bytes");
543 }
544 return null;
545 }
546 if((codes.length() % 2) != 0) {
547
548 codes = "0" + codes;
549 }
550 byte[] bytes = new byte[codes.length() / 2];
551 for(int i = 0; i < bytes.length; ++i) {
552 int charIdx = i*2;
553 bytes[i] = (byte)(Integer.parseInt(codes.substring(charIdx, charIdx + 2),
554 16));
555 }
556 return bytes;
557 }
558
559
560
561
562
563
564 static int asUnsignedChar(char c)
565 {
566 return c & 0xFFFF;
567 }
568
569
570
571
572
573 void writeNonNullIndexTextValue(
574 Object value, ByteStream bout, boolean isAscending)
575 throws IOException
576 {
577
578 String str = toIndexCharSequence(value);
579
580
581
582 int prevLength = bout.getLength();
583
584
585 ExtraCodesStream extraCodes = null;
586 ByteStream unprintableCodes = null;
587 ByteStream crazyCodes = null;
588 int charOffset = 0;
589 for(int i = 0; i < str.length(); ++i) {
590
591 char c = str.charAt(i);
592 CharHandler ch = getCharHandler(c);
593
594 int curCharOffset = charOffset;
595 byte[] bytes = ch.getInlineBytes(c);
596 if(bytes != null) {
597
598 bout.write(bytes);
599
600
601 ++charOffset;
602 }
603
604 if(ch.getType() == Type.SIMPLE) {
605
606 continue;
607 }
608
609 bytes = ch.getExtraBytes();
610 byte extraCodeModifier = ch.getExtraByteModifier();
611 if((bytes != null) || (extraCodeModifier != 0)) {
612 if(extraCodes == null) {
613 extraCodes = new ExtraCodesStream(str.length());
614 }
615
616
617 writeExtraCodes(curCharOffset, bytes, extraCodeModifier, extraCodes);
618 }
619
620 bytes = ch.getUnprintableBytes();
621 if(bytes != null) {
622 if(unprintableCodes == null) {
623 unprintableCodes = new ByteStream();
624 }
625
626
627 writeUnprintableCodes(curCharOffset, bytes, unprintableCodes,
628 extraCodes);
629 }
630
631 byte crazyFlag = ch.getCrazyFlag();
632 if(crazyFlag != 0) {
633 if(crazyCodes == null) {
634 crazyCodes = new ByteStream();
635 }
636
637
638 crazyCodes.write(crazyFlag);
639 }
640 }
641
642
643 bout.write(END_TEXT);
644
645 boolean hasExtraCodes = trimExtraCodes(
646 extraCodes, (byte)0, INTERNATIONAL_EXTRA_PLACEHOLDER);
647 boolean hasUnprintableCodes = (unprintableCodes != null);
648 boolean hasCrazyCodes = (crazyCodes != null);
649 if(hasExtraCodes || hasUnprintableCodes || hasCrazyCodes) {
650
651
652 if(hasExtraCodes) {
653 extraCodes.writeTo(bout);
654 }
655
656 if(hasCrazyCodes || hasUnprintableCodes) {
657
658
659 bout.write(END_TEXT);
660 bout.write(END_TEXT);
661
662
663 if(hasCrazyCodes) {
664
665 writeCrazyCodes(crazyCodes, bout);
666
667
668
669 if(hasUnprintableCodes) {
670 bout.write(CRAZY_CODES_UNPRINT_SUFFIX);
671 }
672 }
673
674
675 if(hasUnprintableCodes) {
676
677
678 bout.write(END_TEXT);
679
680 unprintableCodes.writeTo(bout);
681 }
682 }
683 }
684
685
686 if(!isAscending) {
687
688
689
690 bout.write(END_EXTRA_TEXT);
691
692
693 IndexData.flipBytes(bout.getBytes(), prevLength,
694 (bout.getLength() - prevLength));
695 }
696
697
698 bout.write(END_EXTRA_TEXT);
699 }
700
701 protected static String toIndexCharSequence(Object value)
702 throws IOException {
703
704
705 String str = ColumnImpl.toCharSequence(value).toString();
706
707
708
709 int len = str.length();
710 if(len > MAX_TEXT_INDEX_CHAR_LENGTH) {
711 str = str.substring(0, MAX_TEXT_INDEX_CHAR_LENGTH);
712 len = MAX_TEXT_INDEX_CHAR_LENGTH;
713 }
714
715
716 if((len > 0) && (str.charAt(len - 1) == ' ')) {
717 do {
718 --len;
719 } while((len > 0) && (str.charAt(len - 1) == ' '));
720
721 str = str.substring(0, len);
722 }
723
724 return str;
725 }
726
727
728
729
730 private static void writeExtraCodes(
731 int charOffset, byte[] bytes, byte extraCodeModifier,
732 ExtraCodesStream extraCodes)
733 {
734
735 int numChars = extraCodes.getNumChars();
736 if(numChars < charOffset) {
737 int fillChars = charOffset - numChars;
738 extraCodes.writeFill(fillChars, INTERNATIONAL_EXTRA_PLACEHOLDER);
739 extraCodes.incrementNumChars(fillChars);
740 }
741
742 if(bytes != null) {
743
744
745 extraCodes.write(bytes);
746 extraCodes.incrementNumChars(1);
747
748 } else {
749
750
751
752 int lastIdx = extraCodes.getLength() - 1;
753 if(lastIdx >= 0) {
754
755
756 byte lastByte = extraCodes.get(lastIdx);
757 lastByte += extraCodeModifier;
758 extraCodes.set(lastIdx, lastByte);
759
760 } else {
761
762
763
764 extraCodes.write(extraCodeModifier);
765 extraCodes.setUnprintablePrefixLen(1);
766 }
767 }
768 }
769
770
771
772
773
774
775 private static boolean trimExtraCodes(ByteStream extraCodes,
776 byte minTrimCode, byte maxTrimCode)
777 {
778 if(extraCodes == null) {
779 return false;
780 }
781
782 extraCodes.trimTrailing(minTrimCode, maxTrimCode);
783
784
785 return (extraCodes.getLength() > 0);
786 }
787
788
789
790
791 private static void writeUnprintableCodes(
792 int charOffset, byte[] bytes, ByteStream unprintableCodes,
793 ExtraCodesStream extraCodes)
794 {
795
796
797
798 int unprintCharOffset = charOffset;
799 if(extraCodes != null) {
800
801
802
803 unprintCharOffset = extraCodes.getLength() +
804 (charOffset - extraCodes.getNumChars()) -
805 extraCodes.getUnprintablePrefixLen();
806 }
807
808
809
810 int offset =
811 (UNPRINTABLE_COUNT_START +
812 (UNPRINTABLE_COUNT_MULTIPLIER * unprintCharOffset))
813 | UNPRINTABLE_OFFSET_FLAGS;
814
815
816 unprintableCodes.write((offset >> 8) & 0xFF);
817 unprintableCodes.write(offset & 0xFF);
818
819 unprintableCodes.write(UNPRINTABLE_MIDFIX);
820 unprintableCodes.write(bytes);
821 }
822
823
824
825
826 private static void writeCrazyCodes(ByteStream crazyCodes, ByteStream bout)
827 {
828
829
830 int numCrazyChars = crazyCodes.getLength();
831
832
833 trimExtraCodes(crazyCodes, CRAZY_CODE_2, CRAZY_CODE_2);
834
835 if(crazyCodes.getLength() > 0) {
836
837
838
839 byte curByte = CRAZY_CODE_START;
840 int idx = 0;
841 for(int i = 0; i < crazyCodes.getLength(); ++i) {
842 byte nextByte = crazyCodes.get(i);
843 nextByte <<= ((2 - idx) * 2);
844 curByte |= nextByte;
845
846 ++idx;
847 if(idx == 3) {
848
849 bout.write(curByte);
850 curByte = CRAZY_CODE_START;
851 idx = 0;
852 }
853 }
854
855
856 if(idx > 0) {
857 bout.write(curByte);
858 }
859 }
860
861
862
863 writeCrazyCodesSuffix(numCrazyChars, bout);
864 }
865
866
867
868
869
870 private static void writeCrazyCodesSuffix(int numCrazyChars, ByteStream bout)
871 {
872 int numRepeats =
873 ((numCrazyChars + (CRAZY_CODES_PER_SUFFIX_REPEAT - 1)) /
874 CRAZY_CODES_PER_SUFFIX_REPEAT);
875
876 bout.write(CRAZY_CODES_SUFFIX_START);
877 bout.write(CRAZY_CODES_SUFFIX_MIDFIX);
878 writeCrazyCodesSuffixRepeats(numRepeats, bout);
879 bout.write(CRAZY_CODES_SUFFIX_SEPARATOR);
880 writeCrazyCodesSuffixRepeats(numRepeats, bout);
881 }
882
883 private static void writeCrazyCodesSuffixRepeats(int numRepeats,
884 ByteStream bout)
885 {
886 for(int i = 0; i < numRepeats; ++i) {
887 bout.write(CRAZY_CODES_SUFFIX_REPEAT);
888 }
889 }
890
891
892
893
894
895 private static final class ExtraCodesStream extends ByteStream
896 {
897 private int _numChars;
898 private int _unprintablePrefixLen;
899
900 private ExtraCodesStream(int length) {
901 super(length);
902 }
903
904 public int getNumChars() {
905 return _numChars;
906 }
907
908 public void incrementNumChars(int inc) {
909 _numChars += inc;
910 }
911
912 public int getUnprintablePrefixLen() {
913 return _unprintablePrefixLen;
914 }
915
916 public void setUnprintablePrefixLen(int len) {
917 _unprintablePrefixLen = len;
918 }
919 }
920
921 }