package org.jruby.javasupport;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.proxy.InternalJavaProxy;
import org.jruby.javasupport.proxy.JavaProxyClass;
import org.jruby.javasupport.proxy.JavaProxyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import static org.jruby.RubyModule.undefinedMethodMessage;
import static org.jruby.util.CodegenUtils.getBoxType;
import static org.jruby.util.CodegenUtils.prettyParams;
import static org.jruby.util.RubyStringBuilder.ids;
@JRubyClass(name="Java::JavaMethod")
public class JavaMethod extends JavaCallable {
private final Method method;
private final Class<?> boxedReturnType;
private final boolean isFinal;
private final JavaUtil.JavaConverter returnConverter;
public final Method getValue() { return method; }
public static RubyClass createJavaMethodClass(Ruby runtime, RubyModule javaModule) {
RubyClass result =
javaModule.defineClassUnder("JavaMethod", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
JavaAccessibleObject.registerRubyMethods(runtime, result);
JavaCallable.registerRubyMethods(runtime, result);
result.defineAnnotatedMethods(JavaMethod.class);
return result;
}
public JavaMethod(Ruby runtime, Method method) {
super(runtime, runtime.getJavaSupport().getJavaMethodClass(), method.getParameterTypes());
this.method = method;
this.isFinal = Modifier.isFinal(method.getModifiers());
final Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive() && returnType != void.class) {
this.boxedReturnType = getBoxType(returnType);
} else {
this.boxedReturnType = returnType;
}
if (RubyInstanceConfig.SET_ACCESSIBLE) {
try {
if ( Modifier.isPublic(method.getModifiers()) &&
! Modifier.isPublic(method.getDeclaringClass().getModifiers()) ) {
Java.trySetAccessible(method);
}
} catch (SecurityException se) {
runtime.getWarnings().warn("failed to setAccessible: " + method + ", exception follows: " + se.getMessage());
}
}
returnConverter = JavaUtil.getJavaConverter(returnType);
}
@Deprecated
public static JavaMethod create(Ruby runtime, Method method) {
return new JavaMethod(runtime, method);
}
@Deprecated
public static JavaMethod create(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
try {
return create(runtime, javaClass.getMethod(methodName, argumentTypes));
}
catch (NoSuchMethodException e) {
throw runtime.newNameError(undefinedMethodMessage(runtime, ids(runtime, methodName), ids(runtime, javaClass.getName()), false), methodName);
}
}
@Deprecated
public static JavaMethod createDeclared(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
try {
return create(runtime, javaClass.getDeclaredMethod(methodName, argumentTypes));
}
catch (NoSuchMethodException e) {
throw runtime.newNameError(undefinedMethodMessage(runtime, ids(runtime, methodName), ids(runtime, javaClass.getName()), false), methodName);
}
}
public static JavaMethod getMatchingDeclaredMethod(Ruby runtime, Class<?> javaClass, String methodName, Class<?>[] argumentTypes) {
try {
return new JavaMethod(runtime, javaClass.getDeclaredMethod(methodName, argumentTypes));
}
catch (NoSuchMethodException e) {
MethodSearch: for ( Method method : javaClass.getDeclaredMethods() ) {
if ( method.getName().equals(methodName) ) {
Class<?>[] targetTypes = method.getParameterTypes();
if (targetTypes.length == 0 && argumentTypes.length == 0) {
return new JavaMethod(runtime, method);
}
TypeScan: for (int i = 0; i < argumentTypes.length; i++) {
if (i >= targetTypes.length) continue MethodSearch;
if (targetTypes[i].isAssignableFrom(argumentTypes[i])) {
continue TypeScan;
} else {
continue MethodSearch;
}
}
return new JavaMethod(runtime, method);
}
}
}
return null;
}
@Override
public final boolean equals(Object other) {
return other instanceof JavaMethod && this.method.equals( ((JavaMethod) other).method );
}
@Override
public final int hashCode() {
return method.hashCode();
}
@JRubyMethod
@Override
public RubyString name() {
return getRuntime().newString(method.getName());
}
@JRubyMethod(name = "public?")
@Override
public RubyBoolean public_p() {
return getRuntime().newBoolean(Modifier.isPublic(method.getModifiers()));
}
@JRubyMethod(name = "final?")
public RubyBoolean final_p() {
return getRuntime().newBoolean(Modifier.isFinal(method.getModifiers()));
}
@JRubyMethod(rest = true)
public IRubyObject invoke(ThreadContext context, IRubyObject[] args) {
checkArity(args.length - 1);
final IRubyObject invokee = args[0];
final Object[] arguments = convertArguments(args, 1);
if ( invokee.isNil() ) {
return invokeWithExceptionHandling(context, method, null, arguments);
}
final Object javaInvokee;
if (!isStatic()) {
javaInvokee = JavaUtil.unwrapJavaValue(invokee);
if ( javaInvokee == null ) {
throw getRuntime().newTypeError("invokee not a java object");
}
if ( ! method.getDeclaringClass().isInstance(javaInvokee) ) {
throw getRuntime().newTypeError(
"invokee not instance of method's class" +
" (got" + javaInvokee.getClass().getName() +
" wanted " + method.getDeclaringClass().getName() + ")");
}
if ( javaInvokee instanceof InternalJavaProxy &&
! isFinal ) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm = jpc.getMethod( method.getName(), parameterTypes );
if ( jpm != null && jpm.hasSuperImplementation() ) {
return invokeWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, arguments);
}
}
}
else {
javaInvokee = null;
}
return invokeWithExceptionHandling(context, method, javaInvokee, arguments);
}
@JRubyMethod(rest = true)
public IRubyObject invoke_static(ThreadContext context, IRubyObject[] args) {
checkArity(args.length);
final Object[] arguments = convertArguments(args, 0);
return invokeWithExceptionHandling(context, method, null, arguments);
}
@JRubyMethod
public IRubyObject return_type() {
Class<?> klass = method.getReturnType();
if (klass.equals(void.class)) {
return getRuntime().getNil();
}
return JavaClass.get(getRuntime(), klass);
}
@JRubyMethod
public IRubyObject type_parameters() {
return Java.getInstance(getRuntime(), method.getTypeParameters());
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee, Object[] args) {
checkArity(args.length);
checkInstanceof(javaInvokee);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee, args);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee, args);
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee) {
assert method.getDeclaringClass().isInstance(javaInvokee);
checkArity(0);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee);
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee, Object arg0) {
assert method.getDeclaringClass().isInstance(javaInvokee);
checkArity(1);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee, arg0);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0);
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee, Object arg0, Object arg1) {
assert method.getDeclaringClass().isInstance(javaInvokee);
checkArity(2);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee, arg0, arg1);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1);
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee, Object arg0, Object arg1, Object arg2) {
assert method.getDeclaringClass().isInstance(javaInvokee);
checkArity(3);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee, arg0, arg1, arg2);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1, arg2);
}
public IRubyObject invokeDirect(ThreadContext context, Object javaInvokee, Object arg0, Object arg1, Object arg2, Object arg3) {
assert method.getDeclaringClass().isInstance(javaInvokee);
checkArity(4);
if (mightBeProxy(javaInvokee)) {
return tryProxyInvocation(context, javaInvokee, arg0, arg1, arg2, arg3);
}
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1, arg2, arg3);
}
public IRubyObject invokeStaticDirect(ThreadContext context, Object[] args) {
checkArity(args.length);
return invokeDirectWithExceptionHandling(context, method, null, args);
}
public IRubyObject invokeStaticDirect(ThreadContext context) {
checkArity(0);
return invokeDirectWithExceptionHandling(context, method, null);
}
public IRubyObject invokeStaticDirect(ThreadContext context, Object arg0) {
checkArity(1);
return invokeDirectWithExceptionHandling(context, method, null, arg0);
}
public IRubyObject invokeStaticDirect(ThreadContext context, Object arg0, Object arg1) {
checkArity(2);
return invokeDirectWithExceptionHandling(context, method, null, arg0, arg1);
}
public IRubyObject invokeStaticDirect(ThreadContext context, Object arg0, Object arg1, Object arg2) {
checkArity(3);
return invokeDirectWithExceptionHandling(context, method, null, arg0, arg1, arg2);
}
public IRubyObject invokeStaticDirect(ThreadContext context, Object arg0, Object arg1, Object arg2, Object arg3) {
checkArity(4);
return invokeDirectWithExceptionHandling(context, method, null, arg0, arg1, arg2, arg3);
}
private void checkInstanceof(Object javaInvokee) throws RaiseException {
if (!method.getDeclaringClass().isInstance(javaInvokee)) {
throw getRuntime().newTypeError("invokee not instance of method's class (" + "got" + javaInvokee.getClass().getName() + " wanted " + method.getDeclaringClass().getName() + ")");
}
}
private IRubyObject invokeWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object[] arguments) {
try {
Object result = method.invoke(javaInvokee, arguments);
return returnConverter.convert(getRuntime(), result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arguments);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectSuperWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object... arguments) {
try {
Object result = method.invoke(javaInvokee, arguments);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arguments);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object[] arguments) {
try {
Object result = method.invoke(javaInvokee, arguments);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arguments);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee) {
try {
Object result = method.invoke(javaInvokee);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object arg0) {
try {
Object result = method.invoke(javaInvokee, arg0);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arg0);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object arg0, Object arg1) {
try {
Object result = method.invoke(javaInvokee, arg0, arg1);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arg0, arg1);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object arg0, Object arg1, Object arg2) {
try {
Object result = method.invoke(javaInvokee, arg0, arg1, arg2);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arg0, arg1, arg2);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject invokeDirectWithExceptionHandling(ThreadContext context, Method method, Object javaInvokee, Object arg0, Object arg1, Object arg2, Object arg3) {
try {
Object result = method.invoke(javaInvokee, arg0, arg1, arg2, arg3);
return convertReturn(result);
} catch (IllegalArgumentException iae) {
return handlelIllegalArgumentEx(iae, method, arg0, arg1, arg2, arg3);
} catch (IllegalAccessException iae) {
return handleIllegalAccessEx(iae, method);
} catch (InvocationTargetException ite) {
return handleInvocationTargetEx(context, ite);
} catch (Throwable t) {
return handleThrowable(context, t);
}
}
private IRubyObject convertReturn(Object result) {
if (result != null && result.getClass() != boxedReturnType) {
return JavaUtil.convertJavaToUsableRubyObject(getRuntime(), result);
}
return JavaUtil.convertJavaToUsableRubyObjectWithConverter(getRuntime(), result, returnConverter);
}
public String getName() {
return method.getName();
}
@Override
public Class<?>[] getExceptionTypes() {
return method.getExceptionTypes();
}
@Override
public Type[] getGenericParameterTypes() {
return method.getGenericParameterTypes();
}
@Override
public Type[] getGenericExceptionTypes() {
return method.getGenericExceptionTypes();
}
@Override
public Annotation[][] getParameterAnnotations() {
return method.getParameterAnnotations();
}
@Override
public final boolean isVarArgs() {
return method.isVarArgs();
}
@Override
protected String nameOnInspection() {
return getType().toString() + '/' + method.getName();
}
@JRubyMethod
public RubyString inspect() {
StringBuilder str = new StringBuilder();
str.append("#<");
str.append( getType().toString() ).append('/').append(method.getName());
inspectParameterTypes(str, this);
str.append('>');
return RubyString.newString(getRuntime(), str);
}
@JRubyMethod(name = "static?")
public RubyBoolean static_p() {
return getRuntime().newBoolean(isStatic());
}
public RubyBoolean bridge_p() {
return getRuntime().newBoolean(method.isBridge());
}
private boolean isStatic() {
return Modifier.isStatic(method.getModifiers());
}
@Override
public final int getModifiers() {
return method.getModifiers();
}
@Override
public String toGenericString() {
return method.toGenericString();
}
@Override
public final AccessibleObject accessibleObject() {
return method;
}
private boolean mightBeProxy(Object javaInvokee) {
return javaInvokee instanceof InternalJavaProxy && !isFinal;
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee, Object... args) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, args);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee, args);
}
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee);
}
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee, Object arg0) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, arg0);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0);
}
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee, Object arg0, Object arg1) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, arg0, arg1);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1);
}
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee, Object arg0, Object arg1, Object arg2) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, arg0, arg1, arg2);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1, arg2);
}
}
private IRubyObject tryProxyInvocation(ThreadContext context, Object javaInvokee, Object arg0, Object arg1, Object arg2, Object arg3) {
JavaProxyClass jpc = ((InternalJavaProxy) javaInvokee).___getProxyClass();
JavaProxyMethod jpm;
if ((jpm = jpc.getMethod(method.getName(), parameterTypes)) != null && jpm.hasSuperImplementation()) {
return invokeDirectSuperWithExceptionHandling(context, jpm.getSuperMethod(), javaInvokee, arg0, arg1, arg2, arg3);
} else {
return invokeDirectWithExceptionHandling(context, method, javaInvokee, arg0, arg1, arg2, arg3);
}
}
public static RaiseException newMethodNotFoundError(Ruby runtime, Class target, String prettyName, String simpleName) {
return runtime.newNameError("java method not found: " + target.getName() + "." + prettyName, simpleName);
}
public static RaiseException newArgSizeMismatchError(Ruby runtime, Class ... argTypes) {
return runtime.newArgumentError("argument count mismatch for method signature " + prettyParams(argTypes));
}
}