Compare.java

package org.dynamoframework.filter;

/*-
 * #%L
 * Dynamo Framework
 * %%
 * Copyright (C) 2014 - 2024 Open Circle Solutions
 * %%
 * 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.
 * #L%
 */

import java.util.Objects;

/**
 * A filter that compares a property value against a provided value
 *
 * @author bas.rutten
 */
public abstract class Compare extends AbstractFilter implements PropertyFilter {

	public enum Operation {
		EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL
	}

	private final String propertyId;

	private final Operation operation;

	private final Object value;

	/**
	 * A filter that evaluates to true if the property value is equal to the provided value
	 *
	 * @author bas.rutten
	 */
	public static final class Equal extends Compare {

		/**
		 * Constructor
		 *
		 * @param propertyId
		 * @param value
		 */
		public Equal(String propertyId, Object value) {
			super(propertyId, value, Operation.EQUAL);
		}
	}

	/**
	 * A filter that evaluates to true if the property value is greater than the provided value
	 *
	 * @author bas.rutten
	 */
	public static final class Greater extends Compare {

		/**
		 * Constructor
		 *
		 * @param propertyId
		 * @param value
		 */
		public Greater(String propertyId, Object value) {
			super(propertyId, value, Operation.GREATER);
		}
	}

	/**
	 * A filter that evaluates to true if the property value is less than the provided value
	 *
	 * @author bas.rutten
	 */
	public static final class Less extends Compare {

		/**
		 * Constructor
		 *
		 * @param propertyId
		 * @param value
		 */
		public Less(String propertyId, Object value) {
			super(propertyId, value, Operation.LESS);
		}
	}

	/**
	 * A filter that evaluates to true if the property value is greater than or equal to the
	 * provided value
	 *
	 * @author bas.rutten
	 */
	public static final class GreaterOrEqual extends Compare {

		/**
		 * @param propertyId
		 * @param value
		 */
		public GreaterOrEqual(String propertyId, Object value) {
			super(propertyId, value, Operation.GREATER_OR_EQUAL);
		}
	}

	/**
	 * A filter that evaluates to true if the property value is less than or equal to the provided
	 * value
	 *
	 * @author bas.rutten
	 */
	public static final class LessOrEqual extends Compare {

		/**
		 * Constructor
		 *
		 * @param propertyId
		 * @param value
		 */
		public LessOrEqual(String propertyId, Object value) {
			super(propertyId, value, Operation.LESS_OR_EQUAL);
		}
	}

	/**
	 * Base constructor
	 *
	 * @param propertyId
	 * @param value
	 * @param operation
	 */
	Compare(String propertyId, Object value, Operation operation) {
		this.propertyId = propertyId;
		this.value = value;
		this.operation = operation;
	}

	@Override
	public boolean evaluate(Object that) {
		if (null == that) {
			return false;
		}
		Object val = getProperty(that, getPropertyId());
		if (val == null) {
			return false;
		}
		switch (getOperation()) {
			case EQUAL:
				return compareEquals(val);
			case GREATER:
				return compareValue(val) > 0;
			case LESS:
				return compareValue(val) < 0;
			case GREATER_OR_EQUAL:
				return compareValue(val) >= 0;
			case LESS_OR_EQUAL:
				return compareValue(val) <= 0;
			default:
				return false;
		}
	}

	/**
	 * Checks if the this value equals the given value. Favors Comparable over equals to better
	 * support e.g. BigDecimal where equals is stricter than compareTo.
	 *
	 * @param otherValue The value to compare to
	 * @return true if the values are equal, false otherwise
	 */
	@SuppressWarnings("unchecked")
	private boolean compareEquals(Object otherValue) {
		if (value == null || otherValue == null) {
			return otherValue == value;
		} else if (value == otherValue) {
			return true;
		} else if (value instanceof Comparable
			&& otherValue.getClass().isAssignableFrom(getValue().getClass())) {
			return ((Comparable<Object>) value).compareTo(otherValue) == 0;
		} else {
			return value.equals(otherValue);
		}
	}

	@SuppressWarnings({"unchecked", "rawtypes"})
	protected int compareValue(Object value1) {
		if (null == value) {
			return null == value1 ? 0 : -1;
		} else if (null == value1) {
			return 1;
		} else if (getValue() instanceof Comparable
			&& value1.getClass().isAssignableFrom(getValue().getClass())) {
			return -((Comparable) getValue()).compareTo(value1);
		}
		throw new IllegalArgumentException(
			"Could not compare the arguments: " + value1 + ", " + getValue());
	}

	@Override
	public boolean equals(Object obj) {

		// Only objects of the same class can be equal
		if (obj == null || !getClass().equals(obj.getClass())) {
			return false;
		}
		final Compare o = (Compare) obj;

		if (!Objects.equals(propertyId, o.propertyId)) {
			return false;
		}

		return Objects.equals(this.value, o.value);
	}

	@Override
	public int hashCode() {
		return (null != getPropertyId() ? getPropertyId().hashCode() : 0)
			^ (null != getValue() ? getValue().hashCode() : 0);
	}

	@Override
	public String getPropertyId() {
		return propertyId;
	}

	public Operation getOperation() {
		return operation;
	}

	public Object getValue() {
		return value;
	}

	@Override
	public String toString() {
		return getPropertyId() + " " + super.toString() + " " + getValue();
	}

}