/**
* 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.client;
import java.text.DateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.datatype.Duration;
import org.apache.cxf.jaxrs.ext.search.ConditionType;
import org.apache.cxf.jaxrs.ext.search.SearchUtils;
import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
/**
* Builds a FIQL search condition.
* <p>
* Examples:
*
* <pre>
* SearchConditionBuilder b = SearchConditionBuilder.instance("fiql");
* b.is("price").equalTo(123.5).query();
* // gives "price==123.5"
* b.is("price").greaterThan(30).and().is("price").lessThan(50).query();
* // gives "price=gt=30.0;price=lt=50.0"
* </pre>
*
* For very complex junctions nested "and"/"or" are allowed (breaking a bit fluency of interface) and looks
* like the following example:
*
* <pre>
* SearchConditionBuilder b = SearchConditionBuilder.instance("fiql");
* b.is("price").lessThan(100).and().or(
* b.is("title").equalTo("The lord*"),
* b.is("author").equalTo("R.R.Tolkien")).query();
* // gives "price=lt=100.0;(title==The lord*,author==R.R.Tolkien)"
* </pre>
*/
public class FiqlSearchConditionBuilder extends SearchConditionBuilder {
protected Map<String, String> properties;
public FiqlSearchConditionBuilder() {
this(Collections.<String, String> emptyMap());
}
public FiqlSearchConditionBuilder(Map<String, String> properties) {
this.properties = properties;
}
protected Builder newBuilderInstance() {
return new Builder(properties);
}
public String query() {
return "";
}
public Property is(String property) {
return newBuilderInstance().is(property);
}
public CompleteCondition and(CompleteCondition c1, CompleteCondition c2,
CompleteCondition... cn) {
return newBuilderInstance().and(c1, c2, cn);
}
public CompleteCondition and(List<CompleteCondition> conditions) {
return newBuilderInstance().and(conditions);
}
public CompleteCondition or(List<CompleteCondition> conditions) {
return newBuilderInstance().or(conditions);
}
public CompleteCondition or(CompleteCondition c1, CompleteCondition c2, CompleteCondition... cn) {
return newBuilderInstance().or(c1, c2, cn);
}
protected static class Builder implements Property, CompleteCondition, PartialCondition {
protected String result = "";
protected Builder parent;
protected DateFormat df;
protected boolean timeZoneSupported;
protected String currentCompositeOp;
public Builder(Map<String, String> properties) {
parent = null;
df = SearchUtils.getDateFormat(properties);
timeZoneSupported = SearchUtils.isTimeZoneSupported(properties, Boolean.FALSE);
}
public Builder(Builder parent) {
this.parent = parent;
df = parent.getDateFormat();
timeZoneSupported = parent.isTimeZoneSupported();
}
public String query() {
return buildPartial(null);
}
protected DateFormat getDateFormat() {
return df;
}
protected boolean isTimeZoneSupported() {
return timeZoneSupported;
}
// builds from parent but not further then exclude builder
protected String buildPartial(Builder exclude) {
if (parent != null && !parent.equals(exclude)) {
return parent.buildPartial(exclude) + result;
} else {
return result;
}
}
public CompleteCondition after(Date date) {
return condition(FiqlParser.GT, toString(date));
}
public CompleteCondition before(Date date) {
return condition(FiqlParser.LT, toString(date));
}
public CompleteCondition comparesTo(ConditionType type, String value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition comparesTo(ConditionType type, Double value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition comparesTo(ConditionType type, Integer value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition comparesTo(ConditionType type, Long value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition comparesTo(ConditionType type, Date value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition comparesTo(ConditionType type, Duration value) {
return condition(toFiqlPrimitiveCondition(type), value);
}
public CompleteCondition equalTo(String value, String...moreValues) {
return condition(FiqlParser.EQ, value, (Object[])moreValues);
}
public CompleteCondition equalTo(Double number, Double... moreValues) {
return condition(FiqlParser.EQ, number, (Object[])moreValues);
}
public CompleteCondition equalTo(Long number, Long... moreValues) {
return condition(FiqlParser.EQ, number, (Object[])moreValues);
}
public CompleteCondition equalTo(Integer number, Integer... moreValues) {
return condition(FiqlParser.EQ, number, (Object[])moreValues);
}
public CompleteCondition equalTo(Date date, Date... moreValues) {
return condition(FiqlParser.EQ, date, (Object[])moreValues);
}
public CompleteCondition equalTo(Duration distanceFromNow, Duration... moreValues) {
return condition(FiqlParser.EQ, distanceFromNow, (Object[])moreValues);
}
public CompleteCondition greaterOrEqualTo(Double number) {
return condition(FiqlParser.GE, number);
}
public CompleteCondition greaterOrEqualTo(Long number) {
return condition(FiqlParser.GE, number);
}
public CompleteCondition greaterOrEqualTo(Integer number) {
return condition(FiqlParser.GE, number);
}
public CompleteCondition greaterThan(Double number) {
return condition(FiqlParser.GT, number);
}
public CompleteCondition greaterThan(Long number) {
return condition(FiqlParser.GT, number);
}
public CompleteCondition greaterThan(Integer number) {
return condition(FiqlParser.GT, number);
}
public CompleteCondition lessOrEqualTo(Double number) {
return condition(FiqlParser.LE, number);
}
public CompleteCondition lessOrEqualTo(Long number) {
return condition(FiqlParser.LE, number);
}
public CompleteCondition lessOrEqualTo(Integer number) {
return condition(FiqlParser.LE, number);
}
public CompleteCondition lessThan(Double number) {
return condition(FiqlParser.LT, number);
}
public CompleteCondition lessThan(Long number) {
return condition(FiqlParser.LT, number);
}
public CompleteCondition lessThan(Integer number) {
return condition(FiqlParser.LT, number);
}
public CompleteCondition lexicalAfter(String literal) {
return condition(FiqlParser.GT, literal);
}
public CompleteCondition lexicalBefore(String literal) {
return condition(FiqlParser.LT, literal);
}
public CompleteCondition lexicalNotAfter(String literal) {
return condition(FiqlParser.LE, literal);
}
public CompleteCondition lexicalNotBefore(String literal) {
return condition(FiqlParser.GE, literal);
}
public CompleteCondition notAfter(Date date) {
return condition(FiqlParser.LE, toString(date));
}
public CompleteCondition notBefore(Date date) {
return condition(FiqlParser.GE, toString(date));
}
public CompleteCondition notEqualTo(String literalOrPattern) {
return condition(FiqlParser.NEQ, literalOrPattern);
}
public CompleteCondition notEqualTo(Double number) {
return condition(FiqlParser.NEQ, number);
}
public CompleteCondition notEqualTo(Long number) {
return condition(FiqlParser.NEQ, number);
}
public CompleteCondition notEqualTo(Integer number) {
return condition(FiqlParser.NEQ, number);
}
public CompleteCondition notEqualTo(Date date) {
return condition(FiqlParser.NEQ, toString(date));
}
public CompleteCondition after(Duration distanceFromNow) {
return condition(FiqlParser.GT, distanceFromNow);
}
public CompleteCondition before(Duration distanceFromNow) {
return condition(FiqlParser.LT, distanceFromNow);
}
public CompleteCondition notAfter(Duration distanceFromNow) {
return condition(FiqlParser.LE, distanceFromNow);
}
public CompleteCondition notBefore(Duration distanceFromNow) {
return condition(FiqlParser.GE, distanceFromNow);
}
public CompleteCondition notEqualTo(Duration distanceFromNow) {
return condition(FiqlParser.NEQ, distanceFromNow);
}
protected CompleteCondition condition(String operator, Object value, Object...moreValues) {
String name = result;
result += operator + toString(value);
if (moreValues != null && moreValues.length > 0) {
for (Object next : moreValues) {
result += "," + name + operator + toString(next);
}
currentCompositeOp = FiqlParser.OR;
}
return this;
}
public PartialCondition and() {
if (currentCompositeOp == FiqlParser.OR
|| parent != null && parent.currentCompositeOp == FiqlParser.OR) {
if (parent != null) {
parent.result = "(" + parent.result;
result += ")";
} else {
wrap();
}
currentCompositeOp = FiqlParser.AND;
}
result += FiqlParser.AND;
return this;
}
public Property and(String name) {
return and().is(name);
}
public PartialCondition or() {
if (currentCompositeOp == FiqlParser.AND
|| parent != null && parent.currentCompositeOp == FiqlParser.AND) {
if (parent != null) {
parent.result = "(" + parent.result;
result += ")";
} else {
wrap();
}
currentCompositeOp = FiqlParser.OR;
}
result += FiqlParser.OR;
return this;
}
public Property or(String name) {
return or().is(name);
}
public CompleteCondition wrap() {
result = "(" + result + ")";
this.currentCompositeOp = null;
return this;
}
public CompleteCondition and(CompleteCondition c1, CompleteCondition c2, CompleteCondition... cn) {
result += "(" + ((Builder)c1).buildPartial(this) + FiqlParser.AND
+ ((Builder)c2).buildPartial(this);
for (CompleteCondition c : cn) {
result += FiqlParser.AND + ((Builder)c).buildPartial(this);
}
result += ")";
return this;
}
public CompleteCondition or(CompleteCondition c1, CompleteCondition c2, CompleteCondition... cn) {
result += "(" + ((Builder)c1).buildPartial(this) + FiqlParser.OR
+ ((Builder)c2).buildPartial(this);
for (CompleteCondition c : cn) {
result += FiqlParser.OR + ((Builder)c).buildPartial(this);
}
result += ")";
return this;
}
public CompleteCondition and(List<CompleteCondition> conditions) {
return conditionsList(FiqlParser.AND, conditions);
}
public CompleteCondition or(List<CompleteCondition> conditions) {
return conditionsList(FiqlParser.OR, conditions);
}
protected CompleteCondition conditionsList(String op, List<CompleteCondition> conditions) {
if (conditions.size() == 1) {
result += ((Builder)conditions.get(0)).buildPartial(this);
} else {
result += "(";
for (Iterator<CompleteCondition> it = conditions.iterator(); it.hasNext();) {
result += ((Builder)it.next()).buildPartial(this);
if (it.hasNext()) {
result += op;
}
}
result += ")";
}
return this;
}
public Property is(String property) {
Builder b = new Builder(this);
b.result = property;
return b;
}
protected String toString(Object value) {
if (value == null) {
return null;
}
if (value.getClass() == Date.class) {
String s = df.format((Date)value);
if (timeZoneSupported) {
// zone in XML is "+01:00" in Java is "+0100"; adding semicolon
int len = s.length();
return s.substring(0, len - 2) + ":" + s.substring(len - 2, len);
} else {
return s;
}
} else {
return value.toString();
}
}
protected String toFiqlPrimitiveCondition(ConditionType type) {
String fiqlType = FiqlParser.CONDITION_MAP.get(type);
if (fiqlType == null) {
throw new IllegalArgumentException("Only primitive condition types are supported");
}
return fiqlType;
}
}
}