Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Not all reflection is bad

Code Block
public class Reflectomatic {
    public static List<Field> allFieldsIn(Class<?> declaringClass, Matcher<? super Field> fieldMatcher) {
        List<Field> fields = new ArrayList<Field>();
        collectFields(declaringClass, fieldMatcher, fields);
        return fields;
    }

    public static Field fieldIn(Class<?> declaringClass, Matcher<? super Field> fieldMatcher) {
        List<Field> fields = new ArrayList<Field>();
        collectFields(declaringClass, fieldMatcher, fields);
        if (fields.size() != 1) {
            throw new Defect("Wanted a single field, but got " + fields.size());
        }
        return fields.get(0);
    }

    private static void collectFields(Class<?> type, Matcher<? super Field> fieldMatcher, List<Field> collection) {
        if (type != Object.class) {
            for (Field field : type.getDeclaredFields()) {
                if (fieldMatcher.matches(field)) {
                    collection.add(field);
                }
            }

            collectFields(type.getSuperclass(), fieldMatcher, collection);
        }
    }

    public static List<Method> allMethodsIn(Class<?> declaringClass, Matcher<? super Method> methodMatcher) {
        List<Method> methods = new ArrayList<Method>();
        collectMethods(declaringClass, methodMatcher, methods);
        return methods;
    }

    public static List<Method> allDeclaredMethodsIn(Class<?> declaringClass, Matcher<? super Method> methodMatcher) {
        List<Method> methods = new ArrayList<Method>();
        collectDeclaredMethods(declaringClass, methodMatcher, methods);
        return methods;
    }

    private static void collectMethods(Class<?> type, Matcher<? super Method> methodMatcher, List<Method> collection) {
        if (type != Object.class) {
            collectDeclaredMethods(type, methodMatcher, collection);
            collectMethods(type.getSuperclass(), methodMatcher, collection);
        }
    }

    private static void collectDeclaredMethods(Class<?> type, Matcher<? super Method> methodMatcher, List<Method> collection) {
        for (Method method : type.getDeclaredMethods()) {
            if (methodMatcher.matches(method)) {
                collection.add(method);
            }
        }
    }

    public static Set<Class> allInterfacesDeclaredBy(Object object) {
        Set<Class> allInterfaces = com.google.common.collect.Sets.newHashSet();

        Class type = object.getClass();
        while (type != Object.class) {
            Class[] interfaces = type.getInterfaces();
            allInterfaces.addAll(Arrays.asList(interfaces));
            type = type.getSuperclass();
        }

        return allInterfaces;
    }

    public static Matcher<Member> isPublic() {
        return hasModifiers(java.lang.reflect.Modifier.PUBLIC);
    }

    public static Matcher<Member> hasModifiers(final int expectedModifiers) {
        return new TypeSafeMatcher<Member>() {
            public boolean matchesSafely(Member member) {
                return (member.getModifiers() & expectedModifiers) != 0;
            }

            public void describeTo(Description description) {
                description.appendText("a public member");
            }
        };
    }

    public static Matcher<Member> isStatic() {
        return hasModifiers(Modifier.STATIC);
    }

    public static <T> Constructor<?> findConstructor(Class<T> type, Class<?>... argTypes) {
        for (Constructor<?> constructor : type.getDeclaredConstructors()) {
            if (Arrays.equals(constructor.getParameterTypes(), argTypes)) {
                return constructor;
            }
        }

        throw new IllegalArgumentException("no matching constructor found");
    }

    private static Map<Class<?>, Class<?>> boxes = new HashMap<Class<?>, Class<?>>() {{
        put(boolean.class, Boolean.class);
        put(char.class, Character.class);
        put(short.class, Short.class);
        put(int.class, Integer.class);
        put(long.class, Long.class);
        put(float.class, Float.class);
        put(double.class, Double.class);
    }};

    @SuppressWarnings("unchecked")
    public static <T> Class<T> boxedTypeFor(Class<T> type) {
        if (boxes.containsKey(type)) {
            return (Class<T>) boxes.get(type);
        } else {
            return type;
        }
    }

    public static void inject(Object thingToInjectInto, Object thingToInject, String fieldName) throws IllegalAccessException {
        Field field = Reflectomatic.allFieldsIn(thingToInjectInto.getClass(), ReflectionMatchers.fieldNamed(org.hamcrest.Matchers.equalTo(fieldName))).get(0);
        field.setAccessible(true);
        field.set(thingToInjectInto, thingToInject);
    }

    public static void copyFields(Object to, Object from) {
        for (Field f : allFieldsIn(to.getClass(), not(finalField()))) {
            f.setAccessible(true);
            try {
                f.set(to, f.get(from));
            } catch (IllegalAccessException e) {
                throw new Defect("could not set accessible field", e);
            }
        }
    }
}

And some matchers that match that....

Code Block
public class ReflectionMatchers {
    public static <T> Matcher<T> samePublicFieldsAs(final T example) {
        return new TypeSafeMatcher<T>() {
            Object actualValue;
            Object exampleValue;
            Field currentField;

            public boolean matchesSafely(Object o) {
                if (example.getClass() != o.getClass()) {
                    return false;
                }

                for (Field field : Reflectomatic.allFieldsIn(example.getClass(), Reflectomatic.isPublic())) {
                    try {
                        exampleValue = field.get(example);
                        actualValue = field.get(o);
                        currentField = field;

                        if (!safeEquals(exampleValue, actualValue)) return false;

                    } catch (IllegalAccessException e) {
                        throw new Defect("could not get value of public field");
                    }
                }

                return true;
            }

            public void describeTo(Description description) {
                description.appendText("Not all public fields matched, " +
                    " for field '" + currentField.getName() + "'" +
                    "\n\t\texpected:").appendValue(exampleValue)
                        .appendText("\n\t\tbut got: ").appendValue(actualValue);
            }
        };
    }

    private static boolean safeEquals(Object exampleValue, Object actualValue) {
        return (exampleValue == null && actualValue == null)
                || (exampleValue != null && exampleValue.equals(actualValue));
    }

    public static Matcher<? super Field> fieldNamed(final Matcher<? super String> matcher) {
        return new TypeSafeMatcher<Field>() {
            public boolean matchesSafely(Field field) {
                return matcher.matches(field.getName());
            }

            public void describeTo(Description description) {
                description.appendText("Field named ");
                description.appendDescriptionOf(matcher);
            }
        };
    }

    public static Matcher<? super Field> primitiveField() {
        return new  TypeSafeMatcher<Field>() {
            public boolean matchesSafely(Field field) {
                return field.getDeclaringClass().isPrimitive();
            }

            public void describeTo(Description description) {
                description.appendText("Field is primitive");
            }
        };
    }

    public static Matcher<? super Field> finalField() {
        return new  TypeSafeMatcher<Field>() {
            public boolean matchesSafely(Field field) {
                return Modifier.isFinal(field.getModifiers());
            }

            public void describeTo(Description description) {
                description.appendText("Field is final");
            }
        };
    }

    public static Matcher<? super Method> methodNamed(final Matcher<String> matcher) {
        return new TypeSafeMatcher<Method>() {
            public boolean matchesSafely(Method method) {
                return matcher.matches(method.getName());
            }

            public void describeTo(Description description) {
                description.appendText("Method named ");
                description.appendDescriptionOf(matcher);
            }
        };
    }

    public static Matcher<? super Field> nonStaticField() {
        return new TypeSafeMatcher<Field>() {
            @Override
            public boolean matchesSafely(Field field) {
                return !Modifier.isStatic(field.getModifiers());
            }

            public void describeTo(Description description) {
                description.appendText("Non static fields");
            }
        };
    }

    public static Matcher<? super Field> isNull(final Object o) {
        return new TypeSafeMatcher<Field>() {
            @Override
            public boolean matchesSafely(Field field) {
                try {
                    return field.get(o) == null;
                } catch (IllegalAccessException e) {
                    throw new Defect("Should not happen", e);
                }
            }

            public void describeTo(Description description) {
                description.appendText("Null fields");
            }
        };
    }
}