From: <hib...@li...> - 2006-08-03 20:15:43
|
Author: ste...@jb... Date: 2006-08-03 16:15:36 -0400 (Thu, 03 Aug 2006) New Revision: 10207 Added: trunk/Hibernate3/src/org/hibernate/tool/instrument/BasicInstrumentationTask.java Log: HHH-1968 : unify bytecode instrumentation Added: trunk/Hibernate3/src/org/hibernate/tool/instrument/BasicInstrumentationTask.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/tool/instrument/BasicInstrumentationTask.java 2006-08-03 19:59:42 UTC (rev 10206) +++ trunk/Hibernate3/src/org/hibernate/tool/instrument/BasicInstrumentationTask.java 2006-08-03 20:15:36 UTC (rev 10207) @@ -0,0 +1,383 @@ +package org.hibernate.tool.instrument; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; +import org.hibernate.bytecode.util.ClassDescriptor; +import org.hibernate.bytecode.util.ByteCodeHelper; +import org.hibernate.bytecode.util.FieldFilter; +import org.hibernate.bytecode.ClassTransformer; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; +import java.util.HashSet; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.CRC32; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.FileInputStream; +import java.io.DataInputStream; +import java.io.ByteArrayInputStream; + +/** + * Super class for all Hibernate instrumentation tasks. Provides the basic + * templating of how instrumentation should occur. + * + * @author Steve Ebersole + */ +public abstract class BasicInstrumentationTask extends Task { + + private static final int ZIP_MAGIC = 0x504B0304; + private static final int CLASS_MAGIC = 0xCAFEBABE; + + protected final Logger logger = new Logger(); + private List filesets = new ArrayList(); + private Set classNames = new HashSet(); + private boolean extended; + private boolean verbose; + + public void addFileset(FileSet set) { + this.filesets.add( set ); + } + + protected final Iterator filesets() { + return filesets.iterator(); + } + + public boolean isExtended() { + return extended; + } + + public void setExtended(boolean extended) { + this.extended = extended; + } + + public boolean isVerbose() { + return verbose; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void execute() throws BuildException { + if ( isExtended() ) { + collectClassNames(); + } + logger.info( "starting instrumentation" ); + Project project = getProject(); + Iterator filesets = filesets(); + while ( filesets.hasNext() ) { + FileSet fs = ( FileSet ) filesets.next(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] includedFiles = ds.getIncludedFiles(); + File d = fs.getDir( project ); + for ( int i = 0; i < includedFiles.length; ++i ) { + File file = new File( d, includedFiles[i] ); + try { + processFile( file ); + } + catch ( Exception e ) { + throw new BuildException( e ); + } + } + } + } + + private void collectClassNames() { + logger.info( "collecting class names for extended instrumentation determination" ); + Project project = getProject(); + Iterator filesets = filesets(); + while ( filesets.hasNext() ) { + FileSet fs = ( FileSet ) filesets.next(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] includedFiles = ds.getIncludedFiles(); + File d = fs.getDir( project ); + for ( int i = 0; i < includedFiles.length; ++i ) { + File file = new File( d, includedFiles[i] ); + try { + collectClassNames( file ); + } + catch ( Exception e ) { + throw new BuildException( e ); + } + } + } + logger.info( classNames.size() + " class(es) being checked" ); + } + + private void collectClassNames(File file) throws Exception { + if ( isClassFile( file ) ) { + byte[] bytes = ByteCodeHelper.readByteCode( file ); + ClassDescriptor descriptor = getClassDescriptor( bytes ); + classNames.add( descriptor.getName() ); + } + else if ( isJarFile( file ) ) { + ZipEntryHandler collector = new ZipEntryHandler() { + public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception { + if ( !entry.isDirectory() ) { + // see if the entry represents a class file + DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) ); + if ( din.readInt() == CLASS_MAGIC ) { + classNames.add( getClassDescriptor( byteCode ).getName() ); + } + } + } + }; + ZipFileProcessor processor = new ZipFileProcessor( collector ); + processor.process( file ); + } + } + + protected void processFile(File file) throws Exception { + if ( isClassFile( file ) ) { + processClassFile(file); + } + else if ( isJarFile( file ) ) { + processJarFile(file); + } + else { + logger.verbose( "ignoring " + file.toURL() ); + + } + } + + protected final boolean isClassFile(File file) throws IOException { + return checkMagic( file, CLASS_MAGIC ); + } + + protected final boolean isJarFile(File file) throws IOException { + return checkMagic(file, ZIP_MAGIC); + } + + protected final boolean checkMagic(File file, long magic) throws IOException { + DataInputStream in = new DataInputStream( new FileInputStream( file ) ); + try { + int m = in.readInt(); + return magic == m; + } + finally { + in.close(); + } + } + + protected void processClassFile(File file) throws Exception { + logger.verbose( "Starting class file : " + file.toURL() ); + byte[] bytes = ByteCodeHelper.readByteCode( file ); + ClassDescriptor descriptor = getClassDescriptor( bytes ); + ClassTransformer transformer = getClassTransformer( descriptor ); + if ( transformer == null ) { + logger.verbose( "skipping file : " + file.toURL() ); + return; + } + + logger.info( "processing class [" + descriptor.getName() + "]; file = " + file.toURL() ); + byte[] transformedBytes = transformer.transform( + getClass().getClassLoader(), + descriptor.getName(), + null, + null, + descriptor.getBytes() + ); + + OutputStream out = new FileOutputStream( file ); + try { + out.write( transformedBytes ); + out.flush(); + } + finally { + try { + out.close(); + } + catch ( IOException ignore) { + // intentionally empty + } + } + } + + protected void processJarFile(final File file) throws Exception { + logger.verbose( "starting jar file : " + file.toURL() ); + + File tempFile = File.createTempFile( + file.getName(), + null, + new File( file.getAbsoluteFile().getParent() ) + ); + + try { + FileOutputStream fout = new FileOutputStream( tempFile, false ); + try { + final ZipOutputStream out = new ZipOutputStream( fout ); + ZipEntryHandler transformer = new ZipEntryHandler() { + public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception { + logger.verbose( "starting entry : " + entry.toString() ); + if ( !entry.isDirectory() ) { + // see if the entry represents a class file + DataInputStream din = new DataInputStream( new ByteArrayInputStream( byteCode ) ); + if ( din.readInt() == CLASS_MAGIC ) { + ClassDescriptor descriptor = getClassDescriptor( byteCode ); + ClassTransformer transformer = getClassTransformer( descriptor ); + if ( transformer == null ) { + logger.verbose( "skipping entry : " + entry.toString() ); + } + else { + logger.info( "processing class [" + descriptor.getName() + "]; entry = " + file.toURL() ); + byteCode = transformer.transform( + getClass().getClassLoader(), + descriptor.getName(), + null, + null, + descriptor.getBytes() + ); + } + } + else { + logger.verbose( "ignoring zip entry : " + entry.toString() ); + } + } + + ZipEntry outEntry = new ZipEntry( entry.getName() ); + outEntry.setMethod( entry.getMethod() ); + outEntry.setComment( entry.getComment() ); + outEntry.setSize( byteCode.length ); + + if ( outEntry.getMethod() == ZipEntry.STORED ){ + CRC32 crc = new CRC32(); + crc.update( byteCode ); + outEntry.setCrc( crc.getValue() ); + outEntry.setCompressedSize( byteCode.length ); + } + out.putNextEntry( outEntry ); + out.write( byteCode ); + out.closeEntry(); + } + }; + ZipFileProcessor processor = new ZipFileProcessor( transformer ); + processor.process( file ); + out.close(); + } + finally{ + fout.close(); + } + + if ( file.delete() ) { + File newFile = new File( tempFile.getAbsolutePath() ); + if( !newFile.renameTo( file ) ) { + throw new IOException( "can not rename " + tempFile + " to " + file ); + } + } + else { + throw new IOException("can not delete " + file); + } + } + finally { + tempFile.delete(); + } + } + + protected boolean isBeingIntrumented(String className) { + logger.verbose( "checking to see if class [" + className + "] is set to be instrumented" ); + return classNames.contains( className ); + } + + protected abstract ClassDescriptor getClassDescriptor(byte[] byecode) throws Exception; + + protected abstract ClassTransformer getClassTransformer(ClassDescriptor descriptor); + + protected class CustomFieldFilter implements FieldFilter { + private final ClassDescriptor descriptor; + + public CustomFieldFilter(ClassDescriptor descriptor) { + this.descriptor = descriptor; + } + + public boolean shouldInstrumentField(String className, String fieldName) { + if ( descriptor.getName().equals( className ) ) { + logger.verbose( "accepting transformation of field [" + className + "." + fieldName + "]" ); + return true; + } + else { + logger.verbose( "not accepting transformation of field [" + className + "." + fieldName + "]" ); + return false; + } + } + + public boolean shouldTransformFieldAccess( + String transformingClassName, + String fieldOwnerClassName, + String fieldName) { + if ( descriptor.getName().equals( fieldOwnerClassName ) ) { + logger.verbose( "accepting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" ); + return true; + } + else if ( isExtended() && isBeingIntrumented( fieldOwnerClassName ) ) { + logger.verbose( "accepting extended transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" ); + return true; + } + else { + logger.verbose( "not accepting transformation of field access [" + fieldOwnerClassName + "." + fieldName + "]" ); + return false; + } + } + } + + protected class Logger { + public void verbose(String message) { + if ( verbose ) { + System.out.println( message ); + } + else { + log( message, Project.MSG_VERBOSE ); + } + } + + public void debug(String message) { + log( message, Project.MSG_DEBUG ); + } + + public void info(String message) { + log( message, Project.MSG_INFO ); + } + + public void warn(String message) { + log( message, Project.MSG_WARN ); + } + } + + + private static interface ZipEntryHandler { + public void handleEntry(ZipEntry entry, byte[] byteCode) throws Exception; + } + + private static class ZipFileProcessor { + private final ZipEntryHandler entryHandler; + + public ZipFileProcessor(ZipEntryHandler entryHandler) { + this.entryHandler = entryHandler; + } + + public void process(File file) throws Exception { + ZipInputStream zip = new ZipInputStream( new FileInputStream( file ) ); + + try { + ZipEntry entry; + while ( (entry = zip.getNextEntry()) != null ) { + byte bytes[] = ByteCodeHelper.readByteCode( zip ); + entryHandler.handleEntry( entry, bytes ); + zip.closeEntry(); + } + } + finally { + zip.close(); + } + } + } +} |