Java Reflection Method Invoke Using Fluent Interface

Here is an example of creating a fluent builder interface for invoking a method via Java Reflection. While the code to invoke Java methods via reflection is not complex, it can be improved. We can introduce a ‘façade’ and this façade can use the fluent builder pattern. Of course, doing all this just to invoke a method is not the point, but to illustrate or experiment with the pattern.

One example of using this is:

.name(“greet”).on(anObject).with(“Hello world!”).of(String.class).invoke();

Or in prose, this is what the above is doing: Invoke method name “greet”, on object anObject, with the argument “Hello world!”, of class types String.class. Note that some of the words in the prose were used as the method names. Fluent Interface is somewhat of a mini Domain Specific Language (mDSL).

The final invoke() will use the information and generate whatever is required in order to perform the method invocation. More on this later.

Background
Fluent Interfaces (not just about chained setters) are an old concept that is becoming more well known and used. One popular blog post was FluentInterface by Martin Fowler. Since then there are many resources on this.

The main motivation was the issues with current practices in creating interfaces. In Item 2: Consider a builder when faced with many constructor parameters by Joshua Bloch, the disadvantages in “Telescoping constructors” and the JavaBean pattern are analyzed and why a builder may be more useful.

I wanted to learn more about this, so I wanted an approachable application, but not too easy. Reflective invocation of a method is very doable. Currently, you must find a Method, then invoke with the correct arguments. But, all this is kind of kludgy, for example the Method‘s invoke method is

public Object invoke(Object obj, Object… args) throws …

But, if the underlying method is static, then the obj argument is ignored. It may be null. So, there could have been an overload for this situation that only required the arguments. Just reduces the API noise. And that null use is suspect. In my opinion any API that deliberately allows the use of null is nuts. Oops, other language are all about null, like JavaScript.

 

Approach
We create a fluent interface that combines the Method look up and the invocation on an object into a Facade. Then the invoke method of the builder will perform the actual invocation and it will use whatever is available to properly perform this.

In more complex situations, a builder can employ various techniques to create the final object or invoke the desired action. One technique is to execute a Decision Table.

One complication is that I wanted this to be usable as a static utility class. Thus, a static method of the utility class will create a nested class that implements the builder pattern.

Example implementation
Source code available at gist: https://gist.github.com/josefbetancourt/3ffcb3044e558fc1b3e8

The list of “verbs” is:

  1. name: what is the method name?
  2. on: on what object or class to invoke?
  3. of …: types of arguments?
  4. using …: using what parameters?
  5. nosy: is method private?
  6. method: use a known method object?
  7. clazz: instead of method what class?
  8. returning: what type of object to return?
  9. invoke: Invoke by figuring out what to do.

Of course, not all of these are required to invoke a method, not even a target object, since a static method can be invoked on a class.

An example use is:

ReflectionUtil.invokes()
   .name(“hello”).on(innerTest).using(“Hello world!”).of(String.class).invoke();
 

Code example

Listing 1, implementation
package com.octodecillion.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;

/**
 * Reflection Invoker using Fluent Builder.
 * 
 * Example is a singleton utility class that contains
 * a facade to a fluent builder pattern.
 * 
 * @author jbetancourt
 * @since 20130201T2233-5
 */
public final class Example {

	private static final Example instance = new Example();

	private Example() {
		// it's a singleton utility class.
	}

	public static Invoker invokes() {
		return instance.new Invoker();
	}

	/**
	 * Example use:  
	 * invokes().name("hello").on(innerTest).using("Hello world!").of(String.class).invoke();
	 * 
	 */
	class Invoker {
		private String name;
		private Object object;
		private Class<? extends Object> clazz;
		private Class<?>[] classes;
		private Object[] params;
		private Method method;
		private boolean allowPrivate;

		public Invoker name(final String name) {
			this.name = name;
			return this;
		}

		public Invoker on(final Object obj) {
			object = obj;
			return this;
		}

		public Invoker on(final Class<?> clz) {
			clazz = clz;
			return this;
		}

		public Invoker of(final Class<?>... classes) {
			this.classes = classes;
			return this;
		}

		public Invoker using(final Object... params) {
			this.params = params;
			return this;
		}

		public Invoker nosy() {
			allowPrivate = true;
			return this;
		}

		public Invoker method(final Method method) {
			this.method = method;
			return this;
		}

		public <T> Invoker returning(@SuppressWarnings("unused") final T clazz) {
			return this;
		}

		public Object invoke() throws NoSuchMethodException, SecurityException,
				IllegalAccessException, IllegalArgumentException,
				InvocationTargetException {

			boolean paramsButNoTypes = (method == null)
					&& ((params != null) && (classes == null));

			if (paramsButNoTypes) {
				throw new IllegalStateException(String.format(
						"params:%s,classes:%s", params, classes));
			}

			if (method == null) {
				if (object == null) {
					method = clazz.getDeclaredMethod(name, classes);
				} else {
					Class<? extends Object> clz = (clazz == null ? object
							.getClass() : clazz);
					method = clz.getDeclaredMethod(name, classes);
				}
			}

			if (allowPrivate) {
				return invokePrivate();
			}

			return method.invoke(object, params);

		}

		private Object invokePrivate() {
			try {
				final Object objF = object;
				final Method methodF = method;
				final Object[] paramsF = params;

				return AccessController
						.doPrivileged(new PrivilegedExceptionAction<Object>() {
							Object result;

							@Override
							public Object run() throws Exception {
								if (!methodF.isAccessible()) {
									methodF.setAccessible(true);
								}

								result = methodF.invoke(objF, paramsF);
								return result;
							}

						});
			} catch (Exception ex) {
				throw new IllegalStateException("Cannot set method,'" + method
						+ "' accessible.", ex);
			}
		}
	} // end class Invoker

} // end class Example

Note that the above code is not necessarily usable in the real world. It has not been reviewed for thread safety, safe publishing of create object, and other possible issues.

 

Example Test

Listing 2, unit test
package com.octodecillion.utils;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * 
 * 
 * @author jbetancourt
 *
 */
@RunWith(JUnit4.class)
public class ExampleTest {

	/** test on class with static public methods */
	@Test
	public final void invokes_on_static() throws Exception {

		Object actual = Example.invokes().name("hello")
				.on(WithStaticMethod.class).using("Hello world!")
				.of(String.class).invoke();

		assertThat((String) actual, is("Hello world!"));

	}

	/** test on class with static private methods */
	@Test
	public final void invokes_on_private_method() throws Exception {
		WithStaticMethod obj = new WithStaticMethod();
		Object actual = Example.invokes().name("privateHello").on(obj).nosy()
				.invoke();

		assertThat((String) actual, is("Hello private world!"));

	}

	static class WithStaticMethod {
		public static String hello(final String msg) {
			return msg;
		}

		private String privateHello() {
			return "Hello private world!";
		}

	}

	/** test on inner class with public methods */
	@Test
	public final void testInvokes() throws Exception {

		class InnerClass {
			public String hello(final String msg) {
				return msg;
			}

			public String hello() {
				return "Hello default world!";
			}

			private String privateHello() {
				return "Hello private world!";
			}

		} // end InnerTest

		InnerClass innerTest = new InnerClass();

		Object actual = Example.invokes().name("hello").on(innerTest)
				.using("Hello world!").of(String.class).invoke();

		assertThat((String) actual, is("Hello world!"));

		actual = Example.invokes().name("hello").on(innerTest).invoke();

		assertThat((String) actual, is("Hello default world!"));

		actual = Example.invokes().name("privateHello").on(innerTest).nosy()
				.invoke();

		assertThat((String) actual, is("Hello private world!"));

	}

}

 

Summary
Presented was an easy example of the start of a possible Fluent Interface for a utility. One problem though, is that the invoke method will attempt to invoke with what was supplied by the developer. In a more complex application of the Fluent Interface this can lead to run-time exceptions if this is not done correctly, or if the invoke or build method is not up to snuff. An interesting pattern Step Builder pattern by Marco Castigliego addresses some of the shortcomings of a pure Fluent Builder pattern. In the Step Pattern the steps, guided by interfaces, “guides” the creation of the object or action.

 

Links

Updates
March 4, 2013, 22:07: Now that I finished the above learning exercise, I wanted to find out how others did something similar. So far, found the FEST which has a Fluent Reflection module. So, I wasn’t off to a bad start, but FEST is more comprehensive! An interesting post by Alex Ruiz is: Fluent interfaces: don’t chain for the sake of chaining

Similar Posts:

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

One thought on “Java Reflection Method Invoke Using Fluent Interface”

Leave a Reply

Your email address will not be published. Required fields are marked *