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