2

I regularly use the Scala REPL for rapid Java iteration and testing, but sometimes I want to trigger some private behavior of a class, and have to recompile the code in order to make the method visible. I'd like to be able to call private Java methods directly in the REPL, without needing to make code changes.

What I've got so far:

// Calls private Java methods
// We currently define an overload for every n-argument method
// there's probably a way to do this in one method?
def callPrivate(obj: AnyRef, methodName: String) = {
  val method = obj.getClass().getDeclaredMethod(methodName)
  val returnType = method.getReturnType
  method.setAccessible(true)
  println("Call .asInstanceOf[%s] to cast" format method.getReturnType.getName)
  method.getReturnType.cast(method.invoke(obj))
}

def callPrivate(obj: AnyRef, methodName: String, arg: AnyRef) = {
  val method = obj.getClass().getDeclaredMethod(methodName, arg.getClass())
  method.setAccessible(true)
  method.invoke(obj, arg)
}

Which can be used like:

scala> callPrivate(myObj, "privateMethod", arg).asInstanceOf[ReturnedClass]

But this requires defining a near duplicate method for every n-argument method type (and requires an external cast, but I suspect that's unavoidable). Is there any way to refactor this so that one function can handle any number of arguments?

Note: I'm using Scala 2.9.1, so I'm looking for solutions using Java Reflection. Answers using Scala Reflection are welcome, but don't address my problem directly.

1 Answer 1

5

DISCLAIMER: There has been a while since the last time I programmed in Scala and I don't have any kind of Scala environment around to test what I am showing you. So it might have small syntax errors here and there, bear with me. Hope the rest is useful

In theory you could provide our callPrivate method with an extra variable argument that specifies the method parameters:

def callPrivate(obj: AnyRef, methodName: String, parameters:AnyRef*) = {
  val parameterTypes = parameters.map(_.getClass())
  val method = obj.getClass.getDeclaredMethod(methodName, parameterTypes:_*)
  method.setAccessible(true)
  method.invoke(obj, parameters:_*)
}

There is a flaw however. This won't work if you have a method somewhere with a signature like this:

public X someMethod(A parameter);

and A is inherited (or implemented) by class B. If you try to invoke your Scala method this way callPrivate(someObject, "someMethod", new B()) it won't work mostly because the getDeclaredMethod lookup will search for someMethod(B) instead of someMethod(A) - even when new B() is of type A too!

So that's a naive implementation. You could potentially get all the valid types of all the method parameters and perform the getDeclaredMethodlookup with all the combinations for them, however there is one more caveat in that direction: You might bump with overloaded methods that accept different combinations of the same set of parameters and you will not know which one to call (i.e. you may have someMethod(A,B) and someMethod(B,A) and you won't be able to know which one should be invoked)

One way avoid that is to force the caller to provide you with tuples instead of raw instances, each tuple has the parameter value and the parameter type to be used. So it is up to the caller to specify which method he want to invoke.

def callPrivateTyped(obj: AnyRef, methodName: String, parameters:(AnyRef,Class[_])*) = {
  val parameterValues = parameters.map(_._1)
  val parameterTypes = parameters.map(_._2)
  val method = obj.getClass.getDeclaredMethod(methodName, parameterTypes:_*)
  method.setAccessible(true)
  println("Call .asInstanceOf[%s] to cast" format method.getReturnType.getName)
  method.invoke(obj, parameterValues:_*)
}

// for convenience
def callPrivate(obj: AnyRef, methodName: String, parameters:AnyRef*) = {
  callPrivateTyped(obj, methodName, parameters.map(c => (c, c.getClass)):_*)
}

That should do the trick.

Also, one more thing: Keep in mind that the way you are using getDeclaredMethod will only return methods (with any scope) that are implemented in obj.getClass(), meaning that it won't return any inherited method. I don't know if that is by design, if not you will need to add a recursive lookup over the superclasses of your obj.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks so much, that was exactly the pointers I needed. I wasn't able to get casting to work (calling method.getReturnType.cast() simply returns Any instead of java.lang.Object) but the varargs behavior now works. I took the liberty of updating your answer with my current implementation which compiles; if you'd prefer I can post it as a separate answer.
There is still room for improvement. I guess that with generics properly placed you benefit of type inference and avoid the casting on the client. Probably you need to declare your method like callPrivateTyped[T](...):T and before returning you can do method.invoke(obj, parameterValues:_*).asInstanceOf(T). Doing so you can change your caller to just invoke var result:SomeType = callPrivateTyped(....) (or maybe var result = callPrivateTyped[SomeType](....)) - I am not adding this to my answer because generics are a tricky thing to write without testing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.