Merge branch 'disable-repid-class-cache' into 'ibm-trunk'

Disable TypeDescriptorCache lookup for ClassDesc to Class resolution

Doing this requires creating a standard utility for converting from repid to class names / classes in yoko-util, which can then be used by code in both yoko-core and yoko-rmi-impl (thus eliminating duplication and inconsistency).

See merge request !50
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/Util.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/Util.java
index 2b3195a..49aa61f 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/Util.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/Util.java
@@ -337,253 +337,6 @@
         }
     }
 
-    //
-    // Convert a repository ID into a class name.
-    // See the IDL-to-Java mapping, section 1.13.8.
-    //
-    public static String idToClassName(String id, String suffix) {
-        String result = null;
-
-        if (id.startsWith("IDL:")) {
-            try {
-                StringBuffer buf = new StringBuffer();
-
-                int end = id.lastIndexOf(':');
-                String s;
-                if (end < 0)
-                    s = id.substring(4);
-                else
-                    s = id.substring(4, end);
-
-                //
-                // If the ID contains a prefix, then fix each of the 
-                // dotted components of the prefix
-                //
-                int slash = s.indexOf('/');
-                if (slash > 0) {
-                    String prefix = s.substring(0, slash);
-                    String rest = s.substring(slash + 1);
-                    java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(
-                            prefix, ".");
-                    while (tokenizer.hasMoreTokens()) {
-                        String tok = tokenizer.nextToken();
-                        buf.append(fixName(tok));
-                        buf.append('.');
-                    }
-                    s = rest;
-                }
-
-                //
-                // "Fix" the remainder of the ID
-                //
-                java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(
-                        s, "/");
-                while (tokenizer.hasMoreTokens()) {
-                    String tok = tokenizer.nextToken();
-                    buf.append(fixName(tok));
-                    if (tokenizer.hasMoreTokens())
-                        buf.append('.');
-                }
-
-                result = buf.toString() + suffix;
-            } catch (IndexOutOfBoundsException ex) // if id has bad format
-            {
-                result = null;
-            }
-        }
-        else if (id.startsWith ("RMI:")) {
-            int end = id.indexOf (':', 4);
-            result = end < 0
-                ? id.substring (4)
-                : id.substring (4, end);
-        }
-        if (result != null) {
-            result = removeUnicodeEscapes(result); 
-        }
-        return result;
-    }
-    
-    
-    /**
-     * Remove Unicode \Uxxxx escape sequences from a string 
-     * received from the client ORB.
-     * 
-     * @param in     The input string.
-     * 
-     * @return The string with any unicode escape sequences converted 
-     *         into the appropriate character values.
-     */
-    public static String removeUnicodeEscapes(String in) {
-        // if no escape sequences are in the string, this is easy
-        int escape = in.indexOf("\\U"); 
-        if (escape < 0) {
-            return in;
-        }
-        
-        // get a string buffer at least as long as the input string 
-        StringBuffer out = new StringBuffer(in.length()); 
-        int start = 0; 
-        
-        while (escape >= 0) {
-            // add the next real segment to the buffer
-            out.append(in.substring(start, escape));
-            // step over the escape sequence 
-            escape += 2; 
-            
-            int value = 0; 
-            for (int i=0; i<4; i++) {
-                char ch = in.charAt(escape++);
-                switch (ch) {
-                  case '0': 
-                  case '1': 
-                  case '2': 
-                  case '3': 
-                  case '4':
-                  case '5': 
-                  case '6': 
-                  case '7': 
-                  case '8': 
-                  case '9':
-                     value = (value << 4) + ch - '0';
-                     break;
-                  case 'a': 
-                  case 'b': 
-                  case 'c':
-                  case 'd': 
-                  case 'e': 
-                  case 'f':
-                     value = (value << 4) + 10 + ch - 'a';
-                     break;
-                  case 'A': 
-                  case 'B': 
-                  case 'C':
-                  case 'D': 
-                  case 'E': 
-                  case 'F':
-                     value = (value << 4) + 10 + ch - 'A';
-                     break;
-                  default:
-                      // not sure what to do here.  Just treat it as a 0 nibble      
-                      value = (value << 4);
-                }
-            }
-            // now append this as a char value 
-            out.append((char)value); 
-            // now step and find the next one 
-            start = escape; 
-            escape = in.indexOf("\\U", escape); 
-        }
-        // don't forget the trailing segment 
-        if (start < in.length()) {
-            out.append(in.substring(start));
-        }
-        return out.toString(); 
-    }
-
-    //
-    // Convert a repository ID into a class.
-    // See the IDL-to-Java mapping, section 1.13.8.
-    //
-    public static Class idToClass(String id, String suffix) {
-        logger.fine("Searching for class from " + id + " using suffix " + suffix); 
-        Class result = null;
-        String className = idToClassName(id, suffix);
-
-        logger.fine("Converted class name is " + className); 
-        
-        if (className != null) {
-            try {
-                // get the appropriate class for the loading.
-                ClassLoader loader = Thread.currentThread().getContextClassLoader();
-                result = javax.rmi.CORBA.Util.loadClass(className, null, loader); 
-            } catch (ClassNotFoundException ex) {
-                logger.fine("Converted class name not found"); 
-                // ignore
-            }
-        }
-
-        return result;
-    }
-
-    //
-    // Check the given name against Java keywords and reserved suffixes
-    //
-    public static String fixName(String name) {
-        Assert._OB_assert(name.indexOf('.') == -1); // Not for absolute names
-
-        int nameLen = name.length();
-        if (nameLen == 0)
-            return name;
-
-        final String[] kwds = {
-        //
-                // Note: This list must be sorted alphabetically
-                //
-                "abstract", "assert", "boolean", "break", "byte", "case",
-                "catch", "char", "class", "clone", "const", "continue",
-                "default", "do", "double", "else", "equals", "extends",
-                "false", "final", "finalize", "finally", "float", "for",
-                "getClass", "goto", "hashCode", "if", "implements", "import",
-                "instanceof", "int", "interface", "long", "native", "new",
-                "notify", "notifyAll", "null", "package", "private",
-                "protected", "public", "return", "short", "static", "super",
-                "switch", "synchronized", "this", "throw", "throws",
-                "toString", "transient", "true", "try", "void", "volatile",
-                "wait", "while" };
-
-        int l = 0;
-        int r = kwds.length;
-
-        while (l < r) {
-            int m = (l + r) / 2;
-            int res = kwds[m].compareTo(name);
-            if (res == 0)
-                return "_" + name;
-            else if (res > 0)
-                r = m;
-            else
-                l = m + 1;
-        }
-
-        //
-        // Prepend an underscore for each of the reserved suffixes below
-        //
-        final String[] reserved = { "Helper", "Holder", "Operations", "POA",
-                "POATie", "Package", "ValueFactory" };
-
-        String result = name;
-        String curr = name;
-
-        boolean match;
-        do {
-            int currLen = curr.length();
-
-            match = false;
-            for (int i = 0; i < reserved.length; i++) {
-                if (curr.endsWith(reserved[i])) {
-                    //
-                    // Prepend an underscore to result
-                    //
-                    result = "_" + result;
-
-                    //
-                    // Remove suffix from curr
-                    //
-                    int resLen = reserved[i].length();
-                    if (currLen > resLen)
-                        curr = curr.substring(0, currLen - resLen);
-                    else
-                        curr = "";
-
-                    match = true;
-                    break;
-                }
-            }
-        } while (match);
-
-        return result;
-    }
-
     public static void insertException(org.omg.CORBA.Any any,
             java.lang.Exception ex) {
         //
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueFactoryManager.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueFactoryManager.java
index c1b9840..6f6ed0d 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueFactoryManager.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueFactoryManager.java
@@ -20,6 +20,8 @@
 import java.util.logging.Logger;
 import java.util.logging.Level;
 
+import org.apache.yoko.util.cmsf.RepIds;
+
 public final class ValueFactoryManager {
     static final Logger logger = Logger.getLogger(ValueFactoryManager.class.getName());
     //
@@ -176,7 +178,7 @@
         //
         // Try to convert the repository ID into a class name.
         //
-        Class c = Util.idToClass(id, "DefaultFactory");
+        Class c = RepIds.query(id).suffix("DefaultFactory").toClass();
         if (c != null) {
             try {
                 logger.finer("Attempting to create value factory from class " + c.getName());
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueReader.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueReader.java
index bcaf2aa..8796418 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueReader.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueReader.java
@@ -29,6 +29,7 @@
 import org.apache.yoko.orb.CORBA.InputStream;
 import org.apache.yoko.orb.CORBA.OutputStream;
 import org.apache.yoko.orb.OCI.Buffer;
+import org.apache.yoko.util.cmsf.RepIds;
 import org.omg.CORBA.Any;
 import org.omg.CORBA.CompletionStatus;
 import org.omg.CORBA.CustomMarshal;
@@ -272,7 +273,7 @@
             if (WStringValueHelper.id().equals(id))
                 return new WStringValueHelper();
 
-            final Class helperClass = Util.idToClass(id, "Helper");
+            final Class helperClass = RepIds.query(id).suffix("Helper").toClass();
 
             if (helperClass != null) {
                 try {
@@ -942,7 +943,7 @@
             }
         }
 
-        final String className = Util.idToClassName(repid, "");
+        final String className = RepIds.query(repid).toClassName();
         String codebase  = h.codebase;
 
         if (codebase == null) {
@@ -1319,14 +1320,14 @@
             // that is compatible with the base type.  This will require resolving the classes.
             if (id == null) {
                 // see if we can resolve the type for the stored type code
-                final Class<?> baseType = Util.idToClass(tcId, "");
+                final Class<?> baseType = RepIds.query(tcId).toClass();
                 if (baseType != null) {
                     for (idPos = 0; idPos < h.ids.length; idPos++) {
                         if (logger.isLoggable(Level.FINER))
                             logger.finer(String.format(
                                     "Considering base types of id \"%s\" against \"%s\"",
                                     tcId, h.ids[idPos]));
-                        final Class idType = Util.idToClass(h.ids[idPos], "");
+                        final Class idType = RepIds.query(h.ids[idPos]).toClass();
                         if (idType != null) {
                             // if these classes are assignment compatible, go with that as the type.
                             if (logger.isLoggable(Level.FINER))
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueWriter.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueWriter.java
index 2b7d927..b4df52f 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueWriter.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/ValueWriter.java
@@ -22,6 +22,7 @@
 import javax.rmi.CORBA.ValueHandler;
 
 import org.apache.yoko.orb.CORBA.ORB;
+import org.apache.yoko.util.cmsf.RepIds;
 import org.apache.yoko.util.osgi.ProviderLocator;
 import org.omg.CORBA.WStringValueHelper;
 import org.omg.CORBA.portable.BoxedValueHelper;
@@ -218,7 +219,7 @@
                 org.omg.CORBA.TypeCode origType = org.apache.yoko.orb.CORBA.TypeCode
                         ._OB_getOrigType(type);
                 String id = origType.id();
-                helperClass = Util.idToClass(id, "Helper");
+                helperClass = RepIds.query(id).suffix("Helper").toClass();
             } catch (org.omg.CORBA.TypeCodePackage.BadKind ex) {
                 Assert._OB_assert(ex);
             }
diff --git a/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/ClassDescDescriptor.java b/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/ClassDescDescriptor.java
index c2ff742..eff9cb1 100644
--- a/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/ClassDescDescriptor.java
+++ b/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/ClassDescDescriptor.java
@@ -10,6 +10,7 @@
 import javax.rmi.CORBA.ClassDesc;
 import javax.rmi.CORBA.Util;
 
+import org.apache.yoko.util.cmsf.RepIds;
 import org.omg.CORBA.MARSHAL;
 
 public class ClassDescDescriptor extends ClassBaseDescriptor {
@@ -40,21 +41,10 @@
                     String repid = (String) repid_field.get(desc);
                     String codebase = (String) codebase_field.get(desc);
 
-                    TypeDescriptor typeDesc = repository.getDescriptor(repid);
-                    if (null != typeDesc) {
-                        Class<?> type = typeDesc.getJavaClass();
-                        if (null != type) return type;
-                    }
+                    Class<?> result = RepIds.query(repid).codebase(codebase).toClass();
+                    if (null != result) return result;
 
-                    int beg = repid.indexOf(':');
-                    int end = repid.indexOf(':', beg + 1);
-
-                    className = repid.substring(beg + 1, end);
-                    ClassLoader loader = Thread.currentThread().getContextClassLoader();
-
-                    return Util.loadClass(className, codebase, loader);
-                } catch (ClassNotFoundException ex) {
-                    throw (MARSHAL)new MARSHAL("cannot load class " + className).initCause(ex);
+                    throw new MARSHAL(String.format("Cannot load class \"%s\"", className));
                 } catch (IllegalAccessException ex) {
                     throw (MARSHAL)new MARSHAL("no such field: " + ex).initCause(ex);
                 }
diff --git a/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/TypeRepository.java b/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/TypeRepository.java
index 72d2d75..2663c2c 100755
--- a/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/TypeRepository.java
+++ b/yoko-rmi-impl/src/main/java/org/apache/yoko/rmi/impl/TypeRepository.java
@@ -363,136 +363,4 @@
 
         return clzdesc;
     }
-
-    public static String idToClass(String repid) {
-        // debug
-        logger.finer("idToClass " + repid);
-
-        if (repid.startsWith("IDL:")) {
-
-            ByteString id = new ByteString(repid);
-
-            try {
-                int end = id.lastIndexOf(':');
-                ByteString s = end < 0 ? id.substring(4) : id.substring(4, end);
-
-                ByteBuffer bb = new ByteBuffer();
-
-                //
-                // reverse order of dot-separated name components up
-                // till the first slash.
-                //
-                int firstSlash = s.indexOf('/');
-                if (firstSlash > 0) {
-                    ByteString prefix = s.substring(0, firstSlash);
-                    ByteString[] elems = prefix.split('.');
-
-                    for (int i = elems.length - 1; i >= 0; i--) {
-                        bb.append(fixName(elems[i]));
-                        bb.append('.');
-                    }
-
-                    s = s.substring(firstSlash + 1);
-                }
-
-                //
-                // Append slash-separated name components ...
-                //
-                ByteString[] elems = s.split('/');
-                for (int i = 0; i < elems.length; i++) {
-                    bb.append(fixName(elems[i]));
-                    if (i != elems.length - 1)
-                        bb.append('.');
-                }
-
-                String result = bb.toString();
-
-                logger.finer("idToClassName " + repid + " => " + result);
-
-                return result;
-            } catch (IndexOutOfBoundsException ex) {
-                logger.log(Level.FINE, "idToClass " + ex.getMessage(), ex);
-                return null;
-            }
-
-        } else if (repid.startsWith("RMI:")) {
-            int end = repid.indexOf(':', 4);
-            return end < 0 ? repid.substring(4) : repid.substring(4, end);
-        }
-
-        return null;
-    }
-
-    static String fixName(String name) {
-        return (new ByteString(name)).toString();
-    }
-
-    static ByteString fixName(ByteString name) {
-        if (keyWords.contains(name)) {
-            ByteBuffer buf = new ByteBuffer();
-            buf.append('_');
-            buf.append(name);
-            return buf.toByteString();
-        }
-
-        ByteString result = name;
-        ByteString current = name;
-
-        boolean match = true;
-        while (match) {
-
-            int len = current.length();
-            match = false;
-
-            for (ByteString reservedPostfix : reservedPostfixes) {
-                if (current.endsWith(reservedPostfix)) {
-                    ByteBuffer buf = new ByteBuffer();
-                    buf.append('_');
-                    buf.append(result);
-                    result = buf.toByteString();
-
-                    int resultLen = reservedPostfix.length();
-                    if (len > resultLen)
-                        current = current.substring(0, len - resultLen);
-                    else
-                        current = new ByteString("");
-
-                    match = true;
-                    break;
-                }
-            }
-
-        }
-
-        return name;
-    }
-
-    private static final Set<ByteString> keyWords;
-    private static final Set<ByteString> reservedPostfixes;
-
-    static {
-        keyWords = createByteStringSet(
-                "abstract", "boolean", "break", "byte", "case",
-                "catch", "char", "class", "clone", "const", "continue",
-                "default", "do", "double", "else", "equals", "extends",
-                "false", "final", "finalize", "finally", "float", "for",
-                "getClass", "goto", "hashCode", "if", "implements", "import",
-                "instanceof", "int", "interface", "long", "native", "new",
-                "notify", "notifyAll", "null", "package", "private",
-                "protected", "public", "return", "short", "static", "super",
-                "switch", "synchronized", "this", "throw", "throws",
-                "toString", "transient", "true", "try", "void", "volatile",
-                "wait", "while");
-        reservedPostfixes = createByteStringSet(
-                "Helper", "Holder", "Operations", "POA", "POATie", "Package", "ValueFactory");
-    }
-
-    private static Set<ByteString> createByteStringSet(String...words) {
-        final Set<ByteString> set = new HashSet<>(words.length);
-        for (String word : words) {
-            set.add(new ByteString(word));
-        }
-        return Collections.unmodifiableSet(set);
-    }
-
 }
diff --git a/yoko-util/src/main/java/org/apache/yoko/util/cmsf/RepIds.java b/yoko-util/src/main/java/org/apache/yoko/util/cmsf/RepIds.java
new file mode 100644
index 0000000..3fd7d4a
--- /dev/null
+++ b/yoko-util/src/main/java/org/apache/yoko/util/cmsf/RepIds.java
@@ -0,0 +1,288 @@
+package org.apache.yoko.util.cmsf;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import javax.rmi.CORBA.Util;
+
+public enum RepIds {
+    ;
+
+    public interface Query {
+        public Query suffix(String suffix);
+        public Query codebase(String codebase);
+        public Class<?> toClass();
+        public String toClassName();
+    }
+
+    private static final class QueryImpl implements Query {
+        public final String repid;
+        public final String suffix;
+        public final String codebase;
+
+        private QueryImpl(String repid) {
+            this(Objects.requireNonNull(repid), "", null);
+        }
+
+        private QueryImpl(String repid, String suffix, String codebase) {
+            this.repid = repid;
+            this.suffix = suffix;
+            this.codebase = codebase;
+        }
+
+        @Override
+        public QueryImpl suffix(String suffix) {
+            return new QueryImpl(repid, Objects.requireNonNull(suffix), codebase);
+        }
+
+        @Override
+        public QueryImpl codebase(String codebase) {
+            return new QueryImpl(repid, suffix, codebase);
+        }
+
+        @Override
+        public Class<?> toClass() {
+            return RepIds.toClass(this);
+        }
+
+        @Override
+        public String toClassName() {
+            return RepIds.toClassName(this);
+        }
+    }
+
+    private static final Logger LOGGER = Logger.getLogger(RepIds.class.getName());
+
+    public static Query query(String repid) {
+        return new QueryImpl(repid);
+    }
+
+    private static Class<?> toClass(final QueryImpl query) {
+        final String repid = query.repid;
+        final String suffix = query.suffix;
+        final String codebase = query.codebase;
+        if (LOGGER.isLoggable(Level.FINE))
+            LOGGER.fine(String.format("Searching for class from repid \"%s\" using suffix \"%s\"", repid, suffix));
+        Class<?> result = null;
+
+        //Special case IDL:omg.org/CORBA/WStringValue:1.0
+        if ("IDL:omg.org/CORBA/WStringValue:1.0".equals(repid) && "".equals(suffix)) return String.class;
+
+        final String className = toClassName(query);
+
+        if (LOGGER.isLoggable(Level.FINE))
+            LOGGER.fine(String.format("Class name from repid \"%s\" using suffix \"%s\" is \"%s\"", repid, suffix, className));
+
+        if (className != null) {
+            try {
+                // get the appropriate class for the loading.
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                result = Util.loadClass(className, codebase, loader);
+            } catch (ClassNotFoundException ex) {
+                if (LOGGER.isLoggable(Level.FINE))
+                    LOGGER.fine(String.format("Class \"%s\" not found", className));
+                // ignore
+            }
+        }
+
+        return result;
+    }
+
+    private static final Pattern dotPattern = Pattern.compile(Pattern.quote("."));
+    private static final Pattern slashPattern = Pattern.compile(Pattern.quote("/"));
+
+    private static String toClassName(QueryImpl query) {
+        final String repid = query.repid;
+        final String suffix = query.suffix;
+
+        //Special case IDL:omg.org/CORBA/WStringValue:1.0
+        if ("IDL:omg.org/CORBA/WStringValue:1.0".equals(repid) && "".equals(suffix)) return String.class.getName();
+
+        String result = null;
+        if (repid.startsWith("IDL:")) {
+            result = idlToClassName(repid);
+        } else if (repid.startsWith("RMI:")) {
+            result = rmiToClassName(repid);
+        }
+        if (result != null) {
+            result += suffix;
+            result = removeUnicodeEscapes(result);
+        }
+        return result;
+    }
+
+    private static String rmiToClassName(final String repid) {
+        String result;
+        final int end = repid.indexOf (':', 4);
+        result = end < 0 ? repid.substring (4) : repid.substring (4, end);
+        return result;
+    }
+
+    private static String idlToClassName(final String repid) {
+        try {
+            final StringBuilder sb = new StringBuilder(repid.length());
+
+            final int end = repid.lastIndexOf(':');
+            String s = end < 0 ? repid.substring(4) : repid.substring(4, end);
+
+            //
+            // reverse order of dot-separated name components up
+            // till the first slash.
+            //
+            final int firstSlash = s.indexOf('/');
+            if (firstSlash > 0) {
+                String prefix = s.substring(0, firstSlash);
+                String[] elems = dotPattern.split(prefix);
+                Collections.reverse(Arrays.asList(elems)); //reverses the order in the underlying array - i.e. 'elems'
+                for (String elem: elems) {
+                    sb.append(fixName(elem)).append('.');
+                }
+
+                s = s.substring(firstSlash + 1);
+            }
+
+            //
+            // Append slash-separated name components ...
+            //
+            for (String elem: slashPattern.split(s)) {
+                sb.append(fixName(elem)).append('.');
+            }
+            sb.deleteCharAt(sb.length() - 1); // eliminate final '.'
+
+            return sb.toString();
+        } catch (IndexOutOfBoundsException ex) {
+            // id has bad format
+            return null;
+        }
+    }
+
+    private static String removeUnicodeEscapes(String in) {
+        // if no escape sequences are in the string, this is easy
+        int escape = in.indexOf("\\U");
+        if (escape < 0) {
+            return in;
+        }
+
+        StringBuilder out = new StringBuilder(in.length());
+        int start = 0;
+
+        while (escape >= 0) {
+            out.append(in.substring(start, escape));
+            // step over the escape sequence
+            escape += 2;
+
+            int value = 0;
+            for (int i=0; i<4; i++) {
+                char ch = in.charAt(escape++);
+                switch (ch) {
+                  case '0':
+                  case '1':
+                  case '2':
+                  case '3':
+                  case '4':
+                  case '5':
+                  case '6':
+                  case '7':
+                  case '8':
+                  case '9':
+                     value = (value << 4) + ch - '0';
+                     break;
+                  case 'a':
+                  case 'b':
+                  case 'c':
+                  case 'd':
+                  case 'e':
+                  case 'f':
+                     value = (value << 4) + 10 + ch - 'a';
+                     break;
+                  case 'A':
+                  case 'B':
+                  case 'C':
+                  case 'D':
+                  case 'E':
+                  case 'F':
+                     value = (value << 4) + 10 + ch - 'A';
+                     break;
+                  default:
+                      // not sure what to do here.  Just treat it as a 0 nibble
+                      value = (value << 4);
+                }
+            }
+            // now append this as a char value
+            out.append((char)value);
+            // now step and find the next one
+            start = escape;
+            escape = in.indexOf("\\U", escape);
+        }
+        // don't forget the trailing segment
+        if (start < in.length()) {
+            out.append(in.substring(start));
+        }
+        return out.toString();
+    }
+
+    private static final Set<String> keywords = createStringSet(
+            "abstract", "assert", "boolean", "break", "byte", "case",
+            "catch", "char", "class", "clone", "const", "continue",
+            "default", "do", "double", "else", "equals", "extends",
+            "false", "final", "finalize", "finally", "float", "for",
+            "getClass", "goto", "hashCode", "if", "implements", "import",
+            "instanceof", "int", "interface", "long", "native", "new",
+            "notify", "notifyAll", "null", "package", "private",
+            "protected", "public", "return", "short", "static", "super",
+            "switch", "synchronized", "this", "throw", "throws",
+            "toString", "transient", "true", "try", "void", "volatile",
+            "wait", "while");
+
+    private static Set<String> createStringSet(String...strings) {
+        return Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(strings)));
+    }
+
+    private static final List<String> reservedSuffixes = createStringList(
+            "Helper", "Holder", "Operations", "POA",
+            "POATie", "Package", "ValueFactory");
+
+    private static List<String> createStringList(String...strings) {
+        return Collections.unmodifiableList(Arrays.asList(strings));
+    }
+
+    private static String fixName(String name) {
+        assert(name.indexOf('.') == -1); // Not for absolute names
+
+        int nameLen = name.length();
+        if (nameLen == 0)
+            return name;
+
+        if (keywords.contains(name)) return "_" + name;
+
+        //
+        // Prepend an underscore for each of the reserved suffixes
+        //
+        String result = name;
+        String curr = name;
+
+        OUTER_LOOP: while (true) {
+            for (String reservedSuffix: reservedSuffixes) {
+                if (curr.endsWith(reservedSuffix)) {
+                    result = "_" + result;
+
+                    int currLength = curr.length();
+                    int resLength = reservedSuffix.length();
+                    if (currLength == resLength)
+                        return result;
+                    curr = curr.substring(0, currLength - resLength);
+                    continue OUTER_LOOP;
+                }
+            }
+            return result;
+        }
+    }
+}