Introducing Fluent Reflection

I am in the processes of releasing the first alpha build of fluent-reflection, you can get the snapshot version from the fluent-reflection snapshot repository.

Motivation

Reflection code is difficult to write in Java, and even harder to read once it has been written. Although the Java Reflection API allows complete access to the Java runtime type system, the complexity of using the Java Reflection API reduces its usefulness. I don’t want to have to write any more code like this:

final List<Method> getMethods = new ArrayList<Method>();
 
Class<? super Bean> klass = Bean.class;
while (klass != null) {
   final Method[] declaredMethods = klass.getDeclaredMethods();
   for (final Method method : declaredMethods) {
      if (method.getName().startsWith("get") && 
          method.getParameterTypes().length == 0 && 
          !method.getReturnType().equals(void.class)) {
         getMethods.add(method);
      }
   }
   klass = klass.getSuperclass();
   [...]
}

Solution

There are three APIs which have heavily influenced the design of the fluent-reflection API.

  1. LambdaJ
  2. Hamcrest
  3. Jmock 2

All of them are examples of a style of API design known as fluent interfaces. They use a combination of static methods, method chaining, generic methods, static factory methods and object scoping to produce a type safe and readable programming style.

In particular, the combination of method chaining and generic methods can help the user discover the usage of each part of the API and also takes advantage of Java’s static type system to prevent programming errors.

Using fluent-reflection the above code can be re-written in declarative style:

final List<ReflectedMethod> getMethods = 
   object(new Bean()).methods(
      callableHasNameStartingWith("get").
      and(callableHasNoArguments()).
      and(not(callableHasVoidReturn())));

Extensibility

The query methods in the API are given queries described by Hamcrest Matchers. So users can easily construct custom and reusable queries.

For example, this Matcher can select methods with a particular annotation:

class MatcherAnnotatedWith extends ReflectionMatcher<ReflectedAnnotated> {
    private final Class<? extends Annotation> annotation;
 
    public MatcherCallableAnnotatedWith(final Class<? extends Annotation> annotation) {
        this.annotation = annotation;
    }
 
    @Override protected boolean matchesSafely(final ReflectedAnnotated item) {
        return item.annotation(reflectedTypeReflectingOn(annotation)) != null;
    }
 
    @Override public void describeTo(final Description description) {
        description.appendText("callable annotated with ").appendValue(annotation);
    }
}

Mashability

Because the API uses the de facto standard Hamcrest matchers, and the Java collections framework it can be combined with other libraries in powerful ways.

For example, calling all of the @PostConstruct methods on an Object using LambdaJ and fluent-reflection:

forEach(
   object(subject).methods(annotatedWith(PostConstruct.class)),
   ReflectedMethod.class).call();

Fail Fast – Fail During Compile

One of the fastest ways for faulty software to fail is at compile time.

Here is an example of slow failing code, it will fail at runtime if it comes across an implementation of A that it did not expect. This can happen if a developer adds a new implementation of A but forgets to update this code (or is not aware of it, or it is located in some other dependent library).

interface A { }
class B implements A { }
class C implements A { }
 
void doSomething(A a) {
   // not recommend
   if(a instanceof B) {
      doMyBThings((B) a);
   } else if(a instanceof C) {
      doMyCThings((C) a);
   } else {
      throw IllegalArgumentException("unexpected kind of A");
   }
}

One way to move these failures to compile time would be to move the responsibility for doing my B things and doing my C things on to the classes B and C respectively.

interface A { void doMyThings(); }
class B implements A { void doMyThings(){ /* do my B things */ } }
class C implements A { void doMyThings(){ /* do my C things */ } }
 
void doSomething(A a) {
   a.doMyThings();
}

If I add a new type D the compiler forces us to implement doMyThings()

class D implements A { void doMyThings(){ /* do my D things */ } }

Sometimes it is not appropriate to put the responsibility to doMyThings() onto A. It might create undesirable coupling (a dependency) or have some other undesirable property. I can maintain the fail fast property in in other ways.

interface A { void doThings(Things things); }
class B implements A { void doThings(Things things){ things.doBThings(this); } }
class C implements A { void doThings(Things things){ things.doCThings(this); } }
 
interface Things {
      void doBThings(B b);
      void doCThings(C c);
}
 
void doSomething(A a) {
   a.doThings(new Things(){
      void doBThings(B b){ /* do my B things */}
      void doCThings(C c){ /* do my C things */}
   });
}

Adding a new type D, the compiler forces me to implement doThings on D, which leads me to add a new method onto the Things interface, which forces any implementers of Things to handle D types.

class D implements A { void doThings(Things things){ things.doDThings(this); } }
 
interface Things {
      void doBThings(B b);
      void doCThings(C c);
      void doDThings(D d);
}
 
void doSomething(A a) {
   a.doThings(new Things(){
      void doBThings(B b){ /* do my B things */}
      void doCThings(C c){ /* do my C things */}
      void doDThings(D d){ /* do my D things */}
   });
}

However, a user might not want to handle every new implementation of A, I can provide default implementations of the methods:

class BaseThings implements Things {
      void doBThings(B b) { };
      void doCThings(C c) { };
}
 
void doSomething(A a) {
   a.doThings(new BaseThings(){
      void doBThings(B b){ /* do my B things */}
      void doCThings(C c){ /* do my C things */}
   });
}

When I add D I also add a default implementation, so I do not have to add any new handling code:

class BaseThings implements Things {
      void doBThings(B b) { };
      void doCThings(C c) { };
      void doDThings(D d) { };
}
 
void doSomething(A a) {
   a.doThings(new BaseThings(){
      void doBThings(B b){ /* do my B things */}
      void doCThings(C c){ /* do my C things */}
   });
}

I can choose to either have my code break when new implementations of A are added (by extending Things) or not to break (by extending BaseThings).