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