/**
* Copyright (c) 2009--2013 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.common.util;
import com.redhat.rhn.common.MethodInvocationException;
import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.translation.TranslationException;
import com.redhat.rhn.common.translation.Translator;
import org.apache.log4j.Logger;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* A simple class that assists with method invocation. We should just use
* the jakarta-commons MethodUtils class, but that class can't deal with
* static methods, so it is useless to us.
* @version $Rev$
*/
public class MethodUtil {
private static Logger log = Logger.getLogger(MethodUtil.class);
/**
* Private constructore
*/
private MethodUtil() {
}
/* This is insanity itself, but the reflection APIs ignore inheritance.
* So, if you ask for a method that accepts (Integer, HashMap, HashMap),
* and the class only has (Integer, Map, Map), you won't find the method.
* This method uses Class.isAssignableFrom to solve this problem.
*/
private static boolean isCompatible(Class[] declaredParams, Object[] params) {
if (params.length != declaredParams.length) {
return false;
}
for (int i = 0; i < params.length; i++) {
if (!declaredParams[i].isInstance(params[i])) {
return false;
}
}
return true;
}
/**
* Invoke a static method from a class. Note that if more than one method
* qualifies for calling, eg. more than one method is found with compatible
* parameters, the actual invocation target is undefined.
* @param clazz The Class to search for the specified method
* @param method The method to execute.
* @param args the Arguments to the method.
* @return The result of the called method.
* @throws NoSuchMethodException If the method can't be found
* @throws IllegalAccessException if the method cannot be accessed
* @throws InvocationTargetException if the method throws an exception
*/
public static Object invokeStaticMethod(Class clazz, String method,
Object[] args)
throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
Method[] meths = clazz.getMethods();
for (int i = 0; i < meths.length; i++) {
if (!meths[i].getName().equals(method)) {
continue;
}
if (!Modifier.isStatic(meths[i].getModifiers())) {
throw new MethodNotStaticException("Method " + method + " is not static");
}
if (isCompatible(meths[i].getParameterTypes(), args)) {
return meths[i].invoke(null, args);
}
}
throw new NoSuchMethodException("Could not find " + method + " in " + clazz);
}
/**
* Call the specified method with the specified arguments, converting the
* argument type if necessary. Note that if more than one method qualifies
* for calling, eg. more than one method is found with compatible
* parameters, the actual invocation target is undefined.
* @param o The object from which to call the method
* @param methodCalled The method to call
* @param params a Collection of the parameters to methodCalled
* @return the results of the method of the subclass
*/
public static Object callMethod(Object o, String methodCalled,
Object... params) {
/* This whole method is currently an ugly mess that needs to be
* refactored. rbb
*/
if (log.isDebugEnabled()) {
log.debug("Trying to call: " + methodCalled + " in " + o.getClass());
}
Class myClass = o.getClass();
Method[] methods;
try {
methods = myClass.getMethods();
}
catch (SecurityException e) {
// This should _never_ happen, because the Handler classes must
// have public classes if they're expected to work.
throw new IllegalArgumentException("no public methods in class " + myClass);
}
Method foundMethod = null;
Object[] converted = new Object[params.length];
boolean rightMethod = false;
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodCalled)) {
foundMethod = methods[i];
Class[] types = foundMethod.getParameterTypes();
if (types.length != params.length) {
continue;
}
// We have a method that might work, now we need to loop
// through the params and make sure that the types match
// with what was provided in the Collection. If they don't
// match, try to do a translation, if that fails try the next
boolean found = true;
for (int j = 0; j < types.length; j++) {
Object curr = params[j];
if (log.isDebugEnabled()) {
log.debug("Trying to translate from: " +
((curr == null) ? null : curr.getClass()) +
" to: " + types[j] +
" isInstance: " + types[j].isInstance(curr));
}
if (curr != null && curr.getClass().isPrimitive() &&
types[j].isPrimitive()) {
if (log.isDebugEnabled()) {
log.debug("2 primitives");
}
converted[j] = curr;
}
if ((curr == null && !types[j].isPrimitive()) ||
types[j].isInstance(curr)) {
if (log.isDebugEnabled()) {
log.debug("same type");
}
converted[j] = curr;
continue;
}
try {
if (log.isDebugEnabled()) {
log.debug("calling converter: " + curr);
}
converted[j] = Translator.convert(curr, types[j]);
}
catch (TranslationException e) {
log.debug("Couldn't translate between " + curr +
" and " + types[j]);
// move on to the next method.
found = false;
break;
}
}
if (found) {
rightMethod = found;
break;
}
}
}
if (!rightMethod) {
String message = "Could not find method called: " + methodCalled +
" in class: " + o.getClass().getName() + " with params: [";
for (int i = 0; i < params.length; i++) {
if (params[i] != null) {
message = message + ("type: " + params[i].getClass().getName() +
", value: " + params[i]);
if (i < params.length - 1) {
message = message + ", ";
}
}
}
message = message + "]";
throw new MethodNotFoundException(message);
}
try {
return foundMethod.invoke(o, converted);
}
catch (IllegalAccessException e) {
throw new MethodInvocationException("Could not access " + methodCalled, e);
}
catch (InvocationTargetException e) {
e.printStackTrace();
throw new MethodInvocationException("Something bad happened when " +
"calling " + methodCalled, e);
}
}
/**
* Get an instance of a class that can have its classname overridden in our config
* system. If you want a class to not return an instance of the passed in parameter
* you need to define a config with that same name. For example:
*
* List someList = getClassFromConfig("java.lang.LinkedList");
*
* would return a new LinkedList() object.
*
* But if you define a config var with:
*
* java.lang.LinkedList = com.redhat.rhn.utilRhnSuperLinkedList
*
* you will get an instance of that class. Beware this can cause some weird issues
* if done improperly.
*
* @param className to fetch
* @param args arguments to the constructor.
* @return Object created. will throw exception explosion if you
* define this incorrectly
*/
public static Object getClassFromConfig(String className, Object... args) {
return callNewMethod(getClassNameFromConfig(className), args);
}
/**
* Return the classname from the config. Useful if you want to configure a different
* class to be returned in specific instances.
* @param className to check for overridden value.
* @return className from config or the className from parameter if not found
*/
private static String getClassNameFromConfig(String className) {
return Config.get().getString(className, className);
}
/**
* Create a new instance of the classname passed in.
*
* @param className
* @return instance of class passed in.
*/
private static Object callNewMethod(String className, Object... args) {
Object retval = null;
try {
Class clazz = Thread.currentThread().
getContextClassLoader().loadClass(className);
if (args == null || args.length == 0) {
retval = clazz.newInstance();
}
else {
try {
Constructor[] ctors = clazz.getConstructors();
for (Constructor ctor : ctors) {
if (isCompatible(ctor.getParameterTypes(), args)) {
return ctor.newInstance(args);
}
}
}
catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
catch (InstantiationException e) {
throw new RuntimeException(e);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return retval;
}
}