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.File;
20  import java.lang.reflect.Field;
21  import java.nio.ByteBuffer;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.TreeMap;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import com.healthmarketscience.jackcess.ColumnBuilder;
30  import com.healthmarketscience.jackcess.Cursor;
31  import com.healthmarketscience.jackcess.CursorBuilder;
32  import com.healthmarketscience.jackcess.DataType;
33  import com.healthmarketscience.jackcess.Database;
34  import com.healthmarketscience.jackcess.Index;
35  import com.healthmarketscience.jackcess.Row;
36  import com.healthmarketscience.jackcess.Table;
37  import com.healthmarketscience.jackcess.TableBuilder;
38  import static com.healthmarketscience.jackcess.impl.JetFormatTest.*;
39  import junit.framework.TestCase;
40  import static com.healthmarketscience.jackcess.TestUtil.*;
41  
42  /**
43   * @author James Ahlborn
44   */
45  public class IndexCodesTest extends TestCase {
46  
47    private static final Map<Character,String> SPECIAL_CHARS =
48      new HashMap<Character,String>();
49    static {
50      SPECIAL_CHARS.put('\b', "\\b");
51      SPECIAL_CHARS.put('\t', "\\t");
52      SPECIAL_CHARS.put('\n', "\\n");
53      SPECIAL_CHARS.put('\f', "\\f");
54      SPECIAL_CHARS.put('\r', "\\r");
55      SPECIAL_CHARS.put('\"', "\\\"");
56      SPECIAL_CHARS.put('\'', "\\'");
57      SPECIAL_CHARS.put('\\', "\\\\");
58    }
59    
60    public IndexCodesTest(String name) throws Exception {
61      super(name);
62    }
63  
64    public void testIndexCodes() throws Exception
65    {
66      for (final TestDB testDB : TestDB.getSupportedForBasename(Basename.INDEX_CODES)) {
67        Database db = openMem(testDB);
68  
69        for(Table t : db) {
70          for(Index index : t.getIndexes()) {
71    //         System.out.println("Checking " + t.getName() + "." + index.getName());
72            checkIndexEntries(testDB, t, index);
73          }
74        }
75  
76        db.close();
77      }
78    }
79  
80    private static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception
81    {
82  //         index.initialize();
83  //         System.out.println("Ind " + index);
84  
85      Cursor cursor = CursorBuilder.createCursor(index);
86      while(cursor.moveToNextRow()) {
87  
88        Row row = cursor.getCurrentRow();
89        Cursor.Position curPos = cursor.getSavepoint().getCurrentPosition();
90        boolean success = false;
91        try {
92          findRow(testDB, t, index, row, curPos);
93          success = true;
94        } finally {
95          if(!success) {
96            System.out.println("CurPos: " + curPos);
97            System.out.println("Value: " + row + ": " + 
98                               toUnicodeStr(row.get("data")));
99          }          
100       }
101     }
102     
103   }
104   
105   private static void findRow(final TestDB testDB, Table t, Index index,
106                               Row expectedRow,
107                               Cursor.Position expectedPos)
108     throws Exception
109   {
110     Object[] idxRow = ((IndexImpl)index).constructIndexRow(expectedRow);
111     Cursor cursor = CursorBuilder.createCursor(index, idxRow, idxRow);
112 
113     Cursor.Position startPos = cursor.getSavepoint().getCurrentPosition();
114     
115     cursor.beforeFirst();
116     while(cursor.moveToNextRow()) {
117       Row row = cursor.getCurrentRow();
118       if(expectedRow.equals(row)) {
119         // verify that the entries are indeed equal
120         Cursor.Position curPos = cursor.getSavepoint().getCurrentPosition();
121         assertEquals(entryToString(expectedPos), entryToString(curPos));
122         return;
123       }
124     }
125 
126     // TODO long rows not handled completely yet in V2010
127     // seems to truncate entry at 508 bytes with some trailing 2 byte seq
128     if(testDB.getExpectedFileFormat() == Database.FileFormat.V2010) {
129       String rowId = expectedRow.getString("name");
130       String tName = t.getName();
131       if(("Table11".equals(tName) || "Table11_desc".equals(tName)) &&
132          ("row10".equals(rowId) || "row11".equals(rowId) || 
133           "row12".equals(rowId))) {
134         System.out.println(
135             "TODO long rows not handled completely yet in V2010: " + tName +
136                            ", " + rowId);
137         return;
138       }
139     }
140 
141     fail("testDB: " + testDB + ";\nCould not find expected row " + expectedRow + " starting at " +
142          entryToString(startPos));
143   }
144 
145   
146   //////
147   //
148   // The code below is for use in reverse engineering index entries.
149   //
150   //////
151   
152   public void testNothing() throws Exception {
153     // keep this so build doesn't fail if other tests are disabled
154   }
155 
156   public void x_testCreateIsoFile() throws Exception
157   {
158     Database db = create(Database.FileFormat.V2000, true);
159 
160     Table t = new TableBuilder("test")
161       .addColumn(new ColumnBuilder("row", DataType.TEXT))
162       .addColumn(new ColumnBuilder("data", DataType.TEXT))
163       .toTable(db);
164     
165     for(int i = 0; i < 256; ++i) {
166       String str = "AA" + ((char)i) + "AA";
167       t.addRow("row" + i, str);
168     }
169 
170     db.close();
171   }
172 
173   public void x_testCreateAltIsoFile() throws Exception
174   {
175     Database db = openCopy(Database.FileFormat.V2000, new File("/tmp/test_ind.mdb"), true);
176 
177     Table t = db.getTable("Table1");
178 
179     for(int i = 0; i < 256; ++i) {
180       String str = "AA" + ((char)i) + "AA";
181       t.addRow("row" + i, str,
182                (byte)42 + i, (short)53 + i, 13 * i,
183                (6.7d / i), null, null, true);
184     }
185     
186     db.close();
187   }
188 
189   public void x_testWriteAllCodesMdb() throws Exception
190   {
191     Database db = create(Database.FileFormat.V2000, true);
192 
193 //     Table t = new TableBuilder("Table1")
194 //       .addColumn(new ColumnBuilder("key", DataType.TEXT))
195 //       .addColumn(new ColumnBuilder("data", DataType.TEXT))
196 //       .toTable(db);
197 
198 //     for(int i = 0; i <= 0xFFFF; ++i) {
199 //       // skip non-char chars
200 //       char c = (char)i;
201 //       if(Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
202 //         continue;
203 //       }
204 //       String key = toUnicodeStr(c);
205 //       String str = "AA" + c + "AA";
206 //       t.addRow(key, str);
207 //     }
208 
209     Table t = new TableBuilder("Table5")
210       .addColumn(new ColumnBuilder("name", DataType.TEXT))
211       .addColumn(new ColumnBuilder("data", DataType.TEXT))
212       .toTable(db);
213 
214     char c = (char)0x3041;   // crazy 7F 02 ... A0
215     char c2 = (char)0x30A2;  // crazy 7F 02 ... 
216     char c3 = (char)0x2045;  // inat 27 ... 1C
217     char c4 = (char)0x3043;  // crazy 7F 03 ... A0
218     char c5 = (char)0x3046;  // crazy 7F 04 ... 
219     char c6 = (char)0x30F6;  // crazy 7F 0D ... A0
220     char c7 = (char)0x3099;  // unprint 03
221     char c8 = (char)0x0041;  // A
222     char c9 = (char)0x002D;  // - (unprint)
223     char c10 = (char)0x20E1; // unprint F2
224     char c11 = (char)0x309A; // unprint 04
225     char c12 = (char)0x01C4; // (long extra)
226     char c13 = (char)0x005F; // _ (long inline)
227     char c14 = (char)0xFFFE; // removed
228 
229     char[] cs = new char[]{c7, c8, c3, c12, c13, c14, c, c2, c9};
230     addCombos(t, 0, "", cs, 5);
231 
232 //     t = new TableBuilder("Table2")
233 //       .addColumn(new ColumnBuilder("data", DataType.TEXT))
234 //       .toTable(db);
235     
236 //     writeChars(0x0000, t);
237 
238 //     t = new TableBuilder("Table3")
239 //       .addColumn(new ColumnBuilder("data", DataType.TEXT))
240 //       .toTable(db);
241     
242 //     writeChars(0x0400, t);
243 
244 
245     db.close();
246   }
247 
248   public void x_testReadAllCodesMdb() throws Exception
249   {
250 //     Database db = openCopy(new File("/data2/jackcess_test/testAllIndexCodes.mdb"));
251 //     Database db = openCopy(new File("/data2/jackcess_test/testAllIndexCodes_orig.mdb"));
252 //     Database db = openCopy(new File("/data2/jackcess_test/testSomeMoreCodes.mdb"));
253     Database db = openCopy(Database.FileFormat.V2000, new File("/data2/jackcess_test/testStillMoreCodes.mdb"));
254     Table t = db.getTable("Table5");
255 
256     Index ind = t.getIndexes().iterator().next();
257     ((IndexImpl)ind).initialize();
258     
259     System.out.println("Ind " + ind);
260 
261     Cursor cursor = CursorBuilder.createCursor(ind);
262     while(cursor.moveToNextRow()) {
263       System.out.println("=======");
264       String entryStr = 
265         entryToString(cursor.getSavepoint().getCurrentPosition());
266       System.out.println("Entry Bytes: " + entryStr);
267       System.out.println("Value: " + cursor.getCurrentRow() + "; " +
268                          toUnicodeStr(cursor.getCurrentRow().get("data")));
269     }
270 
271     db.close();
272   }
273 
274   private int addCombos(Table t, int rowNum, String s, char[] cs, int len)
275     throws Exception
276   {
277     if(s.length() >= len) {
278       return rowNum;
279     }
280 
281     for(int i = 0; i < cs.length; ++i) {
282       String name = "row" + (rowNum++);
283       String ss = s + cs[i];
284       t.addRow(name, ss);
285       rowNum = addCombos(t, rowNum, ss, cs, len);
286     }
287 
288     return rowNum;
289   }
290 
291   private void writeChars(int hibyte, Table t) throws Exception
292   {
293     char other = (char)(hibyte | 0x41);
294     for(int i = 0; i < 0xFF; ++i) {
295       char c = (char)(hibyte | i);
296       String str = "" + other + c + other;
297       t.addRow(str);
298     }
299   }
300 
301   public void x_testReadIsoMdb() throws Exception
302   {
303 //     Database db = open(new File("/tmp/test_ind.mdb"));
304 //     Database db = open(new File("/tmp/test_ind2.mdb"));
305     Database db = open(Database.FileFormat.V2000, new File("/tmp/test_ind3.mdb"));
306 //     Database db = open(new File("/tmp/test_ind4.mdb"));
307 
308     Table t = db.getTable("Table1");
309     Index index = t.getIndex("B");
310     ((IndexImpl)index).initialize();
311     System.out.println("Ind " + index);
312 
313     Cursor cursor = CursorBuilder.createCursor(index);
314     while(cursor.moveToNextRow()) {
315       System.out.println("=======");
316       System.out.println("Savepoint: " + cursor.getSavepoint());
317       System.out.println("Value: " + cursor.getCurrentRow());
318     }
319     
320     db.close();
321   }
322   
323   public void x_testReverseIsoMdb2010() throws Exception
324   {
325     Database db = open(Database.FileFormat.V2010, new File("/data2/jackcess_test/testAllIndexCodes3_2010.accdb"));
326 
327     Table t = db.getTable("Table1");
328     Index index = t.getIndexes().iterator().next();
329     ((IndexImpl)index).initialize();
330     System.out.println("Ind " + index);
331 
332     Pattern inlinePat = Pattern.compile("7F 0E 02 0E 02 (.*)0E 02 0E 02 01 00");
333     Pattern unprintPat = Pattern.compile("01 01 01 80 (.+) 06 (.+) 00");
334     Pattern unprint2Pat = Pattern.compile("0E 02 0E 02 0E 02 0E 02 01 02 (.+) 00");
335     Pattern inatPat = Pattern.compile("7F 0E 02 0E 02 (.*)0E 02 0E 02 01 02 02 (.+) 00");
336     Pattern inat2Pat = Pattern.compile("7F 0E 02 0E 02 (.*)0E 02 0E 02 01 (02 02 (.+))?01 01 (.*)FF 02 80 FF 80 00");
337 
338     Map<Character,String[]> inlineCodes = new TreeMap<Character,String[]>();
339     Map<Character,String[]> unprintCodes = new TreeMap<Character,String[]>();
340     Map<Character,String[]> unprint2Codes = new TreeMap<Character,String[]>();
341     Map<Character,String[]> inatInlineCodes = new TreeMap<Character,String[]>();
342     Map<Character,String[]> inatExtraCodes = new TreeMap<Character,String[]>();
343     Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
344     Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
345     Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
346     
347     
348     Cursor cursor = CursorBuilder.createCursor(index);
349     while(cursor.moveToNextRow()) {
350 //       System.out.println("=======");
351 //       System.out.println("Savepoint: " + cursor.getSavepoint());
352 //       System.out.println("Value: " + cursor.getCurrentRow());
353       Cursor.Savepoint savepoint = cursor.getSavepoint();
354       String entryStr = entryToString(savepoint.getCurrentPosition());
355 
356       Row row = cursor.getCurrentRow();
357       String value = row.getString("data");
358       String key = row.getString("key");
359       char c = value.charAt(2);
360 
361       System.out.println("=======");
362       System.out.println("RowId: " +
363                          savepoint.getCurrentPosition().getRowId());
364       System.out.println("Entry: " + entryStr);
365 //         System.out.println("Row: " + row);
366       System.out.println("Value: (" + key + ")" + value);
367       System.out.println("Char: " + c + ", " + (int)c + ", " +
368                          toUnicodeStr(c));
369 
370       String type = null;
371       if(entryStr.endsWith("01 00")) {
372 
373         // handle inline codes
374         type = "INLINE";
375         Matcher m = inlinePat.matcher(entryStr);
376         m.find();
377         handleInlineEntry(m.group(1), c, inlineCodes);
378 
379       } else if(entryStr.contains("01 01 01 80")) {
380         
381         // handle most unprintable codes
382         type = "UNPRINTABLE";
383         Matcher m = unprintPat.matcher(entryStr);
384         m.find();
385         handleUnprintableEntry(m.group(2), c, unprintCodes);
386 
387       } else if(entryStr.contains("01 02 02") && 
388                 !entryStr.contains("FF 02 80 FF 80")) {
389 
390         // handle chars w/ symbols
391         type = "CHAR_WITH_SYMBOL";
392         Matcher m = inatPat.matcher(entryStr);
393         m.find();
394         handleInternationalEntry(m.group(1), m.group(2), c,
395                                  inatInlineCodes, inatExtraCodes);
396         
397       } else if(entryStr.contains("0E 02 0E 02 0E 02 0E 02 01 02")) {
398 
399         // handle chars w/ symbols
400         type = "UNPRINTABLE_2";
401         Matcher m = unprint2Pat.matcher(entryStr);
402         m.find();
403         handleUnprintable2Entry(m.group(1), c, unprint2Codes);
404         
405       } else if(entryStr.contains("FF 02 80 FF 80")) {
406 
407         type = "CRAZY_INAT";
408         Matcher m = inat2Pat.matcher(entryStr);
409         m.find();
410         handleInternational2Entry(m.group(1), m.group(3), m.group(4), c,
411                                   inat2Codes, inat2ExtraCodes,
412                                   inat2CrazyCodes);
413 
414       } else {
415 
416         // throw new RuntimeException("unhandled " + entryStr);
417         System.out.println("unhandled " + entryStr);
418       }      
419         
420       System.out.println("Type: " + type);
421     }
422 
423     System.out.println("\n***CODES");
424     for(int i = 0; i <= 0xFFFF; ++i) {
425 
426       if(i == 256) {
427         System.out.println("\n***EXTENDED CODES");
428       }
429 
430       // skip non-char chars
431       char c = (char)i;
432       if(Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
433         continue;
434       }
435 
436       if(c == (char)0xFFFE) {
437         // this gets replaced with FFFD, treat it the same
438         c = (char)0xFFFD;
439       }
440 
441       Character cc = c;
442       String[] chars = inlineCodes.get(cc);
443       if(chars != null) {
444         if((chars.length == 1) && (chars[0].length() == 0)) {
445           System.out.println("X");
446         } else {
447           System.out.println("S" + toByteString(chars));
448         }
449         continue;
450       }
451 
452       chars = inatInlineCodes.get(cc);
453       if(chars != null) {
454         String[] extra = inatExtraCodes.get(cc);
455         System.out.println("I" + toByteString(chars) + "," +
456                            toByteString(extra));
457         continue;
458       }
459         
460       chars = unprintCodes.get(cc);
461       if(chars != null) {
462         System.out.println("U" + toByteString(chars));
463         continue;
464       }
465 
466       chars = unprint2Codes.get(cc);
467       if(chars != null) {
468         if(chars.length > 1) {
469           throw new RuntimeException("long unprint codes");
470         }
471         int val = Integer.parseInt(chars[0], 16) - 2;
472         String valStr = ByteUtil.toHexString(new byte[]{(byte)val}).trim();
473         System.out.println("P" + valStr);
474         continue;
475       }
476 
477       chars = inat2Codes.get(cc);
478       if(chars != null) {
479         String [] crazyCodes = inat2CrazyCodes.get(cc);
480         String crazyCode = "";
481         if(crazyCodes != null) {
482           if((crazyCodes.length != 1) || !"A0".equals(crazyCodes[0])) {
483             throw new RuntimeException("CC " + Arrays.asList(crazyCodes));
484           }
485           crazyCode = "1";
486         }
487 
488         String[] extra = inat2ExtraCodes.get(cc);
489         System.out.println("Z" + toByteString(chars) + "," +
490                            toByteString(extra) + "," +
491                            crazyCode);
492         continue;
493       }
494 
495       throw new RuntimeException("Unhandled char " + toUnicodeStr(c));
496     }
497     System.out.println("\n***END CODES");
498     
499     db.close();
500   }
501  
502   public void x_testReverseIsoMdb() throws Exception
503   {
504     Database db = open(Database.FileFormat.V2000, new File("/data2/jackcess_test/testAllIndexCodes3.mdb"));
505 
506     Table t = db.getTable("Table1");
507     Index index = t.getIndexes().iterator().next();
508     ((IndexImpl)index).initialize();
509     System.out.println("Ind " + index);
510 
511     Pattern inlinePat = Pattern.compile("7F 4A 4A (.*)4A 4A 01 00");
512     Pattern unprintPat = Pattern.compile("01 01 01 80 (.+) 06 (.+) 00");
513     Pattern unprint2Pat = Pattern.compile("4A 4A 4A 4A 01 02 (.+) 00");
514     Pattern inatPat = Pattern.compile("7F 4A 4A (.*)4A 4A 01 02 02 (.+) 00");
515     Pattern inat2Pat = Pattern.compile("7F 4A 4A (.*)4A 4A 01 (02 02 (.+))?01 01 (.*)FF 02 80 FF 80 00");
516 
517     Map<Character,String[]> inlineCodes = new TreeMap<Character,String[]>();
518     Map<Character,String[]> unprintCodes = new TreeMap<Character,String[]>();
519     Map<Character,String[]> unprint2Codes = new TreeMap<Character,String[]>();
520     Map<Character,String[]> inatInlineCodes = new TreeMap<Character,String[]>();
521     Map<Character,String[]> inatExtraCodes = new TreeMap<Character,String[]>();
522     Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
523     Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
524     Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
525     
526     
527     Cursor cursor = CursorBuilder.createCursor(index);
528     while(cursor.moveToNextRow()) {
529 //       System.out.println("=======");
530 //       System.out.println("Savepoint: " + cursor.getSavepoint());
531 //       System.out.println("Value: " + cursor.getCurrentRow());
532       Cursor.Savepoint savepoint = cursor.getSavepoint();
533       String entryStr = entryToString(savepoint.getCurrentPosition());
534 
535       Row row = cursor.getCurrentRow();
536       String value = row.getString("data");
537       String key = row.getString("key");
538       char c = value.charAt(2);
539       System.out.println("=======");
540       System.out.println("RowId: " +
541                          savepoint.getCurrentPosition().getRowId());
542       System.out.println("Entry: " + entryStr);
543 //         System.out.println("Row: " + row);
544       System.out.println("Value: (" + key + ")" + value);
545       System.out.println("Char: " + c + ", " + (int)c + ", " +
546                          toUnicodeStr(c));
547 
548       String type = null;
549       if(entryStr.endsWith("01 00")) {
550 
551         // handle inline codes
552         type = "INLINE";
553         Matcher m = inlinePat.matcher(entryStr);
554         m.find();
555         handleInlineEntry(m.group(1), c, inlineCodes);
556 
557       } else if(entryStr.contains("01 01 01 80")) {
558         
559         // handle most unprintable codes
560         type = "UNPRINTABLE";
561         Matcher m = unprintPat.matcher(entryStr);
562         m.find();
563         handleUnprintableEntry(m.group(2), c, unprintCodes);
564 
565       } else if(entryStr.contains("01 02 02") && 
566                 !entryStr.contains("FF 02 80 FF 80")) {
567 
568         // handle chars w/ symbols
569         type = "CHAR_WITH_SYMBOL";
570         Matcher m = inatPat.matcher(entryStr);
571         m.find();
572         handleInternationalEntry(m.group(1), m.group(2), c,
573                                  inatInlineCodes, inatExtraCodes);
574         
575       } else if(entryStr.contains("4A 4A 4A 4A 01 02")) {
576 
577         // handle chars w/ symbols
578         type = "UNPRINTABLE_2";
579         Matcher m = unprint2Pat.matcher(entryStr);
580         m.find();
581         handleUnprintable2Entry(m.group(1), c, unprint2Codes);
582         
583       } else if(entryStr.contains("FF 02 80 FF 80")) {
584 
585         type = "CRAZY_INAT";
586         Matcher m = inat2Pat.matcher(entryStr);
587         m.find();
588         handleInternational2Entry(m.group(1), m.group(3), m.group(4), c,
589                                   inat2Codes, inat2ExtraCodes,
590                                   inat2CrazyCodes);
591 
592       } else {
593 
594         throw new RuntimeException("unhandled " + entryStr);
595       }      
596         
597       System.out.println("Type: " + type);
598     }
599 
600     System.out.println("\n***CODES");
601     for(int i = 0; i <= 0xFFFF; ++i) {
602 
603       if(i == 256) {
604         System.out.println("\n***EXTENDED CODES");
605       }
606 
607       // skip non-char chars
608       char c = (char)i;
609       if(Character.isHighSurrogate(c) || Character.isLowSurrogate(c)) {
610         continue;
611       }
612 
613       if(c == (char)0xFFFE) {
614         // this gets replaced with FFFD, treat it the same
615         c = (char)0xFFFD;
616       }
617 
618       Character cc = c;
619       String[] chars = inlineCodes.get(cc);
620       if(chars != null) {
621         if((chars.length == 1) && (chars[0].length() == 0)) {
622           System.out.println("X");
623         } else {
624           System.out.println("S" + toByteString(chars));
625         }
626         continue;
627       }
628 
629       chars = inatInlineCodes.get(cc);
630       if(chars != null) {
631         String[] extra = inatExtraCodes.get(cc);
632         System.out.println("I" + toByteString(chars) + "," +
633                            toByteString(extra));
634         continue;
635       }
636         
637       chars = unprintCodes.get(cc);
638       if(chars != null) {
639         System.out.println("U" + toByteString(chars));
640         continue;
641       }
642 
643       chars = unprint2Codes.get(cc);
644       if(chars != null) {
645         if(chars.length > 1) {
646           throw new RuntimeException("long unprint codes");
647         }
648         int val = Integer.parseInt(chars[0], 16) - 2;
649         String valStr = ByteUtil.toHexString(new byte[]{(byte)val}).trim();
650         System.out.println("P" + valStr);
651         continue;
652       }
653 
654       chars = inat2Codes.get(cc);
655       if(chars != null) {
656         String [] crazyCodes = inat2CrazyCodes.get(cc);
657         String crazyCode = "";
658         if(crazyCodes != null) {
659           if((crazyCodes.length != 1) || !"A0".equals(crazyCodes[0])) {
660             throw new RuntimeException("CC " + Arrays.asList(crazyCodes));
661           }
662           crazyCode = "1";
663         }
664 
665         String[] extra = inat2ExtraCodes.get(cc);
666         System.out.println("Z" + toByteString(chars) + "," +
667                            toByteString(extra) + "," +
668                            crazyCode);
669         continue;
670       }
671 
672       throw new RuntimeException("Unhandled char " + toUnicodeStr(c));
673     }
674     System.out.println("\n***END CODES");
675     
676     db.close();
677   }
678 
679   private static String toByteString(String[] chars)
680   {
681     String str = join(chars, "", "");
682     if(str.length() > 0 && str.charAt(0) == '0') {
683       str = str.substring(1);
684     }
685     return str;
686   }
687 
688   private static void handleInlineEntry(
689       String entryCodes, char c, Map<Character,String[]> inlineCodes)
690     throws Exception
691   {
692     inlineCodes.put(c, entryCodes.trim().split(" "));
693   }
694   
695   private static void handleUnprintableEntry(
696       String entryCodes, char c, Map<Character,String[]> unprintCodes)
697     throws Exception
698   {
699     unprintCodes.put(c, entryCodes.trim().split(" "));
700   }
701   
702   private static void handleUnprintable2Entry(
703       String entryCodes, char c, Map<Character,String[]> unprintCodes)
704     throws Exception
705   {
706     unprintCodes.put(c, entryCodes.trim().split(" "));
707   }
708   
709   private static void handleInternationalEntry(
710       String inlineCodes, String entryCodes, char c,
711       Map<Character,String[]> inatInlineCodes,
712       Map<Character,String[]> inatExtraCodes)
713     throws Exception
714   {
715     inatInlineCodes.put(c, inlineCodes.trim().split(" "));
716     inatExtraCodes.put(c, entryCodes.trim().split(" "));
717   }
718 
719   private static void handleInternational2Entry(
720       String inlineCodes, String entryCodes, String crazyCodes, char c,
721       Map<Character,String[]> inatInlineCodes,
722       Map<Character,String[]> inatExtraCodes,
723       Map<Character,String[]> inatCrazyCodes)
724     throws Exception
725   {
726     inatInlineCodes.put(c, inlineCodes.trim().split(" "));
727     if(entryCodes != null) {
728       inatExtraCodes.put(c, entryCodes.trim().split(" "));
729     }
730     if((crazyCodes != null) && (crazyCodes.length() > 0)) {
731       inatCrazyCodes.put(c, crazyCodes.trim().split(" "));
732     }
733   }
734 
735   private static String toUnicodeStr(Object obj) throws Exception {
736     StringBuilder sb = new StringBuilder();
737     for(char c : obj.toString().toCharArray()) {
738       sb.append(toUnicodeStr(c)).append(" ");
739     }
740     return sb.toString();
741   }
742   
743   private static String toUnicodeStr(char c) throws Exception {
744     String specialStr = SPECIAL_CHARS.get(c);
745     if(specialStr != null) {
746       return specialStr;
747     }
748     
749     String digits = Integer.toHexString(c).toUpperCase();
750     while(digits.length() < 4) {
751       digits = "0" + digits;
752     }
753     return "\\u" + digits;
754   }
755 
756   private static String join(String[] strs, String joinStr, String prefixStr) {
757     if(strs == null) {
758       return "";
759     }
760     StringBuilder builder = new StringBuilder();
761     for(int i = 0; i < strs.length; ++i) {
762       if(strs[i].length() == 0) {
763         continue;
764       }
765       builder.append(prefixStr).append(strs[i]);
766       if(i < (strs.length - 1)) {
767         builder.append(joinStr);
768       }
769     }
770     return builder.toString();
771   }
772   
773   public static String entryToString(Cursor.Position curPos)
774     throws Exception
775   {
776     Field eField = curPos.getClass().getDeclaredField("_entry");
777     eField.setAccessible(true);
778     IndexData.Entry entry = (IndexData.Entry)eField.get(curPos);
779     Field ebField = entry.getClass().getDeclaredField("_entryBytes");
780     ebField.setAccessible(true);
781     byte[] entryBytes = (byte[])ebField.get(entry);
782 
783     return ByteUtil.toHexString(ByteBuffer.wrap(entryBytes),
784                                 0, entryBytes.length, false);
785   }
786   
787 }