1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import static com.android.internal.util.Preconditions.*;
20 
21 import android.annotation.Nullable;
22 
23 import java.util.Objects;
24 
25 /**
26  * Immutable class for describing the range of two numeric values.
27  * <p>
28  * A range (or "interval") defines the inclusive boundaries around a contiguous span of
29  * values of some {@link Comparable} type; for example,
30  * "integers from 1 to 100 inclusive."
31  * </p>
32  * <p>
33  * All ranges are bounded, and the left side of the range is always {@code <=}
34  * the right side of the range.
35  * </p>
36  *
37  * <p>Although the implementation itself is immutable, there is no restriction that objects
38  * stored must also be immutable. If mutable objects are stored here, then the range
39  * effectively becomes mutable. </p>
40  */
41 @android.ravenwood.annotation.RavenwoodKeepWholeClass
42 public final class Range<T extends Comparable<? super T>> {
43     /**
44      * Create a new immutable range.
45      *
46      * <p>
47      * The endpoints are {@code [lower, upper]}; that
48      * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal}
49      * to {@code upper}.
50      * </p>
51      *
52      * @param lower The lower endpoint (inclusive)
53      * @param upper The upper endpoint (inclusive)
54      *
55      * @throws NullPointerException if {@code lower} or {@code upper} is {@code null}
56      */
Range(final T lower, final T upper)57     public Range(final T lower, final T upper) {
58         mLower = checkNotNull(lower, "lower must not be null");
59         mUpper = checkNotNull(upper, "upper must not be null");
60 
61         if (lower.compareTo(upper) > 0) {
62             throw new IllegalArgumentException("lower must be less than or equal to upper");
63         }
64     }
65 
66     /**
67      * Create a new immutable range, with the argument types inferred.
68      *
69      * <p>
70      * The endpoints are {@code [lower, upper]}; that
71      * is the range is bounded. {@code lower} must be {@link Comparable#compareTo lesser or equal}
72      * to {@code upper}.
73      * </p>
74      *
75      * @param lower The lower endpoint (inclusive)
76      * @param upper The upper endpoint (inclusive)
77      *
78      * @throws NullPointerException if {@code lower} or {@code upper} is {@code null}
79      */
create(final T lower, final T upper)80     public static <T extends Comparable<? super T>> Range<T> create(final T lower, final T upper) {
81         return new Range<T>(lower, upper);
82     }
83 
84     /**
85      * Get the lower endpoint.
86      *
87      * @return a non-{@code null} {@code T} reference
88      */
getLower()89     public T getLower() {
90         return mLower;
91     }
92 
93     /**
94      * Get the upper endpoint.
95      *
96      * @return a non-{@code null} {@code T} reference
97      */
getUpper()98     public T getUpper() {
99         return mUpper;
100     }
101 
102     /**
103      * Checks if the {@code value} is within the bounds of this range.
104      *
105      * <p>A value is considered to be within this range if it's {@code >=}
106      * the lower endpoint <i>and</i> {@code <=} the upper endpoint (using the {@link Comparable}
107      * interface.)</p>
108      *
109      * @param value a non-{@code null} {@code T} reference
110      * @return {@code true} if the value is within this inclusive range, {@code false} otherwise
111      *
112      * @throws NullPointerException if {@code value} was {@code null}
113      */
contains(T value)114     public boolean contains(T value) {
115         checkNotNull(value, "value must not be null");
116 
117         boolean gteLower = value.compareTo(mLower) >= 0;
118         boolean lteUpper  = value.compareTo(mUpper) <= 0;
119 
120         return gteLower && lteUpper;
121     }
122 
123     /**
124      * Checks if another {@code range} is within the bounds of this range.
125      *
126      * <p>A range is considered to be within this range if both of its endpoints
127      * are within this range.</p>
128      *
129      * @param range a non-{@code null} {@code T} reference
130      * @return {@code true} if the range is within this inclusive range, {@code false} otherwise
131      *
132      * @throws NullPointerException if {@code range} was {@code null}
133      */
contains(Range<T> range)134     public boolean contains(Range<T> range) {
135         checkNotNull(range, "value must not be null");
136 
137         boolean gteLower = range.mLower.compareTo(mLower) >= 0;
138         boolean lteUpper = range.mUpper.compareTo(mUpper) <= 0;
139 
140         return gteLower && lteUpper;
141     }
142 
143     /**
144      * Compare two ranges for equality.
145      *
146      * <p>A range is considered equal if and only if both the lower and upper endpoints
147      * are also equal.</p>
148      *
149      * @return {@code true} if the ranges are equal, {@code false} otherwise
150      */
151     @Override
equals(@ullable Object obj)152     public boolean equals(@Nullable Object obj) {
153         if (obj == null) {
154             return false;
155         } else if (this == obj) {
156             return true;
157         } else if (obj instanceof Range) {
158             @SuppressWarnings("rawtypes")
159             Range other = (Range) obj;
160             return mLower.equals(other.mLower) && mUpper.equals(other.mUpper);
161         }
162         return false;
163     }
164 
165     /**
166      * Clamps {@code value} to this range.
167      *
168      * <p>If the value is within this range, it is returned.  Otherwise, if it
169      * is {@code <} than the lower endpoint, the lower endpoint is returned,
170      * else the upper endpoint is returned. Comparisons are performed using the
171      * {@link Comparable} interface.</p>
172      *
173      * @param value a non-{@code null} {@code T} reference
174      * @return {@code value} clamped to this range.
175      */
clamp(T value)176     public T clamp(T value) {
177         checkNotNull(value, "value must not be null");
178 
179         if (value.compareTo(mLower) < 0) {
180             return mLower;
181         } else if (value.compareTo(mUpper) > 0) {
182             return mUpper;
183         } else {
184             return value;
185         }
186     }
187 
188     /**
189      * Returns the intersection of this range and another {@code range}.
190      * <p>
191      * E.g. if a {@code <} b {@code <} c {@code <} d, the
192      * intersection of [a, c] and [b, d] ranges is [b, c].
193      * As the endpoints are object references, there is no guarantee
194      * which specific endpoint reference is used from the input ranges:</p>
195      * <p>
196      * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
197      * intersection of [a, b] and [a', c] ranges could be either
198      * [a, b] or ['a, b], where [a, b] could be either the exact
199      * input range, or a newly created range with the same endpoints.</p>
200      *
201      * @param range a non-{@code null} {@code Range<T>} reference
202      * @return the intersection of this range and the other range.
203      *
204      * @throws NullPointerException if {@code range} was {@code null}
205      * @throws IllegalArgumentException if the ranges are disjoint.
206      */
intersect(Range<T> range)207     public Range<T> intersect(Range<T> range) {
208         checkNotNull(range, "range must not be null");
209 
210         int cmpLower = range.mLower.compareTo(mLower);
211         int cmpUpper = range.mUpper.compareTo(mUpper);
212 
213         if (cmpLower <= 0 && cmpUpper >= 0) {
214             // range includes this
215             return this;
216         } else if (cmpLower >= 0 && cmpUpper <= 0) {
217             // this inludes range
218             return range;
219         } else {
220             return Range.create(
221                     cmpLower <= 0 ? mLower : range.mLower,
222                     cmpUpper >= 0 ? mUpper : range.mUpper);
223         }
224     }
225 
226     /**
227      * Returns the intersection of this range and the inclusive range
228      * specified by {@code [lower, upper]}.
229      * <p>
230      * See {@link #intersect(Range)} for more details.</p>
231      *
232      * @param lower a non-{@code null} {@code T} reference
233      * @param upper a non-{@code null} {@code T} reference
234      * @return the intersection of this range and the other range
235      *
236      * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
237      * @throws IllegalArgumentException if the ranges are disjoint.
238      */
intersect(T lower, T upper)239     public Range<T> intersect(T lower, T upper) {
240         checkNotNull(lower, "lower must not be null");
241         checkNotNull(upper, "upper must not be null");
242 
243         int cmpLower = lower.compareTo(mLower);
244         int cmpUpper = upper.compareTo(mUpper);
245 
246         if (cmpLower <= 0 && cmpUpper >= 0) {
247             // [lower, upper] includes this
248             return this;
249         } else {
250             return Range.create(
251                     cmpLower <= 0 ? mLower : lower,
252                     cmpUpper >= 0 ? mUpper : upper);
253         }
254     }
255 
256     /**
257      * Returns the smallest range that includes this range and
258      * another {@code range}.
259      * <p>
260      * E.g. if a {@code <} b {@code <} c {@code <} d, the
261      * extension of [a, c] and [b, d] ranges is [a, d].
262      * As the endpoints are object references, there is no guarantee
263      * which specific endpoint reference is used from the input ranges:</p>
264      * <p>
265      * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
266      * extension of [a, b] and [a', c] ranges could be either
267      * [a, c] or ['a, c], where ['a, c] could be either the exact
268      * input range, or a newly created range with the same endpoints.</p>
269      *
270      * @param range a non-{@code null} {@code Range<T>} reference
271      * @return the extension of this range and the other range.
272      *
273      * @throws NullPointerException if {@code range} was {@code null}
274      */
extend(Range<T> range)275     public Range<T> extend(Range<T> range) {
276         checkNotNull(range, "range must not be null");
277 
278         int cmpLower = range.mLower.compareTo(mLower);
279         int cmpUpper = range.mUpper.compareTo(mUpper);
280 
281         if (cmpLower <= 0 && cmpUpper >= 0) {
282             // other includes this
283             return range;
284         } else if (cmpLower >= 0 && cmpUpper <= 0) {
285             // this inludes other
286             return this;
287         } else {
288             return Range.create(
289                     cmpLower >= 0 ? mLower : range.mLower,
290                     cmpUpper <= 0 ? mUpper : range.mUpper);
291         }
292     }
293 
294     /**
295      * Returns the smallest range that includes this range and
296      * the inclusive range specified by {@code [lower, upper]}.
297      * <p>
298      * See {@link #extend(Range)} for more details.</p>
299      *
300      * @param lower a non-{@code null} {@code T} reference
301      * @param upper a non-{@code null} {@code T} reference
302      * @return the extension of this range and the other range.
303      *
304      * @throws NullPointerException if {@code lower} or {@code
305      *                              upper} was {@code null}
306      */
extend(T lower, T upper)307     public Range<T> extend(T lower, T upper) {
308         checkNotNull(lower, "lower must not be null");
309         checkNotNull(upper, "upper must not be null");
310 
311         int cmpLower = lower.compareTo(mLower);
312         int cmpUpper = upper.compareTo(mUpper);
313 
314         if (cmpLower >= 0 && cmpUpper <= 0) {
315             // this inludes other
316             return this;
317         } else {
318             return Range.create(
319                     cmpLower >= 0 ? mLower : lower,
320                     cmpUpper <= 0 ? mUpper : upper);
321         }
322     }
323 
324     /**
325      * Returns the smallest range that includes this range and
326      * the {@code value}.
327      * <p>
328      * See {@link #extend(Range)} for more details, as this method is
329      * equivalent to {@code extend(Range.create(value, value))}.</p>
330      *
331      * @param value a non-{@code null} {@code T} reference
332      * @return the extension of this range and the value.
333      *
334      * @throws NullPointerException if {@code value} was {@code null}
335      */
extend(T value)336     public Range<T> extend(T value) {
337         checkNotNull(value, "value must not be null");
338         return extend(value, value);
339     }
340 
341     /**
342      * Return the range as a string representation {@code "[lower, upper]"}.
343      *
344      * @return string representation of the range
345      */
346     @Override
toString()347     public String toString() {
348         return String.format("[%s, %s]", mLower, mUpper);
349     }
350 
351     /**
352      * {@inheritDoc}
353      */
354     @Override
hashCode()355     public int hashCode() {
356         return Objects.hash(mLower, mUpper);
357     }
358 
359     private final T mLower;
360     private final T mUpper;
361 }
362