001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang;
018
019 import java.io.Serializable;
020 import java.util.Iterator;
021 import java.util.NoSuchElementException;
022
023 /**
024 * <p>A contiguous range of characters, optionally negated.</p>
025 *
026 * <p>Instances are immutable.</p>
027 *
028 * @author Apache Software Foundation
029 * @author Chris Feldhacker
030 * @author Gary Gregory
031 * @since 1.0
032 * @version $Id: CharRange.java 906030 2010-02-03 12:25:26Z niallp $
033 */
034 public final class CharRange implements Serializable {
035
036 /**
037 * Required for serialization support. Lang version 2.0.
038 *
039 * @see java.io.Serializable
040 */
041 private static final long serialVersionUID = 8270183163158333422L;
042
043 /** The first character, inclusive, in the range. */
044 private final char start;
045 /** The last character, inclusive, in the range. */
046 private final char end;
047 /** True if the range is everything except the characters specified. */
048 private final boolean negated;
049
050 /** Cached toString. */
051 private transient String iToString;
052
053 // Static
054 //-----------------------------------------------------------------------
055 /**
056 * <p>Constructs a <code>CharRange</code> over a single character.</p>
057 *
058 * @param ch only character in this range
059 * @return the new CharRange object
060 * @see CharRange#CharRange(char, char, boolean)
061 * @since 2.5
062 */
063 public static CharRange is(char ch) {
064 return new CharRange(ch, ch, false);
065 }
066
067 /**
068 * <p>Constructs a negated <code>CharRange</code> over a single character.</p>
069 *
070 * @param ch only character in this range
071 * @return the new CharRange object
072 * @see CharRange#CharRange(char, char, boolean)
073 * @since 2.5
074 */
075 public static CharRange isNot(char ch) {
076 return new CharRange(ch, ch, true);
077 }
078
079 /**
080 * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
081 *
082 * @param start first character, inclusive, in this range
083 * @param end last character, inclusive, in this range
084 * @return the new CharRange object
085 * @see CharRange#CharRange(char, char, boolean)
086 * @since 2.5
087 */
088 public static CharRange isIn(char start, char end) {
089 return new CharRange(start, end, false);
090 }
091
092 /**
093 * <p>Constructs a negated <code>CharRange</code> over a set of characters.</p>
094 *
095 * @param start first character, inclusive, in this range
096 * @param end last character, inclusive, in this range
097 * @return the new CharRange object
098 * @see CharRange#CharRange(char, char, boolean)
099 * @since 2.5
100 */
101 public static CharRange isNotIn(char start, char end) {
102 return new CharRange(start, end, true);
103 }
104
105 //-----------------------------------------------------------------------
106 /**
107 * <p>Constructs a <code>CharRange</code> over a single character.</p>
108 *
109 * @param ch only character in this range
110 */
111 public CharRange(char ch) {
112 this(ch, ch, false);
113 }
114
115 /**
116 * <p>Constructs a <code>CharRange</code> over a single character,
117 * optionally negating the range.</p>
118 *
119 * <p>A negated range includes everything except the specified char.</p>
120 *
121 * @param ch only character in this range
122 * @param negated true to express everything except the range
123 */
124 public CharRange(char ch, boolean negated) {
125 this(ch, ch, negated);
126 }
127
128 /**
129 * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
130 *
131 * @param start first character, inclusive, in this range
132 * @param end last character, inclusive, in this range
133 */
134 public CharRange(char start, char end) {
135 this(start, end, false);
136 }
137
138 /**
139 * <p>Constructs a <code>CharRange</code> over a set of characters,
140 * optionally negating the range.</p>
141 *
142 * <p>A negated range includes everything except that defined by the
143 * start and end characters.</p>
144 *
145 * <p>If start and end are in the wrong order, they are reversed.
146 * Thus <code>a-e</code> is the same as <code>e-a</code>.</p>
147 *
148 * @param start first character, inclusive, in this range
149 * @param end last character, inclusive, in this range
150 * @param negated true to express everything except the range
151 */
152 public CharRange(char start, char end, boolean negated) {
153 super();
154 if (start > end) {
155 char temp = start;
156 start = end;
157 end = temp;
158 }
159
160 this.start = start;
161 this.end = end;
162 this.negated = negated;
163 }
164
165 // Accessors
166 //-----------------------------------------------------------------------
167 /**
168 * <p>Gets the start character for this character range.</p>
169 *
170 * @return the start char (inclusive)
171 */
172 public char getStart() {
173 return this.start;
174 }
175
176 /**
177 * <p>Gets the end character for this character range.</p>
178 *
179 * @return the end char (inclusive)
180 */
181 public char getEnd() {
182 return this.end;
183 }
184
185 /**
186 * <p>Is this <code>CharRange</code> negated.</p>
187 *
188 * <p>A negated range includes everything except that defined by the
189 * start and end characters.</p>
190 *
191 * @return <code>true</code> is negated
192 */
193 public boolean isNegated() {
194 return negated;
195 }
196
197 // Contains
198 //-----------------------------------------------------------------------
199 /**
200 * <p>Is the character specified contained in this range.</p>
201 *
202 * @param ch the character to check
203 * @return <code>true</code> if this range contains the input character
204 */
205 public boolean contains(char ch) {
206 return (ch >= start && ch <= end) != negated;
207 }
208
209 /**
210 * <p>Are all the characters of the passed in range contained in
211 * this range.</p>
212 *
213 * @param range the range to check against
214 * @return <code>true</code> if this range entirely contains the input range
215 * @throws IllegalArgumentException if <code>null</code> input
216 */
217 public boolean contains(CharRange range) {
218 if (range == null) {
219 throw new IllegalArgumentException("The Range must not be null");
220 }
221 if (negated) {
222 if (range.negated) {
223 return start >= range.start && end <= range.end;
224 }
225 return range.end < start || range.start > end;
226 }
227 if (range.negated) {
228 return start == 0 && end == Character.MAX_VALUE;
229 }
230 return start <= range.start && end >= range.end;
231 }
232
233 // Basics
234 //-----------------------------------------------------------------------
235 /**
236 * <p>Compares two CharRange objects, returning true if they represent
237 * exactly the same range of characters defined in the same way.</p>
238 *
239 * @param obj the object to compare to
240 * @return true if equal
241 */
242 public boolean equals(Object obj) {
243 if (obj == this) {
244 return true;
245 }
246 if (obj instanceof CharRange == false) {
247 return false;
248 }
249 CharRange other = (CharRange) obj;
250 return start == other.start && end == other.end && negated == other.negated;
251 }
252
253 /**
254 * <p>Gets a hashCode compatible with the equals method.</p>
255 *
256 * @return a suitable hashCode
257 */
258 public int hashCode() {
259 return 83 + start + 7 * end + (negated ? 1 : 0);
260 }
261
262 /**
263 * <p>Gets a string representation of the character range.</p>
264 *
265 * @return string representation of this range
266 */
267 public String toString() {
268 if (iToString == null) {
269 StringBuffer buf = new StringBuffer(4);
270 if (isNegated()) {
271 buf.append('^');
272 }
273 buf.append(start);
274 if (start != end) {
275 buf.append('-');
276 buf.append(end);
277 }
278 iToString = buf.toString();
279 }
280 return iToString;
281 }
282
283 // Expansions
284 //-----------------------------------------------------------------------
285 /**
286 * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
287 *
288 * @return an iterator to the chars represented by this range
289 * @since 2.5
290 */
291 public Iterator iterator() {
292 return new CharacterIterator(this);
293 }
294
295 /**
296 * Character {@link Iterator}.
297 */
298 private static class CharacterIterator implements Iterator {
299 /** The current character */
300 private char current;
301
302 private CharRange range;
303 private boolean hasNext;
304
305 /**
306 * Construct a new iterator for the character range.
307 *
308 * @param r The character range
309 */
310 private CharacterIterator(CharRange r) {
311 range = r;
312 hasNext = true;
313
314 if (range.negated) {
315 if (range.start == 0) {
316 if (range.end == Character.MAX_VALUE) {
317 // This range is an empty set
318 hasNext = false;
319 } else {
320 current = (char) (range.end + 1);
321 }
322 } else {
323 current = 0;
324 }
325 } else {
326 current = range.start;
327 }
328 }
329
330 /**
331 * Prepare the next character in the range.
332 */
333 private void prepareNext() {
334 if (range.negated) {
335 if (current == Character.MAX_VALUE) {
336 hasNext = false;
337 } else if (current + 1 == range.start) {
338 if (range.end == Character.MAX_VALUE) {
339 hasNext = false;
340 } else {
341 current = (char) (range.end + 1);
342 }
343 } else {
344 current = (char) (current + 1);
345 }
346 } else if (current < range.end) {
347 current = (char) (current + 1);
348 } else {
349 hasNext = false;
350 }
351 }
352
353 /**
354 * Has the iterator not reached the end character yet?
355 *
356 * @return <code>true</code> if the iterator has yet to reach the character date
357 */
358 public boolean hasNext() {
359 return hasNext;
360 }
361
362 /**
363 * Return the next character in the iteration
364 *
365 * @return <code>Character</code> for the next character
366 */
367 public Object next() {
368 if (hasNext == false) {
369 throw new NoSuchElementException();
370 }
371 char cur = current;
372 prepareNext();
373 return new Character(cur);
374 }
375
376 /**
377 * Always throws UnsupportedOperationException.
378 *
379 * @throws UnsupportedOperationException
380 * @see java.util.Iterator#remove()
381 */
382 public void remove() {
383 throw new UnsupportedOperationException();
384 }
385 }
386 }