Author: amichalec
Date: Thu Mar 11 18:14:20 2010
New Revision: 921968
URL: http://svn.apache.org/viewvc?rev=921968&view=rev
Log:
FIQL parser and SearchCondition syntax tree builder
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/AndSearchCondition.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParseException.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParser.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/FiqlParserTest.java
Modified:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/ConditionType.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/OrSearchCondition.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/AndSearchCondition.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/AndSearchCondition.java?rev=921968&view=auto
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/AndSearchCondition.java
(added)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/AndSearchCondition.java
Thu Mar 11 18:14:20 2010
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Composite 'and' search condition
+ */
+public class AndSearchCondition<T> implements SearchCondition<T> {
+
+ private List<SearchCondition<T>> conditions;
+
+ public AndSearchCondition() {
+
+ }
+
+ public AndSearchCondition(List<SearchCondition<T>> conditions) {
+ this.conditions = conditions;
+ }
+
+ public void setConditions(List<SearchCondition<T>> conditions) {
+ this.conditions = conditions;
+ }
+
+ public boolean isMet(T pojo) {
+ for (SearchCondition<T> sc : conditions) {
+ if (!sc.isMet(pojo)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public T getCondition() {
+ return null;
+ }
+
+ public ConditionType getConditionType() {
+ return ConditionType.AND;
+ }
+
+ public List<SearchCondition<T>> getConditions() {
+ return Collections.unmodifiableList(conditions);
+ }
+
+ public List<T> findAll(List<T> pojos) {
+ List<T> result = new ArrayList<T>();
+ for (T pojo : pojos) {
+ if (isMet(pojo)) {
+ result.add(pojo);
+ }
+ }
+ return result;
+ }
+}
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java?rev=921968&view=auto
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java
(added)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/Beanspector.java
Thu Mar 11 18:14:20 2010
@@ -0,0 +1,222 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Bean introspection utility.
+ */
+class Beanspector<T> {
+
+ private Class<T> tclass;
+ private T tobj;
+ private Map<String, Method> getters = new HashMap<String, Method>();
+ private Map<String, Method> setters = new HashMap<String, Method>();
+
+ public Beanspector(Class<T> tclass) {
+ if (tclass == null) {
+ throw new IllegalArgumentException("tclass is null");
+ }
+ this.tclass = tclass;
+ init();
+ }
+
+ public Beanspector(T tobj) {
+ if (tobj == null) {
+ throw new IllegalArgumentException("tobj is null");
+ }
+ this.tobj = tobj;
+ init();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void init() {
+ if (tclass == null) {
+ tclass = (Class<T>)tobj.getClass();
+ }
+ for (Method m : tclass.getMethods()) {
+ if (isGetter(m)) {
+ getters.put(getterName(m), m);
+ } else if (isSetter(m)) {
+ setters.put(setterName(m), m);
+ }
+ }
+ // check type equality for getter-setter pairs
+ Set<String> pairs = new HashSet<String>(getters.keySet());
+ pairs.retainAll(setters.keySet());
+ for (String accessor : pairs) {
+ Class<?> getterClass = getters.get(accessor).getReturnType();
+ Class<?> setterClass =
setters.get(accessor).getParameterTypes()[0];
+ if (!getterClass.equals(setterClass)) {
+ throw new IllegalArgumentException(String
+ .format("Accessor '%s' type mismatch, getter type is %s
while setter type is %s",
+ accessor, getterClass.getName(),
setterClass.getName()));
+ }
+ }
+ }
+
+ public T getBean() {
+ return tobj;
+ }
+
+ public Set<String> getGettersNames() {
+ return Collections.unmodifiableSet(getters.keySet());
+ }
+
+ public Set<String> getSettersNames() {
+ return Collections.unmodifiableSet(setters.keySet());
+ }
+
+ public Class<?> getAccessorType(String getterOrSetterName) throws
Exception {
+ Method m = getters.get(getterOrSetterName);
+ if (m == null) {
+ m = setters.get(getterOrSetterName);
+ }
+ if (m == null) {
+ String msg = String.format("Accessor '%s' not found, "
+ + "known setters are: %s, known getters
are: %s", getterOrSetterName,
+ setters.keySet(), getters.keySet());
+ throw new IntrospectionException(msg);
+ }
+ return m.getReturnType();
+ }
+
+ public Beanspector<T> swap(T newobject) throws Exception {
+ if (newobject == null) {
+ throw new IllegalArgumentException("newobject is null");
+ }
+ tobj = newobject;
+ return this;
+ }
+
+ public Beanspector<T> instantiate() throws Exception {
+ tobj = tclass.newInstance();
+ return this;
+ }
+
+ public Beanspector<T> setValue(String setterName, Object value) throws
Throwable {
+ Method m = setters.get(setterName);
+ if (m == null) {
+ String msg = String.format("Setter '%s' not found, " + "known
setters are: %s", setterName,
+ setters.keySet());
+ throw new IntrospectionException(msg);
+ }
+ setValue(m, value);
+ return this;
+ }
+
+ public Beanspector<T> setValue(Map<String, Object> settersWithValues)
throws Throwable {
+ for (Map.Entry<String, Object> entry : settersWithValues.entrySet()) {
+ setValue(entry.getKey(), entry.getValue());
+ }
+ return this;
+ }
+
+ public Beanspector<T> setValue(Method setter, Object value) throws
Throwable {
+ Class<?> paramType = setter.getParameterTypes()[0];
+ value = narrowNumerics(paramType, value);
+ try {
+ setter.invoke(tobj, value);
+ return this;
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ } catch (IllegalArgumentException e) {
+ String msg = String.format("; setter parameter type: %s, set value
type: %s",
+ paramType.getName(),
value.getClass().getName());
+ throw new IllegalArgumentException(e.getMessage() + msg);
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+
+ // narrowing conversion is compile-time so in reflection is not supported
+ // this is clunky workaround for numeric datatypes
+ private Object narrowNumerics(Class<?> targetType, Object value) {
+ Class<?> sourceType = value.getClass();
+ if (sourceType == Double.class) {
+ if (targetType == Integer.class || targetType == Integer.TYPE) {
+ value = ((Double)value).intValue();
+ } else if (targetType == Short.class || targetType == Short.TYPE) {
+ value = ((Double)value).shortValue();
+ } else if (targetType == Byte.class || targetType == Byte.TYPE) {
+ value = ((Double)value).byteValue();
+ }
+ } else if (sourceType == Float.class) {
+ if (targetType == Integer.class || targetType == Integer.TYPE) {
+ value = ((Float)value).intValue();
+ } else if (targetType == Short.class || targetType == Short.TYPE) {
+ value = ((Float)value).shortValue();
+ } else if (targetType == Byte.class || targetType == Byte.TYPE) {
+ value = ((Float)value).byteValue();
+ }
+ } else if (sourceType == Integer.class) {
+ if (targetType == Short.class || targetType == Short.TYPE) {
+ value = ((Integer)value).shortValue();
+ } else if (targetType == Byte.class || targetType == Byte.TYPE) {
+ value = ((Integer)value).byteValue();
+ }
+ } else if (sourceType == Short.class
+ && (targetType == Byte.class || targetType == Byte.TYPE)) {
+ value = ((Integer)value).byteValue();
+
+ }
+ return value;
+ }
+
+ public Object getValue(String getterName) throws Throwable {
+ return getValue(getters.get(getterName));
+ }
+
+ public Object getValue(Method getter) throws Throwable {
+ try {
+ return getter.invoke(tobj);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+
+ private boolean isGetter(Method m) {
+ return m.getParameterTypes().length == 0
+ && (m.getName().startsWith("get") ||
m.getName().startsWith("is"));
+ }
+
+ private String getterName(Method m) {
+ return m.getName().replace("is", "").replace("get", "").toLowerCase();
+ }
+
+ private boolean isSetter(Method m) {
+ return m.getReturnType().equals(void.class) &&
m.getParameterTypes().length == 1
+ && (m.getName().startsWith("set") ||
m.getName().startsWith("is"));
+ }
+
+ private String setterName(Method m) {
+ return m.getName().replace("is", "").replace("set", "").toLowerCase();
+ }
+
+}
Modified:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/ConditionType.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/ConditionType.java?rev=921968&r1=921967&r2=921968&view=diff
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/ConditionType.java
(original)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/ConditionType.java
Thu Mar 11 18:14:20 2010
@@ -25,12 +25,12 @@ package org.apache.cxf.jaxrs.ext.search;
*/
public enum ConditionType {
EQUALS,
+ NOT_EQUALS,
LESS_THAN,
GREATER_THAN,
LESS_OR_EQUALS,
GREATER_OR_EQUALS,
OR,
AND,
- NOT,
CUSTOM
}
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParseException.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParseException.java?rev=921968&view=auto
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParseException.java
(added)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParseException.java
Thu Mar 11 18:14:20 2010
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+public class FiqlParseException extends Exception {
+
+ public FiqlParseException() {
+ super();
+ }
+
+ public FiqlParseException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+ public FiqlParseException(String arg0) {
+ super(arg0);
+ }
+
+ public FiqlParseException(Throwable arg0) {
+ super(arg0);
+ }
+
+}
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParser.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParser.java?rev=921968&view=auto
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParser.java
(added)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/FiqlParser.java
Thu Mar 11 18:14:20 2010
@@ -0,0 +1,339 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+
+/**
+ * Parses <a
href="http://tools.ietf.org/html/draft-nottingham-atompub-fiql-00">FIQL</a>
expression to
+ * construct {...@link SearchCondition} structure. Since this class operates
on Java type T, not on XML
+ * structures "selectors" part of specification is not applicable; instead
selectors describes getters of type
+ * T used as search condition type (see {...@link
SimpleSearchCondition#isMet(Object)} for details.
+ *
+ * @param <T> type of search condition.
+ */
+public class FiqlParser<T> {
+
+ public static final String OR = ",";
+ public static final String AND = ";";
+
+ public static final String GT = "=gt=";
+ public static final String GE = "=ge=";
+ public static final String LT = "=lt=";
+ public static final String LE = "=le=";
+ public static final String EQ = "==";
+ public static final String NEQ = "!=";
+
+ private static Map<String, ConditionType> operatorsMap;
+ static {
+ operatorsMap = new HashMap<String, ConditionType>();
+ operatorsMap.put(GT, ConditionType.GREATER_THAN);
+ operatorsMap.put(GE, ConditionType.GREATER_OR_EQUALS);
+ operatorsMap.put(LT, ConditionType.LESS_THAN);
+ operatorsMap.put(LE, ConditionType.LESS_OR_EQUALS);
+ operatorsMap.put(EQ, ConditionType.EQUALS);
+ operatorsMap.put(NEQ, ConditionType.NOT_EQUALS);
+ }
+
+ private Beanspector<T> beanspector;
+
+ /**
+ * Creates FIQL parser.
+ *
+ * @param tclass - class of T used to create condition objects in built
syntax tree. Class T must have
+ * accessible no-arg constructor and complementary setters to
these used in FIQL expressions.
+ */
+ public FiqlParser(Class<T> tclass) {
+ beanspector = new Beanspector<T>(tclass);
+ }
+
+ /**
+ * Parses expression and builds search filter. Names used in FIQL
expression are names of getters/setters
+ * in type T.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * class Condition {
+ * public String getFoo() {...}
+ * public void setFoo(String foo) {...}
+ * public int getBar() {...}
+ * public void setBar(int bar) {...}
+ * }
+ *
+ * FiqlParser<Condition> parser = new
FiqlParser<Condition>(Condition.class);
+ * parser.parse("foo==mystery*;bar=ge=10");
+ * </pre>
+ *
+ * @param fiqlExpression expression of filter.
+ * @return tree of {...@link SearchCondition} objects representing runtime
search structure.
+ * @throws FiqlParseException when expression does not follow FIQL grammar
+ */
+ public SearchCondition<T> parse(String fiqlExpression) throws
FiqlParseException {
+ ASTNode<T> ast = parseAndsOrsParens(fiqlExpression);
+ // System.out.println(ast);
+ return ast.build();
+ }
+
+ private ASTNode<T> parseAndsOrsParens(String expr) throws
FiqlParseException {
+ String s1 = "([\\p{ASCII}&&[^;,()]]+|\\([\\p{ASCII}]+\\))([;,])?";
+ Pattern p = Pattern.compile(s1);
+ Matcher m = p.matcher(expr);
+ List<String> subexpressions = new ArrayList<String>();
+ List<String> operators = new ArrayList<String>();
+ int lastEnd = -1;
+ while (m.find()) {
+ subexpressions.add(m.group(1));
+ operators.add(m.group(2));
+ if (lastEnd != -1 && lastEnd != m.start()) {
+ throw new FiqlParseException(String
+ .format("Unexpected characters \"%s\" starting at position
%d", expr.substring(lastEnd, m
+ .start()), lastEnd));
+ }
+ lastEnd = m.end();
+ }
+ if (operators.get(operators.size() - 1) != null) {
+ String op = operators.get(operators.size() - 1);
+ String ex = subexpressions.get(subexpressions.size() - 1);
+ throw new FiqlParseException("Dangling operator at the end of
expression: ..." + ex + op);
+ }
+ // looking for adjacent ANDs then group them into ORs
+ // Note: in case not ANDs is found (e.g only ORs) every single
subexpression is
+ // treated as "single item group of ANDs"
+ int from = 0;
+ int to = 0;
+ SubExpression ors = new SubExpression(OR);
+ while (to < operators.size()) {
+ while (to < operators.size() && AND.equals(operators.get(to))) {
+ to++;
+ }
+ SubExpression ands = new SubExpression(AND);
+ for (; from <= to; from++) {
+ String subex = subexpressions.get(from);
+ ASTNode<T> node = null;
+ if (subex.startsWith("(")) {
+ node = parseAndsOrsParens(subex.substring(1,
subex.length() - 1));
+ } else {
+ node = parseComparison(subex);
+ }
+ ands.add(node);
+ }
+ to = from;
+ if (ands.getSubnodes().size() == 1) {
+ ors.add(ands.getSubnodes().get(0));
+ } else {
+ ors.add(ands);
+ }
+ }
+ if (ors.getSubnodes().size() == 1) {
+ return ors.getSubnodes().get(0);
+ } else {
+ return ors;
+ }
+ }
+
+ private Comparison parseComparison(String expr) throws FiqlParseException {
+ String comparators = GT + "|" + GE + "|" + LT + "|" + LE + "|" + EQ +
"|" + NEQ;
+ String s1 = "[\\p{ASCII}]+(" + comparators + ")";
+ Pattern p = Pattern.compile(s1);
+ Matcher m = p.matcher(expr);
+ if (m.find()) {
+ String name = expr.substring(0, m.start(1));
+ String operator = m.group(1);
+ String value = expr.substring(m.end(1));
+ if ("".equals(value)) {
+ throw new FiqlParseException("Not a comparison expression: " +
expr);
+ }
+ Object castedValue = parseDatatype(name, value);
+ return new Comparison(name, operator, castedValue);
+ } else {
+ throw new FiqlParseException("Not a comparison expression: " +
expr);
+ }
+ }
+
+ private Object parseDatatype(String setter, String value) throws
FiqlParseException {
+ Object castedValue = value;
+ Class<?> valueType;
+ try {
+ valueType = beanspector.getAccessorType(setter);
+ } catch (Exception e) {
+ throw new FiqlParseException(e);
+ }
+ if (Number.class.isAssignableFrom(valueType)) {
+ try {
+ castedValue = Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ throw new FiqlParseException("Cannot parse " + value + " as
number", e);
+ }
+ } else if (Date.class.isAssignableFrom(valueType)) {
+ DateFormat df;
+ try {
+ df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+ // zone in XML is "+01:00" in Java is "+0100"; stripping
semicolon
+ int idx = value.lastIndexOf(':');
+ if (idx != -1) {
+ String v = value.substring(0, idx) + value.substring(idx +
1);
+ castedValue = df.parse(v);
+ } else {
+ castedValue = df.parse(value);
+ }
+ } catch (ParseException e) {
+ // is that duration?
+ try {
+ Date now = new Date();
+
DatatypeFactory.newInstance().newDuration(value).addTo(now);
+ castedValue = now;
+ } catch (DatatypeConfigurationException e1) {
+ throw new FiqlParseException(e1);
+ } catch (IllegalArgumentException e1) {
+ throw new FiqlParseException("Can parse " + value + "
neither as date nor duration", e);
+ }
+ }
+ }
+ return castedValue;
+ }
+
+ // node of abstract syntax tree
+ private interface ASTNode<T> {
+ SearchCondition<T> build() throws FiqlParseException;
+ }
+
+ private class SubExpression implements ASTNode<T> {
+ private String operator;
+ private List<ASTNode<T>> subnodes = new ArrayList<ASTNode<T>>();
+
+ public SubExpression(String operator) {
+ this.operator = operator;
+ }
+
+ public void add(ASTNode<T> node) {
+ subnodes.add(node);
+ }
+
+ public List<ASTNode<T>> getSubnodes() {
+ return Collections.unmodifiableList(subnodes);
+ }
+
+ @Override
+ public String toString() {
+ String s = operator.equals(AND) ? "AND" : "OR";
+ s += ":[";
+ for (int i = 0; i < subnodes.size(); i++) {
+ s += subnodes.get(i);
+ if (i < subnodes.size() - 1) {
+ s += ", ";
+ }
+ }
+ s += "]";
+ return s;
+ }
+
+ public SearchCondition<T> build() throws FiqlParseException {
+ boolean hasSubtree = false;
+ for (ASTNode<T> node : subnodes) {
+ if (node instanceof FiqlParser.SubExpression) {
+ hasSubtree = true;
+ break;
+ }
+ }
+ if (!hasSubtree && AND.equals(operator)) {
+ try {
+ // Optimization: single SimpleSearchCondition for 'AND'
conditions
+ Map<String, ConditionType> map = new HashMap<String,
ConditionType>();
+ beanspector.instantiate();
+ for (ASTNode<T> node : subnodes) {
+ FiqlParser<T>.Comparison comp = (Comparison)node;
+ map.put(comp.getName(),
operatorsMap.get(comp.getOperator()));
+ beanspector.setValue(comp.getName(), comp.getValue());
+ }
+ return new SimpleSearchCondition<T>(map,
beanspector.getBean());
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ List<SearchCondition<T>> scNodes = new
ArrayList<SearchCondition<T>>();
+ for (ASTNode<T> node : subnodes) {
+ scNodes.add(node.build());
+ }
+ if (OR.equals(operator)) {
+ return new OrSearchCondition<T>(scNodes);
+ } else {
+ return new AndSearchCondition<T>(scNodes);
+ }
+ }
+ }
+ }
+
+ private class Comparison implements ASTNode<T> {
+ private String name;
+ private String operator;
+ private Object value;
+
+ public Comparison(String name, String operator, Object value) {
+ this.name = name;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return name + " " + operator + " " + value + " (" +
value.getClass().getSimpleName() + ")";
+ }
+
+ public SearchCondition<T> build() throws FiqlParseException {
+ T cond = createTemplate(name, value);
+ ConditionType ct = operatorsMap.get(operator);
+ return new SimpleSearchCondition<T>(ct, cond);
+ }
+
+ private T createTemplate(String setter, Object val) throws
FiqlParseException {
+ try {
+ beanspector.instantiate().setValue(setter, val);
+ return beanspector.getBean();
+ } catch (Throwable e) {
+ throw new FiqlParseException(e);
+ }
+ }
+ }
+}
Modified:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/OrSearchCondition.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/OrSearchCondition.java?rev=921968&r1=921967&r2=921968&view=diff
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/OrSearchCondition.java
(original)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/OrSearchCondition.java
Thu Mar 11 18:14:20 2010
@@ -18,6 +18,7 @@
*/
package org.apache.cxf.jaxrs.ext.search;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -62,8 +63,13 @@ public class OrSearchCondition<T> implem
}
public List<T> findAll(List<T> pojos) {
- // TODO Auto-generated method stub
- return null;
+ List<T> result = new ArrayList<T>();
+ for (T pojo : pojos) {
+ if (isMet(pojo)) {
+ result.add(pojo);
+ }
+ }
+ return result;
}
}
Modified:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java?rev=921968&r1=921967&r2=921968&view=diff
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java
(original)
+++
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchCondition.java
Thu Mar 11 18:14:20 2010
@@ -18,28 +18,35 @@
*/
package org.apache.cxf.jaxrs.ext.search;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+/**
+ * Simple search condition comparing primitive objects or complex object by
its getters. For details see
+ * {...@link #isMet(Object)} description.
+ *
+ * @param <T> type of search condition.
+ */
public class SimpleSearchCondition<T> implements SearchCondition<T> {
private static Set<ConditionType> supportedTypes = new
HashSet<ConditionType>();
static {
supportedTypes.add(ConditionType.EQUALS);
+ supportedTypes.add(ConditionType.NOT_EQUALS);
supportedTypes.add(ConditionType.GREATER_THAN);
supportedTypes.add(ConditionType.GREATER_OR_EQUALS);
supportedTypes.add(ConditionType.LESS_THAN);
supportedTypes.add(ConditionType.LESS_OR_EQUALS);
}
private ConditionType cType;
- private Map<String, ConditionType> getters2operators;
private T condition;
- private List<Method> getters;
+ private Map<String, ConditionType> getters2operators;
+ private Map<String, Object> getters2values;
+ private Beanspector<T> beanspector;
/**
* Creates search condition with same operator (equality, inequality)
applied in all comparison; see
@@ -119,8 +126,8 @@ public class SimpleSearchCondition<T> im
* inequalities requires type T implementing {...@link Comparable}.
* <p>
* For other types comparison of given object against template object is
done using these <b>getters</b>;
- * returned "is met" value is <b>conjunction (*and*)</b> of comparisons
per each getter. Getters of
- * template object that return null or throw exception are not used in
comparison, in extreme if all
+ * returned "is met" value is <b>conjunction ('and' operator)</b> of
comparisons per each getter. Getters
+ * of template object that return null or throw exception are not used in
comparison, in extreme if all
* getters are excluded it means every given pojo object matches. If
* {...@link #SimpleSearchCondition(ConditionType, Object) constructor
with shared operator} was used, then
* getters are compared using the same operator. If {...@link
#SimpleSearchCondition(Map, Object) constructor
@@ -130,9 +137,19 @@ public class SimpleSearchCondition<T> im
* {...@link Object#equals(Object)}, using inequalities requires that
getter type implements
* {...@link Comparable}.
* <p>
+ * For equality comparison and String type in template object (either
being primitive or getter from
+ * complex type) it is allowed to used asterisk at the beginning or at the
end of text as wild card (zero
+ * or more of any characters) e.g. "foo*", "*foo" or "*foo*". Inner
asterisks are not interpreted as wild
+ * cards.
+ * <p>
* <b>Example:</b>
*
* <pre>
+ * SimpleSearchCondition<Integer> ssc = new
SimpleSearchCondition<Integer>(
+ * ConditionType.GREATER_THAN, 10);
+ * ssc.isMet(20);
+ * // true since 20>10
+ *
* class Entity {
* public String getName() {...
* public int getLevel() {...
@@ -140,17 +157,17 @@ public class SimpleSearchCondition<T> im
* }
*
* Entity template = new Entity("bbb", 10, null);
- * SimpleSearchCondition<Entity> ssc = new
SimpleSearchCondition<Entity>(
+ * ssc = new SimpleSearchCondition<Entity>(
* ConditionType.GREATER_THAN, template);
*
* ssc.isMet(new Entity("aaa", 20, "some mesage"));
- * // false: is not met, expression '"aaa">"bbb" and 20>10' is not true
+ * // false: is not met, expression '"aaa">"bbb" and 20>10' is not
true
* // since "aaa" is not greater than "bbb"; not that message is null in
template hence ingored
*
* ssc.isMet(new Entity("ccc", 30, "other message"));
- * // true: is met, expression '"ccc">"bbb" and 30>10' is true
+ * // true: is met, expression '"ccc">"bbb" and 30>10' is true
*
- * Map<String,ConditionType> map;
+ * Map<String,ConditionType> map;
* map.put("name", ConditionType.EQUALS);
* map.put("level", ConditionType.GREATER_THAN);
* ssc = new SimpleSearchCondition<Entity>(
@@ -168,16 +185,17 @@ public class SimpleSearchCondition<T> im
return compare(pojo, cType, condition);
} else {
boolean matches = false;
- for (Method getter : loadGetters()) {
+ Map<String, Object> get2val = getGettersAndValues();
+ for (String getter : get2val.keySet()) {
ConditionType ct = cType;
if (ct == null) {
- ct = getters2operators.get(getterName(getter));
+ ct = getters2operators.get(getter);
if (ct == null) {
continue;
}
}
Object lval = getValue(getter, pojo);
- Object rval = getValue(getter, condition);
+ Object rval = get2val.get(getter);
matches = compare(lval, ct, rval);
if (!matches) {
break;
@@ -187,49 +205,55 @@ public class SimpleSearchCondition<T> im
}
}
- private List<Method> loadGetters() {
- if (getters == null) {
- getters = new ArrayList<Method>();
- for (Method m : condition.getClass().getMethods()) {
- if (isGetter(m)) {
- getters.add(m);
- }
+ /**
+ * Creates cache of getters from template (condition) object and its
values returned during one-pass
+ * invocation. Method isMet() will use its keys to introspect getters of
passed pojo object, and values
+ * from map in comparison.
+ *
+ * @return template (condition) object getters mapped to their non-null
values
+ */
+ private Map<String, Object> getGettersAndValues() {
+ if (getters2values == null) {
+ getters2values = new HashMap<String, Object>();
+ beanspector = new Beanspector<T>(condition);
+ for (String getter : beanspector.getGettersNames()) {
+ Object value = getValue(getter, condition);
+ getters2values.put(getter, value);
}
+ //we do not need compare class objects
+ getters2values.keySet().remove("class");
}
- return getters;
+ return getters2values;
}
- private boolean isPrimitive(T pojo) {
- return pojo.getClass().getName().startsWith("java.lang");
- }
-
- private boolean isGetter(Method m) {
- return m.getParameterTypes().length == 0
- && (m.getName().startsWith("get") ||
m.getName().startsWith("is"));
- }
-
- private String getterName(Method m) {
- return m.getName().replace("is", "").replace("get", "").toLowerCase();
- }
-
- private Object getValue(Method getter, T pojo) {
+ private Object getValue(String getter, T pojo) {
try {
- return getter.invoke(pojo);
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- // getter exception is null equivalent
+ return beanspector.swap(pojo).getValue(getter);
+ } catch (Throwable e) {
return null;
}
}
+ private boolean isPrimitive(T pojo) {
+ return pojo.getClass().getName().startsWith("java.lang");
+ }
+
@SuppressWarnings("unchecked")
private boolean compare(Object lval, ConditionType cond, Object rval) {
boolean compares = true;
- if (cond == ConditionType.EQUALS) {
- compares = (lval != null) ? lval.equals(rval) : true;
+ if (cond == ConditionType.EQUALS || cond == ConditionType.NOT_EQUALS) {
+ if (rval == null) {
+ compares = true;
+ } else {
+ if (lval instanceof String) {
+ compares = textCompare((String)lval, (String)rval);
+ } else {
+ compares = lval.equals(rval);
+ }
+ if (cond == ConditionType.NOT_EQUALS) {
+ compares = !compares;
+ }
+ }
} else {
if (lval instanceof Comparable && rval instanceof Comparable) {
Comparable lcomp = (Comparable)lval;
@@ -257,6 +281,32 @@ public class SimpleSearchCondition<T> im
return compares;
}
+ private boolean textCompare(String lval, String rval) {
+ // check wild cards
+ boolean starts = false;
+ boolean ends = false;
+ if (rval.charAt(0) == '*') {
+ starts = true;
+ rval = rval.substring(1);
+ }
+ if (rval.charAt(rval.length() - 1) == '*') {
+ ends = true;
+ rval = rval.substring(0, rval.length() - 1);
+ }
+ if (starts || ends) {
+ // wild card tests
+ if (starts && !ends) {
+ return lval.endsWith(rval);
+ } else if (ends && !starts) {
+ return lval.startsWith(rval);
+ } else {
+ return lval.contains(rval);
+ }
+ } else {
+ return lval.equals(rval);
+ }
+ }
+
public List<T> findAll(List<T> pojos) {
List<T> result = new ArrayList<T>();
for (T pojo : pojos) {
Added:
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/FiqlParserTest.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/FiqlParserTest.java?rev=921968&view=auto
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/FiqlParserTest.java
(added)
+++
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/FiqlParserTest.java
Thu Mar 11 18:14:20 2010
@@ -0,0 +1,240 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.xml.datatype.DatatypeFactory;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class FiqlParserTest {
+ private FiqlParser<Condition> parser = new
FiqlParser<Condition>(Condition.class);
+
+ @Test(expected = FiqlParseException.class)
+ public void testCompareWrongComparator() throws FiqlParseException {
+ parser.parse("name>booba");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testCompareMissingName() throws FiqlParseException {
+ parser.parse("==30");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testCompareMissingValue() throws FiqlParseException {
+ parser.parse("name=gt=");
+ }
+
+ @Test
+ public void testCompareValueTextSpaces() throws FiqlParseException {
+ parser.parse("name=gt=some text");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testCompareNameTextSpaces() throws FiqlParseException {
+ parser.parse("some name=gt=text");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testDanglingOperator() throws FiqlParseException {
+ parser.parse("name==a;(level==10;),");
+ }
+
+ @Test
+ public void testMultilevelExpression() throws FiqlParseException {
+ parser.parse("name==a;(level==10,(name!=b;name!=c;(level=gt=10)))");
+ }
+
+ @Test
+ public void testRedundantParens() throws FiqlParseException {
+ parser.parse("name==a;((((level==10))))");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testUnmatchedParen() throws FiqlParseException {
+ parser.parse("name==a;(level==10,(name!=b)");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testUnmatchedParen2() throws FiqlParseException {
+ parser.parse("name==bbb;))()level==111");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testMissingComparison() throws FiqlParseException {
+ parser.parse("name==bbb;,level==111");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testSetterMissing() throws FiqlParseException {
+ parser.parse("noSuchSetter==xxx");
+ }
+
+ @Test(expected = FiqlParseException.class)
+ public void testSetterWrongType() throws FiqlParseException {
+ parser.parse("exception==text");
+ }
+
+ @Test
+ public void testSetterNumericText() throws FiqlParseException {
+ parser.parse("name==10");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMismatchedAccessorTypes() throws FiqlParseException {
+ new FiqlParser<MismatchedTypes>(MismatchedTypes.class);
+ }
+
+ @Test
+ public void testParseName() throws FiqlParseException {
+ SearchCondition<Condition> filter = parser.parse("name==king");
+ assertTrue(filter.isMet(new Condition("king", 10, new Date())));
+ assertTrue(filter.isMet(new Condition("king", 0, null)));
+ assertFalse(filter.isMet(new Condition("diamond", 10, new Date())));
+ assertFalse(filter.isMet(new Condition("diamond", 0, null)));
+ }
+
+ @Test
+ public void testParseLevel() throws FiqlParseException {
+ SearchCondition<Condition> filter = parser.parse("level=gt=10");
+ assertTrue(filter.isMet(new Condition("whatever", 15, new Date())));
+ assertTrue(filter.isMet(new Condition(null, 15, null)));
+ assertFalse(filter.isMet(new Condition("blah", 5, new Date())));
+ assertFalse(filter.isMet(new Condition("foobar", 0, null)));
+ }
+
+ @Test
+ public void testParseDate() throws FiqlParseException, ParseException {
+ SearchCondition<Condition> filter =
parser.parse("time=le=2010-03-11T18:00:00.000+00:00");
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+ assertTrue(filter.isMet(new Condition("whatever", 15,
df.parse("2010-03-11T18:00:00.000+0000"))));
+ assertTrue(filter.isMet(new Condition(null, null,
df.parse("2010-03-10T22:22:00.000+0000"))));
+ assertFalse(filter.isMet(new Condition("blah", null,
df.parse("2010-03-12T00:00:00.000+0000"))));
+ assertFalse(filter.isMet(new Condition(null, 123,
df.parse("2010-03-12T00:00:00.000+0000"))));
+ }
+
+ @Test
+ public void testParseDateDuration() throws Exception {
+ SearchCondition<Condition> filter = parser.parse("time=gt=-PT1M");
+ Date now = new Date();
+ Date tenMinutesAgo = new Date();
+
DatatypeFactory.newInstance().newDuration("-PT10M").addTo(tenMinutesAgo);
+ assertTrue(filter.isMet(new Condition(null, null, now)));
+ assertFalse(filter.isMet(new Condition(null, null, tenMinutesAgo)));
+ }
+
+ @Test
+ public void testParseComplex1() throws FiqlParseException {
+ SearchCondition<Condition> filter =
parser.parse("name==ami*;level=gt=10");
+ assertTrue(filter.isMet(new Condition("amichalec", 12, new Date())));
+ assertTrue(filter.isMet(new Condition("ami", 12, new Date())));
+ assertFalse(filter.isMet(new Condition("ami", 8, null)));
+ assertFalse(filter.isMet(new Condition("am", 20, null)));
+ }
+
+ @Test
+ public void testParseComplex2() throws FiqlParseException {
+ SearchCondition<Condition> filter =
parser.parse("name==ami*,level=gt=10");
+ assertTrue(filter.isMet(new Condition("ami", 0, new Date())));
+ assertTrue(filter.isMet(new Condition("foo", 20, null)));
+ assertFalse(filter.isMet(new Condition("foo", 0, null)));
+ }
+
+ @Test
+ public void testParseComplex3() throws FiqlParseException {
+ SearchCondition<Condition> filter =
parser.parse("name==foo*;(name!=*bar,level=gt=10)");
+ assertTrue(filter.isMet(new Condition("fooooo", 0, null)));
+ assertTrue(filter.isMet(new Condition("fooooobar", 20, null)));
+ assertFalse(filter.isMet(new Condition("fooobar", 0, null)));
+ assertFalse(filter.isMet(new Condition("bar", 20, null)));
+ }
+
+ @Test
+ public void testParseComplex4() throws FiqlParseException {
+ SearchCondition<Condition> filter =
parser.parse("name==foo*;name!=*bar,level=gt=10");
+ assertTrue(filter.isMet(new Condition("zonk", 20, null)));
+ assertTrue(filter.isMet(new Condition("foobaz", 0, null)));
+ assertTrue(filter.isMet(new Condition("foobar", 20, null)));
+ assertFalse(filter.isMet(new Condition("fooxxxbar", 0, null)));
+ }
+
+ @Ignore
+ static class Condition {
+ private String name;
+ private Integer level;
+ private Date time;
+
+ public Condition() {
+ }
+
+ public Condition(String name, Integer level, Date time) {
+ this.name = name;
+ this.level = level;
+ this.time = time;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getLevel() {
+ return level;
+ }
+
+ public void setLevel(Integer level) {
+ this.level = level;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+
+ public void setTime(Date time) {
+ this.time = time;
+ }
+
+ public void setException(Exception ex) {
+ // do nothing
+ }
+
+ }
+
+ // TODO refactor to Beanspector unit tests
+ @Ignore
+ static class MismatchedTypes {
+ public Date getFoo() {
+ return null;
+ }
+
+ public void setFoo(String val) {
+ }
+ }
+}
Modified:
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java?rev=921968&r1=921967&r2=921968&view=diff
==============================================================================
---
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
(original)
+++
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/ext/search/SimpleSearchConditionTest.java
Thu Mar 11 18:14:20 2010
@@ -26,9 +26,10 @@ import java.util.Map;
import org.junit.BeforeClass;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
public class SimpleSearchConditionTest {
@@ -65,6 +66,7 @@ public class SimpleSearchConditionTest {
private static SearchCondition<DoubleAttr> dc2Leq;
private static List<ConditionType> supported =
Arrays.asList(ConditionType.EQUALS,
+
ConditionType.NOT_EQUALS,
ConditionType.GREATER_OR_EQUALS,
ConditionType.GREATER_THAN,
ConditionType.LESS_OR_EQUALS,
@@ -121,7 +123,7 @@ public class SimpleSearchConditionTest {
public void testCtorMapNull() {
new SimpleSearchCondition<SingleAttr>((Map<String,
ConditionType>)null, attr);
}
-
+
@Test
public void testCtorMapCondSupported() {
for (ConditionType ct : ConditionType.values()) {
@@ -287,16 +289,50 @@ public class SimpleSearchConditionTest {
Map<String, ConditionType> map = new HashMap<String, ConditionType>();
map.put("foo", ConditionType.LESS_THAN);
map.put("bar", ConditionType.GREATER_THAN);
-
+
// expression "template.getFoo() < pojo.getFoo() & template.getBar() >
pojo.getBar()"
assertTrue(new SimpleSearchCondition<DoubleAttr>(map, new
DoubleAttr("bbb", "ccc"))
.isMet(new DoubleAttr("aaa", "ddd")));
-
+
// expression "template.getBar() > pojo.getBar()"
assertTrue(new SimpleSearchCondition<DoubleAttr>(map, new
DoubleAttr(null, "ccc"))
.isMet(new DoubleAttr("!not-interpreted!", "ddd")));
}
+ @Test
+ public void testIsMetWildcardEnds() {
+ SimpleSearchCondition<String> ssc = new
SimpleSearchCondition<String>(ConditionType.EQUALS, "bar*");
+ assertTrue(ssc.isMet("bar"));
+ assertTrue(ssc.isMet("barbaz"));
+ assertFalse(ssc.isMet("foobar"));
+ }
+
+ @Test
+ public void testIsMetWildcardStarts() {
+ SimpleSearchCondition<String> ssc = new
SimpleSearchCondition<String>(ConditionType.EQUALS, "*bar");
+ assertTrue(ssc.isMet("bar"));
+ assertFalse(ssc.isMet("barbaz"));
+ assertTrue(ssc.isMet("foobar"));
+ }
+
+ @Test
+ public void testIsMetWildcardStartsEnds() {
+ SimpleSearchCondition<String> ssc = new
SimpleSearchCondition<String>(ConditionType.EQUALS, "*bar*");
+ assertTrue(ssc.isMet("bar"));
+ assertTrue(ssc.isMet("barbaz"));
+ assertTrue(ssc.isMet("foobar"));
+ }
+
+ @Test
+ public void testIsMetWildcardMultiAsterisk() {
+ SimpleSearchCondition<String> ssc = new
SimpleSearchCondition<String>(ConditionType.EQUALS, "*ba*r*");
+ assertFalse(ssc.isMet("bar"));
+ assertTrue(ssc.isMet("ba*r"));
+ assertTrue(ssc.isMet("fooba*r"));
+ assertTrue(ssc.isMet("fooba*rbaz"));
+ assertFalse(ssc.isMet("foobarbaz"));
+ }
+
static class SingleAttr {
private String foo;