/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hotswap.agent.plugin.hibernate3.session.util;
import java.lang.reflect.Modifier;
import org.hotswap.agent.javassist.CannotCompileException;
import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.CtConstructor;
import org.hotswap.agent.javassist.CtMethod;
import org.hotswap.agent.javassist.CtNewConstructor;
import org.hotswap.agent.javassist.CtNewMethod;
import org.hotswap.agent.javassist.NotFoundException;
import org.hotswap.agent.javassist.bytecode.AccessFlag;
import org.hotswap.agent.plugin.hibernate3.session.proxy.ReInitializableHelper;
/**
* Utility functions for instrumenting classes.
*
* @author alpapad@gmail.com
*/
public class ProxyUtil {
/**
* Adds the method.
*
* @param classLoader
* the class loader
* @param classPool
* the class pool
* @param clazz
* the clazz
* @param returns
* the returns
* @param method
* the method
* @param args
* the args
* @throws CannotCompileException
* the cannot compile exception
*/
public static void addMethod(ClassLoader classLoader, ClassPool classPool, CtClass clazz, String returns, String method, String[] args) throws CannotCompileException {
try {
CtMethod oldMethod = clazz.getDeclaredMethod(method, getParamTypes(classPool, args));
if (oldMethod != null) {
oldMethod.setName('_' + method);
}
} catch (NotFoundException e) {
}
StringBuilder body = new StringBuilder();
body.append(" public ").append(returns).append(' ')//
.append(method).append('(').append(getMethodArgs(args)).append(')').append("{\n");
body.append(" ");
if (!"void".equals(returns)) {
body.append("return ");
}
body.append(ReInitializableHelper.class.getName()).append('.')//
.append(method).append('(').append(getCallArgs(args)).append(')').append(";\n");
body.append('}');
CtMethod newMethod = CtNewMethod.make(body.toString(), clazz);
clazz.addMethod(newMethod);
}
/**
* Gets the param types.
*
* @param classPool
* the class pool
* @param args
* the args
* @return the param types
* @throws NotFoundException
* the not found exception
*/
private static CtClass[] getParamTypes(ClassPool classPool, String args[]) throws NotFoundException {
if (args == null || args.length == 0) {
return new CtClass[0];
} else {
CtClass[] res = new CtClass[args.length];
for (int i = 0; i < args.length; i++) {
res[i] = classPool.get(args[i]);
}
return res;
}
}
/**
* Gets the method args.
*
* @param args
* the args
* @return the method args
*/
private static String getMethodArgs(String args[]) {
if (args == null || args.length == 0) {
return "";
} else {
StringBuilder l = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i > 0) {
l.append(',');
}
l.append(args[i]).append(' ').append("$arg").append(i);
}
return l.toString();
}
}
/**
* Gets the call args.
*
* @param args
* the args
* @return the call args
*/
private static String getCallArgs(String args[]) {
if (args == null || args.length == 0) {
return "this";
} else {
StringBuilder l = new StringBuilder("this");
for (int i = 0; i < args.length; i++) {
l.append(',').append("$arg").append(i);
}
return l.toString();
}
}
/**
* Ensure proxyable.
*
* @param clazz
* the clazz
* @throws CannotCompileException
* the cannot compile exception
*/
public static void ensureProxyable(CtClass clazz) throws CannotCompileException {
int flags = clazz.getClassFile().getAccessFlags();
flags = AccessFlag.setPublic(flags);
flags = AccessFlag.clear(flags, AccessFlag.FINAL);
clazz.getClassFile().setAccessFlags(flags);
try {
CtConstructor ct = clazz.getDeclaredConstructor(new CtClass[] {});
if (Modifier.isPrivate(ct.getModifiers())) {
ct.setModifiers(AccessFlag.setProtected(ct.getModifiers()));
}
} catch (NotFoundException ex) {
CtConstructor c = CtNewConstructor.make("protected " + clazz.getSimpleName() + "() {}", clazz);
clazz.addConstructor(c);
}
}
/**
* Make proxy.
*
* @param proxy
* the proxy
* @param proxied
* the proxied
* @param classPool
* the class pool
* @param classLoader
* the class loader
* @throws Exception
* the exception
*/
public static void makeProxy(CtClass proxy, CtClass proxied, ClassPool classPool, ClassLoader classLoader) throws Exception {
proxy.setSuperclass(proxied);
for (CtMethod m : proxied.getMethods()) {
int mod = m.getModifiers();
if (!Modifier.isFinal(mod) //
&& !Modifier.isStatic(mod) //
&& isVisible(mod, proxied.getPackageName(), m) //
&& (!m.getDeclaringClass().getName().equals("java.lang.Object"))) {
String meth = toProxy(m);
CtMethod newMethod = CtNewMethod.make(meth, proxy);
proxy.addMethod(newMethod);
}
}
for (CtClass i : proxied.getInterfaces()) {
proxy.addInterface(i);
}
}
/**
* To proxy.
*
* @param m
* the m
* @return the string
* @throws NotFoundException
* the not found exception
*/
private static String toProxy(CtMethod m) throws NotFoundException {
StringBuilder r = new StringBuilder("public ");
String ret = m.getReturnType().getName();
r.append(ret).append(' ');
r.append(m.getName()).append('(');
for (int i = 0; i < m.getParameterTypes().length; i++) {
r.append(m.getParameterTypes()[i].getName()).append(" a").append(i);
if (i < m.getParameterTypes().length - 1) {
r.append(',');
}
}
r.append("){\n ");
if (!"void".equals(ret)) {
r.append("return");
}
r.append(" currentInstance.").append(m.getName()).append('(');
for (int i = 0; i < m.getParameterTypes().length; i++) {
r.append(" a").append(i);
if (i < m.getParameterTypes().length - 1) {
r.append(',');
}
}
r.append(");\n");
r.append("}\n");
return r.toString();
}
/**
* Returns true if the method is visible from the package.
*
* @param mod
* the modifiers of the method.
* @param from
* the from
* @param meth
* the meth
* @return true, if is visible
*/
private static boolean isVisible(int mod, String from, CtMethod meth) {
if ((mod & Modifier.PRIVATE) != 0) {
return false;
} else if ((mod & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
return true;
} else {
String p = getPackageName(from);
String q = getPackageName(meth.getDeclaringClass().getName());
if (p == null) {
return q == null;
} else {
return p.equals(q);
}
}
}
/**
* Gets the package name.
*
* @param name
* the name
* @return the package name
*/
private static String getPackageName(String name) {
int i = name.lastIndexOf('.');
if (i < 0) {
return null;
} else {
return name.substring(0, i);
}
}
}