package context.arch.intelligibility.expression; import context.arch.intelligibility.DescriptiveExplainerDelegate; import context.arch.intelligibility.expression.Comparison.Relation; /** * Continuous range that can specify whether the bounds are inclusive or exclusive. * This is similar to org.apache.commons.lang.math.NumberRange. * Easiest to think of this as a Number range, but it also applies to any Comparable object * @author Brian Y. Lim * */ public class ComparableRange<T extends Comparable<? super T>> { private T min; private T max; private boolean minInclusive = false; private boolean maxInclusive = false; private String name = "number"; /** * Creates an unbounded range. */ public ComparableRange() {} public ComparableRange(String name) { this.name = name; } public void setName(String name) { this.name = name; } public ComparableRange(T min, boolean minInclusive, T max, boolean maxInclusive) { this.min = min; this.max = max; this.minInclusive = minInclusive; this.maxInclusive = maxInclusive; } public boolean isMinInclusive() { return minInclusive; } public boolean isMaxInclusive() { return maxInclusive; } public boolean setRange(ComparableRange<T> other) { // System.out.print("setRange("+other+"): "); return setRange(other.getMin(), other.minInclusive, other.getMax(), other.maxInclusive); } /* * Doesn't undo if it partially fails */ private boolean setRange(T min, boolean minInclusive, T max, boolean maxInclusive) { boolean tightened = false; if (min != null) { tightened = setBound(min, true, minInclusive); } if (max != null) { tightened |= setBound(max, false, maxInclusive); // OR with previous } // System.out.println("range: " + this); return tightened; } /** * Would either not change the bound, or tighten it. * @param bound * @param relation * @return */ public boolean setBound(T bound, Relation relation) { boolean min; boolean inclusive; switch (relation) { case EQUALS: // set both bounds to the same, and inclusive boolean ret = setBound(bound, true, true); ret &= setBound(bound, false, true); return ret; case GREATER_THAN: min = true; inclusive = false; break; case GREATER_THAN_OR_EQUAL: min = true; inclusive = true; break; case LESS_THAN: min = false; inclusive = false; break; case LESS_THAN_OR_EQUAL: min = false; inclusive = true; break; default: return false; } boolean ret = setBound(bound, min, inclusive); // System.out.print("setBound("+bound+", "+relation+"): "); // System.out.println("range: " + this); return ret; } public boolean setBound(T bound, boolean min, boolean inclusive) { if (containsValue(bound)) { if (min) { this.min = bound; this.minInclusive = inclusive; } else { this.max = bound; this.maxInclusive = inclusive; } return true; } return false; } public boolean containsValue(Comparable<? super T> value) { return satisfiesMin(value) && satisfiesMax(value); } public boolean containsRange(ComparableRange<? super T> range) { boolean minSatisfied = satisfiesMin(range.getMin(), range.minInclusive) && satisfiesMax(range.getMin()); boolean maxSatisfied = satisfiesMax(range.getMax(), range.maxInclusive) && satisfiesMin(range.getMax()); return minSatisfied && maxSatisfied; } /** * Whether value is more than min; value and max may be inclusive or exclusive. * Several cases for range.min to satisfy min: * 1) range.min(incl. or excl.) >= min(incl.) * 2) range.min(excl.) >= min(excl.) * @param value * @param valueInclusive * @return */ private boolean satisfiesMin(Comparable<? super T> value, boolean valueInclusive) { // null would mean unbounded if (getMin() == null) { return true; } if (minInclusive || // when current boundary is inclusive, then other doesn't matter (!valueInclusive && !minInclusive) // both boundaries exclusive ) { //return value.doubleValue() >= getMin().doubleValue(); // true if value >= min return value.compareTo(getMin()) >= 0; } else { //return value.doubleValue() > getMin().doubleValue(); // true if value > min return value.compareTo(getMin()) > 0; } } private boolean satisfiesMin(Comparable<? super T> value) { return satisfiesMin(value, true); } /** * Whether value is less than max; value and max may be inclusive or exclusive. * @param value * @param valueInclusive * @return */ private boolean satisfiesMax(Comparable<? super T> value, boolean valueInclusive) { // null would mean unbounded if (getMax() == null) { return true; } if (maxInclusive || // when current boundary is inclusive, then other doesn't matter (!valueInclusive && !maxInclusive) // both boundaries exclusive ) { //return value.doubleValue() <= getMax().doubleValue(); return value.compareTo(getMax()) <= 0; } else { //return value.doubleValue() < getMax().doubleValue(); return value.compareTo(getMax()) < 0; } } private boolean satisfiesMax(Comparable<? super T> value) { return satisfiesMax(value, true); } public T getMax() { return max; } public T getMin() { return min; } @Override public String toString() { if (min == max) { // not null and equal // means this Range is really an equality return name + " = " + min; } return (min == null ? "" : (min.toString() + " " + (minInclusive ? Relation.LESS_THAN_OR_EQUAL : Relation.LESS_THAN) + " ")) + name + (max == null ? "" : (" " + (maxInclusive ? Relation.LESS_THAN_OR_EQUAL : Relation.LESS_THAN) + " " + max.toString())); } /** * Returns toString() in pretty form where the names and values are made pretty. * @param descExplainer used as a look-up dictionary to convert variable names to pretty names * @return */ public String toPrettyString(DescriptiveExplainerDelegate descExplainer) { if (min == max) { // not null and equal // means this Range is really an equality return name + " = " + min + descExplainer.getUnit(name); } // only lower bound if (max == null) { return descExplainer.getPrettyName(name) + " " + (minInclusive ? Relation.LESS_THAN_OR_EQUAL : Relation.LESS_THAN) + " " + descExplainer.getPrettyValue(name, min) + descExplainer.getUnit(name); } // only upper bound or both bounds return (min == null ? "" : (descExplainer.getPrettyValue(name, min) + " " + (minInclusive ? Relation.LESS_THAN_OR_EQUAL : Relation.LESS_THAN) + " ")) + descExplainer.getPrettyName(name) + (" " + (maxInclusive ? Relation.LESS_THAN_OR_EQUAL : Relation.LESS_THAN) + " " + descExplainer.getPrettyValue(name, max)) + descExplainer.getUnit(name); } }