All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.bytecode.invocation.SInvocation Maven / Gradle / Ivy

package jadex.bytecode.invocation;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import jadex.bytecode.IByteCodeClassLoader;
import jadex.bytecode.SASM;
import jadex.commons.SAccess;
import jadex.bytecode.vmhacks.VmHacks;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.collection.WeakKeyValueMap;

/**
 *  Factory for providing fast reflective access to methods.
 */
public class SInvocation
{
	/** Class name suffix counter. */ 
	public static AtomicLong NAME_SUFFIX_COUNTER = new AtomicLong();
	
	/** 
	 *  Flag if default / protected access via ASM is available.
	 *  Cannot be final due to bootstrapping. 
	 */
	public static boolean DEFAULT_ACCESS = false;
	
	/** 
	 *  Flag if private access via ASM is available.
	 *  Cannot be final due to bootstrapping.
	 */
	public static boolean PRIVATE_ACCESS = false;
	
	/** Cached invoker classes, the invoker class does not prevent GC (tested). */
	protected static volatile WeakHashMap> INVOKER_CLASSES =
		new WeakHashMap>();
	
	/** Cached accessor classes. */
	protected static volatile WeakHashMap, WeakKeyValueMap, Class>> ACCESSOR_CLASSES =
			new WeakHashMap, WeakKeyValueMap, Class>>();
	
	/** Cached extractor classes. */
	protected static volatile WeakHashMap, WeakKeyValueMap, Class>> EXTRACTOR_CLASSES =
			new WeakHashMap, WeakKeyValueMap, Class>>();
	
	// Ensure VmHacks is initialized.
	static
	{
		VmHacks.get();
		enableEnhancedAccess();
	}
	
	/**
	 *  Directly invokes a method based on the method name and arguments.
	 *  Method resolution is very basic and a performance penalty is incurred,
	 *  do not use if you plan on making repeated calls or need good method
	 *  resolution, use getInvoker() methods instead.
	 *  
	 *  @param obj Object on which the method is to be called.
	 *  @param methodname Name of the methods.
	 *  @param args Invocation arguments.
	 *  @return Return value of invocation.
	 */
	public static final Object invoke(Object obj, String methodname, Object... args)
	{
		return invoke(obj, null, methodname, args);
	}
	
	/**
	 *  Directly invokes a method based on the method name and arguments.
	 *  Method resolution is very basic and a performance penalty is incurred,
	 *  do not use if you plan on making repeated calls or need good method
	 *  resolution, use getInvoker() methods instead.
	 *  
	 *  @param obj Object on which the method is to be called.
	 *  @param clazz Class definition for static calls, can be null if obj is defined.
	 *  @param methodname Name of the method.
	 *  @param args Invocation arguments.
	 *  @return Return value of invocation.
	 */
	public static final Object invoke(Object obj, Class clazz, String methodname, Object... args)
	{
		clazz = clazz == null ? obj.getClass() : clazz;
		Method[] methods = SReflect.getAllMethods(clazz, methodname);
		if (methods.length == 1)
			return newInvoker(methods[0]).invoke(obj, args);
		
		int argcount = args != null ? args.length : 0;
		for (int i = 0; i < methods.length; ++i)
		{
			if (methods[i].getParameterTypes().length == argcount)
				return newInvoker(methods[i]).invoke(obj, args);
		}
		throw new IllegalArgumentException("No unambiguous method + " + methodname + " found, try " + SInvocation.class.getName() + "getInvoker() methods.");
	}
	
	/**
	 *  Creates a new invoker for a method.
	 * 
	 *  @param method The method.
	 *  @return Instantiated invoker.
	 */
	public static final IMethodInvoker newInvoker(Method method)
	{
		Class ic = getInvokerClass(method);
		if (ic == null)
			return new FallBackInvoker(method);
		
		return newInvoker(ic);
	}
	
	/**
	 *  Creates a new invoker for a method.
	 * 
	 *  @param method The method.
	 *  @param cl ClassLoader to use.
	 *  @return Instantiated invoker.
	 */
	public static final IMethodInvoker newInvoker(Method method, IByteCodeClassLoader cl)
	{
		Class ic = createInvokerClass(cl, method);
		if (ic == null)
			return new FallBackInvoker(method);
		
		return newInvoker(ic);
	}
	
	/**
	 *  Instantiate a new method invoker from the invoker class.
	 *  
	 *  @param invokerclass The invoker class.
	 *  @return Instantiated invoker.
	 */
	protected static final IMethodInvoker newInvoker(Class invokerclass)
	{
		try
		{
			Constructor c = invokerclass.getConstructor((Class[]) null);
			return (IMethodInvoker) c.newInstance((Object[]) null);
		}
		catch (Exception e)
		{
			throw SUtil.throwUnchecked(e);
		}
	}
	
	/**
	 *  Gets an invoker class.
	 *  
	 *  @param clazz The class used to map methods.
	 *  @param methodidmap The method ID map to store the mapping.
	 *  @return The generated invoker.
	 */
	public static final Class getInvokerClass(Method method)
	{
		Class ic = INVOKER_CLASSES.get(method);
		
		if (ic == null)
		{
			if (INVOKER_CLASSES.containsKey(method))
				return null;
			
			synchronized(NAME_SUFFIX_COUNTER)
			{
				ic = INVOKER_CLASSES.get(method);
				if (ic == null)
				{
					ClassLoader cl = method.getDeclaringClass().getClassLoader();
					
					IByteCodeClassLoader bcl = SASM.getByteCodeClassLoader(cl);
					
					ic = createInvokerClass(bcl, method);
					
					WeakHashMap> newgenclasses = new WeakHashMap>(INVOKER_CLASSES);
					newgenclasses.put(method, ic);
					INVOKER_CLASSES = newgenclasses;
				}
			}
			
		}
		
		return ic;
	}
	
	/**
	 *  Creates the invoker class.
	 *  
	 *  @param cl ClassLoader to use for generated class.
	 *  @param clazz The class used to map methods.
	 *  @return The generated invoker.
	 */
	@SuppressWarnings("unchecked")
	protected static final Class createInvokerClass(IByteCodeClassLoader cl, Method method)
	{
		Class ret = null;
		try
		{
			Class clazz = method.getDeclaringClass();
			
			// Check ClassLoader validity
			if (!clazz.equals(cl.loadClass(clazz.getName())))
				throw new IllegalArgumentException("Code generation classloader " + cl + " does not have access to class " + clazz + " defined in method " + method.getName());
			
//			boolean notpublic = (method.getModifiers() & Modifier.PUBLIC) == 0;
			boolean isstatic = (method.getModifiers() & Modifier.STATIC) != 0;
			
			String classname = "MethodInvoker_" + method.getName() + "_" + NAME_SUFFIX_COUNTER.incrementAndGet();
			int accesslevel = determineAccessLevel(Opcodes.ACC_PUBLIC, method.getModifiers());
			ExtendedClassWriter cw = createClass(clazz, classname, accesslevel, IMethodInvoker.class);
			if (cw == null)
				return null;
			// Implement the invoke method.
			Method invmethod = IMethodInvoker.class.getMethod("invoke", new Class[] { Object.class, Object[].class });
			MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, invmethod.getName(), Type.getMethodDescriptor(invmethod), null, null);
	        mv.visitCode();
//			System.out.println("" + ": " + method.getName());
			
	        // If not static, load the object, cast to right type, ready the parameters, then invoke.
			if (!isstatic)
			{
				mv.visitVarInsn(Opcodes.ALOAD, 1);
				mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(clazz));
			}
			
			// Prepare method parameters
			prepareParameters(mv, method.getParameterTypes());
			
			// Invoke static if static method.
			if (isstatic)
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(method.getDeclaringClass()), method.getName(), Type.getMethodDescriptor(method), false);
			else
				mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(method.getDeclaringClass()), method.getName(), Type.getMethodDescriptor(method), false);
			
			// Wrap primitive return value.
			if (SReflect.isBasicType(method.getReturnType()) && !void.class.equals(method.getReturnType()))
			{
				Class wt = SReflect.getWrappedType(method.getReturnType());
				Method wm = wt.getMethod("valueOf", method.getReturnType());
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(wt), wm.getName(), Type.getMethodDescriptor(wm), false);
			}
			
			// If return type is void, return null
			if (void.class.equals(method.getReturnType()))
				mv.visitInsn(Opcodes.ACONST_NULL);
			
			// Return
			mv.visitInsn(Opcodes.ARETURN);
			
			mv.visitMaxs(0, 0);
			mv.visitEnd();
			
			cw.visitEnd();
			byte[] classcode = cw.toByteArray();
//			System.out.println("INV CL: " + cl);
			if (cw.requiresParentLoader())
				ret = (Class) cl.doDefineClassInParent(null, classcode, 0, classcode.length, clazz.getProtectionDomain());
			else	
				ret = (Class) cl.doDefineClass(classcode);
//			ret = (Class) SASM.UNSAFE.defineClass(null, classcode, 0, classcode.length, ClassLoader.getSystemClassLoader(), null);
//			ret = (Class) SASM.NATIVE_HELPER.defineClass(null, classcode, ClassLoader.getSystemClassLoader());
		}
		catch (Exception e)
		{
			SUtil.throwUnchecked(e);
		}
		return ret;
	}
	
	/**
	 *  Creates a new accessor from an accessor class.
	 * 
	 *  @param accessorclass The accessor class.
	 *  @param delegate The delegation object / accessor target.
	 *  @return Instantiated accessor.
	 */
	public static final  T newAccessor(Class iface, Class targetclass, Object delegate)
	{
		Class accessorclass = getAccessorClass(iface, targetclass);
		T ret = null;
		if (accessorclass != null)
		{
			try
			{
				@SuppressWarnings("unchecked")
				Constructor c = (Constructor) accessorclass.getConstructor((Class[]) null);
				ret = c.newInstance((Object[]) null);
				Field f = accessorclass.getDeclaredField("delegate");
				f.set(ret, delegate);
			}
			catch (Exception e)
			{
				SUtil.throwUnchecked(e);
			}
		}
		else
		{
			ret = createFallbackAccessor(iface, targetclass, delegate);
		}
		return ret;
	}
	
	/**
	 *  Gets class for an interface-based accessor.
	 *  
	 *  @param iface The accessor interface.
	 *  @param targetclazz The target class.
	 *  @return The accessor class.
	 */
	@SuppressWarnings("unchecked")
	public static final  Class getAccessorClass(Class iface, Class targetclazz)
	{
		Class ac = null;
		WeakKeyValueMap, Class> map = ACCESSOR_CLASSES.get(targetclazz);
		if (map != null)
			ac = map.get(iface);
		
		if (ac == null && (map == null || !map.containsKey(iface)))
		{
			synchronized(NAME_SUFFIX_COUNTER)
			{
				map = ACCESSOR_CLASSES.get(targetclazz);
				if (map != null)
					ac = map.get(iface);
				
				if (ac == null)
				{
					if (map == null)
						map = new WeakKeyValueMap, Class>();
					
					ClassLoader cl = targetclazz.getClassLoader();
					
					IByteCodeClassLoader bcl = SASM.getByteCodeClassLoader(cl);
					
					ac = createAccessorClass(bcl, iface, targetclazz);
					
					map.put(iface, ac);
					WeakHashMap, WeakKeyValueMap, Class>> newgenclasses =
							new WeakHashMap, WeakKeyValueMap, Class>>(ACCESSOR_CLASSES);
					newgenclasses.put(targetclazz, map);
					ACCESSOR_CLASSES = newgenclasses;
				}
			}
		}
		
		return (Class) ac;
	}
	
	/**
	 *  Generates an accessor class based on an interface.
	 *  Methods between the interface and the target class
	 *  are matched.
	 *  
	 *  @param cl The ClassLoader used to load the generated byte code.
	 *  @param iface The accessor interface.
	 *  @param clazz The target class of the accessor.
	 *  @return The generated class.
	 */
	@SuppressWarnings("unchecked")
	public static final  Class createAccessorClass(IByteCodeClassLoader cl, Class iface, Class clazz)
	{
		if (iface == null || !iface.isInterface())
			throw new IllegalArgumentException("Class is not an interface: " + iface);
		
		Method[] ifacemethods = SReflect.getAllMethods(iface);
		
		int accesslevel = Opcodes.ACC_PUBLIC;
		Method[] targets = new Method[ifacemethods.length];
		for (int i = 0; i < ifacemethods.length; ++i)
		{
			Method[] cms = SReflect.getAllMethods(clazz, ifacemethods[i].getName());
			
			Class[][] paramtypes = new Class[cms.length][];
			for (int j = 0; j < cms.length; ++j)
				paramtypes[j] = cms[j].getParameterTypes();
			
			int[] match = SReflect.matchArgumentTypes(ifacemethods[i].getParameterTypes(), paramtypes);
			
			if (match == null || match.length == 0)
				throw new IllegalArgumentException("No match found for interface method " + ifacemethods[i]);
			
			targets[i] = cms[match[0]];
			accesslevel = determineAccessLevel(accesslevel, targets[i].getModifiers());
		}
		
		String classname = SInvocation.class.getPackage().getName() + ".accessors.ClassAccessor_" + clazz.getName() + "_" + NAME_SUFFIX_COUNTER.incrementAndGet();
		ExtendedClassWriter cw = createClass(clazz, classname, accesslevel, iface);
		if (cw == null)
			return null;
		
		String internalname = cw.getInternalName();
		cw.visitField(Opcodes.ACC_PUBLIC, "delegate", Type.getDescriptor(clazz), null, null);
		
		for (int i = 0; i < ifacemethods.length; ++i)
		{
			Method invmethod = ifacemethods[i];
			MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, invmethod.getName(), Type.getMethodDescriptor(invmethod), null, null);
	        mv.visitCode();
	        
	        if ((invmethod.getModifiers() & Modifier.STATIC) == 0)
	        {
	        	mv.visitVarInsn(Opcodes.ALOAD, 0);
	        	mv.visitFieldInsn(Opcodes.GETFIELD, internalname, "delegate", Type.getDescriptor(clazz));
	        }
	        
	        int aload = 0;
	        Class[] paramtypes = targets[i].getParameterTypes();
	        for (int j = 0; j < paramtypes.length; ++j)
	        {
	        	if (byte.class.equals(paramtypes[j]) ||
	        		short.class.equals(paramtypes[j]) ||
	        		char.class.equals(paramtypes[j]) ||
	        		boolean.class.equals(paramtypes[j]) ||
	        		int.class.equals(paramtypes[j]))
	        	{
	        		mv.visitVarInsn(Opcodes.ILOAD, ++aload);
	        	}
	        	else if (long.class.equals(paramtypes[j]))
	        	{
	        		mv.visitVarInsn(Opcodes.LLOAD, ++aload);
	        		++aload;
	        	}
	        	else if (float.class.equals(paramtypes[j]))
	        	{
	        		mv.visitVarInsn(Opcodes.FLOAD, ++aload);
	        	}
	        	else if (double.class.equals(paramtypes[j]))
	        	{
	        		mv.visitVarInsn(Opcodes.DLOAD, ++aload);
	        		++aload;
	        	}
	        	else
	        	{
	        		mv.visitVarInsn(Opcodes.ALOAD, ++aload);
	        	}
	        }
	        
			if ((targets[i].getModifiers() & Modifier.STATIC) != 0)
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(targets[i].getDeclaringClass()), targets[i].getName(), Type.getMethodDescriptor(targets[i]), false);
			else
				mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(targets[i].getDeclaringClass()), targets[i].getName(), Type.getMethodDescriptor(targets[i]), false);
			
			
			InsnList insn = new InsnList();
			SASM.makeReturn(insn, Type.getType(invmethod.getReturnType()));
			insn.accept(mv);
	        
	        mv.visitMaxs(0, 0);
	        mv.visitEnd();
		}
		
		try
		{
			cw.visitEnd();
			byte[] classcode = cw.toByteArray();
			Class genclass = null;
			if (cw.requiresParentLoader())
			{
				genclass = (Class) cl.doDefineClassInParent(null, classcode, 0, classcode.length, clazz.getProtectionDomain());
			}
			else
			{
				genclass = (Class) cl.doDefineClass(classcode);
			}
			
			return genclass;
		}
		catch (Exception e)
		{
			throw SUtil.throwUnchecked(e);
		}
	}
	
	public static final IExtractor newExtractor(Class extractorclass)
	{
		try
		{
			System.out.println(extractorclass);
			Constructor c = extractorclass.getConstructor((Class[]) null);
			return c.newInstance((Object[]) null);
		}
		catch (Exception e)
		{
			throw SUtil.throwUnchecked(e);
		}
	}
	
	/**
	 *  Creates extractor class.
	 *  
	 *  @param cl ClassLoader to use for generated class.
	 *  @param clazz The class used to map methods.
	 *  @return The generated invoker.
	 */
	@SuppressWarnings("unchecked")
	public static final Class createExtractorClass(IByteCodeClassLoader cl, Class clazz, String[] propnames, Member[] accessormember)
	{
		if (propnames.length != accessormember.length)
			throw new IllegalArgumentException("Number of properties and methods must match.");
		
		int accesslevel = Opcodes.ACC_PUBLIC;
		for (int i = 0; i < accessormember.length; ++i)
			accesslevel = determineAccessLevel(accesslevel, accessormember[i].getModifiers());
		
		String classnamesuffix = "ClassAccessor_" + clazz.getName() + "_" + NAME_SUFFIX_COUNTER.incrementAndGet();
		ExtendedClassWriter cw = createClass(clazz, classnamesuffix, accesslevel, IExtractor.class);
		String internalname = cw.getInternalName();
		
		cw.visitField(Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, "PROPERTYNAMES", Type.getDescriptor(String[].class), null, null);
		MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", Type.getMethodDescriptor(Type.getType(void.class), new Type[0]), null, null);
		InsnList nl = new InsnList();
		SASM.pushImmediate(nl, propnames.length);
		nl.add(new TypeInsnNode(Opcodes.ANEWARRAY, Type.getInternalName(String.class)));
		for (int i = 0; i < propnames.length; ++i)
		{
			nl.add(new InsnNode(Opcodes.DUP));
			SASM.pushImmediate(nl, i);
			nl.add(new LdcInsnNode(propnames[i]));
			nl.add(new InsnNode(Opcodes.AASTORE));
		}
		nl.add(new FieldInsnNode(Opcodes.PUTSTATIC, internalname, "PROPERTYNAMES", Type.getDescriptor(String[].class)));
		nl.add(new InsnNode(Opcodes.RETURN));
		nl.accept(mv);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
		
		try
		{
			Method exmethod = IExtractor.class.getMethod("extract", new Class[] { Object.class });
			mv = cw.visitMethod(Opcodes.ACC_PUBLIC, exmethod.getName(), Type.getMethodDescriptor(exmethod), null, null);
	        mv.visitCode();
	        
	        mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(Tuple2.class));
			mv.visitInsn(Opcodes.DUP);
			
			mv.visitFieldInsn(Opcodes.GETSTATIC, internalname, "PROPERTYNAMES", Type.getDescriptor(String[].class));
			
			nl = new InsnList();
			SASM.pushImmediate(nl, propnames.length);
			nl.add(new TypeInsnNode(Opcodes.ANEWARRAY, Type.getInternalName(Object.class)));
			for (int i = 0; i < propnames.length; ++i)
			{
				nl.add(new InsnNode(Opcodes.DUP));
				SASM.pushImmediate(nl, i);
				if (accessormember[i] instanceof Method)
				{
					Method accessormethod = (Method) accessormember[i];
					nl.add(new VarInsnNode(Opcodes.ALOAD, 1));
					nl.add(new TypeInsnNode(Opcodes.CHECKCAST, Type.getInternalName(clazz)));
					nl.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getInternalName(accessormethod.getDeclaringClass()), accessormethod.getName(), Type.getMethodDescriptor(accessormethod), false));
				}
				else if (accessormember[i] instanceof Field)
				{
					Field accessorfield = (Field) accessormember[i];
					nl.add(new FieldInsnNode(Opcodes.GETFIELD, Type.getInternalName(accessorfield.getDeclaringClass()), accessorfield.getName(), Type.getDescriptor(accessorfield.getType())));
				}
				else
				{
					throw new IllegalArgumentException("Illegal accessor member: " + accessormember[i]);
				}
				nl.add(new InsnNode(Opcodes.AASTORE));
			}
			nl.accept(mv);
			
			mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Tuple2.class), "", Type.getConstructorDescriptor(Tuple2.class.getConstructor(Object.class, Object.class)), false);
			mv.visitInsn(Opcodes.ARETURN);
			mv.visitMaxs(0, 0);
			mv.visitEnd();
		}
		catch (Exception e)
		{
			SUtil.throwUnchecked(e);
		}
		
		try
		{
			cw.visitEnd();
			byte[] classcode = cw.toByteArray();
			Class genclass = null;
			if (cw.requiresParentLoader())
			{
				genclass = (Class) cl.doDefineClassInParent(null, classcode, 0, classcode.length, clazz.getProtectionDomain());
			}
			else
			{
				genclass = (Class) cl.doDefineClass(classcode);
			}
			
			return genclass;
		}
		catch (Exception e)
		{
			throw SUtil.throwUnchecked(e);
		}
	}
	
	/**
	 *  Creates the initial setup for a new class in ASM.
	 *  
	 *  @param classname Simple name of the class.
	 *  @param privileged If the class should be "privileged" to allow
	 *  				  access to non-publics.
	 *  @return Preinitialized class writer.
	 */
	protected static final ExtendedClassWriter createClass(Class targetclass, String classname, int accesslevel, Class... interfaces)
	{
		if (!VmHacks.get().hasAsm())
			return null;
		
		Class superclass = Object.class;
		String genpackage = SInvocation.class.getPackage().getName() + ".generated";
//		accesslevel = Opcodes.ACC_PUBLIC;
		boolean needsparentcl = false;
		if (accesslevel == Opcodes.ACC_PRIVATE || (accesslevel != Opcodes.ACC_PUBLIC && PRIVATE_ACCESS))
		{
			if (!PRIVATE_ACCESS)
				return null;
			
			try
			{
				Class reffacclass = Class.forName("sun.reflect.ReflectionFactory");
				superclass = Class.forName("sun.reflect.MagicAccessorImpl", true, reffacclass.getClassLoader());
			}
			catch (Exception e)
			{
				return null;
			}
		}
		else if (accesslevel != Opcodes.ACC_PUBLIC)
		{
			if (!DEFAULT_ACCESS)
				return null;
			
			// At least protected, inject into the package...
			genpackage = targetclass.getPackage().getName();
			//additional suffix to avoid clash
			classname += "_" + Math.abs(SUtil.FAST_RANDOM.nextLong());
			needsparentcl = true;
		}
		
		String[] internalifaces = interfaces != null ? new String[interfaces.length] : new String[0];
		for (int i = 0; i < internalifaces.length; ++i)
			internalifaces[i] = Type.getType(interfaces[i]).getInternalName();
		
		// Create class implementing the handler.
		final String internalname = (genpackage + "." + classname).replace('.', '/');
		ExtendedClassWriter cw = new ExtendedClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES, internalname, needsparentcl);
		
		cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, internalname, null, Type.getType(superclass).getInternalName(), internalifaces);
		
		// Create empty constructor for our invoker handler.
		MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", Type.getMethodDescriptor(Type.getType(void.class), new Type[0]), null, null);
		mv.visitCode();
		mv.visitVarInsn(Opcodes.ALOAD, 0);
		try
		{
			mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "", Type.getConstructorDescriptor(Object.class.getConstructor((Class[])null)), false);
		}
		catch (Exception e)
		{
			return null;
		}
		mv.visitInsn(Opcodes.RETURN);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
		
		return cw;
	}
	
	/**
	 *  Readies the parameters for delegated method invocation.
	 *  Converts primitives as appropriate.
	 *  
	 *  @param mv The MethodVisitor being used.
	 *  @param parameters The parameters of the method.
	 */
	protected static final void prepareParameters(MethodVisitor mv, Class[] parameters)
	{
		for (int i = 0; i < parameters.length; ++i)
		{
			// Load reference of argument array
			mv.visitVarInsn(Opcodes.ALOAD, 2);
			
			// Select index of parameter in array.
			InsnList nl = new InsnList();
			SASM.pushImmediate(nl, i);
			nl.accept(mv);
			
			// Load parameter onto stack
			mv.visitInsn(Opcodes.AALOAD);
			
			// If the actual parameter is a primitive, we need to do some conversion...
			if (SReflect.isBasicType(parameters[i]))
			{
				Class cc = null;
				Method cm = null;
				try
				{
					if (parameters[i].equals(boolean.class))
					{
						cc = Boolean.class;
						cm = cc.getMethod("booleanValue", new Class[0]);
					}
					else if (parameters[i].equals(int.class))
					{
						cc = Integer.class;
						cm = cc.getMethod("intValue", new Class[0]);
					}
					else if (parameters[i].equals(double.class))
					{
						cc = Double.class;
						cm = cc.getMethod("doubleValue", new Class[0]);
					}
					else if (parameters[i].equals(float.class))
					{
						cc = Float.class;
						cm = cc.getMethod("floatValue", new Class[0]);
					}
					else if (parameters[i].equals(long.class))
					{
						cc = Long.class;
						cm = cc.getMethod("longValue", new Class[0]);
					}
					else if (parameters[i].equals(short.class))
					{
						cc = Short.class;
						cm = cc.getMethod("shortValue", new Class[0]);
					}
					else if (parameters[i].equals(byte.class))
					{
						cc = Byte.class;
						cm = cc.getMethod("byteValue", new Class[0]);
					}
					else if (parameters[i].equals(char.class))
					{
						cc = Character.class;
						cm = cc.getMethod("charValue", new Class[0]);
					}
				}
				catch (Exception e)
				{
					SUtil.throwUnchecked(e);
				}
				
				// Convert wrapped parameter to primitive.
				mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(cc));
				mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(cc), cm.getName(), Type.getMethodDescriptor(cm), false);
			}
			else if (!Object.class.equals(parameters[i]))
			{
				// Cast parameter since argument array is type Object[]
				mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(parameters[i]));
			}
		}
	}
	
	/**
	 *  Determines the necessary access level based on the current access level.
	 *   
	 *  @param currentlevel The current level.
	 *  @param modifiers Modifiers of the target
	 *  @return Needed access level.
	 */
	protected static final int determineAccessLevel(int currentlevel, int modifiers)
	{
		if ((modifiers & Modifier.PUBLIC) == 0)
		{
			if ((modifiers & Modifier.PRIVATE) != 0)
				currentlevel = Opcodes.ACC_PRIVATE;
			else if ((modifiers & Modifier.PROTECTED) != 0 && currentlevel == Opcodes.ACC_PUBLIC)
				currentlevel = Opcodes.ACC_PROTECTED;
			else if (currentlevel == Opcodes.ACC_PUBLIC)
				currentlevel = 0;
		}
		return currentlevel;
	}
	
	/**
	 *  Implements an accessor based on a dynamic proxy.
	 *  
	 *  @param cl ClassLoader to use.
	 *  @param iface The interface to implement.
	 *  @param clazz The target class.
	 *  @param obj The target class or null if all static.
	 *  @return Accessor.
	 */
	protected static final  T createFallbackAccessor(final Class iface, final Class clazz, final Object obj)
	{
		InvocationHandler handler = new InvocationHandler()
		{
			protected Map invocationmap;
			{
				invocationmap = new HashMap();
				try
				{
					Method[] methods = iface.getMethods();
					for (Method method : methods)
					{
						Class cclazz = clazz;
						Method cmethod = null;
						while (cmethod == null && cclazz != null)
						{
							cmethod = cclazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
							cclazz = cclazz.getSuperclass();
						}
						SReflect.getMethod(clazz, method.getName(), method.getParameterTypes());
						invocationmap.put(method, newInvoker(cmethod));
					}
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
			{
				IMethodInvoker invoker = invocationmap.get(method);
				return invoker.invoke(obj, args);
			}
		};
		
		ClassLoader cl = clazz.getClassLoader();
		@SuppressWarnings("unchecked")
		T ret = (T) Proxy.newProxyInstance(cl, new Class[] { iface }, handler);
		return ret;
	}
	
	/**
	 *  Tries to enable enhanced direct access.
	 */
	protected static final void enableEnhancedAccess()
	{
		AccessTestClass testobj = new AccessTestClass();
		DEFAULT_ACCESS = true;
		DEFAULT_ACCESS = hasMethodAccess("defaultTest", testobj);
		PRIVATE_ACCESS = true;
		PRIVATE_ACCESS = hasMethodAccess("privateTest", testobj);
	}
	
	/**
	 *  Tests if access to a method using the invocation API is possible.
	 *  
	 *  @param testmethod Name of the test method.
	 *  @param testobject Test object with method.
	 *  @return True, if accessible.
	 */
	private static final boolean hasMethodAccess(String testmethod, Object testobject)
	{
		IByteCodeClassLoader dummycl = SASM.createByteCodeClassLoader(null, AccessTestClass.class.getClassLoader());
		Method cicm = null;
		try
		{
			cicm = SInvocation.class.getDeclaredMethod("createInvokerClass", new Class[] { IByteCodeClassLoader.class, Method.class });
		}
		catch (Exception e)
		{
			return false;
		}
		SAccess.setAccessible(cicm, true);
		boolean acc = false;
		try
		{
			Method m = testobject.getClass().getDeclaredMethod(testmethod, (Class[]) null);
			Class invclass = (Class) cicm.invoke(null, dummycl, m);
			if (invclass != null)
			{
				Constructor c = invclass.getConstructor((Class[]) null);
				IMethodInvoker inv = (IMethodInvoker) c.newInstance((Object[]) null);
				inv.invoke(testobject, (Object[]) null);
				acc = true;
			}
		}
		catch (Throwable t)
		{
			return false;
		}
		return acc;
	}
	
	/**
	 *  Class writer with some meta information.
	 *
	 */
	protected static class ExtendedClassWriter extends ClassWriter
	{
		/** Class internal name. */
		protected String internalname;
		
		/** Flag whether the resulting class requires the class loader parent. */
		protected boolean requiresparentloader;
		
		/**
		 *  Creates the writer.
		 *  
		 *  @param flags ClassWriter flags.
		 *  @param internalname Class internal name.
		 *  @param requiresparentloader Flag whether the resulting class requires the class loader parent.
		 */
		public ExtendedClassWriter(int flags, String internalname, boolean requiresparentloader)
		{
			super(flags);
			this.internalname = internalname;
			this.requiresparentloader = requiresparentloader;
		}
		
		/**
		 *  Gets the internal name.
		 * @return The internal name.
		 */
		public String getInternalName()
		{
			return internalname;
		}
		
		/**
		 *  Returns flag whether the resulting class requires the class loader parent
		 *  
		 *  @return Flag whether the resulting class requires the class loader parent
		 */
		public boolean requiresParentLoader()
		{
			return requiresparentloader;
		}
	}
	
	protected static class SortingInjectorWrapper implements IInjector
	{
		/**
		 *  Injects properties into a bean.
		 *  
		 *  @param object The target bean object.
		 *  @param properties The bean properties, names followed by values,
		 *  				  size must be even.
		 */
		public void inject(Object object, Object... properties)
		{
			if (properties.length < 50)
			{
				for (int i = 0; i < properties.length; i = i + 2)
				{
					
				}
			}
		}
		
		
	}
	
	/**
	 *  Fallback invoker using reflection in case a byte-engineered variant is not available.
	 *
	 */
	protected static class FallBackInvoker implements IMethodInvoker
	{
		/** The method. */
		protected Method method;
		
		/**
		 *  Creates the invoker.
		 *  
		 *  @param method Method to invoke.
		 */
		public FallBackInvoker(Method method)
		{
//			System.err.println("WARNING FALLBACK MODE ENABLED");
			SAccess.setAccessible(method, true);
			this.method = method;
		}
		
		
		/**
		 *  Invokes a method on an object.
		 *  
		 *  @param object The object
		 *  @param methodid The ID of the method.
		 *  @param args The method arguments.
		 *  @return The result, null if void.
		 */
		public Object invoke(Object object, Object... args)
		{
			try
			{
				return method.invoke(object, args);
			}
			catch (Exception e)
			{
				throw SUtil.throwUnchecked(e);
			}
		}
	}
	
	/**
	 *  Class used to test access level via ASM.
	 *
	 */
	public static class AccessTestClass
	{
		/**
		 *  Used to test default access privileges.
		 */
		protected Object defaultTest()
		{
			return privateTest();
		}
		
		/**
		 *  Used to test private access privileges.
		 */
		private Object privateTest()
		{
			return null;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy