001/* 002 * Copyright 2017 Product Mog 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 public boolean retainAll(@Nullable Collection<?> c) { 324 throw new UnsupportedOperationException(); 325 } 326 327 /** 328 * Guaranteed to throw an exception and leave the range unmodified. 329 * 330 * @throws UnsupportedOperationException always 331 * @deprecated Unsupported operation; this type is immutable. 332 */ 333 @Override 334 @Deprecated 335 public void clear() { 336 throw new UnsupportedOperationException(); 337 } 338 339 /** 340 * Generates a {@code String} representation of this object. 341 * 342 * @return a string representation of this object, not null 343 */ 344 @Override 345 @Nonnull 346 public String toString() { 347 return format("%s{values=%s, infinite=%s}", getClass().getSimpleName(), getValues().stream() 348 .map(value -> value.toString()) 349 .collect(Collectors.joining(", ")), getInfinite()); 350 } 351 352 /** 353 * Checks if this object is equal to another one. 354 * 355 * @param other the object to check, null returns false 356 * @return true if this is equal to the other object, false otherwise 357 */ 358 @Override 359 public boolean equals(@Nullable Object other) { 360 if (this == other) 361 return true; 362 363 if (other == null || !getClass().equals(other.getClass())) 364 return false; 365 366 Range valueRange = (Range) other; 367 368 return Objects.equals(getValues(), valueRange.getValues()) 369 && Objects.equals(getInfinite(), valueRange.getInfinite()); 370 } 371 372 /** 373 * A hash code for this object. 374 * 375 * @return a suitable hash code 376 */ 377 @Override 378 public int hashCode() { 379 return Objects.hash(getValues(), getInfinite()); 380 } 381 382 /** 383 * Gets the values that comprise this range. 384 * 385 * @return the values that comprise this range, not null 386 */ 387 @Nonnull 388 public List<T> getValues() { 389 return values; 390 } 391 392 /** 393 * Gets whether this range is infinite. 394 * 395 * @return whether this range is infinite - that is, whether the range's pattern repeats indefinitely, not null 396 */ 397 @Nonnull 398 public Boolean getInfinite() { 399 return infinite; 400 } 401}