001/* 002 * Copyright 2017-2022 Product Mog LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.lokalized; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.Immutable; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Objects; 030import java.util.stream.Collectors; 031 032import static java.lang.String.format; 033import static java.util.Objects.requireNonNull; 034 035/** 036 * Represents a concrete range of values. 037 * <p> 038 * This class is not designed to hold large or "infinite" ranges; it is not stream-based. 039 * Instead, you might supply a small representative range of values and specify the range is "infinite" 040 * if it is understood that the value pattern repeats indefinitely. 041 * <p> 042 * For example, you might generate an infinite powers-of-ten range with the 4 values {@code 1, 10, 100, 1_000}. 043 * <p> 044 * Ranges are constructed via static methods. 045 * <p> 046 * Examples: 047 * <ul> 048 * <li>{@code Range.ofFiniteValues("a", "b", "c")}</li> 049 * <li>{@code Range.ofInfiniteValues(1, 10, 100, 1_000, 10_000)}</li> 050 * <li>{@code Range.emptyFiniteRange()}</li> 051 * <li>{@code Range.emptyInfiniteRange()}</li> 052 * </ul> 053 * 054 * @author <a href="https://revetkn.com">Mark Allen</a> 055 */ 056@Immutable 057public class Range<T> implements Collection<@NonNull T> { 058 @NonNull 059 private static final Range<?> EMPTY_FINITE_RANGE; 060 @NonNull 061 private static final Range<?> EMPTY_INFINITE_RANGE; 062 063 @NonNull 064 private final List<@NonNull T> values; 065 @NonNull 066 private final Boolean infinite; 067 068 static { 069 EMPTY_FINITE_RANGE = new Range<>(Collections.emptySet(), false); 070 EMPTY_INFINITE_RANGE = new Range<>(Collections.emptySet(), true); 071 } 072 073 /** 074 * Provides an infinite range for the given values. 075 * 076 * @param values the values of the range, not null 077 * @param <T> the type of values contained in the range 078 * @return an infinite range, not null 079 */ 080 @NonNull 081 public static <T> Range<T> ofInfiniteValues(@NonNull Collection<@NonNull T> values) { 082 requireNonNull(values); 083 return values.size() == 0 ? emptyInfiniteRange() : new Range(values, true); 084 } 085 086 /** 087 * Provides an infinite range for the given values. 088 * 089 * @param values the values of the range, may be null 090 * @param <T> the type of values contained in the range 091 * @return an infinite range, not null 092 */ 093 @NonNull 094 public static <T> Range<T> ofInfiniteValues(@NonNull T @Nullable ... values) { 095 return values == null || values.length == 0 ? emptyInfiniteRange() : new Range(values, true); 096 } 097 098 /** 099 * Provides a finite range for the given values. 100 * 101 * @param values the values of the range, not null 102 * @param <T> the type of values contained in the range 103 * @return a finite range, not null 104 */ 105 @NonNull 106 public static <T> Range<T> ofFiniteValues(@NonNull Collection<@NonNull T> values) { 107 requireNonNull(values); 108 return values.size() == 0 ? emptyFiniteRange() : new Range(values, false); 109 } 110 111 /** 112 * Provides a finite range for the given values. 113 * 114 * @param values the values of the range, may be null 115 * @param <T> the type of values contained in the range 116 * @return a finite range, not null 117 */ 118 @NonNull 119 public static <T> Range<T> ofFiniteValues(@NonNull T @Nullable ... values) { 120 return values == null || values.length == 0 ? emptyFiniteRange() : new Range(values, false); 121 } 122 123 /** 124 * Gets the empty finite range. 125 * 126 * @param <T> the type of values contained in the range 127 * @return the empty finite range, not null 128 */ 129 public static <T> Range<T> emptyFiniteRange() { 130 return (Range<T>) EMPTY_FINITE_RANGE; 131 } 132 133 /** 134 * Gets the empty infinite range. 135 * 136 * @param <T> the type of values contained in the range 137 * @return the empty infinite range, not null 138 */ 139 public static <T> Range<T> emptyInfiniteRange() { 140 return (Range<T>) EMPTY_INFINITE_RANGE; 141 } 142 143 /** 144 * Creates a range with the given values and "infinite" flag. 145 * 146 * @param values the values that comprise this range, not null 147 * @param infinite whether this range is infinite - that is, whether the range's pattern repeats indefinitely, not null 148 */ 149 private Range(@NonNull Collection<@NonNull T> values, @NonNull Boolean infinite) { 150 requireNonNull(values); 151 requireNonNull(infinite); 152 153 this.values = values.size() == 0 ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(values)); 154 this.infinite = infinite; 155 } 156 157 /** 158 * Creates a range with the given values and "infinite" flag. 159 * 160 * @param values the values that comprise this range, may be null 161 * @param infinite whether this range is infinite - that is, whether the range's pattern repeats indefinitely, not null 162 */ 163 private Range(@NonNull T @NonNull [] values, @NonNull Boolean infinite) { 164 requireNonNull(values); 165 requireNonNull(infinite); 166 167 this.values = values == null ? Collections.emptyList() 168 : Collections.unmodifiableList(new ArrayList<>(Arrays.asList(values))); 169 this.infinite = infinite; 170 } 171 172 /** 173 * Returns the number of elements in this range. 174 * 175 * @return the number of elements in this range 176 */ 177 @Override 178 public int size() { 179 return getValues().size(); 180 } 181 182 /** 183 * Returns true if this range contains no elements. 184 * 185 * @return true if this range contains no elements 186 */ 187 @Override 188 public boolean isEmpty() { 189 return getValues().isEmpty(); 190 } 191 192 /** 193 * Returns true if this range contains the specified value. 194 * <p> 195 * More formally, returns true if and only if this range contains at least one value v such that {@code (o==null ? v==null : o.equals(v))}. 196 * 197 * @param value value whose presence in this range is to be tested 198 * @return true if this range contains the specified value 199 */ 200 @Override 201 public boolean contains(@Nullable Object value) { 202 return getValues().contains(value); 203 } 204 205 /** 206 * Returns an iterator over the values in this range in proper sequence. 207 * <p> 208 * The returned iterator is <a href="https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#fail-fast" target="_blank">fail-fast</a>. 209 * 210 * @return an iterator over the values in this range in proper sequence, not null 211 */ 212 @NonNull 213 @Override 214 public Iterator<@NonNull T> iterator() { 215 return getValues().iterator(); 216 } 217 218 /** 219 * Returns an array containing all of the values in this range in proper sequence (from first to last value). 220 * <p> 221 * The returned array will be "safe" in that no references to it are maintained by this range. (In other words, this method must allocate a new array). The caller is thus free to modify the returned array. 222 * <p> 223 * This method acts as bridge between array-based and collection-based APIs. 224 * 225 * @return an array containing all of the values in this range in proper sequence, not null 226 */ 227 @NonNull 228 @Override 229 public Object[] toArray() { 230 return getValues().toArray(); 231 } 232 233 /** 234 * Returns an array containing all of the values in this range in proper sequence (from first to last element); the runtime type of the returned array is that of the specified array. If the range fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this range. 235 * <p> 236 * If the range fits in the specified array with room to spare (i.e., the array has more elements than the range), the element in the array immediately following the end of the collection is set to null. (This is useful in determining the length of the range only if the caller knows that the range does not contain any null elements.) 237 * 238 * @param a the array into which the values of the range are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose. not null 239 * @param <T1> the runtime type of the array to contain the collection 240 * @return an array containing the values of the range, not null 241 */ 242 @NonNull 243 @Override 244 public <T1> T1[] toArray(@NonNull T1[] a) { 245 return getValues().toArray(a); 246 } 247 248 /** 249 * Guaranteed to throw an exception and leave the range unmodified. 250 * 251 * @param t the value to add, ignored 252 * @return no return value; this method always throws UnsupportedOperationException 253 * @throws UnsupportedOperationException always 254 * @deprecated Unsupported operation; this type is immutable. 255 */ 256 @Override 257 @Deprecated 258 public boolean add(@Nullable T t) { 259 throw new UnsupportedOperationException(); 260 } 261 262 /** 263 * Guaranteed to throw an exception and leave the range unmodified. 264 * 265 * @param o the value to remove, ignored 266 * @return no return value; this method always throws UnsupportedOperationException 267 * @throws UnsupportedOperationException always 268 * @deprecated Unsupported operation; this type is immutable. 269 */ 270 @Override 271 @Deprecated 272 public boolean remove(@Nullable Object o) { 273 throw new UnsupportedOperationException(); 274 } 275 276 /** 277 * Returns true if this range contains all of the elements of the specified collection. 278 * 279 * @param c collection to be checked for containment in this range, not null 280 * @return true if this range contains all of the elements of the specified collection 281 */ 282 @Override 283 public boolean containsAll(@NonNull Collection<@Nullable ?> c) { 284 requireNonNull(c); 285 return getValues().containsAll(c); 286 } 287 288 /** 289 * Guaranteed to throw an exception and leave the range unmodified. 290 * 291 * @param c collection containing elements to be added to this range, ignored 292 * @return no return value; this method always throws UnsupportedOperationException 293 * @throws UnsupportedOperationException always 294 * @deprecated Unsupported operation; this type is immutable. 295 */ 296 @Override 297 @Deprecated 298 public boolean addAll(@Nullable Collection<? extends @NonNull T> c) { 299 throw new UnsupportedOperationException(); 300 } 301 302 /** 303 * Guaranteed to throw an exception and leave the range unmodified. 304 * 305 * @param c collection containing elements to be removed from this range, ignored 306 * @return no return value; this method always throws UnsupportedOperationException 307 * @throws UnsupportedOperationException always 308 * @deprecated Unsupported operation; this type is immutable. 309 */ 310 @Override 311 @Deprecated 312 public boolean removeAll(@Nullable Collection<@Nullable ?> c) { 313 throw new UnsupportedOperationException(); 314 } 315 316 /** 317 * Guaranteed to throw an exception and leave the range unmodified. 318 * 319 * @param c collection containing elements to be retained in this range, ignored 320 * @return no return value; this method always throws UnsupportedOperationException 321 * @throws UnsupportedOperationException always 322 * @deprecated Unsupported operation; this type is immutable. 323 */ 324 @Override 325 @Deprecated 326 public boolean retainAll(@Nullable Collection<@Nullable ?> c) { 327 throw new UnsupportedOperationException(); 328 } 329 330 /** 331 * Guaranteed to throw an exception and leave the range unmodified. 332 * 333 * @throws UnsupportedOperationException always 334 * @deprecated Unsupported operation; this type is immutable. 335 */ 336 @Override 337 @Deprecated 338 public void clear() { 339 throw new UnsupportedOperationException(); 340 } 341 342 /** 343 * Generates a {@code String} representation of this object. 344 * 345 * @return a string representation of this object, not null 346 */ 347 @Override 348 @NonNull 349 public String toString() { 350 return format("%s{values=%s, infinite=%s}", getClass().getSimpleName(), getValues().stream() 351 .map(value -> value.toString()) 352 .collect(Collectors.joining(", ")), getInfinite()); 353 } 354 355 /** 356 * Checks if this object is equal to another one. 357 * 358 * @param other the object to check, null returns false 359 * @return true if this is equal to the other object, false otherwise 360 */ 361 @Override 362 public boolean equals(@Nullable Object other) { 363 if (this == other) 364 return true; 365 366 if (other == null || !getClass().equals(other.getClass())) 367 return false; 368 369 Range valueRange = (Range) other; 370 371 return Objects.equals(getValues(), valueRange.getValues()) 372 && Objects.equals(getInfinite(), valueRange.getInfinite()); 373 } 374 375 /** 376 * A hash code for this object. 377 * 378 * @return a suitable hash code 379 */ 380 @Override 381 public int hashCode() { 382 return Objects.hash(getValues(), getInfinite()); 383 } 384 385 /** 386 * Gets the values that comprise this range. 387 * 388 * @return the values that comprise this range, not null 389 */ 390 @NonNull 391 public List<@NonNull T> getValues() { 392 return values; 393 } 394 395 /** 396 * Gets whether this range is infinite. 397 * 398 * @return whether this range is infinite - that is, whether the range's pattern repeats indefinitely, not null 399 */ 400 @NonNull 401 public Boolean getInfinite() { 402 return infinite; 403 } 404}