001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.mappaint;
003
004/**
005 * A scale interval of the form "lower < x <= upper" where 0 <= lower < upper.
006 * (upper can be Double.POSITIVE_INFINITY)
007 * immutable class
008 */
009public class Range {
010    private final double lower;
011    private final double upper;
012
013    /**
014     * The full scale range from zero to infinity
015     */
016    public static final Range ZERO_TO_INFINITY = new Range(0.0, Double.POSITIVE_INFINITY);
017
018    /**
019     * Constructs a new {@code Range}.
020     * @param lower Lower bound. Must be positive or zero
021     * @param upper Upper bound
022     * @throws IllegalArgumentException if the range is invalid ({@code lower < 0 || lower >= upper})
023     */
024    public Range(double lower, double upper) {
025        if (lower < 0 || lower >= upper || Double.isNaN(lower) || Double.isNaN(upper)) {
026            throw new IllegalArgumentException("Invalid range: "+lower+'-'+upper);
027        }
028        this.lower = lower;
029        this.upper = upper;
030    }
031
032    /**
033     * Check if a number is contained in this range
034     * @param x The number to test
035     * @return <code>true</code> if it is in this range
036     */
037    public boolean contains(double x) {
038        return lower < x && x <= upper;
039    }
040
041    /**
042     * provides the intersection of 2 overlapping ranges
043     * @param a first range
044     * @param b second range
045     * @return intersection of {@code a} and {@code b}
046     */
047    public static Range cut(Range a, Range b) {
048        if (b.lower >= a.upper || b.upper <= a.lower)
049            throw new IllegalArgumentException("Ranges do not overlap: "+a+" - "+b);
050        return new Range(Math.max(a.lower, b.lower), Math.min(a.upper, b.upper));
051    }
052
053    /**
054     * under the premise, that x is within this range,
055     * and not within the other range, it shrinks this range in a way
056     * to exclude the other range, but still contain x.
057     *
058     * x                  |
059     *
060     * this   (------------------------------]
061     *
062     * other                   (-------]  or
063     *                         (-----------------]
064     *
065     * result (----------------]
066     * @param x value
067     * @param other other range
068     * @return reduced range
069     */
070    public Range reduceAround(double x, Range other) {
071        if (!contains(x))
072            throw new IllegalArgumentException(x+" is not inside "+this);
073        if (other.contains(x))
074            throw new IllegalArgumentException(x+" is inside "+other);
075
076        if (x < other.lower && other.lower < upper)
077            return new Range(lower, other.lower);
078
079        if (this.lower < other.upper && other.upper < x)
080            return new Range(other.upper, this.upper);
081
082        return this;
083    }
084
085    /**
086     * Gets the lower bound
087     * @return The lower, exclusive, bound
088     */
089    public double getLower() {
090        return lower;
091    }
092
093    /**
094     * Gets the upper bound
095     * @return The upper, inclusive, bound
096     */
097    public double getUpper() {
098        return upper;
099    }
100
101    @Override
102    public String toString() {
103        return String.format("|z%.4f-%.4f", lower, upper);
104    }
105
106    @Override
107    public boolean equals(Object o) {
108        if (this == o) return true;
109        if (o == null || getClass() != o.getClass()) return false;
110        Range range = (Range) o;
111        return Double.compare(range.lower, lower) == 0 &&
112                Double.compare(range.upper, upper) == 0;
113    }
114
115    @Override
116    public int hashCode() {
117        return 31 * Double.hashCode(lower) + Double.hashCode(upper);
118    }
119}