package com.bergerkiller.bukkit.common.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.logging.Level; import org.bukkit.Bukkit; import com.bergerkiller.bukkit.common.Common; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.utils.LogicUtil; import com.bergerkiller.bukkit.common.utils.StringUtil; /** * Wraps around the java.lang.reflect.Method class to provide an error-free alternative<br> * Exceptions are logged, isValid can be used to check if the Method is actually working */ public class SafeMethod<T> implements MethodAccessor<T> { private Method method; private Class<?>[] parameterTypes; private boolean isStatic = false; public SafeMethod(Method method) { if (method == null) { throw new IllegalArgumentException("Can not construct using a null Method"); } this.method = method; this.parameterTypes = this.method.getParameterTypes(); this.isStatic = Modifier.isStatic(this.method.getModifiers()); } public SafeMethod(String methodPath, Class<?>... parameterTypes) { if (LogicUtil.nullOrEmpty(methodPath) || !methodPath.contains(".")) { Bukkit.getLogger().log(Level.SEVERE, "Method path contains no class: " + methodPath); return; } try { String className = StringUtil.getLastBefore(methodPath, "."); String methodName = methodPath.substring(className.length() + 1); Class<?> type = Class.forName(Common.SERVER.getClassName(className)); load(type, methodName, parameterTypes); } catch (Throwable t) { System.out.println("Failed to load method '" + methodPath + "':"); t.printStackTrace(); } } public SafeMethod(Object value, String name, Class<?>... parameterTypes) { load(value == null ? null : value.getClass(), name, parameterTypes); } public SafeMethod(Class<?> source, String name, Class<?>... parameterTypes) { load(source, name, parameterTypes); } private void load(Class<?> source, String name, Class<?>... parameterTypes) { if (source == null) { new Exception("Can not load method '" + name + "' because the class is null!").printStackTrace(); return; } // Find real name and display name String fixedName = Common.SERVER == null ? name : Common.SERVER.getMethodName(source, name, parameterTypes); String dispName = name.equals(fixedName) ? name : (name + "[" + fixedName + "]"); dispName += "("; for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { dispName += ", "; } dispName += parameterTypes[i].getSimpleName(); } dispName += ")"; // try to find the method try { this.method = findRaw(source, fixedName, parameterTypes); if (this.method != null) { this.method.setAccessible(true); this.isStatic = Modifier.isStatic(this.method.getModifiers()); this.parameterTypes = parameterTypes; return; } } catch (SecurityException ex) { new Exception("No permission to access method '" + dispName + "' in class file '" + source.getSimpleName() + "'").printStackTrace(); return; } CommonPlugin.getInstance().handleReflectionMissing("Method", dispName, source); } /** * Gets the name of this method as declared in the Class * * @return Method name */ public String getName() { return method.getName(); } /** * Checks whether this method is overrided in the Class specified * * @param type to check * @return True of this method is overrided in the type specified, False if not */ public boolean isOverridedIn(Class<?> type) { try { Method m = type.getDeclaredMethod(method.getName(), method.getParameterTypes()); return m.getDeclaringClass() != method.getDeclaringClass(); } catch (Throwable t) { return false; } } @Override public boolean isValid() { return this.method != null; } @Override @SuppressWarnings("unchecked") public T invoke(Object instance, Object... args) { if (this.method != null) { if (!this.isStatic && instance == null) { throw new IllegalArgumentException("Non-static methods require a valid instance passed in - the instance was null"); } if (args.length != parameterTypes.length) { throw new IllegalArgumentException("Illegal amount of arguments - check method signature"); } try { return (T) this.method.invoke(instance, args); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalArgumentException e) { // First find a more understandable message for this if (args.length == parameterTypes.length) { for (int i = 0; i < parameterTypes.length; i++) { Object arg = args[i]; if (parameterTypes[i].isPrimitive() && arg == null) { throw new IllegalArgumentException("Passed in null for primitive type parameter #" + i); } else if (arg != null && !parameterTypes[i].isAssignableFrom(arg.getClass())) { throw new IllegalArgumentException("Passed in wrong type for parameter #" + i + " (" + parameterTypes[i].getName() + " expected)"); } } } // Nothing detected yet...resort to the obtained exception throw e; } } return null; } /** * Checks whether a certain method is available in a Class * * @param type of Class * @param name of the method * @param parameterTypes of the method * @return True if available, False if not */ public static boolean contains(Class<?> type, String name, Class<?>... parameterTypes) { return findRaw(type, Common.SERVER.getMethodName(type, name, parameterTypes), parameterTypes) != null; } /** * Tries to recursively find a method in a Class * * @param type of Class * @param name of the method * @param parameterTypes of the method * @return the Method, or null if not found */ private static Method findRaw(Class<?> type, String name, Class<?>... parameterTypes) { Class<?> tmp = type; // Try to find the method in the current and all Super Classes while (tmp != null) { try { return tmp.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException ex) { tmp = tmp.getSuperclass(); } } // Try to find the method in all implemented Interfaces for (Class<?> interfaceClass : type.getInterfaces()) { try { return interfaceClass.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException ex) { } } // Nothing found return null; } }