/* * Copyright 2003,2004,2005 Colin Crist * * 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 hermes.util; import hermes.SystemProperties; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; import javax.jms.ConnectionFactory; import javax.jms.QueueConnectionFactory; import javax.jms.TopicConnectionFactory; import net.sf.cglib.beans.BeanGenerator; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.NoOp; import org.apache.log4j.Logger; /** * Some useful reflection utilities. * * @author colincrist@hermesjms.com * @version $Id: ReflectUtils.java,v 1.3 2006/07/26 09:47:56 colincrist Exp $ */ public class ReflectUtils { private static final Set<String> nonCompliantPackages = new HashSet<String>(); static { String prefixes = System.getProperty(SystemProperties.NON_COMPLIANT_PACKAGES, SystemProperties.DEFAULT_NON_COMPLIANT_PACKAGES); for (StringTokenizer tokens = new StringTokenizer(prefixes, ","); tokens.hasMoreTokens();) { nonCompliantPackages.add(tokens.nextToken()); } } private static final Logger log = Logger.getLogger(ReflectUtils.class); /** * Is this method public and not static? * * @param m * @return */ private static boolean isPublicAndNonStatic(Method m) { return !Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers()); } /** * Is this method a getter? It is not 100% generic as I assume an array * paramter discounts it as being a setter. * * @param m * @return */ public static boolean isGetter(Method m) { return m.getName().startsWith("get") && isPublicAndNonStatic(m) && m.getParameterTypes().length == 0; } /** * Is this method a getter? It is not 100% generic as I assume an array * paramter discounts it as being a getter. * * @param m * @return */ public static boolean isSetter(Method m) { return m.getName().startsWith("set") && isPublicAndNonStatic(m) && m.getParameterTypes().length == 1 && !m.getParameterTypes()[0].isArray(); } /** * Return the property type for a setter or setter (i.e. the return type or * argument) * * @param m * @return */ public static Class getPropertyType(Method m) { if (isSetter(m)) { return m.getParameterTypes()[0]; } else if (isGetter(m)) { return m.getReturnType(); } else { return Void.class; } } /** * Return the property name for a getter or setter. * * @param m * @return */ public static String getPropertyName(Method m) { String s = m.getName().substring(3, m.getName().length()); return Character.toLowerCase(s.charAt(0)) + s.substring(1); } public static boolean getterExists(Class clazz, String propertyName) { final String methodName = "get" + propertyName; try { final Method method = clazz.getMethod(methodName, new Class[] {}); return true; } catch (NoSuchMethodException e) { return false; } } /** * This is complicated, we code generate a new class to mix-in the methods * missing get methods from a bean and then intercept the getters and * setters, caching the succesful set call and returning it from the get * call. * * @param clazz * @return * @throws InstantiationException * @throws IllegalAccessException */ public static Object mixinGetterMethodsAndInstantiate(Class clazz) throws InstantiationException, IllegalAccessException { // // Generate a bean class that has a full set of get/set methods for all // properties. The getters will // in fact never get called as the intercepter will return the cached // result of the previous setter. final Method[] methods = clazz.getMethods(); final BeanGenerator beanGenerator = new BeanGenerator(); beanGenerator.setSuperclass(clazz); for (int i = 0; i < methods.length; i++) { final Method m = methods[i]; if (ReflectUtils.isSetter(m)) { beanGenerator.addProperty(ReflectUtils.getPropertyName(m), ReflectUtils.getPropertyType(m)); } } // // Enhance the class by placing a GetCachingMethodInterceptor // intercepter on all the getter and setter methods. final Class newClazz = (Class) beanGenerator.createClass(); final Callback[] callbacks = new Callback[] { new GetCachingMethodInterceptor(clazz.newInstance()), NoOp.INSTANCE }; return (ConnectionFactory) Enhancer.create(newClazz, new Class[] { ConnectionFactory.class, QueueConnectionFactory.class, TopicConnectionFactory.class }, new CallbackFilter() { public int accept(Method m) { return 0 ; } }, callbacks); } public static ConnectionFactory createConnectionFactory(Class clazz) throws InstantiationException, IllegalAccessException { final ConnectionFactory factory = (ConnectionFactory) clazz.newInstance(); if (nonCompliantPackages.contains(clazz.getPackage().getName())) { log.debug("found a non Java Bean compliant class, " + clazz.getName() + ", generating a new class and implementing an around advice to mixin getters"); // return factory ; return (ConnectionFactory) mixinGetterMethodsAndInstantiate(clazz); } else { return factory; } } }