public static AppendToValueExpression appendTo(String property, String appendText) {
	return new AppendToValueExpression(property, appendText);
} public static EqExpression eq(String property, String value) {
	return eq(val(property), TextNode.valueOf(value));
} public static IntegerValueExpression indexof(String property, String str) {
	return new IndexofValueExpression(property, str);
} public static IntegerValueExpression length(String property) {
	return new LengthValueExpression(property);
} public static ToLowerValueExpression lower(String property) {
	return new ToLowerValueExpression(property);
} public static PrependToValueExpression prependTo(String property, String prependText) {
	return new PrependToValueExpression(property, prependText);
} public static ReplaceValueExpression replace(String property, String target, String replacement) {
	return new ReplaceValueExpression(property, target, replacement);
} public static SubstringValueExpression substring(String property, int startIndex) {
	return new SubstringValueExpression(property, startIndex, null);
} public static TrimValueExpression trim(String property) {
	return new TrimValueExpression(property);
} public static ToUpperValueExpression upper(String property) {
	return new ToUpperValueExpression(property);
} public static ValueExpression val(String property) {
	return new ValueExpression(property);
} public static LongValueExpression round(String property) {
	return new RoundValueExpression(property);
} public static DoubleValueExpression ceil(String property) {
	return new CeilValueExpression(property);
} public static DoubleValueExpression floor(String property) {
	return new FloorValueExpression(property);
} public class Query extends AbstractQuery {

	private Integer top;

	private Integer skip;

	public Query(JsonNode node) {
		super(node);
	}

	/**
	 * Number of objects in an ArrayNode to be returned from top
	 * @param value
	 * @return
	 */
	public Query top(Integer value) {
		setTop(value);
		return this;
	}

	/**
	 * Number of objects in an Arraynode to be skipped from top
	 * @param value
	 * @return
	 */
	public Query skip(Integer value) {
		setSkip(value);
		return this;
	}

	/**
	 * Verifies if the passed expression is true for the JsonNode
	 * 
	 * @param expr
	 *            Comparison expression to be evaluated
	 * @return returns if the expression is true for the JsonNode
	 */
	public boolean is(ComparisonExpression expr) {
		try {
			if (expr != null) {
				return expr.evaluate(node);
			}
		} catch (MissingNodeException e) {
			return false;
		}

		return false;
	}

	/**
	 * Gets the value for the property from the JsonNode
	 * 
	 * @param property
	 * @return
	 */
	public JsonNode value(String property) {
		return value(new ValueExpression(property));
	}

	/**
	 * Gets the value as per expression set from the JsonNode
	 * 
	 * @param expression
	 *            Value expression to be evaluated
	 * @return Returns the value for the passed expression
	 */
	public JsonNode value(ValueExpression expression) {
		return expression.evaluate(node);
	}

	/**
	 * Returns the integer value as per expression set
	 * 
	 * @param expression
	 *            IntegerValueExpression to be evaluated
	 * @return Returns the integer value of the result of expression evaluated
	 */
	public int value(IntegerValueExpression expression) {
		return expression.value(node);
	}
	
	/**
	 * Returns the long value as per expression set
	 * 
	 * @param expression
	 *            LongValueExpression to be evaluated
	 * @return Returns the long value of the result of expression evaluated
	 */
	public long value(LongValueExpression expression) {
		return expression.value(node);
	}
	
	/**
	 * Returns the double value as per expression set
	 * @param expression DoubleValueExpression to be evaluated
	 * @return Returns the double value of the result of expression evaluated
	 */
	public double value(DoubleValueExpression expression) {
		return expression.value(node);
	}

	/**
	 * Checks if property exist in the JsonNode
	 * 
	 * @param property
	 *            JSON property
	 * @return Returns the value for the passed property
	 */
	public boolean isExist(String property) {
		try {
			new ValueExpression(property).evaluate(node);
		} catch (MissingNodeException e) {
			return false;
		}
		return true;
	}

	/**
	 * Allows filtering values in a ArrayNode as per passed ComparisonExpression
	 * 
	 * @param expression
	 * @return
	 */
	public ArrayNode filter(ComparisonExpression expression) {
		
		ArrayNode result = new ObjectMapper().createArrayNode();
		if (node.isArray()) {
			Iterator<JsonNode> iterator = node.iterator();
			int validObjectsCount = 0;
			int topCount=0;
			while (iterator.hasNext()) {
				JsonNode curNode = iterator.next();
				if (expression==null || (expression!=null && new Query(curNode).is(expression))) {
					validObjectsCount++;
					if (this.skip != null && this.skip.intValue() > 0 && this.skip.intValue() >= validObjectsCount) {
						continue;
					}

					if (this.top != null && topCount >= this.top.intValue()) {
						break;
					}
					result.add(curNode);
					topCount++;
				}
			}
		} else {
			throw new UnsupportedExprException("Filters are only supported on ArrayNode objects");
		}
		return result;
	}

	/**
	 * Allows applying filter on a property value, which is ArrayNode, as per
	 * passed ComparisonExpression
	 * 
	 * @param property
	 * @param expression
	 * @return
	 */
	public ArrayNode filter(String property, ComparisonExpression expression) {
		JsonNode propValueNode = this.value(property);
		return Query.q(propValueNode).top(this.getTop()).skip(this.getSkip()).filter(expression);
	}

	/**
	 * Spins up a new instance of Query
	 * 
	 * @param node
	 * @return
	 */
	public static Query q(JsonNode node) {
		return new Query(node);
	}

	public Integer getTop() {
		return top;
	}

	public void setTop(Integer top) {
		this.top = top;
	}

	public Integer getSkip() {
		return skip;
	}

	public void setSkip(Integer skip) {
		this.skip = skip;
	}

} @SuppressWarnings("serial")
public class MissingNodeException extends RuntimeException {

	public MissingNodeException(String message) {
		super(message);
		
	}
} @SuppressWarnings("serial")
public class UnsupportedExprException extends RuntimeException {

	public UnsupportedExprException(String message) {
		super(message);
	}

} public class Expr {
	
	public static EqExpression eq(String property, String value) {
		return eq(val(property), TextNode.valueOf(value));
	}
	
	public static EqExpression eq(String property, Integer value) {
		return eq(val(property), IntNode.valueOf(value));
	}
	
	public static EqExpression eq(String property, Long value) {
		return eq(val(property), LongNode.valueOf(value));
	}
	
	public static EqExpression eq(String property, boolean value) {
		return eq(val(property), BooleanNode.valueOf(value));
	}
	
	public static EqExpression eq(String property, JsonNode value) {
		return eq(val(property), value);
	}
	
	public static EqExpression eq(ValueExpression expression, String value) {
		return eq(expression, TextNode.valueOf(value));
	}
	
	public static EqExpression eq(ValueExpression expression, JsonNode value) {
		return new EqExpression(expression, value);
	}
	
	public static NeExpression ne(String property, String value) {
		return ne(val(property), TextNode.valueOf(value));
	}
	
	public static NeExpression ne(String property, Integer value) {
		return ne(val(property), IntNode.valueOf(value));
	}
	
	public static NeExpression ne(String property, Long value) {
		return ne(val(property), LongNode.valueOf(value));
	}
	
	public static NeExpression ne(String property, boolean value) {
		return ne(val(property), BooleanNode.valueOf(value));
	}
	
	public static NeExpression ne(String property, JsonNode value) {
		return ne(val(property), value);
	}
	
	public static NeExpression ne(ValueExpression expression, String value) {
		return ne(expression, TextNode.valueOf(value));
	}

	public static NeExpression ne(ValueExpression expression, JsonNode value) {
		return new NeExpression(expression, value);
	}
	
	public static GtExpression gt(String property, Integer value) {
		return new GtExpression(val(property), IntNode.valueOf(value));
	}
	
	public static GtExpression gt(String property, Long value) {
		return new GtExpression(val(property), LongNode.valueOf(value));
	}
	
	public static GtExpression gt(String property, Float value) {
		return new GtExpression(val(property), FloatNode.valueOf(value));
	}
	
	public static GtExpression gt(String property, Double value) {
		return new GtExpression(val(property), DoubleNode.valueOf(value));
	}
	
	public static LtExpression lt(String property, Integer value) {
		return new LtExpression(val(property), IntNode.valueOf(value));
	}
	
	public static LtExpression lt(String property, Long value) {
		return new LtExpression(val(property), LongNode.valueOf(value));
	}
	
	public static LtExpression lt(String property, Float value) {
		return new LtExpression(val(property), FloatNode.valueOf(value));
	}
	
	public static LtExpression lt(String property, Double value) {
		return new LtExpression(val(property), DoubleNode.valueOf(value));
	}
	
	public static GeExpression ge(String property, Integer value) {
		return new GeExpression(val(property), IntNode.valueOf(value));
	}
	
	public static GeExpression ge(String property, Long value) {
		return new GeExpression(val(property), LongNode.valueOf(value));
	}
	
	public static GeExpression ge(String property, Float value) {
		return new GeExpression(val(property), FloatNode.valueOf(value));
	}
	
	public static GeExpression ge(String property, Double value) {
		return new GeExpression(val(property), DoubleNode.valueOf(value));
	}
	
	public static LeExpression le(String property, Integer value) {
		return new LeExpression(val(property), IntNode.valueOf(value));
	}
	
	public static LeExpression le(String property, Long value) {
		return new LeExpression(val(property), LongNode.valueOf(value));
	}
	
	public static LeExpression le(String property, Float value) {
		return new LeExpression(val(property), FloatNode.valueOf(value));
	}
	
	public static LeExpression le(String property, Double value) {
		return new LeExpression(val(property), DoubleNode.valueOf(value));
	}
	
	public static AndExpression and(ComparisonExpression...expressions) {
		return new AndExpression(expressions);
	}
	
	public static OrExpression or(ComparisonExpression...expressions) {
		return new OrExpression(expressions);
	}
	
	public static IsNullExpression Null(String property) {
		return new IsNullExpression(val(property));
	}
	
	public static NotExpression not(ComparisonExpression expr) {
		return new NotExpression(expr);
	}
	
	public static SubstringOfExpression substringof(String property, String value) {
		return substringof(val(property), value);
	}
	
	public static SubstringOfExpression substringof(ValueExpression expression, String value) {
		return new SubstringOfExpression(expression, TextNode.valueOf(value));
	}
	
	public static StartsWithExpression startsWith(String property, String value) {
		return startsWith(val(property), value);
	}
	
	public static StartsWithExpression startsWith(ValueExpression expression, String value) {
		return new StartsWithExpression(expression, TextNode.valueOf(value));
	}
	
	public static EndsWithExpression endsWith(String property, String value) {
		return endsWith(val(property), value);
	}
	
	public static EndsWithExpression endsWith(ValueExpression expression, String value) {
		return new EndsWithExpression(expression, TextNode.valueOf(value));
	}
	
	public static ValueExpression val(String property) {
		return new ValueExpression(property);
	}
	
	public static ValueExpression val(ValueExpression valExpression, String property) {
		return new ValueExpression(property, valExpression);
	}
	
	public static AppendToValueExpression appendTo(String property, String appendText) {
		return new AppendToValueExpression(property, appendText);
	}
	
	public static AppendToValueExpression appendTo(ValueExpression valExpression, String property, String appendText) {
		return new AppendToValueExpression(property, appendText, valExpression);
	}
	
	public static AppendToValueExpression appendTo(ValueExpression valExpression, String appendText) {
		return new AppendToValueExpression(null, appendText, valExpression);
	}
	
	public static PrependToValueExpression prependTo(String property, String prependText) {
		return new PrependToValueExpression(property, prependText);
	}
	
	public static PrependToValueExpression prependTo(ValueExpression valExpression, String property, String prependText) {
		return new PrependToValueExpression(property, prependText, valExpression);
	}
	
	public static PrependToValueExpression prependTo(ValueExpression valExpression, String prependText) {
		return new PrependToValueExpression(null, prependText, valExpression);
	}
	
	public static TrimValueExpression trim(String property) {
		return new TrimValueExpression(property);
	}
	
	public static TrimValueExpression trim(ValueExpression valExpression, String property) {
		return new TrimValueExpression(property, valExpression);
	}
	
	public static TrimValueExpression trim(ValueExpression valExpression) {
		return new TrimValueExpression(null, valExpression);
	}
	
	public static ToUpperValueExpression upper(String property) {
		return new ToUpperValueExpression(property);
	}
	
	public static ToUpperValueExpression upper(ValueExpression valExpression, String property) {
		return new ToUpperValueExpression(property, valExpression);
	}
	
	public static ToUpperValueExpression upper(ValueExpression valExpression) {
		return new ToUpperValueExpression(null, valExpression);
	}
	
	public static ToLowerValueExpression lower(String property) {
		return new ToLowerValueExpression(property);
	}
	
	public static ToLowerValueExpression lower(ValueExpression valExpression, String property) {
		return new ToLowerValueExpression(property, valExpression);
	}
	
	public static ToLowerValueExpression lower(ValueExpression valExpression) {
		return new ToLowerValueExpression(null, valExpression);
	}
	
	public static ReplaceValueExpression replace(String property, String target, String replacement) {
		return new ReplaceValueExpression(property, target, replacement);
	}
	
	public static ReplaceValueExpression replace(ValueExpression valExpression, String property, String target, String replacement) {
		return new ReplaceValueExpression(property, target, replacement, valExpression);
	}
	
	public static ReplaceValueExpression replace(ValueExpression valExpression, String target, String replacement) {
		return new ReplaceValueExpression(null, target, replacement, valExpression);
	}
	
	public static SubstringValueExpression substring(String property, int startIndex) {
		return new SubstringValueExpression(property, startIndex, null);
	}
	
	public static SubstringValueExpression substring(String property, int startIndex, int endIndex) {
		return new SubstringValueExpression(property, startIndex, endIndex);
	}
	
	public static SubstringValueExpression substring(ValueExpression valExpression, String property, int startIndex) {
		return new SubstringValueExpression(property, startIndex, null, valExpression);
	}
	
	public static SubstringValueExpression substring(ValueExpression valExpression, String property, int startIndex, int endIndex) {
		return new SubstringValueExpression(property, startIndex, endIndex, valExpression);
	}
	
	public static SubstringValueExpression substring(ValueExpression valExpression, int startIndex) {
		return new SubstringValueExpression(null, startIndex, null, valExpression);
	}
	
	public static SubstringValueExpression substring(ValueExpression valExpression, int startIndex, int endIndex) {
		return new SubstringValueExpression(null, startIndex, endIndex, valExpression);
	}
	
	public static IntegerValueExpression length(String property) {
		return new LengthValueExpression(property);
	}
	
	public static IntegerValueExpression length(ValueExpression valExpression, String property) {
		return new LengthValueExpression(property, valExpression);
	}
	
	public static IntegerValueExpression indexof(String property, String str) {
		return new IndexofValueExpression(property, str);
	}
	
	public static IntegerValueExpression indexof(ValueExpression valExpression, String property, String str) {
		return new IndexofValueExpression(property, str, valExpression);
	}
	
	public static LongValueExpression round(String property) {
		return new RoundValueExpression(property);
	}
	
	public static LongValueExpression round(ValueExpression valExpression, String property) {
		return new RoundValueExpression(property, valExpression);
	}
	
	public static DoubleValueExpression ceil(String property) {
		return new CeilValueExpression(property);
	}
	
	public static DoubleValueExpression ceil(ValueExpression valExpression, String property) {
		return new CeilValueExpression(property, valExpression);
	}
	
	public static DoubleValueExpression floor(String property) {
		return new FloorValueExpression(property);
	}
	
	public static DoubleValueExpression floor(ValueExpression valExpression, String property) {
		return new FloorValueExpression(property, valExpression);
	}
} public class StringValueExpression extends ValueExpression {

	/**
	 * @param property
	 * @param innerExpression
	 */
	public StringValueExpression(String property, ValueExpression innerExpression) {
		super(property, innerExpression);
	}

	public StringValueExpression(String property) {
		super(property);
	}

	
	@Override
	public JsonNode evaluate(JsonNode node) {
		return super.evaluate(node);
	}

	/**
	 * Returns the final String value of this expression
	 * @param node
	 * @return
	 */
	public String value(JsonNode node) {
		return evaluate(node).textValue();
	}
	
}
import static com.kcthota.JSONQuery.expressions.Expr.appendTo;
import static com.kcthota.JSONQuery.expressions.Expr.eq;
import static com.kcthota.JSONQuery.expressions.Expr.indexof;
import static com.kcthota.JSONQuery.expressions.Expr.length;
import static com.kcthota.JSONQuery.expressions.Expr.lower;
import static com.kcthota.JSONQuery.expressions.Expr.prependTo;
import static com.kcthota.JSONQuery.expressions.Expr.replace;
import static com.kcthota.JSONQuery.expressions.Expr.substring;
import static com.kcthota.JSONQuery.expressions.Expr.trim;
import static com.kcthota.JSONQuery.expressions.Expr.upper;
import static com.kcthota.JSONQuery.expressions.Expr.val;
import static com.kcthota.JSONQuery.expressions.Expr.round;
import static com.kcthota.JSONQuery.expressions.Expr.ceil;
import static com.kcthota.JSONQuery.expressions.Expr.floor;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kcthota.JSONQuery.Query;
import com.kcthota.JSONQuery.exceptions.MissingNodeException;
import com.kcthota.JSONQuery.exceptions.UnsupportedExprException;
import com.kcthota.JSONQuery.expressions.Expr;
import com.kcthota.JSONQuery.expressions.StringValueExpression;
		node.putArray("interests").add("hiking").add("biking");
		
		Query q=new Query(node);
		
		assertThat(q.value(prependTo("name/firstName", "Mr. ")).textValue()).isEqualTo("Mr. Krishna");
		
		assertThat(q.value(prependTo(val("name/firstName"), "Mr. ")).textValue()).isEqualTo("Mr. Krishna");
		
		assertThat(q.is(eq(prependTo("name/lastName", "1"), "1Thota"))).isTrue();
		
		assertThat(q.value(prependTo("interests/0", "hill ")).textValue()).isEqualTo("hill hiking");
		
		
		assertThat(q.value(prependTo(upper("interests/0"), null, "hill ")).textValue()).isEqualTo("hill HIKING");
		
		try {
			q.value(prependTo("age", "1"));
			fail("UnsupportedExprException expected when appending to non-string values");
		} catch(UnsupportedExprException e) {
			assertThat(e.getMessage()).isEqualTo("Property value is not a string");
		}
		
	}
	
	@Test
	public void testTrim(){
		ObjectNode node = new ObjectMapper().createObjectNode();
		node.putObject("name").put("firstName", " Krishna ").put("lastName", " Thota ");
		node.put("age", 25);
		node.put("city", "Santa Clara, ");
		node.putArray("interests").add(" hiking ").add(" biking ");
		
		Query q=new Query(node);
		
		assertThat(q.value(trim("name/firstName")).textValue()).isEqualTo("Krishna");
		
		
		assertThat(q.is(eq(trim("name/lastName"), "Thota"))).isTrue();
		
		assertThat(q.value(appendTo(trim("interests/0"), " hills")).textValue()).isEqualTo("hiking hills");
		
		assertThat(trim(val("interests"),"0").value(node)).isEqualTo("hiking");
		
		assertThat(trim(val("city")).value(node)).isEqualTo("Santa Clara,");
		
		try {
			q.value(trim("age"));
			fail("UnsupportedExprException expected when appending to non-string values");
		} catch(UnsupportedExprException e) {
			assertThat(e.getMessage()).isEqualTo("Property value is not a string");
		}
		
	}
	
	@Test
	public void testUpper(){
		ObjectNode node = new ObjectMapper().createObjectNode();
		node.putObject("name").put("firstName", "Krishna").put("lastName", "Thota");
		node.put("age", 25);
		node.put("city", "Santa Clara,");
		node.putArray("interests").add("hiking").add("biking");
		
		Query q=new Query(node);
		
		assertThat(q.value(upper("name/firstName")).textValue()).isEqualTo("KRISHNA");
		
		
		assertThat(q.is(eq(upper("name/lastName"), "THOTA"))).isTrue();
		
		assertThat(q.value(upper(appendTo("interests/0", " hills"))).textValue()).isEqualTo("HIKING HILLS");
		
		assertThat(upper(val("interests"), "0").value(node)).isEqualTo("HIKING");
		
		try {
			q.value(upper("age"));
			fail("UnsupportedExprException expected when appending to non-string values");
		} catch(UnsupportedExprException e) {
			assertThat(e.getMessage()).isEqualTo("Property value is not a string");
		}
		
	}
	
	@Test
	public void testLower(){
		ObjectNode node = new ObjectMapper().createObjectNode();
		node.putObject("name").put("firstName", "Krishna").put("lastName", "Thota");
		node.put("age", 25);
		node.put("city", "Santa Clara,");
		node.putArray("interests").add("HIKING").add("biking");
		
		Query q=new Query(node);
		
		assertThat(q.value(lower("name/firstName")).textValue()).isEqualTo("krishna");
		
		
		assertThat(q.is(eq(lower("name/lastName"), "thota"))).isTrue();
		
		assertThat(q.value(lower(appendTo("interests/0", " hills"))).textValue()).isEqualTo("hiking hills");
		
		assertThat(lower(val("interests"), "0").value(node)).isEqualTo("hiking");
		
		try {
			q.value(lower("age"));
			fail("UnsupportedExprException expected when appending to non-string values");
		} catch(UnsupportedExprException e) {
			assertThat(e.getMessage()).isEqualTo("Property value is not a string");
		}
		
	}
	
	@Test
	public void testReplace(){
		ObjectNode node = new ObjectMapper().createObjectNode();
		node.putObject("name").put("firstName", "Krishna").put("lastName", "Thota");
		node.put("age", 25);
		node.put("city", "Santa Clara");
		node.putArray("interests").add("hiking").add("biking");
		
		Query q=new Query(node);
		
		assertThat(q.value(replace("name/firstName", "Krish", "Chris")).textValue()).isEqualTo("Chrisna");