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)
028            throw new IllegalArgumentException("Invalid range: "+lower+'-'+upper);
029        this.lower = lower;
030        this.upper = upper;
031    }
032
033    /**
034     * Check if a number is contained in this range
035     * @param x The number to test
036     * @return <code>true</code> if it is in this range
037     */
038    public boolean contains(double x) {
039        return lower < x && x <= upper;
040    }
041
042    /**
043     * provides the intersection of 2 overlapping ranges
044     * @param a first range
045     * @param b second range
046     * @return intersection of {@code a} and {@code b}
047     */
048    public static Range cut(Range a, Range b) {
049        if (b.lower >= a.upper || b.upper <= a.lower)
050            throw new IllegalArgumentException("Ranges do not overlap: "+a+" - "+b);
051        return new Range(Math.max(a.lower, b.lower), Math.min(a.upper, b.upper));
052    }
053
054    /**
055     * under the premise, that x is within this range,
056     * and not within the other range, it shrinks this range in a way
057     * to exclude the other range, but still contain x.
058     *
059     * x                  |
060     *
061     * this   (------------------------------]
062     *
063     * other                   (-------]  or
064     *                         (-----------------]
065     *
066     * result (----------------]
067     * @param x value
068     * @param other other range
069     * @return reduced range
070     */
071    public Range reduceAround(double x, Range other) {
072        if (!contains(x))
073            throw new IllegalArgumentException(x+" is not inside "+this);
074        if (other.contains(x))
075            throw new IllegalArgumentException(x+" is inside "+other);
076
077        if (x < other.lower && other.lower < upper)
078            return new Range(lower, other.lower);
079
080        if (this.lower < other.upper && other.upper < x)
081            return new Range(other.upper, this.upper);
082
083        return this;
084    }
085
086    /**
087     * Gets the lower bound
088     * @return The lower, exclusive, bound
089     */
090    public double getLower() {
091        return lower;
092    }
093
094    /**
095     * Gets the upper bound
096     * @return The upper, inclusive, bound
097     */
098    public double getUpper() {
099        return upper;
100    }
101
102    @Override
103    public String toString() {
104        return String.format("|s%s-%s", lower, upper);
105    }
106
107    @Override
108    public boolean equals(Object o) {
109        if (this == o) return true;
110        if (o == null || getClass() != o.getClass()) return false;
111        Range range = (Range) o;
112        return Double.compare(range.lower, lower) == 0 &&
113                Double.compare(range.upper, upper) == 0;
114    }
115
116    @Override
117    public int hashCode() {
118        return Objects.hash(lower, upper);
119    }
120}