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.NotThreadSafe;
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 com.lokalized.Diagnostics.format;
033import static java.util.Objects.requireNonNull;
034
035/**
036 * Represents a structurally immutable, ordered 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 * A range is {@link Iterable}, but deliberately does not implement {@link Collection}: mutation is not part of its
045 * contract. Use {@link #getValues()} when list operations are needed.
046 * <p>
047 * The range copies its input collection and never mutates or exposes its internal list, but it does not copy the
048 * elements themselves. Mutable elements can therefore change this object's observed equality, hash code, and string
049 * representation. Elements should be immutable or otherwise safely shared when a range is used concurrently or as a
050 * map key or set member.
051 * <p>
052 * Ranges are constructed via static methods.
053 * <p>
054 * Examples:
055 * <ul>
056 * <li>{@code Range.ofFiniteValues("a", "b", "c")}</li>
057 * <li>{@code Range.ofInfiniteValues(1, 10, 100, 1_000, 10_000)}</li>
058 * <li>{@code Range.emptyFiniteRange()}</li>
059 * <li>{@code Range.emptyInfiniteRange()}</li>
060 * </ul>
061 *
062 * @param <T> the type of values contained in the range
063 * @author <a href="https://revetkn.com">Mark Allen</a>
064 */
065@NotThreadSafe
066public final class Range<T> implements Iterable<@NonNull T> {
067  @NonNull
068  private static final Range<?> EMPTY_FINITE_RANGE = new Range<>(Collections.emptyList(), false);
069  @NonNull
070  private static final Range<?> EMPTY_INFINITE_RANGE = new Range<>(Collections.emptyList(), true);
071
072  @NonNull
073  private final List<@NonNull T> values;
074  @NonNull
075  private final Boolean infinite;
076
077  /**
078   * Provides an infinite range for the given values.
079   *
080   * @param values the values of the range, not null and containing no null elements
081   * @param <T>    the type of values contained in the range
082   * @return an infinite range, not null
083   */
084  @NonNull
085  public static <T> Range<T> ofInfiniteValues(@NonNull Collection<@NonNull T> values) {
086    requireNonNull(values);
087    return values.isEmpty() ? emptyInfiniteRange() : new Range<>(values, true);
088  }
089
090  /**
091   * Provides an infinite range for the given values.
092   *
093   * @param values the values of the range, not null and containing no null elements
094   * @param <T>    the type of values contained in the range
095   * @return an infinite range, not null
096   */
097  @SafeVarargs
098  @SuppressWarnings("varargs")
099  @NonNull
100  public static <T> Range<T> ofInfiniteValues(@NonNull T @NonNull ... values) {
101    requireNonNull(values);
102    return values.length == 0 ? emptyInfiniteRange() : new Range<>(Arrays.asList(values), true);
103  }
104
105  /**
106   * Provides a finite range for the given values.
107   *
108   * @param values the values of the range, not null and containing no null elements
109   * @param <T>    the type of values contained in the range
110   * @return a finite range, not null
111   */
112  @NonNull
113  public static <T> Range<T> ofFiniteValues(@NonNull Collection<@NonNull T> values) {
114    requireNonNull(values);
115    return values.isEmpty() ? emptyFiniteRange() : new Range<>(values, false);
116  }
117
118  /**
119   * Provides a finite range for the given values.
120   *
121   * @param values the values of the range, not null and containing no null elements
122   * @param <T>    the type of values contained in the range
123   * @return a finite range, not null
124   */
125  @SafeVarargs
126  @SuppressWarnings("varargs")
127  @NonNull
128  public static <T> Range<T> ofFiniteValues(@NonNull T @NonNull ... values) {
129    requireNonNull(values);
130    return values.length == 0 ? emptyFiniteRange() : new Range<>(Arrays.asList(values), false);
131  }
132
133  /**
134   * Gets the empty finite range.
135   *
136   * @param <T> the type of values contained in the range
137   * @return the empty finite range, not null
138   */
139  @SuppressWarnings("unchecked")
140  @NonNull
141  public static <T> Range<T> emptyFiniteRange() {
142    return (Range<T>) EMPTY_FINITE_RANGE;
143  }
144
145  /**
146   * Gets the empty infinite range.
147   *
148   * @param <T> the type of values contained in the range
149   * @return the empty infinite range, not null
150   */
151  @SuppressWarnings("unchecked")
152  @NonNull
153  public static <T> Range<T> emptyInfiniteRange() {
154    return (Range<T>) EMPTY_INFINITE_RANGE;
155  }
156
157  private Range(@NonNull Collection<@NonNull T> values, @NonNull Boolean infinite) {
158    requireNonNull(values);
159    requireNonNull(infinite);
160
161    List<@NonNull T> copiedValues = new ArrayList<>(values.size());
162    for (T value : values)
163      copiedValues.add(requireNonNull(value, "Range values may not contain null"));
164
165    this.values = copiedValues.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(copiedValues);
166    this.infinite = infinite;
167  }
168
169  /**
170   * Returns an iterator over the values in this range in proper sequence.
171   *
172   * @return an immutable iterator over the values in this range, not null
173   */
174  @NonNull
175  @Override
176  public Iterator<@NonNull T> iterator() {
177    return getValues().iterator();
178  }
179
180  /**
181   * Generates a {@code String} representation of this object.
182   *
183   * @return a string representation of this object, not null
184   */
185  @Override
186  @NonNull
187  public String toString() {
188    return format("%s{values=%s, infinite=%s}", getClass().getSimpleName(), getValues().stream()
189        .map(Object::toString)
190        .collect(Collectors.joining(", ")), isInfinite());
191  }
192
193  /**
194   * Checks if this object is equal to another one.
195   *
196   * @param other the object to check, null returns false
197   * @return true if this is equal to the other object, false otherwise
198   */
199  @Override
200  public boolean equals(@Nullable Object other) {
201    if (this == other)
202      return true;
203
204    if (other == null || !getClass().equals(other.getClass()))
205      return false;
206
207    Range<?> valueRange = (Range<?>) other;
208
209    return Objects.equals(getValues(), valueRange.getValues())
210        && Objects.equals(isInfinite(), valueRange.isInfinite());
211  }
212
213  /**
214   * A hash code for this object.
215   *
216   * @return a suitable hash code
217   */
218  @Override
219  public int hashCode() {
220    return Objects.hash(getValues(), isInfinite());
221  }
222
223  /**
224   * Gets the ordered values that comprise this range.
225   *
226   * @return an immutable list of the values that comprise this range, not null
227   */
228  @NonNull
229  public List<@NonNull T> getValues() {
230    return values;
231  }
232
233  /**
234   * Gets whether this range is infinite.
235   *
236   * @return whether this range's pattern repeats indefinitely, not null
237   * @since 3.0.0
238   */
239  @NonNull
240  public Boolean isInfinite() {
241    return infinite;
242  }
243}