/* * `gnu.iou' * Copyright (C) 2006 John Pritchard. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package gnu.iou ; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.Hashtable; import java.util.NoSuchElementException; /** * Clar is a class file rewriter for reading a bytecode class file, * manipulating it, and then copying it out for loading (definition in * the class loader). Clar is loosely based on Chuck McManus' * "ClassFile" program. * * <p> The class file constant pool contains (most notably) field and * method references, and declared field constant values. A compiled * class can be read as a resource, renamed, have references and * values modified, and then can be loaded for a fast dynamic * reference. * * <p> The overhead of creating this dynamic class is on order of a * hundred times greater than getting a method or field using Java * reflection, but the resulting runtime performance is on order of a * hundred times greater than reflected methods and fields. * * <p> <b>Command line</b> * * <p> This class includes a simple command line function for printing * basic class file data. The perspective offered describes the class * file structure. * * <p> At the top of its output, the file magic identifier and version * are represented. On the following lines, Clar prints the * identifying attributes of the class, then its fields and methods, * and then the constant pool. (In the class file, the constant pool * comes before the class information, after the file magic identifier * and version.) * * <pre> * CAFEBABE 3.45 * Class CLASS {37 org/syntelos/pi/pif/pica} * Superclass CLASS {38 java/lang/Object} * Interface CLASS {39 org/syntelos/pi/pif/pico} * * Field {12 target_classname} * * Method {15 <init> @{Code}} * * 1: METHODREF {10 CLASS {38 java/lang/Object}}{27 NAME&TYPE {15 <init>}{16 ()V}} * 2: STRING {28 org.syntelos.pi.pif.pica_} * 3: FIELDREF {9 CLASS {37 org/syntelos/pi/pif/pica}}{29 NAME&TYPE {12 target_classname}{13 Ljava/lang/String;}} * 4: STRING {30 pica_fun} * 5: FIELDREF {9 CLASS {37 org/syntelos/pi/pif/pica}}{31 NAME&TYPE {14 target_function}{13 Ljava/lang/String;}} * 6: METHODREF {32 CLASS {40 org/syntelos/pi/pif/pica_}}{33 NAME&TYPE {30 pica_fun}{20 (Ljava/lang/Object;)Ljava/lang/Object;}} * 7: METHODREF {34 CLASS {41 java/lang/Class}}{35 NAME&TYPE {42 forName}{43 (Ljava/lang/String;)Ljava/lang/Class;}} * 8: CLASS {36 java/lang/ClassNotFoundException} * 9: CLASS {37 org/syntelos/pi/pif/pica} * 10: CLASS {38 java/lang/Object} * 11: CLASS {39 org/syntelos/pi/pif/pico} * 12: target_classname * * 14: target_function * 15: <init> * 16: ()V * </pre> * * <p> * * <p> <b>Usage</b> * * <p> Without using Clar, a dynamic function invocation is done using * the reflection API, like so. * * <pre> * Class cla = Class.forName(clnam) * Method invoker = cla.getDeclaredMethod(metnam,argtypes) * </pre> * * <p> Using Clar, a class with a generic invocation method invoking a * dummy target class and function are defined. At runtime, the * generic class is read as a resource, and the dummy reference * replaced with a dynamically configured reference. * * <pre> * package foo.bar * * public class generic { * * public Object invoke ( Object arg){ * return dummy.invoke(arg) * } * } * * public class dummy { * * public Object invoke ( Object arg){ * return null * } * } * </pre> * * <p> The following is an outline of the method adaptation process * using Clar. * * <pre> * ClassLoader cl = obj.getClass().getClassLoader() * InputStream rin = cl.getResourceAsStream('/foo/bar/dummy.class') * * clar clr = new clar(rin) * clr.subClass('generated.a123') * clr.renameMethodRef('foo.bar.dummy.invoke','com.pack.cla.funName') * byte[] classfile = clr.write() * * cl.defineClass( classfile) * * Class gencla = Class.forName('generated.a123') * method invoker = gencla.newInstance() * </pre> * * <p> If the method will only be invoked once or twice, the * reflection approach is certainly much more efficient. Likewise, an * infrequently called method benefits very little from a fast * invocation. * * @author John Pritchard */ public class clar { public static final byte TYPE_UTF8 = 1; public static final byte TYPE_INTEGER = 3; public static final byte TYPE_FLOAT = 4; public static final byte TYPE_LONG = 5; public static final byte TYPE_DOUBLE = 6; public static final byte TYPE_CLASS = 7; public static final byte TYPE_STRING = 8; public static final byte TYPE_FIELDREF = 9; public static final byte TYPE_METHODREF = 10; public static final byte TYPE_IFMETREF = 11; public static final byte TYPE_NAMEANDTYPE = 12; public static final String TYPENAME_CLASS = "CLASS"; public static final String TYPENAME_FIELDREF = "FIELDREF"; public static final String TYPENAME_METHODREF = "METHODREF"; public static final String TYPENAME_IFMETREF = "IFMETREF"; public static final String TYPENAME_NAMEANDTYPE = "NAME&TYPE"; public static final String TYPENAME_STRING = "STRING"; public static final short ACC_PUBLIC = 0x0001; public static final short ACC_PRIVATE = 0x0002; public static final short ACC_PROTECTED = 0x0004; public static final short ACC_STATIC = 0x0008; public static final short ACC_FINAL = 0x0010; public static final short ACC_SYNCHRONIZED = 0x0020; public static final short ACC_NATIVE = 0x0100; public static final short ACC_ABSTRACT = 0x0400; public static final short ACC_STRICT = 0x0800; // private final static short BEshort(byte a[]){ int a0 = ((a[0])<<8); int a1 = a[1]; return (short)(a0|a1); } // /** * Tree node for the class data. */ public static class cpinfo { protected int type = -1; protected short arg1_idx = -1, arg2_idx = -1; protected String strValue = null; protected int intValue; protected long longValue; protected float floatValue; protected double doubleValue; public cpinfo(){} public cpinfo arg1( cpinfo[] pool){ return pool[arg1_idx]; } public cpinfo arg2( cpinfo[] pool){ return pool[arg2_idx]; } public void read(DataInputStream din) throws IOException { type = din.readByte(); // CP info tag switch (type){ case TYPE_UTF8: chbuf strbuf = new chbuf(); int len = din.readShort(); while (len-- > 0) strbuf.append((char)din.readByte()); strValue = strbuf.toString(); break; case TYPE_INTEGER: intValue = din.readInt(); break; case TYPE_FLOAT: floatValue = din.readFloat(); break; case TYPE_LONG: longValue = din.readLong(); break; case TYPE_DOUBLE: doubleValue = din.readDouble(); break; case TYPE_CLASS: arg1_idx = din.readShort(); break; case TYPE_STRING: arg1_idx = din.readShort(); break; case TYPE_FIELDREF: arg1_idx = din.readShort(); arg2_idx = din.readShort(); break; case TYPE_METHODREF: arg1_idx = din.readShort(); arg2_idx = din.readShort(); break; case TYPE_IFMETREF: arg1_idx = din.readShort(); arg2_idx = din.readShort(); break; case TYPE_NAMEANDTYPE: arg1_idx = din.readShort(); arg2_idx = din.readShort(); break; default: throw new IOException("Bad type `0x"+Integer.toHexString(type)+"'."); } } public void write(DataOutputStream dout) throws IOException { dout.write(type); switch (type){ case TYPE_UTF8: dout.writeShort(strValue.length()); dout.writeBytes(strValue); break; case TYPE_INTEGER: dout.writeInt(intValue); break; case TYPE_FLOAT: dout.writeFloat(floatValue); break; case TYPE_LONG: dout.writeLong(longValue); break; case TYPE_DOUBLE: dout.writeDouble(doubleValue); break; case TYPE_CLASS: case TYPE_STRING: dout.writeShort( arg1_idx); break; case TYPE_FIELDREF: case TYPE_METHODREF: case TYPE_IFMETREF: case TYPE_NAMEANDTYPE: dout.writeShort( arg1_idx); dout.writeShort( arg2_idx); break; default: throw new IOException("Bad type `0x"+Integer.toHexString(type)+"'."); } } public String toString(){ String name = null; switch(type){ case TYPE_UTF8: return strValue; case TYPE_INTEGER: return Integer.toString(intValue); case TYPE_LONG: return Long.toString(longValue); case TYPE_FLOAT: return Float.toString(floatValue); case TYPE_DOUBLE: return Double.toString(doubleValue); case TYPE_CLASS: name = TYPENAME_CLASS; break; case TYPE_STRING: name = TYPENAME_STRING; break; case TYPE_FIELDREF: name = TYPENAME_FIELDREF; break; case TYPE_METHODREF: name = TYPENAME_METHODREF; break; case TYPE_IFMETREF: name = TYPENAME_IFMETREF; break; case TYPE_NAMEANDTYPE: name = TYPENAME_NAMEANDTYPE; break; } chbuf strbuf = new chbuf(); strbuf.append(name); strbuf.append(' '); if ( -1 < arg1_idx){ strbuf.append('{'); strbuf.append(Integer.toString(arg1_idx)); strbuf.append('}'); if ( -1 < arg2_idx){ strbuf.append('{'); strbuf.append(Integer.toString(arg2_idx)); strbuf.append('}'); } } return strbuf.toString(); } public String toString( cpinfo[] pool){ String name = null; switch(type){ case TYPE_UTF8: return strValue; case TYPE_INTEGER: return Integer.toString(intValue); case TYPE_LONG: return Long.toString(longValue); case TYPE_FLOAT: return Float.toString(floatValue); case TYPE_DOUBLE: return Double.toString(doubleValue); case TYPE_CLASS: name = TYPENAME_CLASS; break; case TYPE_STRING: name = TYPENAME_STRING; break; case TYPE_FIELDREF: name = TYPENAME_FIELDREF; break; case TYPE_METHODREF: name = TYPENAME_METHODREF; break; case TYPE_IFMETREF: name = TYPENAME_IFMETREF; break; case TYPE_NAMEANDTYPE: name = TYPENAME_NAMEANDTYPE; break; } chbuf strbuf = new chbuf(); strbuf.append(name); strbuf.append(' '); if ( -1 < arg1_idx){ strbuf.append('{'); strbuf.append(arg1_idx); strbuf.append(' '); strbuf.append(pool[arg1_idx].toString(pool)); strbuf.append('}'); if ( -1 < arg2_idx){ strbuf.append('{'); strbuf.append(arg2_idx); strbuf.append(' '); strbuf.append(pool[arg2_idx].toString(pool)); strbuf.append('}'); } } return strbuf.toString(); } public boolean equals(cpinfo cp){ if (cp == null) return false; else if (cp.type != type) return false; else { switch (cp.type){ case TYPE_UTF8: return cp.strValue.equals(strValue); case TYPE_INTEGER: return (cp.intValue == intValue); case TYPE_FLOAT: return (cp.floatValue == floatValue); case TYPE_LONG: return (cp.longValue == longValue); case TYPE_DOUBLE: return (cp.doubleValue == doubleValue); case TYPE_CLASS: case TYPE_STRING: return (arg1_idx == cp.arg1_idx); case TYPE_FIELDREF: case TYPE_METHODREF: case TYPE_IFMETREF: case TYPE_NAMEANDTYPE: return ((arg1_idx == cp.arg1_idx) && (arg2_idx == cp.arg2_idx)); } return false; } } public cpinfo indexOf( cpinfo pool[]){ cpinfo cpi; for (int cc = 1; cc < pool.length; cc++){ cpi = pool[cc]; if (equals(cpi)) return cpi; } return null; } } // /** * Attribute info */ public static class atti { protected short name_idx ; protected byte[] data = null; public atti(){} public cpinfo getName( cpinfo pool[]){ return pool[name_idx]; } public boolean booleanValue(cpinfo pool[]){ cpinfo cpi = pool[BEshort(data)]; if ( 0 == cpi.intValue) return false; else return true; } public cpinfo cpValue(cpinfo pool[]){ return pool[BEshort(data)]; } public void read(DataInputStream din) throws IOException { name_idx = din.readShort(); int len = din.readInt(), read; data = new byte[len]; if (len != (read = din.read(data,0,len))) throw new IOException("Classfile truncated ("+read+"/"+len+")."); } public void write(DataOutputStream dout) throws IOException, NoSuchElementException { dout.writeShort( name_idx); dout.writeInt(data.length); dout.write(data, 0, data.length); } public String toString(){ return "@{"+name_idx+"}"; } public String toString( cpinfo[] pool){ return "@{"+pool[name_idx].toString(pool)+"}"; } } // /** * Field info */ public static class fldi { protected short access_flags; protected short name_idx; protected short signature_idx; protected atti attributes[]; public fldi(){} public cpinfo getName( cpinfo[] pool){ return pool[name_idx]; } public cpinfo getSignature( cpinfo[] pool){ return pool[signature_idx]; } public void read(DataInputStream din) throws IOException { int count; access_flags = din.readShort(); name_idx = din.readShort(); signature_idx = din.readShort(); count = din.readShort(); if (count != 0){ attributes = new atti[count]; atti at; for (int cc = 0; cc < count; cc++){ at = new atti(); attributes[cc] = at; at.read(din); } } } public void write(DataOutputStream dout) throws IOException { dout.writeShort( access_flags); dout.writeShort( name_idx); dout.writeShort( signature_idx); int count = (null == attributes)?(0):(attributes.length); dout.writeShort(count); if ( 0 < count){ for ( int cc = 0; cc < count; cc++) attributes[cc].write(dout); } } public String toString(){ chbuf strbuf = new chbuf(); strbuf.append("Field {"); strbuf.append(name_idx); if ( null != attributes){ int len = attributes.length; for ( int cc = 0; cc < len; cc++){ strbuf.append(' '); strbuf.append(attributes[cc].toString()); } } strbuf.append('}'); return strbuf.toString(); } public String toString( cpinfo[] pool){ chbuf strbuf = new chbuf(); strbuf.append("Field {"); strbuf.append(name_idx); strbuf.append(' '); strbuf.append(pool[name_idx].toString(pool)); if ( null != attributes){ int len = attributes.length; for ( int cc = 0; cc < len; cc++){ strbuf.append(' '); strbuf.append(attributes[cc].toString(pool)); } } strbuf.append('}'); return strbuf.toString(); } } // /** * Method info */ public static class meti { protected short access_flags; protected short name_idx ; protected short signature_idx ; protected atti attributes[]; public meti(){} public cpinfo getName( cpinfo pool[]){ return pool[name_idx]; } public cpinfo getSignature( cpinfo pool[]){ return pool[signature_idx]; } public void read(DataInputStream din) throws IOException { access_flags = din.readShort(); name_idx = din.readShort(); signature_idx = din.readShort(); int count = din.readShort(); if (0 < count){ attributes = new atti[count]; atti at; for (int cc = 0; cc < count; cc++){ at = new atti(); // function bytecode attributes[cc] = at; at.read(din); } } } public void write(DataOutputStream dout) throws IOException { dout.writeShort(access_flags); dout.writeShort( name_idx); dout.writeShort( signature_idx); int count = (null == attributes)?(0):(attributes.length); dout.writeShort(count); if ( 0 < count){ for ( int cc = 0; cc < count; cc++) attributes[cc].write(dout); } } public String toString(){ chbuf strbuf = new chbuf(); strbuf.append("Method {"); strbuf.append(name_idx); if ( null != attributes){ int len = attributes.length; for ( int cc = 0; cc < len; cc++){ strbuf.append(' '); strbuf.append(attributes[cc].toString()); } } strbuf.append('}'); return strbuf.toString(); } public String toString( cpinfo[] pool){ chbuf strbuf = new chbuf(); strbuf.append("Method {"); strbuf.append(name_idx); strbuf.append(' '); strbuf.append(pool[name_idx].toString(pool)); if ( null != attributes){ int len = attributes.length; for ( int cc = 0; cc < len; cc++){ strbuf.append(' '); strbuf.append(attributes[cc].toString(pool)); } } strbuf.append('}'); return strbuf.toString(); } } // class file private int magic; // 0xCAFEBABE private short version_major; private short version_minor; private cpinfo[] constant_pool = null; private short access_flags; private short class_this_idx; private short class_super_idx; private short[] interface_idxs = null; private fldi[] fields = null; private meti[] methods = null; private atti[] attributes = null; private boolean _ok = false; public clar(){ super(); } /** * Read a class file from the input stream * * @param in Class file * * @exception IOException Bad classfile format. */ public clar( InputStream in) throws IOException { super(); read(in); } /** * Read a class file from the input stream * * @param in Class file * * @exception IOException Bad classfile format. */ public clar( byte[] in) throws IOException { super(); read(new DataInputStream(new ByteArrayInputStream(in))); } /** * Read the named class as a resource. * * @param rn Resource name for class file, eg, relative to this * package: <tt>`pica.class'</tt>; or absolute: * <tt>`/tld/dom/pkg/pic2.class'</tt>. * * @exception IllegalArgumentException For null argument, or * missing class binary, or bad classfile format. */ public clar ( String rn){ super(); if ( null == rn) throw new IllegalArgumentException("Null resource-name argument to `clar' constructor."); else { InputStream rin = null; try { Class cla = getClass(); rin = cla.getResourceAsStream(rn); read(rin); } catch ( IllegalArgumentException ilarg){ throw new IllegalArgumentException("Class file resource not found ("+rn+")."); } catch ( NullPointerException npx){ throw new IllegalArgumentException("Class file not found ("+rn+")."); } catch ( IOException iox){ iox.printStackTrace(); throw new IllegalArgumentException("Class file format error in ("+rn+") is ("+iox.getMessage()+")."); } finally { if ( null != rin) try{rin.close();}catch(IOException iox){} } } } // /** * Class name */ public String getName(){ return constant_pool[cpClass().arg1_idx].strValue.replace('/','.'); } /** * Super class name */ public String getNameSuper(){ return constant_pool[cpSuperClass().arg1_idx].strValue.replace('/','.'); } /** * Rename this class */ public void renameClass ( String classname){ constant_pool[cpClass().arg1_idx].strValue = classname.replace('.','/'); } /** * Rename super class */ public void renameSuper ( String classname){ constant_pool[cpSuperClass().arg1_idx].strValue = classname.replace('.','/'); } /** * Rename class as a subclass of the current class. */ public void subClass ( String classname){ cpinfo cla = cpClass(), supcla = cpSuperClass(); constant_pool[supcla.arg1_idx].strValue = constant_pool[cla.arg1_idx].strValue; constant_pool[cla.arg1_idx].strValue = classname.replace('.','/'); } // "rename" lookup consts private final static int REN_FROM_CN = 1; private final static int REN_FROM_CR = 2; private final static int REN_FROM_MN = 3; private final static Integer REN_FROM_CN_I = new Integer(REN_FROM_CN); private final static Integer REN_FROM_CR_I = new Integer(REN_FROM_CR); private final static Integer REN_FROM_MN_I = new Integer(REN_FROM_MN); // /** * For two methods of identical type signatures ("from" and "to"), * redirect the invocation of "from" to the invocation of "to". * * <pre> * from = "org.syntelos.pi.pif.pica_.pica_fun" * to = "net.pico.brazil.picoFun" * * For: * * package org.syntelos.pi * class pica_ { * public static Object pica_fun ( Object arg) * } * * And: * * package net.pico * class brazil { * public static Object picoFun ( Object arg) * } * </pre> * * <p> When either method name parameter ("from" or "to") is not * qualified with a class name, <i>this</i> class name is used as a * default. Valid unqualified method names include both * ".methodname" and "methodname". * * @param from Fully- qualified class- method name * * @param to Fully- qualified class- method name * * @exception NoSuchElementException For method reference "from" not found. * * @returns Number of principal constants modified. */ public int renameMethodRef ( String from, String to){ cpinfo metrefs[] = cpMethodRefs(); if ( null == metrefs) return 0; else { Hashtable lookup = new Hashtable(); String from_cn, from_cnr, from_mn, to_cn, to_cnr, to_mn; int ix = from.lastIndexOf('.'); if ( 0 > ix){ from = getName()+"."+from; ix = from.lastIndexOf('.'); } else if ( 0 == ix){ from = getName()+from; ix = from.lastIndexOf('.'); } from_cn = from.substring(0,ix); from_cnr = from_cn.replace('.','/'); from_mn = from.substring(ix+1); lookup.put( from_cn, REN_FROM_CN_I); lookup.put( from_cnr, REN_FROM_CR_I); lookup.put( from_mn, REN_FROM_MN_I); ix = to.lastIndexOf('.'); if ( 0 > ix){ to = getName()+"."+to; ix = to.lastIndexOf('.'); } else if ( 0 == ix){ to = getName()+to; ix = to.lastIndexOf('.'); } to_cn = to.substring(0,ix); to_cnr = to_cn.replace('.','/'); to_mn = to.substring(ix+1); String mr_cn, mr_mn; cpinfo metref, mrcla, mrfld, pool[] = constant_pool; int len = metrefs.length, changes = 0; Integer lv; for ( short cc = 0; cc < len; cc++){ metref = metrefs[cc]; mrcla = pool[pool[metref.arg1_idx].arg1_idx]; mr_cn = mrcla.strValue; if ( null != mr_cn ){ mrfld = pool[pool[metref.arg2_idx].arg1_idx]; mr_mn = mrfld.strValue; if ( null != (lv = (Integer)lookup.get(mr_cn))){ switch(lv.intValue()){ case REN_FROM_CR: mrcla.strValue = to_cnr; changes += 1; break; case REN_FROM_CN: mrcla.strValue = to_cn; changes += 1; break; default: throw new IllegalStateException("BBBUGGG in `clar.renameMethodRef' lookup types, found type ("+lv+")."); } } if ( null != (lv = (Integer)lookup.get(mr_mn))){ switch(lv.intValue()){ case REN_FROM_MN: mrfld.strValue = to_mn; changes += 1; break; default: throw new IllegalStateException("BBBUGGG in `clar.renameMethodRef' lookup types, found type ("+lv+")."); } } } } if ( 1 > changes) throw new NoSuchElementException("Method reference ("+from+") not found."); else return changes; } } // /** * For two fields of identical types, redirect the reference of * "from" to the reference of "to". * * <p> When either field name parameter ("from" or "to") is not * qualified with a class name, this class name is used as a * default. Valid unqualified field names include both * ".fieldname" and "fieldname". * * @param from Fully- qualified class- field name, eg, * <tt>"org.syntelos.pi.pif.clar.constant_pool"</tt>. * * @param to Fully- qualified class- field name * * @exception NoSuchElementException For field reference "from" not found. * * @returns Number of principal constants modified. */ public int renameFieldRef ( String from, String to){ cpinfo fldrefs[] = cpFieldRefs(); if ( null == fldrefs) return 0; else { Hashtable lookup = new Hashtable(); String from_cn, from_cnr, from_fn, to_cn, to_cnr, to_fn; int ix = from.lastIndexOf('.'); if ( 0 > ix){ from = getName()+"."+from; ix = from.lastIndexOf('.'); } else if ( 0 == ix){ from = getName()+from; ix = from.lastIndexOf('.'); } from_cn = from.substring(0,ix); from_cnr = from_cn.replace('.','/'); from_fn = from.substring(ix+1); lookup.put( from_cn, REN_FROM_CN_I); lookup.put( from_cnr, REN_FROM_CR_I); lookup.put( from_fn, REN_FROM_MN_I); ix = to.lastIndexOf('.'); if ( 0 > ix){ to = getName()+"."+to; ix = to.lastIndexOf('.'); } else if ( 0 == ix){ to = getName()+to; ix = to.lastIndexOf('.'); } to_cn = to.substring(0,ix); to_cnr = to_cn.replace('.','/'); to_fn = to.substring(ix+1); String fr_cn, fr_fn; cpinfo fldref, frcla, frfld, pool[] = constant_pool; int len = fldrefs.length, changes = 0; Integer lv; for ( short cc = 0; cc < len; cc++){ fldref = fldrefs[cc]; frcla = pool[pool[fldref.arg1_idx].arg1_idx]; frfld = pool[pool[fldref.arg2_idx].arg1_idx]; fr_cn = frcla.strValue; fr_fn = frfld.strValue; if ( null != (lv = (Integer)lookup.get(fr_cn))){ switch(lv.intValue()){ case REN_FROM_CR: frcla.strValue = to_cnr; changes += 1; break; case REN_FROM_CN: frcla.strValue = to_cn; changes += 1; break; default: throw new IllegalStateException("BBBUGGG in `clar.renameMethodRef' lookup types, found type ("+lv+")."); } } if ( null != (lv = (Integer)lookup.get(fr_fn))){ switch(lv.intValue()){ case REN_FROM_MN: frfld.strValue = to_fn; changes += 1; break; default: throw new IllegalStateException("BBBUGGG in `clar.renameMethodRef' lookup types, found type ("+lv+")."); } } } if ( 1 > changes) throw new NoSuchElementException("Field reference ("+from+") not found."); else return changes; } } // /** * Replace all strings matching "from" with "to". * * @param from Existing string value * * @param to Replacement (new) string value * * @returns Number of principal constants modified, zero for no * mods. */ public int replaceString ( String from, String to){ cpinfo cpi, pool[] = constant_pool; int len = pool.length, changes = 0; for ( short cc = 0; cc < len; cc++){ cpi = pool[cc]; if ( TYPE_STRING == cpi.type){ cpi = pool[cpi.arg1_idx]; if ( from.equals(cpi.strValue)){ cpi.strValue = to; changes += 1; } } } return changes; } /** * @param mn Method name * * @returns If the method is static * * @exception NoSuchElementException No method */ public boolean isMethodStatic ( String mn){ meti met = method(mn); if ( null == met) throw new NoSuchElementException("Method ("+mn+") not found."); else if ( ACC_STATIC == (met.access_flags & ACC_STATIC)) return true; else return false; } // /** * @returns This CLASS cpi */ protected cpinfo cpClass(){ return constant_pool[class_this_idx]; } /** * @returns Super CLASS cpi */ protected cpinfo cpSuperClass(){ return constant_pool[class_super_idx]; } /** * @returns METHODREF and IFMETREF cpi's */ protected cpinfo[] cpMethodRefs(){ cpinfo cpi, pool[] = constant_pool, ret[] = null, copier[]; int poolen = pool.length, rix = 0, tt; for ( short cc = 0; cc < poolen; cc++){ cpi = pool[cc]; tt = cpi.type; if ( TYPE_METHODREF == tt || TYPE_IFMETREF == tt){ if ( null == ret) ret = new cpinfo[1]; else { copier = new cpinfo[rix+1]; System.arraycopy(ret,0,copier,0,rix); ret = copier; } ret[rix++] = cpi; } } return ret; } /** * @param mn Method name * * @returns First METHODREF or IFMETREF matching the argument method name. */ protected cpinfo cpMethodRef( String mn){ cpinfo cp1, cp2, pool[] = constant_pool; int poolen = pool.length, tt; for ( short cc = 0; cc < poolen; cc++){ cp1 = pool[cc]; tt = cp1.type; if ( TYPE_METHODREF == tt || TYPE_IFMETREF == tt){ cp2 = pool[pool[cp1.arg2_idx].arg1_idx]; // name if ( mn.equals(cp2.strValue)) return cp1; } } return null; } /** * @param mn Method name * * @returns First method matching the argument method name. */ protected meti method( String mn){ if ( null == methods) return null; else { meti met, mets[] = methods; int len = mets.length; for ( short cc = 0; cc < len; cc++){ met = mets[cc]; if ( mn.equals( met.getName(constant_pool).strValue)) return met; } } return null; } /** * @returns FIELDREF cpi's */ protected cpinfo[] cpFieldRefs(){ cpinfo cpi, pool[] = constant_pool, ret[] = null, copier[]; int poolen = pool.length, rix = 0; for ( short cc = 0; cc < poolen; cc++){ cpi = pool[cc]; if ( TYPE_FIELDREF == cpi.type){ if ( null == ret) ret = new cpinfo[1]; else { copier = new cpinfo[rix+1]; System.arraycopy(ret,0,copier,0,rix); ret = copier; } ret[rix++] = cpi; } } return ret; } /** * @param fn Field name * * @returns First FIELDREF matching the argument field name. */ protected cpinfo cpFieldRef( String fn){ cpinfo cp1, cp2, pool[] = constant_pool; int poolen = pool.length; for ( short cc = 0; cc < poolen; cc++){ cp1 = pool[cc]; if ( TYPE_FIELDREF == cp1.type){ cp2 = pool[pool[cp1.arg2_idx].arg1_idx]; // name if ( fn.equals(cp2.strValue)) return cp1; } } return null; } // public String toString(){ return toString(false); } public String toString( boolean nesting){ chbuf strbuf = new chbuf(); strbuf.append("\tCAFEBABE "); strbuf.append(Integer.toString(version_major)); strbuf.append('.'); strbuf.append(Integer.toString(version_minor)); strbuf.append("\n\tClass "); if (nesting) strbuf.append(constant_pool[class_this_idx].toString(constant_pool)); else strbuf.append(constant_pool[class_this_idx].toString()); strbuf.append("\n\tSuperclass "); if (nesting) strbuf.append(constant_pool[class_super_idx].toString(constant_pool)); else strbuf.append(constant_pool[class_super_idx].toString()); if ( null != interface_idxs){ for ( int cc = 0; cc < interface_idxs.length; cc++){ strbuf.append("\n\tInterface "); if (nesting) strbuf.append(constant_pool[interface_idxs[cc]].toString(constant_pool)); else strbuf.append(constant_pool[interface_idxs[cc]].toString()); } } if ( null != fields){ for ( int cc = 0; cc < fields.length; cc++){ strbuf.append("\n\t"); if (nesting) strbuf.append(fields[cc].toString(constant_pool)); else strbuf.append(fields[cc].toString()); } } if ( null != methods){ for ( int cc = 0; cc < methods.length; cc++){ strbuf.append("\n\t"); if (nesting) strbuf.append(methods[cc].toString(constant_pool)); else strbuf.append(methods[cc].toString()); } } strbuf.append('\n'); return strbuf.toString(); } public short indexOf( cpinfo cpi) throws NoSuchElementException { cpinfo[] pool = constant_pool; int poolen = pool.length; for ( short cc = 0; cc < poolen; cc++){ if (cpi == pool[cc]) return cc; } throw new NoSuchElementException("CP ("+cpi+") not found."); } // /** * Read a class. * @returns Success or failure * @exception Format or I/O error */ public void read(InputStream in) throws IOException { _ok = false; DataInputStream din; if (in instanceof DataInputStream) din = (DataInputStream)in; else din = new DataInputStream(in); /* * Head */ magic = din.readInt(); if (magic != (int) 0xCAFEBABE) throw new IOException("Input not a valid java class file."); version_major = din.readShort(); version_minor = din.readShort(); int cc, count; /* * Read constant pool */ count = din.readShort(); cpinfo cpi; constant_pool = new cpinfo[count]; constant_pool[0] = new cpinfo(); // natively indexed from `1' for ( cc = 1; cc < constant_pool.length; cc++){ constant_pool[cc] = cpi = new cpinfo(); cpi.read(din); if (cpi.type == TYPE_LONG || cpi.type == TYPE_DOUBLE) cc++; // 2 slots } /* * Flags */ access_flags = din.readShort(); /* * Identity */ class_this_idx = din.readShort(); class_super_idx = din.readShort(); /* * Interfaces */ count = din.readShort(); if ( 0 < count){ interface_idxs = new short[count]; for ( cc = 0; cc < count; cc++){ interface_idxs[cc] = din.readShort(); } } /* * Fields */ count = din.readShort(); if ( 0 < count){ fldi fi ; fields = new fldi[count]; for ( cc = 0; cc < count; cc++){ fields[cc] = fi = new fldi(); fi.read(din); } } /* * Methods */ count = din.readShort(); if ( 0 < count){ meti mi; methods = new meti[count]; for ( cc = 0; cc < count; cc++){ methods[cc] = mi = new meti(); mi.read(din); } } /* * Attributes */ count = din.readShort(); if ( 0 < count){ atti at; attributes = new atti[count]; for ( cc = 0; cc < count; cc++){ at = new atti(); attributes[cc] = at; at.read(din); } } _ok = true; } // /** * * @returns Classfile bytes for class loader. * * @see #getName */ public byte[] write() throws IOException { bbo ous = new bbo(1024); write(ous); return ous.toByteArray(); } public void write(OutputStream out) throws IOException { if (!_ok) throw new IOException("Can't write class file, bad format."); else { int cc, count; DataOutputStream dout; if ( out instanceof DataOutputStream) dout = (DataOutputStream)out; else dout = new DataOutputStream(out); /* * Head */ dout.writeInt(magic); dout.writeShort(version_major); dout.writeShort(version_minor); /* * Constant pool */ count = constant_pool.length; dout.writeShort(count); if ( 0 < count){ cpinfo cpi; for ( cc = 1; cc < count; cc++){ cpi = constant_pool[cc]; cpi.write(dout); } } /* * Flags */ dout.writeShort(access_flags); /* * Identity */ dout.writeShort( class_this_idx); dout.writeShort( class_super_idx); /* * Interfaces */ count = (null == interface_idxs)?(0):(interface_idxs.length); dout.writeShort(count); if ( 0 < count){ for ( cc = 0; cc < count; cc++){ dout.writeShort( interface_idxs[cc]); } } /* * Fields */ count = (null == fields)?(0):(fields.length); dout.writeShort(count); if ( 0 < count){ for ( cc = 0; cc < count; cc++) fields[cc].write(dout); } /* * Methods */ count = (null == methods)?(0):(methods.length); dout.writeShort(count); if ( 0 < count){ for ( cc = 0; cc < count; cc++) methods[cc].write(dout); } /* * Attributes */ count = (null == attributes)?(0):(attributes.length); dout.writeShort(count); if ( 0 < count){ for ( cc = 0; cc < count; cc++) attributes[cc].write(dout); } } } // /** * @param cla Print clar * * @param out Destination */ public final static void PrintCP ( clar cla, PrintStream out){ PrintCP(cla,out,false); } /** * @param cla Print clar * * @param out Destination * * @param nesting When true, print tree nodes in- line. */ public final static void PrintCP ( clar cla, PrintStream out, boolean nesting){ out.println(cla.toString(nesting)); cpinfo cpi, cp[] = cla.constant_pool; int count = cp.length; for ( int cc = 1; cc < count; cc++){ cpi = cp[cc]; out.write('\t'); out.print(Integer.toString(cc)); out.write(':'); out.write('\t'); if (nesting) out.println(cpi.toString(cp)); else out.println(cpi.toString()); } } private final static void usage ( PrintStream out){ out.println(); out.println(" Usage"); out.println(); out.println("\tclar -c classname [-i]"); out.println("\tclar -f filename.class [-i]"); out.println("\tclar [ -c ... | -f ... ] -r from to "); out.println(); out.println(" Description"); out.println(); out.println("\tTest `Clar'. The first two forms print the"); out.println("\tconstant pool table for visualizing what's "); out.println("\tgoing on there."); out.println(); out.println("\tThe third form will rename a method and class"); out.println("\treferences as `renameMethodRef', then save a"); out.println("\tclass file in this directory or with `file'."); out.println(); out.println("\tThe `-i' option turns- on inline tree nodes "); out.println("\tfor printing (no `-r')."); out.println(); } /** * Command line test- tool. * * <pre> * Usage * * clar -c classname [-i] * clar -f filename.class [-i] * clar [ -c ... | -f ... ] -r from to * * Description * * Test `Clar'. The first two forms print the * constant pool table for visualizing what's * going on there. * * The third form will rename a method and class * references as `renameMethodRef', then save a * class file in this directory or with `file'. * * The `-i' option turns- on inline tree nodes * for printing (no `-r'). * * </pre> */ public final static void main ( String[] argv){ try { boolean print = false, inline = false; File file = null; String rsrc = null; String from = null, to = null; { String arg; int alen = argv.length; for ( int argc = 0; argc < alen; argc++){ arg = argv[argc]; switch(arg.charAt(0)){ case '-': case '/': switch(arg.charAt(1)){ case 'h': case 'H': case '?': throw new IllegalArgumentException(); case 'i': if (print) inline = true; else throw new IllegalArgumentException("Option `-i' must follow a print option, one of either `-f' or `-c'."); break; case 'f': argc += 1; if ( argc < alen){ arg = argv[argc]; file = new File(arg); print = true; break; } else throw new IllegalArgumentException("Option `-f' requires filename argument."); case 'c': argc += 1; if ( argc < alen){ arg = argv[argc]; rsrc = arg; print = true; break; } else throw new IllegalArgumentException("Option `-p' requires classname argument."); case 'r': print = false; argc += 1; if ( argc < alen){ from = argv[argc]; argc += 1; if ( argc < alen){ to = argv[argc]; break; } else throw new IllegalArgumentException("Option `-r' requires 'to' argument."); } else throw new IllegalArgumentException("Option `-r' requires 'from' argument."); default: throw new IllegalArgumentException("Unrecognized option `"+arg+"'."); } break; default: throw new IllegalArgumentException("Unrecognized option `"+arg+"'."); } } } long start; clar clr = null; if ( null != file){ FileInputStream fin = new FileInputStream(file); start = System.currentTimeMillis(); clr = new clar(fin); System.err.println("Pica setup "+(System.currentTimeMillis()-start)+" ms -- using `new clar(InputStream)'."); fin.close(); } else if ( null != rsrc){ start = System.currentTimeMillis(); clr = new clar(rsrc); System.err.println("Pica setup "+(System.currentTimeMillis()-start)+" ms -- using `new clar(String)'."); } else throw new IllegalArgumentException(); if ( print) PrintCP( clr, System.out, inline); else if ( null != from && null != to){ String cn = to.substring(0,to.lastIndexOf('.')); cn = cn.substring(cn.lastIndexOf('.')+1); cn = "pica/t_"+cn; if ( null != file) file = new File( file.getParent()+"/"+ cn+".class"); else file = new File( cn.replace('/','.')+".class"); FileOutputStream fout = new FileOutputStream (file); start = System.currentTimeMillis(); clr.renameMethodRef( from, to); clr.subClass( cn); System.err.println("Pica rename & subclass "+(System.currentTimeMillis()-start)+" ms."); start = System.currentTimeMillis(); clr.write(fout); System.err.println("Pica write "+(System.currentTimeMillis()-start)+" ms."); System.out.println("Wrote "+file.getAbsolutePath()); fout.close(); } else throw new IllegalArgumentException(); System.exit(0); } catch ( IllegalArgumentException ilarg){ String msg = ilarg.getMessage(); if ( null == msg) usage(System.err); else System.err.println(msg); System.exit(1); } catch ( Exception exc){ exc.printStackTrace(); System.exit(1); } } }