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