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}