Saravanan
Saravanan

Reputation: 7844

Construct a predicate using custom object model in java

I have a object model like the one given below

public class Filter {
    public String field;
    public ConditionalOperator operator;
    public String value;
}

I have a list of objects like List<Employees>

Based on the Filter inputs, I want to construct the predicate on the property and apply that to the list of employees.

Example:

Employees
   FirstName
   LastName
   CreatedOn (Timestamp)
   Status (FullTime/ parttime)
   IsActive(True / False)

Filter conditions will be looking like 

[
{ "field":"FirstName", "operator":"StartsWith", "value":"john"}
]

The operators are like

Contains, StartsWith, EndsWith,Equals

I would like to construct the predicate like PredicateBuilder(fieldName, operator, value) so that I can get like

Predicate<Employees> filter = e -> e.FirstName.StartsWith("john");

I have tried the one link

Predicate with Reflection in java

In this, I was able to infer the propertyname, apply the equals method to the dynamic value like

Class<?> cls = Employees.class;
Class<?> noparams[] = {};
try {
    Method method = cls.getDeclaredMethod("get" + filter.getField(), noparams);
    Predicate<ExecutionReport> first = e -> {
        try {
            Object val = method.invoke(e);
            System.out.println(val);
            return method.invoke(e).toString().startsWith(filter.getField());
        } catch (IllegalAccessException illegalAccessException) {
            illegalAccessException.printStackTrace();
        } catch (InvocationTargetException invocationTargetException) {
            invocationTargetException.printStackTrace();
        }
        return false;
    };
    return first;
} catch (NoSuchMethodException e) {
    e.printStackTrace();
}

Please guide me on how to construct the dynamic predicates, I have been searching the internet but with no luck and i have less information on reflection and predicates in Java

Upvotes: 4

Views: 5532

Answers (2)

ernest_k
ernest_k

Reputation: 45339

The aspect about reading the field dynamically is just part of it. It's the only thing that prevents your implementation from fully being based on static code.

Here's a solution that puts the "comparison" logic in your "operator" enum itself.

enum ConditionalOperator {
    CONTAINS(String::contains), 
    STARTSWITH(String::startsWith), 
    ENDSWITH(String::endsWith), 
    EQUALS(String::equals);

    private final BiPredicate<String, String> predicate;

    private ConditionalOperator(BiPredicate<String, String> predicate) {
        this.predicate = predicate;
    }

    public <T> Predicate<T> toPredicate(Function<T, String> getter, 
               String search) {
        return object -> this.predicate.test(getter.apply(object), search);
    }
}

The toPredicate() method takes a getter that would convert the incoming object to a String.

The next thing is a function method that creates the getter given a type and an field name:

private static <T> Function<T, String> fieldExtractor(Class<T> cls, String field){
    return object -> {
        try {
            Field f = cls.getDeclaredField(field);
            f.setAccessible(true);

            return (String) f.get(object);
        } catch (Exception e) {
            //handle properly
            throw new RuntimeException(e);
        }
    };
}

With the above, you can convert a Filter object to a predicate by doing something like:

Filter filter = <dynamic value>;

Predicate<Employee> filterPredicate = filter.getOperator()  
    .toPredicate(fieldExtractor(Employee.class, filter.getField()), 
                 filter.getValue());

You may even want to cache the result of fieldExtractor if you see fit.

Upvotes: 4

Johannes Kuhn
Johannes Kuhn

Reputation: 15192

Let's start by using some simple building blocks:

public enum ConditionalOperator implements BiPredicate<String, String> {
    Contains((test, value) -> test.contains(value)),
    StartsWith((test, value) -> test.startsWith(value)),
    EndsWith((test, value) -> test.endsWith(value)),
    Equals((test, value) -> test.equals(value));

    private final BiPredicate<String, String> predicate;

    ConditionalOperator(BiPredicate<String, String> predicate) {
        this.predicate = predicate;
    }

    @Override
    public boolean test(String test, String value) {
        return predicate.test(test, value);
    }
}

I took the liberty to implement it as an enum, not sure what it is in your design.

Now we need a value extractor:

public static Function<Employee, String> getField(String name) {
    try {
        Method method = Employee.class.getMethod("get" + name);
        if (method.getReturnType() != String.class) {
            throw new IllegalArgumentException("Employee.get" + name + " does not return a String");
        }
        return e -> {
            try {
                return (String) method.invoke(e);
            } catch (ReflectiveOperationException roe) {
                // Unlikely to happen
                throw new RuntimeException(roe);
            }
        }
    } catch (ReflectiveOperationException roe) {
        // getter does not exist, what now?
        throw new IllegalArgumentException(roe);
    }
}

And last, we need to chain everything together:

public static Predicate<Employee> buildPredicate(Filter f) {
    Function<Employee, String> fieldGetter = getField(f.field());
    ConditionalOperator op = f.operator();
    String value = f.value();
    return e -> op.test(fieldGetter.apply(e), value);
}

This only works for Strings for now, but you can probably adapt it - the easiest is to remove the the check for the return value and instead of casting the result to String call .toString() on it.

Upvotes: 4

Related Questions