]> source.dussan.org Git - jackcess.git/commitdiff
use new general 97 index support
authorJames Ahlborn <jtahlborn@yahoo.com>
Fri, 16 Aug 2019 01:23:38 +0000 (01:23 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Fri, 16 Aug 2019 01:23:38 +0000 (01:23 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/a97_indexes@1312 f203690c-595d-4dc9-a70b-905162fa7fd2

src/main/java/com/healthmarketscience/jackcess/impl/IndexCursorImpl.java
src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java
src/test/java/com/healthmarketscience/jackcess/impl/IndexCodesTest.java

index 6c58182ba0da137a13c5c06f16a56e0e7fea4404..43046ba3c4e05680447e062b495f593556ef334d 100644 (file)
@@ -42,7 +42,7 @@ import org.apache.commons.logging.LogFactory;
  */
 public class IndexCursorImpl extends CursorImpl implements IndexCursor
 {
-  private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);  
+  private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);
 
   /** IndexDirHandler for forward traversal */
   private final IndexDirHandler _forwardDirHandler =
@@ -68,7 +68,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     _index.initialize();
     _entryCursor = entryCursor;
   }
-  
+
   /**
    * Creates an indexed cursor for the given table, narrowed to the given
    * range.
@@ -76,7 +76,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
    * Note, index based table traversal may not include all rows, as certain
    * types of indexes do not include all entries (namely, some indexes ignore
    * null entries, see {@link Index#shouldIgnoreNulls}).
-   * 
+   *
    * @param table the table over which this cursor will traverse
    * @param index index for the table which will define traversal order as
    *              well as enhance certain lookups
@@ -98,14 +98,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
       throw new IllegalArgumentException(
           "Given index is not for given table: " + index + ", " + table);
     }
-    if(!table.getFormat().INDEXES_SUPPORTED) {
-      throw new IllegalArgumentException(
-          "JetFormat " + table.getFormat() + 
-          " does not currently support index lookups");
-    }
     if(index.getIndexData().getUnsupportedReason() != null) {
       throw new IllegalArgumentException(
-          "Given index " + index + 
+          "Given index " + index +
           " is not usable for indexed lookups due to " +
           index.getIndexData().getUnsupportedReason());
     }
@@ -115,7 +110,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     // init the column matcher appropriately for the index type
     cursor.setColumnMatcher(null);
     return cursor;
-  }  
+  }
 
   private Set<String> getIndexEntryPattern()
   {
@@ -135,7 +130,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
   }
 
   @Override
-  public Row findRowByEntry(Object... entryValues) 
+  public Row findRowByEntry(Object... entryValues)
     throws IOException
   {
     if(findFirstRowByEntry(entryValues)) {
@@ -143,16 +138,16 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     }
     return null;
   }
-  
+
   @Override
-  public boolean findFirstRowByEntry(Object... entryValues) 
-    throws IOException 
+  public boolean findFirstRowByEntry(Object... entryValues)
+    throws IOException
   {
     PositionImpl curPos = _curPos;
     PositionImpl prevPos = _prevPos;
     boolean found = false;
     try {
-      found = findFirstRowByEntryImpl(toRowValues(entryValues), true, 
+      found = findFirstRowByEntryImpl(toRowValues(entryValues), true,
                                       _columnMatcher);
       return found;
     } finally {
@@ -167,8 +162,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
   }
 
   @Override
-  public void findClosestRowByEntry(Object... entryValues) 
-    throws IOException 
+  public void findClosestRowByEntry(Object... entryValues)
+    throws IOException
   {
     PositionImpl curPos = _curPos;
     PositionImpl prevPos = _prevPos;
@@ -189,8 +184,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
   }
 
   @Override
-  public boolean currentRowMatchesEntry(Object... entryValues) 
-    throws IOException 
+  public boolean currentRowMatchesEntry(Object... entryValues)
+    throws IOException
   {
     return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher);
   }
@@ -205,17 +200,17 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
                              toRowValues(iterBuilder.getEntryValues()),
                              iterBuilder.getColumnMatcher());
   }
-  
+
   @Override
   protected IndexDirHandler getDirHandler(boolean moveForward) {
     return (moveForward ? _forwardDirHandler : _reverseDirHandler);
   }
-    
+
   @Override
   protected boolean isUpToDate() {
     return(super.isUpToDate() && _entryCursor.isUpToDate());
   }
-    
+
   @Override
   protected void reset(boolean moveForward) {
     _entryCursor.reset(moveForward);
@@ -259,7 +254,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
       return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward,
                                       columnMatcher, rowValues);
     }
-      
+
     // sweet, we can use our index
     if(!findPotentialRow(rowValues, true)) {
       return false;
@@ -274,14 +269,14 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
    * match the given values.  Caller manages save/restore on failure.
    *
    * @param rowValues the column values built from the index column values
-   * @param requireMatch whether or not an exact match is found
+   * @param requireMatch whether or not an exact match is desired
    * @return {@code true} if a valid row was found with the given values,
    *         {@code false} if no row was found
    */
   protected boolean findFirstRowByEntryImpl(Object[] rowValues,
                                             boolean requireMatch,
-                                            ColumnMatcher columnMatcher) 
-    throws IOException 
+                                            ColumnMatcher columnMatcher)
+    throws IOException
   {
     if(!findPotentialRow(rowValues, requireMatch)) {
       return false;
@@ -317,7 +312,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     // determine if the pattern columns exactly match the index columns
     boolean exactColumnMatch = rowPattern.keySet().equals(
         getIndexEntryPattern());
-    
+
     // there may be multiple rows which fit the pattern subset used by
     // the index, so we need to keep checking until our index values no
     // longer match
@@ -337,12 +332,12 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
       }
 
     } while(moveToAnotherRow(moveForward));
-        
+
     // none of the potential rows matched
     return false;
   }
 
-  private boolean currentRowMatchesEntryImpl(Object[] rowValues, 
+  private boolean currentRowMatchesEntryImpl(Object[] rowValues,
                                              ColumnMatcher columnMatcher)
     throws IOException
   {
@@ -353,7 +348,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
 
       Object patValue = rowValues[col.getColumnIndex()];
 
-      if((patValue == IndexData.MIN_VALUE) || 
+      if((patValue == IndexData.MIN_VALUE) ||
          (patValue == IndexData.MAX_VALUE)) {
         // all remaining entry values are "special" (used for partial lookups)
         return true;
@@ -366,9 +361,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
       }
     }
 
-    return true;    
+    return true;
   }
-  
+
   private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
     throws IOException
   {
@@ -400,8 +395,8 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
   }
 
   @Override
-  protected boolean keepSearching(ColumnMatcher columnMatcher, 
-                                  Object searchInfo) 
+  protected boolean keepSearching(ColumnMatcher columnMatcher,
+                                  Object searchInfo)
     throws IOException
   {
     if(searchInfo instanceof Object[]) {
@@ -420,7 +415,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     return _entryCursor.getIndexData().constructPartialIndexRowFromEntry(
         IndexData.MIN_VALUE, entryValues);
   }
-  
+
   @Override
   protected PositionImpl findAnotherPosition(
       RowState rowState, PositionImpl curPos, boolean moveForward)
@@ -451,7 +446,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     public abstract IndexData.Entry getAnotherEntry()
       throws IOException;
   }
-    
+
   /**
    * Handles moving the table index cursor forward.
    */
@@ -469,7 +464,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
       return _entryCursor.getNextEntry();
     }
   }
-    
+
   /**
    * Handles moving the table index cursor backward.
    */
@@ -486,15 +481,15 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     public IndexData.Entry getAnotherEntry() throws IOException {
       return _entryCursor.getPreviousEntry();
     }
-  }    
-    
+  }
+
   /**
    * Value object which maintains the current position of an IndexCursor.
    */
   private static final class IndexPosition extends PositionImpl
   {
     private final IndexData.Entry _entry;
-    
+
     private IndexPosition(IndexData.Entry entry) {
       _entry = entry;
     }
@@ -503,11 +498,11 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
     public RowIdImpl getRowId() {
       return getEntry().getRowId();
     }
-    
+
     public IndexData.Entry getEntry() {
       return _entry;
     }
-    
+
     @Override
     protected boolean equalsImpl(Object o) {
       return getEntry().equals(((IndexPosition)o).getEntry());
@@ -525,7 +520,7 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
   private final class EntryIterator extends BaseIterator
   {
     private final Object[] _rowValues;
-    
+
     private EntryIterator(Collection<String> columnNames, Object[] rowValues,
                           ColumnMatcher columnMatcher)
     {
@@ -541,9 +536,9 @@ public class IndexCursorImpl extends CursorImpl implements IndexCursor
 
     @Override
     protected boolean findNext() throws IOException {
-      return (moveToNextRow() && 
+      return (moveToNextRow() &&
               currentRowMatchesEntryImpl(_rowValues, _colMatcher));
-    }    
+    }
   }
 
 }
index ca91fef869edc13f424d7db21ba8d48e76ee8150..bc2d111c9b3da8294edcd1d3c5897cf7d3521bcb 100644 (file)
@@ -1515,11 +1515,14 @@ public class IndexData {
     case TEXT:
     case MEMO:
       ColumnImpl.SortOrder sortOrder = col.getTextSortOrder();
+      if(ColumnImpl.GENERAL_SORT_ORDER.equals(sortOrder)) {
+        return new GenTextColumnDescriptor(col, flags);
+      }
       if(ColumnImpl.GENERAL_LEGACY_SORT_ORDER.equals(sortOrder)) {
         return new GenLegTextColumnDescriptor(col, flags);
       }
-      if(ColumnImpl.GENERAL_SORT_ORDER.equals(sortOrder)) {
-        return new GenTextColumnDescriptor(col, flags);
+      if(ColumnImpl.GENERAL_97_SORT_ORDER.equals(sortOrder)) {
+        return new Gen97TextColumnDescriptor(col, flags);
       }
       // unsupported sort order
       setUnsupportedReason("unsupported collating sort order " + sortOrder +
@@ -1914,6 +1917,27 @@ public class IndexData {
     }
   }
 
+  /**
+   * ColumnDescriptor for "general 97" sort order text based columns.
+   */
+  private static final class Gen97TextColumnDescriptor
+    extends ColumnDescriptor
+  {
+    private Gen97TextColumnDescriptor(ColumnImpl column, byte flags)
+      throws IOException
+    {
+      super(column, flags);
+    }
+
+    @Override
+    protected void writeNonNullValue(Object value, ByteStream bout)
+      throws IOException
+    {
+      General97IndexCodes.GEN_97_INSTANCE.writeNonNullIndexTextValue(
+          value, bout, isAscending());
+    }
+  }
+
   /**
    * ColumnDescriptor for guid columns.
    */
index 5b7a44036c473a77199b8e6eb15d61038a89b545..878ffdc6c6a66fbbdc1266eaf1ff2a3fa56fa911 100644 (file)
@@ -56,7 +56,7 @@ public class IndexCodesTest extends TestCase {
     SPECIAL_CHARS.put('\'', "\\'");
     SPECIAL_CHARS.put('\\', "\\\\");
   }
-  
+
   public IndexCodesTest(String name) throws Exception {
     super(name);
   }
@@ -77,7 +77,7 @@ public class IndexCodesTest extends TestCase {
     }
   }
 
-  private static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception
+  public static void checkIndexEntries(final TestDB testDB, Table t, Index index) throws Exception
   {
 //         index.initialize();
 //         System.out.println("Ind " + index);
@@ -94,14 +94,14 @@ public class IndexCodesTest extends TestCase {
       } finally {
         if(!success) {
           System.out.println("CurPos: " + curPos);
-          System.out.println("Value: " + row + ": " + 
+          System.out.println("Value: " + row + ": " +
                              toUnicodeStr(row.get("data")));
-        }          
+        }
       }
     }
-    
+
   }
-  
+
   private static void findRow(final TestDB testDB, Table t, Index index,
                               Row expectedRow,
                               Cursor.Position expectedPos)
@@ -111,7 +111,7 @@ public class IndexCodesTest extends TestCase {
     Cursor cursor = CursorBuilder.createCursor(index, idxRow, idxRow);
 
     Cursor.Position startPos = cursor.getSavepoint().getCurrentPosition();
-    
+
     cursor.beforeFirst();
     while(cursor.moveToNextRow()) {
       Row row = cursor.getCurrentRow();
@@ -125,11 +125,12 @@ public class IndexCodesTest extends TestCase {
 
     // TODO long rows not handled completely yet in V2010
     // seems to truncate entry at 508 bytes with some trailing 2 byte seq
-    if(testDB.getExpectedFileFormat() == Database.FileFormat.V2010) {
+    if((testDB != null) &&
+       (testDB.getExpectedFileFormat() == Database.FileFormat.V2010)) {
       String rowId = expectedRow.getString("name");
       String tName = t.getName();
       if(("Table11".equals(tName) || "Table11_desc".equals(tName)) &&
-         ("row10".equals(rowId) || "row11".equals(rowId) || 
+         ("row10".equals(rowId) || "row11".equals(rowId) ||
           "row12".equals(rowId))) {
         System.out.println(
             "TODO long rows not handled completely yet in V2010: " + tName +
@@ -142,13 +143,13 @@ public class IndexCodesTest extends TestCase {
          entryToString(startPos));
   }
 
-  
+
   //////
   //
   // The code below is for use in reverse engineering index entries.
   //
   //////
-  
+
   public void testNothing() throws Exception {
     // keep this so build doesn't fail if other tests are disabled
   }
@@ -161,7 +162,7 @@ public class IndexCodesTest extends TestCase {
       .addColumn(new ColumnBuilder("row", DataType.TEXT))
       .addColumn(new ColumnBuilder("data", DataType.TEXT))
       .toTable(db);
-    
+
     for(int i = 0; i < 256; ++i) {
       String str = "AA" + ((char)i) + "AA";
       t.addRow("row" + i, str);
@@ -182,7 +183,7 @@ public class IndexCodesTest extends TestCase {
                (byte)42 + i, (short)53 + i, 13 * i,
                (6.7d / i), null, null, true);
     }
-    
+
     db.close();
   }
 
@@ -212,10 +213,10 @@ public class IndexCodesTest extends TestCase {
       .toTable(db);
 
     char c = (char)0x3041;   // crazy 7F 02 ... A0
-    char c2 = (char)0x30A2;  // crazy 7F 02 ... 
+    char c2 = (char)0x30A2;  // crazy 7F 02 ...
     char c3 = (char)0x2045;  // inat 27 ... 1C
     char c4 = (char)0x3043;  // crazy 7F 03 ... A0
-    char c5 = (char)0x3046;  // crazy 7F 04 ... 
+    char c5 = (char)0x3046;  // crazy 7F 04 ...
     char c6 = (char)0x30F6;  // crazy 7F 0D ... A0
     char c7 = (char)0x3099;  // unprint 03
     char c8 = (char)0x0041;  // A
@@ -232,13 +233,13 @@ public class IndexCodesTest extends TestCase {
 //     t = new TableBuilder("Table2")
 //       .addColumn(new ColumnBuilder("data", DataType.TEXT))
 //       .toTable(db);
-    
+
 //     writeChars(0x0000, t);
 
 //     t = new TableBuilder("Table3")
 //       .addColumn(new ColumnBuilder("data", DataType.TEXT))
 //       .toTable(db);
-    
+
 //     writeChars(0x0400, t);
 
 
@@ -255,13 +256,13 @@ public class IndexCodesTest extends TestCase {
 
     Index ind = t.getIndexes().iterator().next();
     ((IndexImpl)ind).initialize();
-    
+
     System.out.println("Ind " + ind);
 
     Cursor cursor = CursorBuilder.createCursor(ind);
     while(cursor.moveToNextRow()) {
       System.out.println("=======");
-      String entryStr = 
+      String entryStr =
         entryToString(cursor.getSavepoint().getCurrentPosition());
       System.out.println("Entry Bytes: " + entryStr);
       System.out.println("Value: " + cursor.getCurrentRow() + "; " +
@@ -316,10 +317,10 @@ public class IndexCodesTest extends TestCase {
       System.out.println("Savepoint: " + cursor.getSavepoint());
       System.out.println("Value: " + cursor.getCurrentRow());
     }
-    
+
     db.close();
   }
-  
+
   public void x_testReverseIsoMdb2010() throws Exception
   {
     Database db = open(Database.FileFormat.V2010, new File("/data2/jackcess_test/testAllIndexCodes3_2010.accdb"));
@@ -343,8 +344,8 @@ public class IndexCodesTest extends TestCase {
     Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
     Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
     Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
-    
-    
+
+
     Cursor cursor = CursorBuilder.createCursor(index);
     while(cursor.moveToNextRow()) {
 //       System.out.println("=======");
@@ -377,14 +378,14 @@ public class IndexCodesTest extends TestCase {
         handleInlineEntry(m.group(1), c, inlineCodes);
 
       } else if(entryStr.contains("01 01 01 80")) {
-        
+
         // handle most unprintable codes
         type = "UNPRINTABLE";
         Matcher m = unprintPat.matcher(entryStr);
         m.find();
         handleUnprintableEntry(m.group(2), c, unprintCodes);
 
-      } else if(entryStr.contains("01 02 02") && 
+      } else if(entryStr.contains("01 02 02") &&
                 !entryStr.contains("FF 02 80 FF 80")) {
 
         // handle chars w/ symbols
@@ -393,7 +394,7 @@ public class IndexCodesTest extends TestCase {
         m.find();
         handleInternationalEntry(m.group(1), m.group(2), c,
                                  inatInlineCodes, inatExtraCodes);
-        
+
       } else if(entryStr.contains("0E 02 0E 02 0E 02 0E 02 01 02")) {
 
         // handle chars w/ symbols
@@ -401,7 +402,7 @@ public class IndexCodesTest extends TestCase {
         Matcher m = unprint2Pat.matcher(entryStr);
         m.find();
         handleUnprintable2Entry(m.group(1), c, unprint2Codes);
-        
+
       } else if(entryStr.contains("FF 02 80 FF 80")) {
 
         type = "CRAZY_INAT";
@@ -415,8 +416,8 @@ public class IndexCodesTest extends TestCase {
 
         // throw new RuntimeException("unhandled " + entryStr);
         System.out.println("unhandled " + entryStr);
-      }      
-        
+      }
+
       System.out.println("Type: " + type);
     }
 
@@ -456,7 +457,7 @@ public class IndexCodesTest extends TestCase {
                            toByteString(extra));
         continue;
       }
-        
+
       chars = unprintCodes.get(cc);
       if(chars != null) {
         System.out.println("U" + toByteString(chars));
@@ -495,10 +496,10 @@ public class IndexCodesTest extends TestCase {
       throw new RuntimeException("Unhandled char " + toUnicodeStr(c));
     }
     System.out.println("\n***END CODES");
-    
+
     db.close();
   }
+
   public void x_testReverseIsoMdb() throws Exception
   {
     Database db = open(Database.FileFormat.V2000, new File("/data2/jackcess_test/testAllIndexCodes3.mdb"));
@@ -522,8 +523,8 @@ public class IndexCodesTest extends TestCase {
     Map<Character,String[]> inat2Codes = new TreeMap<Character,String[]>();
     Map<Character,String[]> inat2ExtraCodes = new TreeMap<Character,String[]>();
     Map<Character,String[]> inat2CrazyCodes = new TreeMap<Character,String[]>();
-    
-    
+
+
     Cursor cursor = CursorBuilder.createCursor(index);
     while(cursor.moveToNextRow()) {
 //       System.out.println("=======");
@@ -555,14 +556,14 @@ public class IndexCodesTest extends TestCase {
         handleInlineEntry(m.group(1), c, inlineCodes);
 
       } else if(entryStr.contains("01 01 01 80")) {
-        
+
         // handle most unprintable codes
         type = "UNPRINTABLE";
         Matcher m = unprintPat.matcher(entryStr);
         m.find();
         handleUnprintableEntry(m.group(2), c, unprintCodes);
 
-      } else if(entryStr.contains("01 02 02") && 
+      } else if(entryStr.contains("01 02 02") &&
                 !entryStr.contains("FF 02 80 FF 80")) {
 
         // handle chars w/ symbols
@@ -571,7 +572,7 @@ public class IndexCodesTest extends TestCase {
         m.find();
         handleInternationalEntry(m.group(1), m.group(2), c,
                                  inatInlineCodes, inatExtraCodes);
-        
+
       } else if(entryStr.contains("4A 4A 4A 4A 01 02")) {
 
         // handle chars w/ symbols
@@ -579,7 +580,7 @@ public class IndexCodesTest extends TestCase {
         Matcher m = unprint2Pat.matcher(entryStr);
         m.find();
         handleUnprintable2Entry(m.group(1), c, unprint2Codes);
-        
+
       } else if(entryStr.contains("FF 02 80 FF 80")) {
 
         type = "CRAZY_INAT";
@@ -592,8 +593,8 @@ public class IndexCodesTest extends TestCase {
       } else {
 
         throw new RuntimeException("unhandled " + entryStr);
-      }      
-        
+      }
+
       System.out.println("Type: " + type);
     }
 
@@ -633,7 +634,7 @@ public class IndexCodesTest extends TestCase {
                            toByteString(extra));
         continue;
       }
-        
+
       chars = unprintCodes.get(cc);
       if(chars != null) {
         System.out.println("U" + toByteString(chars));
@@ -672,7 +673,7 @@ public class IndexCodesTest extends TestCase {
       throw new RuntimeException("Unhandled char " + toUnicodeStr(c));
     }
     System.out.println("\n***END CODES");
-    
+
     db.close();
   }
 
@@ -691,21 +692,21 @@ public class IndexCodesTest extends TestCase {
   {
     inlineCodes.put(c, entryCodes.trim().split(" "));
   }
-  
+
   private static void handleUnprintableEntry(
       String entryCodes, char c, Map<Character,String[]> unprintCodes)
     throws Exception
   {
     unprintCodes.put(c, entryCodes.trim().split(" "));
   }
-  
+
   private static void handleUnprintable2Entry(
       String entryCodes, char c, Map<Character,String[]> unprintCodes)
     throws Exception
   {
     unprintCodes.put(c, entryCodes.trim().split(" "));
   }
-  
+
   private static void handleInternationalEntry(
       String inlineCodes, String entryCodes, char c,
       Map<Character,String[]> inatInlineCodes,
@@ -732,20 +733,20 @@ public class IndexCodesTest extends TestCase {
     }
   }
 
-  private static String toUnicodeStr(Object obj) throws Exception {
+  public static String toUnicodeStr(Object obj) throws Exception {
     StringBuilder sb = new StringBuilder();
     for(char c : obj.toString().toCharArray()) {
       sb.append(toUnicodeStr(c)).append(" ");
     }
     return sb.toString();
   }
-  
+
   private static String toUnicodeStr(char c) throws Exception {
     String specialStr = SPECIAL_CHARS.get(c);
     if(specialStr != null) {
       return specialStr;
     }
-    
+
     String digits = Integer.toHexString(c).toUpperCase();
     while(digits.length() < 4) {
       digits = "0" + digits;
@@ -769,7 +770,7 @@ public class IndexCodesTest extends TestCase {
     }
     return builder.toString();
   }
-  
+
   public static String entryToString(Cursor.Position curPos)
     throws Exception
   {
@@ -783,5 +784,5 @@ public class IndexCodesTest extends TestCase {
     return ByteUtil.toHexString(ByteBuffer.wrap(entryBytes),
                                 0, entryBytes.length, false);
   }
-  
+
 }