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