Парсер запросов для построения предиката для фильтрации bean-объектов в памяти - PullRequest
0 голосов
/ 01 февраля 2019

Я ищу анализатор, который может интерпретировать запрос (например, определенный в файле конфигурации) и создать из него java.util.Predicate, который я могу использовать для фильтрации коллекции bean-компонентов.

Например, я хотел бы иметь возможность определять

name = "John*" AND age < 30 and address.city = "Frankfurt"

. Затем я хотел бы использовать этот предикат для фильтрации бобов типа

class Person {
    String getName() { ... }
    int getAge() { ... }
    Address getAddress() { ... }
}
class Address {
    String getCity() { ... }
}

На самом деле его не нужно использоватьэкземпляр Predicate, я просто ищу инструмент, который принимает мой текстовый запрос и коллекцию bean-компонентов и возвращает отфильтрованную коллекцию этих bean-компонентов.

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

Мое окончательное решение (на основе ответа @Maurice Perry) таково (вам также нужны классы из этого ответа):

public static void main(String[] args) {
    // data
    Collection<Person> persons = new ArrayList<>();
    persons.add(new Person("John", 25, new Address("Frankfurt")));
    persons.add(new Person("Jakob", 21, new Address("Frankfurt")));
    persons.add(new Person("Peter", 25, new Address("Frankfurt")));
    persons.add(new Person("Jürgen", 46, new Address("Frankfurt")));
    persons.add(new Person("John", 25, new Address("Frankfurt/Oder")));

    // query
    long startMillis = System.currentTimeMillis();
    QueryPredicate<Person> predicate = new QueryPredicate("name.startsWith('J') && age < 30 && address.city == 'Frankfurt'");
    List<Person> matchingPersons = persons.stream().filter(predicate).collect(Collectors.<Person>toList());
    System.out.println(String.format("Found %d matches in %dms", matchingPersons.size(), System.currentTimeMillis() - startMillis));
}

public static class QueryPredicate<T> implements Predicate<T> {

    private final String query;
    private final ScriptEngine engine;

    public QueryPredicate(String query) {
        this.query = query;
        this.engine = new ScriptEngineManager().getEngineByName("JavaScript");
    }

    @Override
    public boolean test(T t) {
        try {
            Object result = this.engine.eval(this.query, new BeanBindings(t));
            return Boolean.TRUE.equals(result);
        } catch (ScriptException e) {
            return false;
        }
    }
}

// other classes go here, e.g. Person etc
0 голосов
/ 01 февраля 2019

Вы можете использовать javax.script.ScriptEngine:

public static void main(String[] args) {
    try {
        ScriptEngineManager factory = new ScriptEngineManager();
        // create a JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // evaluate JavaScript code from String
        String expression
                = "name == \"John\" && age < 30 && address.city == \"Frankfurt\"";
        Person person = new Person("John", 25, new Address("Frankfurt"));
        System.out.println(engine.eval(expression, new BeanBindings(person)));
    } catch (ScriptException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
    }
}
public static class Person {
    String name;
    int age;
    Address address;

    Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public Address getAddress() {
        return address;
    }
}
public static class Address {
    private String city;

    public Address(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
}

private static class BeanBindings implements Bindings {
    private final Object bean;

    public BeanBindings(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object put(String name, Object value) {
        return null;
    }

    @Override
    public void putAll(Map<? extends String, ? extends Object> toMerge) {
    }

    @Override
    public boolean containsKey(Object key) {
        Method getter = getter((String)key);
        return getter != null;
    }

    @Override
    public Object get(Object key) {
        Method getter = getter((String)key);
        try {
            return getter == null ? null : getter.invoke(bean);
        } catch (IllegalAccessException | IllegalArgumentException
                | InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    @Override
    public Object remove(Object key) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public int size() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean isEmpty() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean containsValue(Object value) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Set<String> keySet() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Collection<Object> values() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Set<Entry<String, Object>> entrySet() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private Method getter(String propName) {
        if (propName == null || propName.length() == 0) {
            throw new IllegalArgumentException("empty property name");
        }
        try {
            String sfx = Character.toUpperCase(propName.charAt(0))
                    + propName.substring(1);
            return bean.getClass().getMethod(
                    "get" + sfx, new Class[0]);
        } catch (NoSuchMethodException ex) {
            return null;
        } catch (SecurityException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }
}
...