/** * Copyright (C) 2008 Mathieu Carbou <mathieu.carbou@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mycila.testing.core.introspect; import static com.mycila.testing.core.api.Ensure.*; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import static java.lang.reflect.Modifier.*; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * @author Mathieu Carbou (mathieu.carbou@gmail.com) */ public final class Filters { private Filters() { } @SuppressWarnings({"unchecked"}) public static <T> Filter<T> all() { return new Filter<T>() { @Override public boolean accept(T object) { return true; } }; } @SuppressWarnings({"unchecked"}) public static <T> Filter<T> none() { return new Filter<T>() { @Override public boolean accept(T object) { return false; } }; } public static <T> Filter<T> not(final Filter<T> filter) { notNull("Filter", filter); return new Filter<T>() { @Override public boolean accept(T object) { return !filter.accept(object); } }; } public static <T> Filter<T> and(final Filter<T>... filters) { notNull("Filters", filters); return new Filter<T>() { @Override public boolean accept(T object) { for (Filter<T> filter : filters) { if (!filter.accept(object)) { return false; } } return true; } }; } public static <T> Filter<T> or(final Filter<T>... filters) { notNull("Filters", filters); return new Filter<T>() { @Override public boolean accept(T object) { for (Filter<T> filter : filters) { if (filter.accept(object)) { return true; } } return false; } }; } /** * Filter that select fields having specified type or a sub-type of the specified type * * @param type Selected type * @return Created filter */ public static Filter<Field> fieldsProviding(final Class<?> type) { notNull("Field type", type); return new Filter<Field>() { @Override public boolean accept(Field object) { return type.isAssignableFrom(object.getType()); } }; } /** * Filter that select fields having specified type or a super-type of the specified type * * @param type Selected type * @return Created filter */ public static Filter<Field> fieldsAccepting(final Class<?> type) { notNull("Field type", type); return new Filter<Field>() { @Override public boolean accept(Field object) { return object.getType().isAssignableFrom(type); } }; } /** * Select methods returning objects that are of a given type or of a sub-type of given type * * @param type The type of the return * @return Teh created filter */ public static Filter<Method> methodsReturning(final Class<?> type) { notNull("Method return type", type); return new Filter<Method>() { @Override public boolean accept(Method object) { return type.isAssignableFrom(object.getReturnType()); } }; } /** * Returns fields annotated by a given annotation * * @param annotation The annotation * @return The created filter */ public static Filter<Field> fieldsAnnotatedBy(final Class<? extends Annotation> annotation) { notNull("Annotation", annotation); return new Filter<Field>() { @Override public boolean accept(Field object) { return object.isAnnotationPresent(annotation); } }; } /** * Returns methods annotated by a given annotation * * @param annotation The annotation * @return The created filter */ public static Filter<Method> methodsAnnotatedBy(final Class<? extends Annotation> annotation) { notNull("Annotation", annotation); return new Filter<Method>() { @Override public boolean accept(Method object) { return object.isAnnotationPresent(annotation); } }; } /** * This filter will exclude methods that are overriden, as defined * in the JLS at http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.8 * * @param methodFilter The method filter used to accept methods * @return Created filter */ public static Filter<Method> excludeOverridenMethods(final Filter<Method> methodFilter) { return new Filter<Method>() { @Override public void add(Method element) { methodFilter.add(element); } @Override protected boolean accept(Method object) { return methodFilter.accept(object); } @Override public List<Method> select() { final LinkedList<Method> methods = new LinkedList<Method>(methodFilter.select()); // first pass to eliminate non overridable methods for (Iterator<Method> iterator = methods.iterator(); iterator.hasNext();) { final Method m = iterator.next(); final int modifiers = m.getModifiers(); if ((modifiers & (FINAL | PRIVATE | STATIC | NATIVE)) != 0) { elements.add(m); iterator.remove(); } else if ((modifiers & (INTERFACE | ABSTRACT | VOLATILE)) != 0) { iterator.remove(); } } // second pass to eliminate each remaining overridable methods while (!methods.isEmpty()) { // pick a remaining method final Method m1 = methods.poll(); boolean overriden = false; // check if it overrides to is overriden against each other ones for (Iterator<Method> iterator = methods.iterator(); iterator.hasNext();) { final Method m2 = iterator.next(); if (m1.getName().equals(m2.getName()) && Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes())) { // if the signature is the same, we have to check for override final Class<?> c = m1.getDeclaringClass(); final Class<?> a = m2.getDeclaringClass(); if (a.isAssignableFrom(c)) { // m1 overrides m2 => remove m2 iterator.remove(); } else if (c.isAssignableFrom(a)) { // if m2 overrides m1 => stop and pick next method overriden = true; break; } } } if (!overriden) { // we removed all methods m1 overrides, and m1 is not overriden by any other method => we keep it elements.add(m1); } } return elements; } }; } }