/*
* Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License, Version 1.0,
* which accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package net.rim.ejde.internal.preprocessing.hook;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import org.eclipse.osgi.baseadaptor.BaseData;
import org.eclipse.osgi.baseadaptor.HookConfigurator;
import org.eclipse.osgi.baseadaptor.HookRegistry;
import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry;
import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.eclipse.osgi.util.ManifestElement;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.osgi.framework.BundleException;
/**
* Hooks the classloading functionality for preprocessing functionality.
*/
public class PreprocessingClassLoadingHook implements ClassLoadingHook, HookConfigurator {
private static final String SOURCE_FILE_CLASS = "org.eclipse.jdt.internal.core.builder.SourceFile";
private static final String SOURCEMAPPER_PACKAGE_NAME = "net.rim.ejde.external.sourceMapper";
// Type reference to the IFile interface
private static Type IFILE_TYPE = Type.getType( "Lorg/eclipse/core/resources/IFile;" );
// Type reference to the JDT SourceFile class
private static Type SOURCEFILE_TYPE = Type.getType( "Lorg/eclipse/jdt/internal/core/builder/SourceFile;" );
// Type reference to the SourceMapperAccess type
private static String SOURCE_MAPPER_CLASS = "net.rim.ejde.external.sourceMapper.SourceMapperAccess";
private static Type SOURCEMAPPERACCESS_TYPE = Type.getType( "Lnet/rim/ejde/external/sourceMapper/SourceMapperAccess;" );
// name of the SourceMapperAccess.isHookCodeInstalled() method
private static final String IS_HOOK_INSTALLED_METHOD = "isHookCodeInstalled";
// name of the SourceFile.getContents() method
private static final String GET_CONTENTS_METHOD = "getContents";
// name of the SourceFile.resource field
private static final String RESOURCE_FIELD = "resource";
// name of the SourceMapperAccess.getMappedSourceFile() method
private static final String GET_MAPPED_SOURCE_FILE_METHOD = "getMappedSourceFile";
/**
* Class adapter for rewriting the <code>SourceFile#getContents</code> method.
*
*/
class SourceFileClassAdapter extends ClassAdapter {
/**
* Construct a new adapter instance.
*
* @param cv
*/
public SourceFileClassAdapter( ClassVisitor cv ) {
super( cv );
}
/**
* @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String,
* java.lang.String[])
*/
public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions ) {
MethodVisitor methodVisitor = super.visitMethod( access, name, desc, signature, exceptions );
if( name.equals( GET_CONTENTS_METHOD ) ) {
// log.debug("SourceFile#getContents spotted. Rewriting method."
// );
methodVisitor = new GetContentsMethodVisitor( methodVisitor );
}
return methodVisitor;
}
}
/**
* Class adapter for rewriting the SourceMapperAccess class
*/
class SourceMapperAccessClassAdapter extends ClassAdapter {
/**
* Construct a new adapter instance.
*
* @param cv
*/
public SourceMapperAccessClassAdapter( ClassVisitor cv ) {
super( cv );
}
/**
* @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String,
* java.lang.String[])
*/
public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions ) {
MethodVisitor methodVisitor = super.visitMethod( access, name, desc, signature, exceptions );
if( name.equals( IS_HOOK_INSTALLED_METHOD ) ) {
// log.trace(
// "SourceMapperAccess#isHookInstalled spotted. Rewriting method."
// );
methodVisitor = new IsHookInstalledMethodVisitor( methodVisitor );
}
return methodVisitor;
}
}
/**
* MethodVisitor implementation to rewrite the getContents method of the SourceFile class.
*/
class GetContentsMethodVisitor extends MethodAdapter {
boolean foundReturn = false;
public GetContentsMethodVisitor( MethodVisitor mv ) {
super( mv );
}
/**
* @see org.objectweb.asm.MethodAdapter#visitFieldInsn(int, java.lang.String, java.lang.String, java.lang.String)
*/
public void visitFieldInsn( int opcode, String owner, String name, String desc ) {
if( ( opcode == Opcodes.GETFIELD ) && ( name.equals( RESOURCE_FIELD ) ) && !foundReturn ) {
insertMappedResourceCode();
} else {
super.visitFieldInsn( opcode, owner, name, desc );
}
}
/**
* @see org.objectweb.asm.MethodAdapter#visitInsn(int)
*/
public void visitInsn( int opcode ) {
if( !foundReturn && ( opcode == Opcodes.ARETURN ) ) {
// log.trace("Found ARETURN in method.");
foundReturn = true;
}
super.visitInsn( opcode );
}
/**
* Insert the code that will attempt to request the mapped resource from the {@link SourceMapperTracker}.
*/
private void insertMappedResourceCode() {
// log.trace("Inserting mapped resource lookup code into method.");
// We want to rewrite the first GETFIELD call for the resource
// object as a call to the mapping function
Label endLabel = new Label();
// Call SourceMapperAccess#getMappedSourceFile(IFile) to get the
// source file
// log.trace("Rewritten SourceFile");
super.visitInsn( Opcodes.POP );
super.visitVarInsn( Opcodes.ALOAD, 0 ); // Load "this"
super.visitFieldInsn( Opcodes.GETFIELD, SOURCEFILE_TYPE.getInternalName(), RESOURCE_FIELD, IFILE_TYPE.getDescriptor() );
super.visitMethodInsn( Opcodes.INVOKESTATIC, SOURCEMAPPERACCESS_TYPE.getInternalName(),
GET_MAPPED_SOURCE_FILE_METHOD, Type.getMethodDescriptor( IFILE_TYPE, new Type[] { IFILE_TYPE } ) );
// If the result was not null, we are done
super.visitInsn( Opcodes.DUP );
super.visitJumpInsn( Opcodes.IFNONNULL, endLabel );
// otherwise we need to clear the extra copy and load the raw
// resource
// log.trace("Mapped resource was null");
super.visitInsn( Opcodes.POP );
// Load the local (raw) resource
// log.trace("Using raw resource");
super.visitVarInsn( Opcodes.ALOAD, 0 ); // Load "this"
super.visitFieldInsn( Opcodes.GETFIELD, SOURCEFILE_TYPE.getInternalName(), RESOURCE_FIELD, IFILE_TYPE.getDescriptor() );
// All done with the rewrite
visitLabel( endLabel );
// log.trace("Finished generated code");
}
}
/**
* MethodVisitor implementation to rewrite the isHookInstalled method of the SourceMapperAccess class.
*/
class IsHookInstalledMethodVisitor extends MethodAdapter {
public IsHookInstalledMethodVisitor( MethodVisitor mv ) {
super( mv );
}
public void visitInsn( int opcode ) {
if( opcode == Opcodes.ICONST_0 ) {
opcode = Opcodes.ICONST_1;
}
super.visitInsn( opcode );
}
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#addClassPathEntry(java.util.ArrayList, java.lang.String,
* org.eclipse.osgi.baseadaptor.loader.ClasspathManager, org.eclipse.osgi.baseadaptor.BaseData,
* java.security.ProtectionDomain)
*/
public boolean addClassPathEntry( ArrayList cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata,
ProtectionDomain sourcedomain ) {
return false;
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#createClassLoader(java.lang.ClassLoader,
* org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate, org.eclipse.osgi.framework.adaptor.BundleProtectionDomain,
* org.eclipse.osgi.baseadaptor.BaseData, java.lang.String[])
*/
public BaseClassLoader createClassLoader( ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain,
BaseData data, String[] bundleclasspath ) {
boolean isJdtCore = "org.eclipse.jdt.core".equals( data.getSymbolicName() );
boolean isEclipseBundleLoader = delegate instanceof BundleLoader;
// If the bundle loader is of the corrent type and is for
// the JDT core bundle, we will go ahead and force an OSGi "wire"
// back to our package implementation from the JDT plugin.
if( isEclipseBundleLoader && isJdtCore ) {
// // log.trace("Adding dynamic import into JDT Core bundle");
//
// try {
// ManifestElement[] dynamicElements = ManifestElement.parseHeader( "DynamicImport-Package",
// SOURCEMAPPER_PACKAGE_NAME );
// ( (BundleLoader) delegate ).addDynamicImportPackage( dynamicElements );
// } catch( BundleException e ) {
// // log.equals(e);
// }
// }
// CAS - The BundleLoader class has been refactored in the 3.5
// (Galileo)
// release to a new package. To avoid issues with class loading, the
// following drops into reflection to do the work.
ManifestElement[] dynamicElements = getSourceMapperManifestElements();
Class< ? extends ClassLoaderDelegate > clazz = delegate.getClass();
Method dynamicImportMethod = null;
try {
dynamicImportMethod = clazz.getMethod( "addDynamicImportPackage", dynamicElements.getClass() );
} catch( NoSuchMethodException e ) {
// Do nothing...
}
if( dynamicImportMethod != null ) {
try {
dynamicImportMethod.invoke( delegate, new Object[] { dynamicElements } );
} catch( Exception e ) {
e.printStackTrace();
}
}
}
// Let the framework know that we did not create the classloader
return null;
}
private static ManifestElement[] sourceMapperManifestElements;
/**
* Retrieve the dynamic manifest elements for use in linking in the source mapper functionality.
*
* @return
*/
private static ManifestElement[] getSourceMapperManifestElements() {
if( sourceMapperManifestElements == null ) {
try {
sourceMapperManifestElements = ManifestElement.parseHeader( "DynamicImport-Package", SOURCEMAPPER_PACKAGE_NAME );
} catch( BundleException e ) {
e.printStackTrace();
}
}
return sourceMapperManifestElements;
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#findLibrary(org.eclipse.osgi.baseadaptor.BaseData,
* java.lang.String)
*/
public String findLibrary( BaseData data, String libName ) {
// Nothing to do
return null;
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#getBundleClassLoaderParent()
*/
public ClassLoader getBundleClassLoaderParent() {
// Nothing to do
return null;
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#initializedClassLoader(org.eclipse.osgi.baseadaptor.loader.BaseClassLoader,
* org.eclipse.osgi.baseadaptor.BaseData)
*/
public void initializedClassLoader( BaseClassLoader baseClassLoader, BaseData data ) {
//
}
/**
* @see org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook#processClass(java.lang.String, byte[],
* org.eclipse.osgi.baseadaptor.loader.ClasspathEntry, org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry,
* org.eclipse.osgi.baseadaptor.loader.ClasspathManager)
*/
public byte[] processClass( String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry,
ClasspathManager manager ) {
byte[] processed = null;
if( SOURCE_FILE_CLASS.equals( name ) || SOURCE_MAPPER_CLASS.equals( name ) ) {
processed = rewriteSourceFileClass( name, classbytes );
}
return processed;
}
/**
* Rewrite the SourceFile class given the specified class bytes.
*
* @param classBytes
* @return
*/
private byte[] rewriteSourceFileClass( String name, byte[] classBytes ) {
byte[] rewritten = classBytes;
// log.trace(name + " located. Rewriting class bytes.");
// Use ASM to rewrite the SourceFile.getMethods call
ClassReader classReader = new ClassReader( classBytes );
ClassWriter classWriter = new ClassWriter( ClassWriter.COMPUTE_MAXS );
// ClassWriter classWriter = new ClassWriter(true);
ClassAdapter adapter = null;
if( SOURCE_FILE_CLASS.equals( name ) ) {
adapter = new SourceFileClassAdapter( classWriter );
} else {
adapter = new SourceMapperAccessClassAdapter( classWriter );
}
classReader.accept( adapter, ClassReader.SKIP_FRAMES );
// classReader.accept(adapter, false);
rewritten = classWriter.toByteArray();
return rewritten;
}
public void addHooks( HookRegistry hookRegistry ) {
hookRegistry.addClassLoadingHook( this );
}
}