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}