/* Copyright 2013 The jeo project. All rights reserved. * * Licensed 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 io.jeo.filter; import io.jeo.util.Convert; import io.jeo.util.Optional; import java.util.Objects; /** * Filter that applies a binary comparison operator to two expression operands. * * @author Justin Deoliveira, OpenGeo */ public class Comparison<T> extends BinaryFilter<T> { /** * Comparison operator type. */ public enum Type { EQUAL("="), NOT_EQUAL("!="), LESS("<") { @Override public Type invert() { return GREATER_OR_EQUAL; } }, LESS_OR_EQUAL("<=") { @Override public Type invert() { return GREATER; } }, GREATER(">") { @Override public Type invert() { return LESS_OR_EQUAL; } }, GREATER_OR_EQUAL(">=") { @Override public Type invert() { return LESS; } }; String op; Type(String op) { this.op = op; } public Type invert() { return this; } @Override public String toString() { return op; } } Type type; public Comparison(Type type, Expression left, Expression right) { super(left, right); this.type = type; } public Type type() { return type; } @Override public boolean test(T obj) { Object o1 = left.evaluate(obj); Object o2 = right.evaluate(obj); return compare(o1, o2); } @Override public Comparison<T> normalize() { return (Comparison<T>) super.normalize(); } @Override public Comparison<T> invert() { return new Comparison<>(type.invert(), right, left); } protected boolean compare(Object o1, Object o2) { if (o1 != null && !o1.getClass().isInstance(o2)) { //attempt to convert Optional<?> converted = Convert.to(o2, o1.getClass()); if (converted.isPresent()) { o2 = converted.get(); } } // if either is NaN, shortcut here as Double.compareTo has different // behavior than expected (adheres to Object.equals ordering) if (isNaN(o1) || isNaN(o2)) { return false; } if (type == Type.EQUAL) { return o1 != null ? o1.equals(o2) : o2 == null; } if (type == Type.NOT_EQUAL) { return o1 != null ? !o1.equals(o2) : o2 != null; } if (o1 == null || o2 == null) { return false; } Comparable<Object> c1 = toComparable(o1); Comparable<Object> c2 = toComparable(o2); int compare = c1.compareTo(c2); switch(type) { case LESS: return compare < 0; case LESS_OR_EQUAL: return compare <= 0; case GREATER: return compare > 0; case GREATER_OR_EQUAL: return compare >= 0; default: throw new IllegalStateException(); } } static boolean isNaN(Object o) { return o instanceof Number && Double.isNaN(((Number)o).doubleValue()); } @SuppressWarnings("unchecked") protected Comparable<Object> toComparable(Object o) { if (o instanceof Comparable) { return (Comparable<Object>) o; } throw new IllegalArgumentException("Unable to convert " + o + " to comparable"); } @Override public <R> R accept(FilterVisitor<R> v, Object obj) { return v.visit(this, obj); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((left == null) ? 0 : left.hashCode()); result = prime * result + ((right == null) ? 0 : right.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Comparison<?> other = (Comparison<?>) obj; if (left == null) { if (other.left != null) return false; } else if (!left.equals(other.left)) return false; if (right == null) { if (other.right != null) return false; } else if (!right.equals(other.right)) return false; if (type != other.type) return false; return true; } @Override public String toString() { return new StringBuilder().append(left).append(" ").append(type).append(" ").append(right) .toString(); } }