1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.lang3;
18
19 import java.io.Serializable;
20 import java.util.Iterator;
21 import java.util.NoSuchElementException;
22 import java.util.Objects;
23
24 /**
25 * A contiguous range of characters, optionally negated.
26 *
27 * <p>Instances are immutable.</p>
28 *
29 * <p>#ThreadSafe#</p>
30 *
31 * @since 1.0
32 * @since 3.21.0 {@code serialVersionUID} changed from {@code 8270183163158333422L} to {@code 2L}.
33 */
34 // TODO: This is no longer public and will be removed later as CharSet is moved
35 // to depend on Range.
36 final class CharRange implements Iterable<Character>, Serializable {
37
38 /**
39 * Character {@link Iterator}.
40 * <p>#NotThreadSafe#</p>
41 */
42 private static final class CharacterIterator implements Iterator<Character> {
43
44 /** The current character */
45 private char current;
46
47 private final CharRange range;
48 private boolean hasNext;
49
50 /**
51 * Constructs a new iterator for the character range.
52 *
53 * @param r The character range.
54 */
55 private CharacterIterator(final CharRange r) {
56 range = r;
57 hasNext = true;
58
59 if (range.negated) {
60 if (range.start == 0) {
61 if (range.end == Character.MAX_VALUE) {
62 // This range is an empty set
63 hasNext = false;
64 } else {
65 current = (char) (range.end + 1);
66 }
67 } else {
68 current = 0;
69 }
70 } else {
71 current = range.start;
72 }
73 }
74
75 /**
76 * Tests whether this iterator reached the end character.
77 *
78 * @return {@code true} if the iterator has yet to reach the character date.
79 */
80 @Override
81 public boolean hasNext() {
82 return hasNext;
83 }
84
85 /**
86 * Returns the next character in the iteration.
87 *
88 * @return {@link Character} for the next character.
89 */
90 @Override
91 public Character next() {
92 if (!hasNext) {
93 throw new NoSuchElementException();
94 }
95 final char cur = current;
96 prepareNext();
97 return Character.valueOf(cur);
98 }
99
100 /**
101 * Prepares the next character in the range.
102 */
103 private void prepareNext() {
104 if (range.negated) {
105 if (current == Character.MAX_VALUE) {
106 hasNext = false;
107 } else if (current + 1 == range.start) {
108 if (range.end == Character.MAX_VALUE) {
109 hasNext = false;
110 } else {
111 current = (char) (range.end + 1);
112 }
113 } else {
114 current = (char) (current + 1);
115 }
116 } else if (current < range.end) {
117 current = (char) (current + 1);
118 } else {
119 hasNext = false;
120 }
121 }
122
123 /**
124 * Always throws UnsupportedOperationException.
125 *
126 * @throws UnsupportedOperationException Always thrown.
127 * @see java.util.Iterator#remove()
128 */
129 @Override
130 public void remove() {
131 throw new UnsupportedOperationException();
132 }
133 }
134
135 /**
136 * Required for serialization support. Lang version 2.0.
137 *
138 * @see java.io.Serializable
139 * @since 3.21.0 {@code serialVersionUID} changed from {@code 8270183163158333422L} to {@value}.
140 */
141 private static final long serialVersionUID = 2L;
142
143 /** Empty array. */
144 static final CharRange[] EMPTY_ARRAY = {};
145
146 /**
147 * Constructs a {@link CharRange} over a single character.
148 *
149 * @param ch only character in this range.
150 * @return the new CharRange object.
151 * @since 2.5
152 */
153 public static CharRange is(final char ch) {
154 return new CharRange(ch, ch, false);
155 }
156
157 /**
158 * Constructs a {@link CharRange} over a set of characters.
159 *
160 * <p>If start and end are in the wrong order, they are reversed.
161 * Thus {@code a-e} is the same as {@code e-a}.</p>
162 *
163 * @param start first character, inclusive, in this range.
164 * @param end last character, inclusive, in this range.
165 * @return the new CharRange object.
166 * @since 2.5
167 */
168 public static CharRange isIn(final char start, final char end) {
169 return new CharRange(start, end, false);
170 }
171
172 /**
173 * Constructs a negated {@link CharRange} over a single character.
174 *
175 * <p>A negated range includes everything except that defined by the
176 * single character.</p>
177 *
178 * @param ch only character in this range.
179 * @return the new CharRange object.
180 * @since 2.5
181 */
182 public static CharRange isNot(final char ch) {
183 return new CharRange(ch, ch, true);
184 }
185
186 /**
187 * Constructs a negated {@link CharRange} over a set of characters.
188 *
189 * <p>A negated range includes everything except that defined by the
190 * start and end characters.</p>
191 *
192 * <p>If start and end are in the wrong order, they are reversed.
193 * Thus {@code a-e} is the same as {@code e-a}.</p>
194 *
195 * @param start first character, inclusive, in this range.
196 * @param end last character, inclusive, in this range.
197 * @return the new CharRange object.
198 * @since 2.5
199 */
200 public static CharRange isNotIn(final char start, final char end) {
201 return new CharRange(start, end, true);
202 }
203
204 /** The first character, inclusive, in the range. */
205 private final char start;
206
207 /** The last character, inclusive, in the range. */
208 private final char end;
209
210 /** True if the range is everything except the characters specified. */
211 private final boolean negated;
212
213 /** Cached toString. */
214 private transient String iToString;
215
216 /**
217 * Constructs a {@link CharRange} over a set of characters,
218 * optionally negating the range.
219 *
220 * <p>A negated range includes everything except that defined by the
221 * start and end characters.</p>
222 *
223 * <p>If start and end are in the wrong order, they are reversed.
224 * Thus {@code a-e} is the same as {@code e-a}.</p>
225 *
226 * @param start first character, inclusive, in this range.
227 * @param end last character, inclusive, in this range.
228 * @param negated true to express everything except the range.
229 */
230 private CharRange(char start, char end, final boolean negated) {
231 if (start > end) {
232 final char temp = start;
233 start = end;
234 end = temp;
235 }
236
237 this.start = start;
238 this.end = end;
239 this.negated = negated;
240 }
241
242 /**
243 * Is the character specified contained in this range.
244 *
245 * @param ch the character to check.
246 * @return {@code true} if this range contains the input character.
247 */
248 public boolean contains(final char ch) {
249 return (ch >= start && ch <= end) != negated;
250 }
251
252 /**
253 * Are all the characters of the passed in range contained in
254 * this range.
255 *
256 * @param range the range to check against.
257 * @return {@code true} if this range entirely contains the input range.
258 * @throws NullPointerException if {@code null} input.
259 */
260 public boolean contains(final CharRange range) {
261 Objects.requireNonNull(range, "range");
262 if (negated) {
263 if (range.negated) {
264 return start >= range.start && end <= range.end;
265 }
266 return range.end < start || range.start > end;
267 }
268 if (range.negated) {
269 return start == 0 && end == Character.MAX_VALUE;
270 }
271 return start <= range.start && end >= range.end;
272 }
273
274 /**
275 * Compares two CharRange objects, returning true if they represent
276 * exactly the same range of characters defined in the same way.
277 *
278 * @param obj the object to compare to.
279 * @return true if equal.
280 */
281 @Override
282 public boolean equals(final Object obj) {
283 if (obj == this) {
284 return true;
285 }
286 if (!(obj instanceof CharRange)) {
287 return false;
288 }
289 final CharRange other = (CharRange) obj;
290 return start == other.start && end == other.end && negated == other.negated;
291 }
292
293 /**
294 * Gets the end character for this character range.
295 *
296 * @return the end char (inclusive).
297 */
298 public char getEnd() {
299 return this.end;
300 }
301
302 /**
303 * Gets the start character for this character range.
304 *
305 * @return the start char (inclusive).
306 */
307 public char getStart() {
308 return this.start;
309 }
310
311 /**
312 * Gets a hashCode compatible with the equals method.
313 *
314 * @return a suitable hashCode.
315 */
316 @Override
317 public int hashCode() {
318 return Objects.hash(end, negated, start);
319 }
320
321 /**
322 * Is this {@link CharRange} negated.
323 *
324 * <p>A negated range includes everything except that defined by the
325 * start and end characters.</p>
326 *
327 * @return {@code true} if negated.
328 */
329 public boolean isNegated() {
330 return negated;
331 }
332
333 /**
334 * Returns an iterator which can be used to walk through the characters described by this range.
335 *
336 * <p>#NotThreadSafe# the iterator is not thread-safe</p>
337 *
338 * @return an iterator to the chars represented by this range
339 * @since 2.5
340 */
341 @Override
342 public Iterator<Character> iterator() {
343 return new CharacterIterator(this);
344 }
345
346 /**
347 * Gets a string representation of the character range.
348 *
349 * @return string representation of this range.
350 */
351 @Override
352 public String toString() {
353 if (iToString == null) {
354 final StringBuilder buf = new StringBuilder(4);
355 if (isNegated()) {
356 buf.append('^');
357 }
358 buf.append(start);
359 if (start != end) {
360 buf.append('-');
361 buf.append(end);
362 }
363 iToString = buf.toString();
364 }
365 return iToString;
366 }
367 }