查询解析器以构建 Predicate 以过滤内存中的 bean
Query parser to build Predicate for filtering in-memory beans
我正在寻找一个可以解释查询(例如在配置文件中定义)并从中构造一个 java.util.Predicate 的解析器,我可以用它来过滤一组 bean。
例如我希望能够定义
name = "John*" AND age < 30 and address.city = "Frankfurt"
然后我想使用这个 Predicate 来过滤
类型的 beans
class Person {
String getName() { ... }
int getAge() { ... }
Address getAddress() { ... }
}
class Address {
String getCity() { ... }
}
实际上它并不真的需要使用 Predicate 实例,我只是在寻找一个工具来获取我的文本查询和 bean 集合以及 returns 这些 bean 的过滤集合。
您可以使用 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());
}
}
}
我的最终解决方案(基于@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
我正在寻找一个可以解释查询(例如在配置文件中定义)并从中构造一个 java.util.Predicate 的解析器,我可以用它来过滤一组 bean。
例如我希望能够定义
name = "John*" AND age < 30 and address.city = "Frankfurt"
然后我想使用这个 Predicate 来过滤
类型的 beansclass Person {
String getName() { ... }
int getAge() { ... }
Address getAddress() { ... }
}
class Address {
String getCity() { ... }
}
实际上它并不真的需要使用 Predicate 实例,我只是在寻找一个工具来获取我的文本查询和 bean 集合以及 returns 这些 bean 的过滤集合。
您可以使用 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());
}
}
}
我的最终解决方案(基于@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