package water; import javassist.*; import sun.misc.Unsafe; import water.api.API; import water.nbhm.UtilUnsafe; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** Class to auto-gen serializer delegate classes. */ public class Weaver { /** Get all woven fields in this class, including subclasses, up to the * normal {@link Iced} serialization classes, skipping static and transient * fields, and the required _ice_id field. * @return Array of {@link Field} holding the list of woven fields. */ public static Field[] getWovenFields( Class clz ) { ArrayList<Field> flds = new ArrayList<>(); while( Iced.class.isAssignableFrom(clz) || Freezable.class.isAssignableFrom(clz) || H2O.H2OCountedCompleter.class.isAssignableFrom(clz) ) { for( Field f : clz.getDeclaredFields() ) { int mods = f.getModifiers(); if( Modifier.isTransient(mods) || Modifier.isStatic(mods) ) continue; if( "_ice_id".equals(f.getName()) ) continue; // Strip the required typeid field flds.add(f); } clz = clz.getSuperclass(); } return flds.toArray(new Field[flds.size()]); } private static final ClassPool _pool; private static final CtClass _dtask, _enum, _serialize;//, _iced, _h2cc, _freezable; private static final Unsafe _unsafe = UtilUnsafe.getUnsafe(); /** Map of class names to their respective loader. * Contains references of the node-local ClassLoaders * so that {@link TypeMap#theFreezable(int)} can make the correct * {@link Class#forName(String, boolean, ClassLoader)} call. */ private static final transient Map<String/*className*/,ClassLoader> CLASSLOADERS; /** Map of class names to their respective ClassPath instance in * the {@link Weaver#_pool}. Class reloads will prune their classpaths. */ private static final transient Map<String/*className*/, ClassPath> CLASSPATHS; static Class classForName(String className) throws ClassNotFoundException { ClassLoader c = CLASSLOADERS.get(className); // was this class dynamically loaded? if( c==null ) return Class.forName(className); // class not dynamically loaded, use Weaver's ClassLoader return Class.forName(className,true,c); } static { try { _pool = ClassPool.getDefault(); _pool.insertClassPath(new ClassClassPath(Weaver.class)); _dtask= _pool.get("water.DTask"); // these also need copyOver _enum = _pool.get("java.lang.Enum"); // Special serialization _serialize = _pool.get("java.io.Serializable"); // Base of serialization // _iced = _pool.get("water.Iced"); // Base of serialization // _h2cc = _pool.get("water.H2O$H2OCountedCompleter"); // Base of serialization // _freezable = _pool.get("water.Freezable"); // Base of serialization CLASSLOADERS = new HashMap<>(); CLASSPATHS = new HashMap<>(); } catch( NotFoundException nfe ) { throw new RuntimeException(nfe); } } public static <T extends Freezable> Icer<T> genDelegate( int id, Class<T> clazz ) { Exception e2; try { T ice = Modifier.isAbstract(clazz.getModifiers()) ? null : (T)_unsafe.allocateInstance(clazz); Class icer_clz = javassistLoadClass(id,clazz); return (Icer<T>)icer_clz.getDeclaredConstructors()[0].newInstance(ice); } catch( InvocationTargetException e ) { e2 = e; } catch( InstantiationException e ) { e2 = e; } catch( IllegalAccessException e ) { e2 = e; } catch( NotFoundException e ) { e2 = e; } catch( CannotCompileException e ) { e2 = e; } catch( NoSuchFieldException e ) { e2 = e; } catch( ClassNotFoundException e ) { e2 = e; } throw new RuntimeException(e2); } // The name conversion from a Iced subclass to an Icer subclass. private static String implClazzName( String name ) { return name + "$Icer"; } // private static boolean hasWovenJSONFields( CtClass cc ) throws NotFoundException { // if( !cc.subtypeOf(_freezable) && // !cc.subtypeOf(_serialize) ) return false; // Cannot serialize in any case // // Iced & H2O$CountedCompleters are interesting oddballs: they have a short // // typeid that is desired field for Freezable-style serialization but not for // // JSON-style. The field is fairly expensively filled in the first time any // // given object is serialized and used in all subsequent fast serializations. // // However, the value is not valid outside *this* execution of the cluster, // // and should not be persisted via e.g. saving the JSON and restoring from // // it later. // if( cc.equals(_iced) || // cc.equals(_h2cc) ) return false; // if( hasWovenJSONFields(cc.getSuperclass()) ) return true; // for( CtField ctf : cc.getDeclaredFields() ) { // int mods = ctf.getModifiers(); // if( !javassist.Modifier.isTransient(mods) && !javassist.Modifier.isStatic(mods) ) return true; // } // return false; // } /** * Load/Reload classes defined at runtime. * * Loading classes at runtime is a matter of simply injecting the * new code into the {@link ClassPool}, and then {@link Weaver#javassistLoadClass(int, Class)} * resolves the generation of (de)serializers. In order to reload classes, though, * each dynamically loaded class must have its very own {@link ClassLoader}, and all * previous {@link Icer}s must be removed. In order to maintain cluster-wide coherency * about which classes are loaded, the {@link TypeMap} is likewise updated whenever a class * is reloaded. * * In order to successfully load classes at runtime (for example a subclass of {@link MRTask}), * each node takes the bytecode and class name and puts a new {@link ByteArrayClassPath} onto * {@link Weaver#_pool}'s classpath. Since there is no mechanism for retrieving these * {@link ClassPath} instances later, they are stored in {@link Weaver#CLASSPATHS} so that * reload events can remove the old paths. Similarly, {@link Weaver#CLASSLOADERS} holds on * to the loaders of dynamically created classes so that classes can be reloaded and old * {@link ClassLoader} instances pruned. * * Finally, in order to {@link Weaver#genDelegate(int, Class)} during a class reload, then the * previous {@link Icer} must be {@link CtClass#detach}ed. In addition, the {@link TypeMap#goForGold(int)} * must turn a null for the {@link Icer}. * * @param name class name * @param b bytecode */ public static void loadDynamic(final String name, final byte[] b) { Futures fs = new Futures(); fs.add(RPC.call(H2O.CLOUD.leader(), new LoadClazz(name,b))).blockForPending(); // leader node loads first new MRTask() { @Override public void setupLocal() { if( H2O.SELF != H2O.CLOUD.leader() ) // already loaded on the leader, load all others new LoadClazz(name,b).compute2(); } }.doAllNodes(); } private static class LoadClazz extends DTask<LoadClazz> { private final String _name; private final byte[] _bytes; LoadClazz(String name, byte[] bytes) { _name=name; _bytes=bytes; } @Override public void compute2() { try { loadClass(_name, _bytes); } catch (NotFoundException e) { } catch (CannotCompileException e) { throw new RuntimeException(e); } tryComplete(); } static void loadClass(String name, byte[] bytes) throws NotFoundException, CannotCompileException { ClassPath path; ClassLoader loader; CtClass ctc = _pool.getOrNull(name); if( ctc!=null ) { ctc.defrost(); ctc.detach(); CtClass icer = _pool.getOrNull(implClazzName(name)); if( icer!=null ) icer.detach(); // drop the Icer _pool.removeClassPath(CLASSPATHS.get(name)); TypeMap.drop(name); // drop the icer from the typemap } CLASSPATHS.put(name, path=new ByteArrayClassPath(name, bytes)); _pool.insertClassPath(path); CLASSLOADERS.put(name, loader = new URLClassLoader(new URL[0], _pool.getClassLoader())); _pool.get(name).toClass(loader); } } // See if javaassist can find this class, already generated private static Class javassistLoadClass(int id, Class iced_clazz) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InvocationTargetException { // End the super class lookup chain at "water.Iced", // returning the known delegate class "water.Icer". String iced_name = iced_clazz.getName(); // if(!Freezable.class.isAssignableFrom(iced_clazz.getSuperclass())) return water.Icer.class; assert !iced_name.startsWith("scala.runtime.AbstractFunction"); // Now look for a pre-cooked Icer. No locking, 'cause we're just looking String icer_name = implClazzName(iced_name); CtClass icer_cc = _pool.getOrNull(icer_name); // Full Name Lookup of Icer if( icer_cc != null ) { synchronized( iced_clazz ) { if( !icer_cc.isFrozen() ) icer_cc.toClass(iced_clazz.getClassLoader()); // Load class (but does not link & init) return Class.forName(icer_name,true,iced_clazz.getClassLoader()); // Found a pre-cooked Icer implementation } } // Serialize parent. No locking; occasionally we'll "onIce" from the // remote leader more than once. Class super_clazz = iced_clazz.getSuperclass(); Class super_icer_clazz; int super_id; if(Freezable.class.isAssignableFrom(super_clazz)) { super_id = TypeMap.onIce(super_clazz.getName()); super_icer_clazz = javassistLoadClass(super_id, super_clazz); } else { super_icer_clazz = Icer.class; super_id = -1; } CtClass super_icer_cc = _pool.get(super_icer_clazz.getName()); CtClass iced_cc = _pool.get(iced_name); // Lookup the based Iced class boolean super_has_jfields = true;//hasWovenJSONFields(iced_cc.getSuperclass()); // Lock on the Iced class (prevent multiple class-gens of the SAME Iced // class, but also to allow parallel class-gens of unrelated Iced). //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized( iced_clazz ) { icer_cc = _pool.getOrNull(icer_name); // Retry under lock if( icer_cc != null ) return Class.forName(icer_name); // Found a pre-cooked Icer implementation icer_cc = genIcerClass(id,iced_cc,iced_clazz,icer_name,super_id,super_icer_cc,super_has_jfields); icer_cc.toClass(iced_clazz.getClassLoader()); // Load class (but does not link & init) return Class.forName(icer_name,true, iced_clazz.getClassLoader()); // Initialize class now, before subclasses } } // Generate the Icer class private static CtClass genIcerClass(int id, CtClass iced_cc, Class iced_clazz, String icer_name, int super_id, CtClass super_icer, boolean super_has_jfields ) throws CannotCompileException, NotFoundException, NoSuchFieldException { // Generate the Icer class String iced_name = iced_cc.getName(); CtClass icer_cc = _pool.makeClass(icer_name); icer_cc.setSuperclass(super_icer); icer_cc.setModifiers(javassist.Modifier.PUBLIC); // Overall debug printing? if (false) { System.out.println("Iced class " + icer_cc.getName() + " is number: " + id); } // Detailed debug printing? boolean debug_print=false; CtField ctfs[] = iced_cc.getDeclaredFields(); for( CtField ctf : ctfs ) debug_print |= ctf.getName().equals("DEBUG_WEAVER"); if( debug_print ) System.out.println("class "+icer_cc.getName()+" extends "+super_icer.getName()+" {"); // Make a copy of the enum array, for later deser for( CtField ctf : ctfs ) { CtClass ctft = ctf.getType(); String name = ctf.getName(); int mods = ctf.getModifiers(); if( javassist.Modifier.isTransient(mods) || javassist.Modifier.isStatic(mods) ) continue; // Only serialize not-transient instance fields (not static) // Check for enum CtClass base = ctft; while( base.isArray() ) base = base.getComponentType(); if( base.subtypeOf(_enum) ) { // either an enum or an array of enum // Insert in the Icer, a copy of the enum values() array from Iced // e.g. private final myEnum[] _fld = myEnum.values(); String src = " private final "+base.getName().replace('$', '.')+"[] "+name+" = "+base.getName().replace('$', '.')+".values();\n"; if( debug_print ) System.out.println(src); CtField ctfr = CtField.make(src,icer_cc); icer_cc.addField(ctfr); } } // The write call String debug = make_body(icer_cc, iced_cc, iced_clazz, "write", null, null, " protected final water.AutoBuffer write"+id+"(water.AutoBuffer ab, "+iced_name+" ice) {\n", super_id == -1?"":" write"+super_id+"(ab,ice);\n", " ab.put%z(ice.%s);\n" , " ab.put%z((%C)_unsafe.get%u(ice,%dL)); // %s\n", " ab.put%z(ice.%s);\n" , " ab.put%z((%C)_unsafe.get%u(ice,%dL)); // %s\n", " ab.put%z(ice.%s);\n" , " ab.put%z((%C)_unsafe.get%u(ice,%dL)); // %s\n", " return ab;\n" + " }"); if( debug_print ) System.out.println(debug); String debugJ= make_body(icer_cc, iced_cc, iced_clazz, "writeJSON", "(supers?ab.put1(','):ab).", " ab.put1(',').", " protected final water.AutoBuffer writeJSON"+id+"(water.AutoBuffer ab, "+iced_name+" ice) {\n", super_id == -1?"":" writeJSON"+super_id+"(ab,ice);\n", "putJSON%z(\"%s\",ice.%s);\n" , "putJSON%z(\"%s\",(%C)_unsafe.get%u(ice,%dL)); // %s\n", "putJSON%z(\"%s\",ice.%s);\n" , "putJSON%z(\"%s\",(%C)_unsafe.get%u(ice,%dL)); // %s\n", "putJSON%z(\"%s\",ice.%s);\n" , "putJSON%z(\"%s\",(%C)_unsafe.get%u(ice,%dL)); // %s\n" , " return ab;\n" + " }"); if( debug_print ) System.out.println(debugJ); // The generic override method. Called virtually at the start of a // serialization call. Only calls thru to the named static method. String wbody = " protected water.AutoBuffer write(water.AutoBuffer ab, water.Freezable ice) {\n"+ " return write"+id+"(ab,("+iced_name+")ice);\n"+ " }"; if( debug_print ) System.out.println(wbody); addMethod(wbody,icer_cc); String wbodyJ= " protected water.AutoBuffer writeJSON(water.AutoBuffer ab, water.Freezable ice) {\n"+ " return writeJSON"+id+"(ab.put1('{'),("+iced_name+")ice).put1('}');\n"+ " }"; if( debug_print ) System.out.println(wbodyJ); addMethod(wbodyJ,icer_cc); // The read call String rbody_impl = make_body(icer_cc, iced_cc, iced_clazz, "read", null, null, " protected final "+iced_name+" read"+id+"(water.AutoBuffer ab, "+iced_name+" ice) {\n", super_id == -1?"":" read"+super_id+"(ab,ice);\n", " ice.%s = ab.get%z();\n", " _unsafe.put%u(ice,%dL,ab.get%z()); //%s\n", " ice.%s = (%C)ab.get%z(%s);\n", " _unsafe.put%u(ice,%dL,ab.get%z(%s));\n", " ice.%s = (%C)ab.get%z(%c.class);\n"," _unsafe.put%u(ice,%dL,(%C)ab.get%z(%c.class)); //%s\n", " return ice;\n" + " }"); if( debug_print ) System.out.println(rbody_impl); String rbodyJ_impl = make_body(icer_cc, iced_cc, iced_clazz, "readJSON", null, null, " protected final "+iced_name+" readJSON"+id+"(water.AutoBuffer ab, "+iced_name+" ice) {\n", super_id == -1?"":" readJSON"+super_id+"(ab,ice);\n", " ice.%s = ab.get%z();\n", " _unsafe.put%u(ice,%dL,ab.get%z()); //%s\n", " ice.%s = (%C)ab.get%z(%s);\n", " _unsafe.put%u(ice,%dL,ab.get%z(%s));\n", " ice.%s = (%C)ab.get%z(%c.class);\n"," _unsafe.put%u(ice,%dL,(%C)ab.get%z(%c.class)); //%s\n", " return ice;\n" + " }"); if( debug_print ) System.out.println(rbodyJ_impl); // The generic override method. Called virtually at the start of a // serialization call. Only calls thru to the named static method. String rbody = " protected water.Freezable read(water.AutoBuffer ab, water.Freezable ice) {\n"+ " return read"+id+"(ab,("+iced_name+")ice);\n"+ " }"; if( debug_print ) System.out.println(rbody); addMethod(rbody,icer_cc); String rbodyJ= " protected water.Freezable readJSON(water.AutoBuffer ab, water.Freezable ice) {\n"+ " return readJSON"+id+"(ab,("+iced_name+")ice);\n"+ " }"; if( debug_print ) System.out.println(rbodyJ); addMethod(rbodyJ,icer_cc); String cnbody = " protected java.lang.String className() { return \""+iced_name+"\"; }"; if( debug_print ) System.out.println(cnbody); addMethod(cnbody,icer_cc); String ftbody = " protected int frozenType() { return "+id+"; }"; if( debug_print ) System.out.println(ftbody); addMethod(ftbody,icer_cc); String cmp2 = " protected void compute1( water.H2O.H2OCountedCompleter dt ) { dt.compute1(); }"; if( debug_print ) System.out.println(cmp2); addMethod(cmp2,icer_cc); // DTasks need to be able to copy all their (non transient) fields from one // DTask instance over another, to match the MRTask API. if( iced_cc.subclassOf(_dtask) ) { String cpbody_impl = make_body(icer_cc, iced_cc, iced_clazz, "copyOver", null, null, " protected void copyOver(water.Freezable fdst, water.Freezable fsrc) {\n", " super.copyOver(fdst,fsrc);\n"+ " "+iced_name+" dst = ("+iced_name+")fdst;\n"+ " "+iced_name+" src = ("+iced_name+")fsrc;\n", " dst.%s = src.%s;\n"," _unsafe.put%u(dst,%dL,_unsafe.get%u(src,%dL)); //%s\n", " dst.%s = src.%s;\n"," _unsafe.put%u(dst,%dL,_unsafe.get%u(src,%dL)); //%s\n", " dst.%s = src.%s;\n"," _unsafe.put%u(dst,%dL,_unsafe.get%u(src,%dL)); //%s\n", " }"); if( debug_print ) System.out.println(cpbody_impl); } String cstrbody = " public "+icer_cc.getSimpleName()+"( "+iced_name+" iced) { super(iced); }"; if( debug_print ) System.out.println(cstrbody); try { icer_cc.addConstructor(CtNewConstructor.make(cstrbody,icer_cc)); } catch( CannotCompileException ce ) { System.err.println("--- Compilation failure while compiling "+icer_cc.getName()+"\n"+cstrbody+"\n------\n"+ce); throw ce; } if( debug_print ) System.out.println("}"); return icer_cc; } // Generate a method body string private static String make_body(CtClass icer_cc, CtClass iced_cc, Class iced_clazz, String impl, String field_sep1, String field_sep2, String header, String supers, String prims, String prims_unsafe, String enums, String enums_unsafe, String iced, String iced_unsafe, String trailer ) throws CannotCompileException, NotFoundException, NoSuchFieldException { StringBuilder sb = new StringBuilder(); sb.append(header); if(impl.equals("writeJSON")) { if (supers.isEmpty()) { sb.append(" boolean supers = false;"); } else { sb.append(" int position = ab.position();\n"); sb.append(supers); sb.append(" boolean supers = ab.position() != position;\n"); } } else sb.append(supers); // Customer serializer? String mimpl = impl+"_impl"; for( CtMethod mth : iced_cc.getDeclaredMethods() ) if( mth.getName().equals(mimpl) ) { // Found custom serializer? int mods = mth.getModifiers(); String ice_handle; String ice_args; if(javassist.Modifier.isStatic(mods)) { ice_handle = iced_clazz.getName() + "."; ice_args = "(ice,ab)"; } else if(javassist.Modifier.isFinal(mods)) { ice_handle = "ice."; ice_args = "(ab)"; }else if(javassist.Modifier.isAbstract(mods)){ ice_handle = null; ice_args = null; } else throw barf(iced_cc," Custom serialization methods must be declared either static or final. Failed for method " + mimpl); // If the custom serializer is actually abstract, then do nothing - it // must be (re)implemented in all child classes which will Do The Right Thing. if( javassist.Modifier.isAbstract(mods) || javassist.Modifier.isVolatile(mods) ) sb.append(impl.startsWith("write") ? " return ab;\n }" : " return ice;\n }"); else { if (!supers.isEmpty() && impl.equals("writeJSON")) { sb.append(" if(supers) {\n"); sb.append(" ab.put1(',');\n"); sb.append(" int pos = ab.position();\n"); sb.append(" " + ice_handle).append(mimpl).append(ice_args).append(";\n"); sb.append(" if(ab.position() == pos) ab.position(pos-1);\n"); // empty json serialization, drop the comma sb.append(" return ab;\n } \n"); sb.append(" return " + ice_handle).append(mimpl).append(ice_args).append(";\n }"); } else sb.append(" return " + ice_handle).append(mimpl).append(ice_args).append(";\n }"); } mimpl = null; // flag it break; } // For all fields... CtField ctfs[] = iced_cc.getDeclaredFields(); for( CtField ctf : ctfs ) { if( mimpl == null ) break; // Custom serializer, do not dump fields int mods = ctf.getModifiers(); if( javassist.Modifier.isTransient(mods) || javassist.Modifier.isStatic(mods) ) continue; // Only serialize not-transient instance fields (not static) if (ctf.hasAnnotation(API.class)) if( ((API)ctf.getAvailableAnnotations()[0]).json() == false ) continue; if( field_sep1 != null ) { sb.append(field_sep1); field_sep1 = null; } else if( field_sep2 != null ) sb.append(field_sep2); CtClass ctft = ctf.getType(); CtClass base = ctft; while( base.isArray() ) base = base.getComponentType(); // Can the generated code access the field? If not - use Unsafe. If so, // use the fieldname (ldX bytecode) directly. Genned code is in the same // package, so public,protected and package-private all have sufficient // access, only private is a problem. boolean can_access = !javassist.Modifier.isPrivate(mods); if( (impl.equals("read") || impl.equals("copyOver")) && javassist.Modifier.isFinal(mods) ) can_access = false; long off = _unsafe.objectFieldOffset(iced_clazz.getDeclaredField(ctf.getName())); int ftype = ftype(iced_cc, ctf.getSignature() ); // Field type encoding if( ftype%20 == 9 || ftype%20 == 11 ) { // Iced/Objects sb.append(can_access ? iced : iced_unsafe); } else if( ftype%20 == 10 ) { // Enums sb.append(can_access ? enums : enums_unsafe); } else { // Primitives sb.append(can_access ? prims : prims_unsafe); } String z = FLDSZ1[ftype % 20]; for(int i = 0; i < ftype / 20; ++i ) z = 'A'+z; subsub(sb, "%z", z); // %z ==> short type name subsub(sb, "%s", ctf.getName()); // %s ==> field name subsub(sb, "%c", dollarsub(base.getName())); // %c ==> base class name subsub(sb, "%C", dollarsub(ctft.getName())); // %C ==> full class name subsub(sb, "%d", ""+off); // %d ==> field offset, only for Unsafe subsub(sb, "%u", utype(ctf.getSignature())); // %u ==> unsafe type name } if( mimpl != null ) // default auto-gen serializer? sb.append(trailer); String body = sb.toString(); addMethod(body,icer_cc); return body; } // Add a gen'd method. Politely print if there's an error during generation. private static void addMethod( String body, CtClass icer_cc ) throws CannotCompileException { try { icer_cc.addMethod(CtNewMethod.make(body,icer_cc)); } catch( CannotCompileException ce ) { System.err.println("--- Compilation failure while compiling "+icer_cc.getName()+"\n"+body+"\n------\n"+ce); throw ce; } } static private final String[] FLDSZ1 = { "Z","1","2","2","4","4f","8","8d", // Primitives "Str","","Enum", // String, Freezable, Enum "Ser" // java.lang.Serializable }; // Field types: // 0-7: primitives // 8,9, 10: String, Freezable, Enum // 11: Java serialized object (implements Serializable) // 20-27: array-of-prim // 28,29, 30: array-of-String, Freezable, Enum // Barfs on all others (eg Values or array-of-Frob, etc) private static int ftype( CtClass ct, String sig ) throws NotFoundException { switch( sig.charAt(0) ) { case 'Z': return 0; // Booleans: I could compress these more case 'B': return 1; // Primitives case 'C': return 2; case 'S': return 3; case 'I': return 4; case 'F': return 5; case 'J': return 6; case 'D': return 7; case 'L': // Handled classes if( sig.equals("Ljava/lang/String;") ) return 8; String clz = sig.substring(1,sig.length()-1).replace('/', '.'); CtClass argClass = _pool.get(clz); if( argClass.subtypeOf(_pool.get("water.Freezable")) ) return 9; if( argClass.subtypeOf(_enum) ) return 10; if( argClass.subtypeOf(_serialize) ) return 11; // Uses Java Serialization break; case '[': // Arrays return ftype(ct, sig.substring(1))+20; // Same as prims, plus 20 } throw barf(ct, sig); } // Unsafe field access private static String utype( String sig ) { switch( sig.charAt(0) ) { case 'Z': return "Boolean"; case 'B': return "Byte"; case 'C': return "Char"; case 'S': return "Char"; case 'I': return "Int"; case 'F': return "Float"; case 'J': return "Long"; case 'D': return "Double"; case 'L': return "Object"; case '[': return "Object"; } throw new RuntimeException("unsafe access to type "+sig); } // Replace the 1st '$' with '.' static private String dollarsub( String s ) { int idx = s.indexOf('$'); return idx == -1 ? s : (s.substring(0,idx)+"."+s.substring(idx+1,s.length())); } // Replace 2-byte strings like "%s" with s2. static private void subsub( StringBuilder sb, String s1, String s2 ) { int idx; while( (idx=sb.indexOf(s1)) != -1 ) sb.replace(idx,idx+2,s2); } private static RuntimeException barf( CtClass ct, String sig ) { return new RuntimeException(ct.getSimpleName()+"."+sig+": Serialization not implemented"); } }