import org.apache.bcel.classfile.*; import org.apache.bcel.generic.*; import org.apache.bcel.Constants; // Developed against BCEL 5.1 import java.io.*; import java.util.jar.JarFile; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.lang.reflect.Modifier; /** * This utility takes a .class or .jar file and replaces it with a no-op equivalent that you * can compile against (but not run against). * * It basically re-creates classes with the same names and signatures as the ones in the original, * making all the methods native (so we don't have to provide behavior) (except constructors, which * are just left with an empty body, easy because they don't have to return anything). * * It does this because there are bugs in the type-erasure used by the early-access JDK compiler, * such that I can't compile against idea.jar directly due to the following error: *
 * [javac] F:\source\nm\dpj\ojde302\fingertips\src\com\compuware\ojde\ij\EditorTrackingFeature.java:16: cannot access com.intellij.openapi.vfs.VirtualFile
 * [javac] bad class file: F:\source\nm\dpj\ojde302\imports\intellij\lib\idea.jar(com/intellij/openapi/vfs/VirtualFile.class)
 * [javac] bad signature:  r? B z? ?
 * [javac] Please remove or make sure it appears in the correct subdirectory of the classpath.
 * [javac] import com.intellij.openapi.vfs.VirtualFile;
 * [javac]                                 ^
 * [javac] 1 error
 * 
* * This only happens when I use the generics compiler, not the real compiler. So, I'm not sure what it is * about the type data in VirtualFile that has it so upset, but this stub generator was built to strip * all that information out. * * @author Paul Mclachlan */ public class CompileStubGenerator { public static void main( String args[] ) throws IOException { if( args.length != 2 ) { System.err.println( "Usage: java -jar classRewriter.jar <.class or .jar> " ); return; } File source = new File( args[0] ); if( !source.exists() ) { System.err.println( "Source '" + source + "' does not exist" ); return; } File target = new File( args[1] ); if( target.exists() ) { System.err.println( "Target '" + target + "' deleted" ); target.delete(); } rewrite( source, target ); } public static void rewrite( File source, File target ) throws IOException { if( source.getName().endsWith( ".class" ) ) { rewriteClass( source.getName(), new BufferedInputStream( new FileInputStream( source ) ), new BufferedOutputStream( new FileOutputStream( target ) ) ); } else if( source.getName().endsWith( ".jar" ) ) { rewriteJar( new JarFile( source ), target ); } else System.err.println( "Unknown file type '" + source.getName() + "'" ); } private static void rewriteJar( JarFile source, File target ) throws IOException { JarOutputStream jos = new JarOutputStream( new BufferedOutputStream( new FileOutputStream( target ) ) ); Enumeration e = source.entries(); while( e.hasMoreElements() ) { JarEntry jfe = (JarEntry) e.nextElement(); String name = jfe.getName(); if( name.endsWith( ".class" ) ) { // The obfuscated, uninteresting classes start with lower-case letters // if( Character.isUpperCase( jfe.getName().charAt( 0 ) ) ) InputStream is = source.getInputStream( jfe ); ZipEntry ze = new ZipEntry( name ); jos.putNextEntry( ze ); ByteArrayOutputStream baos = new ByteArrayOutputStream( (int) jfe.getSize() ); rewriteClass( name.substring( 0, name.length() - ".class".length() ).replace( '/', '.' ), is, baos ); jos.write( baos.toByteArray() ); } } jos.closeEntry(); jos.close(); System.out.println(); } private static void rewriteClass( String name, InputStream input, OutputStream output ) throws IOException { JavaClass jc = new ClassParser( input, name ).parse(); ClassGen cg = new ClassGen( jc.getClassName(), jc.getSuperclassName(), jc.getFileName(), jc.getAccessFlags(), jc.getInterfaceNames() ); Method[] methods = jc.getMethods(); for( int i = 0; i < methods.length; i++ ) { Method jm = methods[i]; if( jm.isPrivate() ) continue; // skip privates (can't compile against them anyway) if( jm.getName().equals( "" ) ) continue; // no point in copying the static constructors int accessFlags = jm.getAccessFlags(); InstructionList instructions = null; if( jm.getName().equals( "" ) ) { // Constructors don't return anything, so we can have a no-op body // and not make it native (they can't be native anyway) instructions = new InstructionList( new NOP() ); } else { // Easiest way to put in the signature of a method is to make it native. // // That way: // // We can still write code that calls "new Blah()" (because class isn't abstract) // We don't need to worry about putting in a "return X" statement (which is complicated // slightly by all the different primitive types. accessFlags |= Modifier.NATIVE; } MethodGen mg = new MethodGen( accessFlags, jm.getReturnType(), jm.getArgumentTypes(), null, jm.getName(), jc.getClassName(), instructions, cg.getConstantPool() ); ExceptionTable et = jm.getExceptionTable(); if( et != null ) { String[] thrownExceptions = et.getExceptionNames(); for( int j = 0; j < thrownExceptions.length; j++ ) mg.addException( thrownExceptions[j] ); } cg.addMethod( mg.getMethod() ); } Field[] fields = jc.getFields(); for( int i = 0; i < fields.length; i++ ) { Field fm = fields[i]; if( fm.isPrivate() ) continue; FieldGen fg = new FieldGen( fm.getAccessFlags(), fm.getType(), fm.getName(), cg.getConstantPool() ); if( fm.isFinal() && fm.isStatic() ) { ConstantPool cp = fm.getConstantPool(); ConstantValue cv = fm.getConstantValue(); if( cv != null ) { Constant constant = cp.getConstant( cv.getConstantValueIndex() ); switch( fm.getType().getType() ) { case Constants.T_BOOLEAN: fg.setInitValue( ((ConstantInteger)constant).getBytes() != 0 ); break; case Constants.T_BYTE: fg.setInitValue( (byte) ((ConstantInteger)constant).getBytes() ); break; case Constants.T_SHORT: fg.setInitValue( (short) ((ConstantInteger)constant).getBytes() ); break; case Constants.T_CHAR: fg.setInitValue( (char) ((ConstantInteger)constant).getBytes() ); break; case Constants.T_DOUBLE: fg.setInitValue( ((ConstantDouble)constant).getBytes() ); break; case Constants.T_FLOAT: fg.setInitValue( ((ConstantFloat)constant).getBytes() ); break; case Constants.T_INT: fg.setInitValue( ((ConstantInteger)constant).getBytes() ); break; case Constants.T_LONG: fg.setInitValue( ((ConstantLong)constant).getBytes() ); break; } } } cg.addField( fg.getField() ); } JavaClass clone = cg.getJavaClass(); clone.dump( output ); } }