Reputation: 7844
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
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
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 String
s 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