Click here to Skip to main content
15,896,398 members
Articles / Programming Languages / Java / Java SE

Delegates, Events and Pass by ref in Java using bytecode Manipulation

Rate me:
Please Sign up or sign in to vote.
4.85/5 (4 votes)
15 May 2012CPOL2 min read 22K   204   5  
Support for Delegates, Events and Pass by ref in Java using bytecode manipulation
package csharp;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.CtPrimitiveType;
import javassist.Modifier;
import javassist.NotFoundException;

public abstract class Delegate {

	private static final Map<Integer, Class<? extends Delegate>> instanceDelegates = new ConcurrentHashMap<Integer, Class<? extends Delegate>>();
	private static final Map<Integer, Delegate> staticDelegates = new ConcurrentHashMap<Integer, Delegate>();

	protected Object object = null;

	protected Delegate() {

	}

	public static <T extends Delegate> T getInstance(
			Class<? extends Delegate> delClazz, Class clazz, String name) {
		try {
			int key = Math
					.abs((delClazz.getCanonicalName() + "_" + clazz.getCanonicalName() + "_static")
							.hashCode());
			Delegate delegate = staticDelegates.get(key);
			if (delegate == null) {
				Class<? extends Delegate> actualClazz = createClass(delClazz,
						name, key, clazz, true);
				delegate = actualClazz.newInstance();
				staticDelegates.put(key, delegate);
			}
			return (T) delegate;
		} catch (InstantiationException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (SecurityException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static <T extends Delegate> T getInstance(
			Class<? extends Delegate> delClazz, Object object, String name) {
		try {
			int key = Math.abs((delClazz.getCanonicalName() + "_"
					+ object.getClass().getCanonicalName() + "_instance").hashCode());
			Class<? extends Delegate> actualClazz = instanceDelegates.get(key);
			if (actualClazz == null) {
				actualClazz = createClass(delClazz, name, key,
						object.getClass(), false);
				instanceDelegates.put(key, actualClazz);
			}
			Delegate delegate = actualClazz.newInstance();
			delegate.object = object;
			return (T) delegate;
		} catch (InstantiationException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (SecurityException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	private static Class<? extends Delegate> createClass(
			Class<? extends Delegate> delClazz, String name, int key,
			Class clazz, boolean isStatic) throws SecurityException,
			NoSuchMethodException {
		try {
			Class<? extends Delegate> actualClazz;
			ClassPool pool = ClassPool.getDefault();
			CtClass newClass = pool.makeClass(clazz.getPackage().getName()
					+ ".D" + key);
			CtClass sup = pool.get(delClazz.getCanonicalName());
			newClass.setSuperclass(sup);
			Method[] methods = delClazz.getDeclaredMethods();

			Method method = null;
			for (Method m : methods) {
				if (m.getName().equals("invoke")) {
					if (method != null) {
						throw new IllegalStateException(delClazz.getCanonicalName()
								+ " contains two or more invoke methods.");
					}

					method = m;
					if (m.getModifiers() != 1025) {
						throw new IllegalStateException(
								delClazz.getCanonicalName()
										+ " required invoke method as 'public abstract ");
					}
				}
			}

			if (method == null) {
				throw new NullPointerException(delClazz.getCanonicalName()
						+ " does not have invoke method.");
			}

			StringBuilder params = new StringBuilder();
			StringBuilder values = new StringBuilder();
			StringBuilder paramTypes = new StringBuilder();

			Class[] parameterTypes = method.getParameterTypes();

			int i = 97;
			for (Class parameterType : parameterTypes) {
				if (params.length() != 0) {
					params.append(",");
					values.append(",");
					paramTypes.append(",");
				}
				params.append(parameterType.getCanonicalName() + " "
						+ new Character((char) i));
				values.append(new Character((char) i));
				paramTypes.append(parameterType.getCanonicalName() + ".class");
				i++;
			}

			Method objectMethod = clazz.getDeclaredMethod(name, parameterTypes);

			StringBuilder exceptions = new StringBuilder();
			if (!Modifier.isPrivate(objectMethod.getModifiers())) {
				Class[] exceptionTypes = method.getExceptionTypes();
				for (Class exceptionType : exceptionTypes) {
					if (exceptions.length() != 0) {
						exceptions.append(",");
					}
					exceptions.append(exceptionType.getCanonicalName());
				}
			} else {
				exceptions.append("java.lang.Exception");
			}

			String body = "public "
					+ method.getReturnType().getCanonicalName()
					+ " invoke("
					+ params.toString()
					+ ")"
					+ ((exceptions.length() != 0) ? " throws "
							+ exceptions.toString() : "") + " {";

			if (!Modifier.isPrivate(objectMethod.getModifiers())) {
				body += ((!method.getReturnType().getCanonicalName()
						.equals("void")) ? "return " : "")
						+ ((isStatic) ? clazz.getCanonicalName() : "(("
								+ clazz.getCanonicalName() + ")object)")
						+ "."
						+ name
						+ "(" + values.toString() + ");";
			} else {
				StringBuilder ps = new StringBuilder(
						"java.util.List ps = new java.util.ArrayList();");

				if (parameterTypes.length != 0) {
					for (String s : paramTypes.toString().split(",")) {
						ps.append("ps.add(" + s + ");");
					}
				}

				StringBuilder vs = new StringBuilder(
						"java.util.List vs = new java.util.ArrayList();");

				if (parameterTypes.length != 0) {
					i = 0;
					for (String s : values.toString().split(",")) {
						if (parameterTypes[i].isPrimitive()) {
							CtPrimitiveType primitiveType = (CtPrimitiveType) pool
									.get(parameterTypes[i].getCanonicalName());
							vs.append("vs.add("
									+ primitiveType.getWrapperName()
									+ ".valueOf(" + s + "));");
						} else {
							vs.append("vs.add(" + s + ");");
						}
						i++;
					}
				}

				body += "try {" + ps.toString() + vs.toString()
						+ "java.lang.reflect.Method method = "
						+ clazz.getCanonicalName() + ".class.getDeclaredMethod(\""
						+ name
						+ "\",(Class[])ps.toArray(new Class[ps.size()]));"
						+ "method.setAccessible(true);" + "method.invoke("
						+ ((isStatic) ? "null" : "object") + ",vs.toArray())"
						+ ";} catch (java.lang.Exception e) {throw e;}";
			}

			body += "}";

			CtMethod m = CtNewMethod.make(body, newClass);
			newClass.addMethod(m);
			actualClazz = newClass.toClass();
			return actualClazz;
		} catch (NotFoundException e) {
			throw new RuntimeException(e.getMessage(), e);
		} catch (CannotCompileException e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions