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}