Создайте предикат, используя пользовательскую модель объекта в java - PullRequest
5 голосов
/ 23 апреля 2020

У меня есть объектная модель, подобная приведенной ниже

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

У меня есть список объектов, таких как List<Employees>

На основе входных данных фильтра я хочу построить предикат на имущество и применить это к списку сотрудников.

Пример:

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

Filter conditions will be looking like 

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

Операторы как

Contains, StartsWith, EndsWith, Equals

Я хотел бы построить предикат как PredicateBuilder(fieldName, operator, value), чтобы я мог получить как

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

Я попробовал одну ссылку

Предикат с отражением в java

В этом я смог вывести имя свойства, применить метод equals к значению Dynami c, например

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();
}

Пожалуйста, объясните мне, как построить предикаты c, я искал inte rnet, но безуспешно, и у меня меньше информации об отражении и предикатах в Java

Ответы [ 2 ]

5 голосов
/ 23 апреля 2020

Аспект динамического чтения поля - это только часть этого. Это единственное, что не позволяет вашей реализации полностью основываться на коде stati c.

Вот решение, которое помещает логи "сравнения" c в само перечисление "operator".

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);
    }
}

Метод toPredicate() принимает метод получения, который преобразует входящий объект в строку.

Следующим шагом является метод функции, который создает метод получения с заданным типом и именем поля:

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);
        }
    };
}

С учетом вышеизложенного вы можете преобразовать объект Filter в предикат, выполнив что-то вроде:

Filter filter = <dynamic value>;

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

Возможно, вы даже захотите кэшировать результат fieldExtractor, если вы считаю нужным.

5 голосов
/ 23 апреля 2020

Давайте начнем с использования нескольких простых строительных блоков:

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);
    }
}

Я взял на себя смелость реализовать его как enum, не уверен, что это в вашем дизайне.

Теперь нам нужен экстрактор значений:

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);
    }
}

И, наконец, нам нужно объединить все вместе:

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);
}

Пока это работает только для String s, но вы, вероятно, можете адаптировать это - проще всего снять проверку на возвращаемое значение и вместо приведения результата к строковому вызову .toString().

...