From 722f07167406ddb3f0a8776cef37a83b8a77686d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Sat, 12 Sep 2015 11:13:44 +0200 Subject: [PATCH] Member finder utility --- .../ebenoit/ebul/reflection/MemberFinder.java | 351 ++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 src/main/java/info/ebenoit/ebul/reflection/MemberFinder.java diff --git a/src/main/java/info/ebenoit/ebul/reflection/MemberFinder.java b/src/main/java/info/ebenoit/ebul/reflection/MemberFinder.java new file mode 100644 index 0000000..1c559fe --- /dev/null +++ b/src/main/java/info/ebenoit/ebul/reflection/MemberFinder.java @@ -0,0 +1,351 @@ +package info.ebenoit.ebul.reflection; + + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.function.Function; +import java.util.function.Predicate; + + + +/** + * Helper to find methods and fields in a class hierarchy. + *

+ * This class can be used to look for specific members in a class hierarchy. It can be set up so that it also checks + * interfaces. + * + * @param + * the type of members being examined + * + * @author E. BenoƮt + */ +public class MemberFinder< T > +{ + + /** Function to extract methods from a class */ + public static final Function< Class< ? > , Method[] > METHOD_EXTRACTOR = c -> c.getMethods( ); + /** Function to extract fields from a class */ + public static final Function< Class< ? > , Field[] > FIELD_EXTRACTOR = c -> c.getFields( ); + + + /** + * Lists methods matching some criteria + *

+ * This method is a shortcut that configures a {@link MemberFinder} instance for methods, and applies it. + * + * @param target + * the class to examine + * @param methodFilter + * the method filtering function (may be null) + * @param classFilter + * the class filtering function (may be null) + * @param interfaces + * whether interfaces should be examined too + * @param classesFirst + * if interfaces are to be examined, true if superclasses should be examined first + * @return an array of methods matching the specified criteria + */ + public static final ArrayList< Method > getAllMethods( final Class< ? > target , + final Predicate< Method > methodFilter , final Predicate< Class< ? > > classFilter , + final boolean interfaces , final boolean classesFirst ) + { + final ArrayList< Method > result = new ArrayList< >( ); + MemberFinder.getAllMethods( result , target , methodFilter , classFilter , interfaces , classesFirst ); + return result; + } + + + /** + * Lists methods matching some criteria + *

+ * This method is a shortcut that configures a {@link MemberFinder} instance for methods, and applies it. + * + * @param output + * the collection to which matching methods will be added + * @param target + * the class to examine + * @param methodFilter + * the method filtering function (may be null) + * @param classFilter + * the class filtering function (may be null) + * @param interfaces + * whether interfaces should be examined too + * @param classesFirst + * if interfaces are to be examined, true if superclasses should be examined first + * @return the output collection to which methods matching the specified criteria were added + */ + public static final Collection< Method > getAllMethods( final Collection< Method > output , + final Class< ? > target , final Predicate< Method > methodFilter , + final Predicate< Class< ? > > classFilter , final boolean interfaces , final boolean classesFirst ) + { + final MemberFinder< Method > mf = new MemberFinder< >( MemberFinder.METHOD_EXTRACTOR , interfaces ); + mf.setClassesFirst( classesFirst ); + mf.setClassFilter( classFilter ); + mf.setMemberFilter( methodFilter ); + mf.find( output , target ); + return output; + } + + + /** + * Lists fields matching some criteria + *

+ * This method is a shortcut that configures a {@link MemberFinder} instance for fields, and applies it. + * + * @param target + * the class to examine + * @param fieldFilter + * the field filtering function (may be null) + * @param classFilter + * the class filtering function (may be null) + * @return an array of fields matching the specified criteria + */ + public static final ArrayList< Field > getAllFields( final Class< ? > target , + final Predicate< Field > fieldFilter , final Predicate< Class< ? > > classFilter ) + { + final ArrayList< Field > result = new ArrayList< >( ); + MemberFinder.getAllFields( result , target , fieldFilter , classFilter ); + return result; + } + + + /** + * Lists fields matching some criteria + *

+ * This method is a shortcut that configures a {@link MemberFinder} instance for fields, and applies it. + * + * @param output + * the collection to which matching fields will be added + * @param target + * the class to examine + * @param fieldFilter + * the field filtering function (may be null) + * @param classFilter + * the class filtering function (may be null) + * @return the output collection to which fields matching the specified criteria were added + */ + public static final Collection< Field > getAllFields( final Collection< Field > output , final Class< ? > target , + final Predicate< Field > fieldFilter , final Predicate< Class< ? > > classFilter ) + { + final MemberFinder< Field > mf = new MemberFinder< >( MemberFinder.FIELD_EXTRACTOR , false ); + mf.setClassFilter( classFilter ); + mf.setMemberFilter( fieldFilter ); + mf.find( output , target ); + return output; + } + + /** Function to use in order to extract the class' members */ + protected final Function< Class< ? > , T[] > extractor; + /** Filter function for classes */ + protected Predicate< Class< ? > > classFilter; + /** Filter function for members */ + protected Predicate< T > memberFilter; + /** true if the finder will go through interfaces, false if it will ignore them */ + protected final boolean interfaces; + /** + * If the finder has to go through interfaces, this flag indicates whether they will be examined before or after a + * class' superclass. It has no effect if {@link #interfaces} is set to false. + */ + protected boolean classesFirst; + + + /** + * Initialises the member finder by setting the appropriate extractor and whether or not it will go through + * interfaces. + *

+ * By default, no class or member filtering functions are set, and superclasses will be examined before implemented + * interfaces. + * + * @param extractor + * the function to use in order to extract the class' members + * @param interfaces + * true if the finder must go through interfaces, false if it must ignore them + */ + public MemberFinder( final Function< Class< ? > , T[] > extractor , final boolean interfaces ) + { + this.extractor = extractor; + this.interfaces = interfaces; + this.classesFirst = true; + } + + + /** + * Gets the member extraction function + * + * @return the function that extracts class members + */ + public Function< Class< ? > , T[] > getExtractor( ) + { + return this.extractor; + } + + + /** + * Gets the interface-checking flag + * + * @return true if the finder will go through interfaces, false if it will ignore them + */ + public boolean willCheckInterfaces( ) + { + return this.interfaces; + } + + + /** + * Gets the class filtering function + * + * @return the class filtering function, or null if no class filtering has been configured + */ + public Predicate< Class< ? > > getClassFilter( ) + { + return this.classFilter; + } + + + /** + * Sets the class filtering function + * + * @param classFilter + * the new class filtering function, or null to disable class filtering + */ + public void setClassFilter( final Predicate< Class< ? > > classFilter ) + { + this.classFilter = classFilter; + } + + + /** + * Gets the member filtering function + * + * @return the member filtering function + */ + public Predicate< T > getMemberFilter( ) + { + return this.memberFilter; + } + + + /** + * Sets the member filtering function + * + * @param memberFilter + * the member filtering function + */ + public void setMemberFilter( final Predicate< T > memberFilter ) + { + this.memberFilter = memberFilter; + } + + + /** + * Checks whether superclasses are to be examined before or after interfaces. + *

+ * Note: meaningless if {@link #willCheckInterfaces()} is false. + * + * @return true if superclasses are examined before interfaces, false if interfaces are + * examined before superclasses. + */ + public boolean willCheckClassesFirst( ) + { + return this.classesFirst; + } + + + /** + * Selects whether superclasses are to be examined before or after interfaces. + *

+ * Note: meaningless if {@link #willCheckInterfaces()} is false. + * + * @param classesFirst + * true if superclasses are to be examined before interfaces, false if + * interfaces are to be examined before superclasses. + */ + public void setClassesFirst( final boolean classesFirst ) + { + this.classesFirst = classesFirst; + } + + + /** + * Finds members matching the currently configured criteria. + * + * @param output + * the collection to which the selected members will be added + * @param target + * the class to examine + * @return the collection to which the selected members were added + */ + public Collection< T > find( final Collection< T > output , final Class< ? > target ) + { + // We only need to make sure we're not going through the same class twice if we have to go through interfaces + final HashSet< Class< ? > > checked; + if ( this.interfaces ) { + checked = new HashSet< >( ); + } else { + checked = null; + } + + this.findInternal( output , checked , target ); + return output; + } + + + /** + * Internal method that implements the actual look-up. + * + * @param output + * the collection to which the selected members will be added + * @param checked + * the set of classes that were already examined; will be null if {@link #interfaces} is set + * to false. + * @param target + * the class being examined + */ + protected void findInternal( final Collection< T > output , final HashSet< Class< ? > > checked , + final Class< ? > target ) + { + if ( target == null || this.classFilter != null && !this.classFilter.test( target ) ) { + return; + } + checked.add( target ); + + if ( this.interfaces && !this.classesFirst ) { + for ( final Class< ? > intf : target.getInterfaces( ) ) { + if ( !checked.contains( intf ) ) { + this.findInternal( output , checked , intf ); + } + } + } + + this.findInternal( output , checked , target.getSuperclass( ) ); + + if ( this.interfaces && this.classesFirst ) { + this.checkInterfaces( output , checked , target ); + } + } + + + /** + * Internal method that checks a class' interfaces recursively + * + * @param output + * the collection to which the selected members will be added + * @param checked + * the set of classes that were already examined + * @param target + * the class being examined + */ + private void checkInterfaces( final Collection< T > output , final HashSet< Class< ? > > checked , + final Class< ? > target ) + { + for ( final Class< ? > intf : target.getInterfaces( ) ) { + if ( !checked.contains( intf ) ) { + this.findInternal( output , checked , intf ); + } + } + } + +} \ No newline at end of file