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}