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