Member finder utility

This commit is contained in:
Emmanuel BENOîT 2015-09-12 11:13:44 +02:00
parent d633aac37d
commit 722f071674

View file

@ -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.
* <p>
* 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 <T>
* the type of members being examined
*
* @author <a href="mailto:ebenoit@ebenoit.info">E. Benoît</a>
*/
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
* <p>
* 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 <code>null</code>)
* @param classFilter
* the class filtering function (may be <code>null</code>)
* @param interfaces
* whether interfaces should be examined too
* @param classesFirst
* if interfaces are to be examined, <code>true</code> 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
* <p>
* 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 <code>null</code>)
* @param classFilter
* the class filtering function (may be <code>null</code>)
* @param interfaces
* whether interfaces should be examined too
* @param classesFirst
* if interfaces are to be examined, <code>true</code> 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
* <p>
* 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 <code>null</code>)
* @param classFilter
* the class filtering function (may be <code>null</code>)
* @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
* <p>
* 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 <code>null</code>)
* @param classFilter
* the class filtering function (may be <code>null</code>)
* @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;
/** <code>true</code> if the finder will go through interfaces, <code>false</code> 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 <code>false</code>.
*/
protected boolean classesFirst;
/**
* Initialises the member finder by setting the appropriate extractor and whether or not it will go through
* interfaces.
* <p>
* 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
* <code>true</code> if the finder must go through interfaces, <code>false</code> 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 <code>true</code> if the finder will go through interfaces, <code>false</code> if it will ignore them
*/
public boolean willCheckInterfaces( )
{
return this.interfaces;
}
/**
* Gets the class filtering function
*
* @return the class filtering function, or <code>null</code> 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 <code>null</code> 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.
* <p>
* Note: meaningless if {@link #willCheckInterfaces()} is <code>false</code>.
*
* @return <code>true</code> if superclasses are examined before interfaces, <code>false</code> if interfaces are
* examined before superclasses.
*/
public boolean willCheckClassesFirst( )
{
return this.classesFirst;
}
/**
* Selects whether superclasses are to be examined before or after interfaces.
* <p>
* Note: meaningless if {@link #willCheckInterfaces()} is <code>false</code>.
*
* @param classesFirst
* <code>true</code> if superclasses are to be examined before interfaces, <code>false</code> 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 <code>null</code> if {@link #interfaces} is set
* to <code>false</code>.
* @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 );
}
}
}
}