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
+ * 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
+ * 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
+ * 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
+ * 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
+ * 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
+ *
+ * Note: meaningless if {@link #willCheckInterfaces()} is
+ * Note: meaningless if {@link #willCheckInterfaces()} is 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
+ * 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
+ * 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
+ * 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.
+ * 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.
+ * 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.
+ * 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