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.lang3;
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 * <p>#ThreadSafe#</p>
029 * @author Apache Software Foundation
030 * @author Chris Feldhacker
031 * @author Gary Gregory
032 * @since 1.0
033 * @version $Id: CharRange.java 918868 2010-03-04 06:22:16Z bayard $
034 */
035 public final class CharRange implements Iterable<Character>, Serializable {
036
037 /**
038 * Required for serialization support. Lang version 2.0.
039 *
040 * @see java.io.Serializable
041 */
042 private static final long serialVersionUID = 8270183163158333422L;
043
044 /** The first character, inclusive, in the range. */
045 private final char start;
046 /** The last character, inclusive, in the range. */
047 private final char end;
048 /** True if the range is everything except the characters specified. */
049 private final boolean negated;
050
051 /** Cached toString. */
052 private transient String iToString;
053
054 /**
055 * <p>Constructs a <code>CharRange</code> over a set of characters,
056 * optionally negating the range.</p>
057 *
058 * <p>A negated range includes everything except that defined by the
059 * start and end characters.</p>
060 *
061 * <p>If start and end are in the wrong order, they are reversed.
062 * Thus <code>a-e</code> is the same as <code>e-a</code>.</p>
063 *
064 * @param start first character, inclusive, in this range
065 * @param end last character, inclusive, in this range
066 * @param negated true to express everything except the range
067 */
068 private CharRange(char start, char end, boolean negated) {
069 super();
070 if (start > end) {
071 char temp = start;
072 start = end;
073 end = temp;
074 }
075
076 this.start = start;
077 this.end = end;
078 this.negated = negated;
079 }
080
081 /**
082 * <p>Constructs a <code>CharRange</code> over a single character.</p>
083 *
084 * @param ch only character in this range
085 * @return the new CharRange object
086 * @see CharRange#CharRange(char, char, boolean)
087 * @since 2.5
088 */
089 public static CharRange is(char ch) {
090 return new CharRange(ch, ch, false);
091 }
092
093 /**
094 * <p>Constructs a negated <code>CharRange</code> over a single character.</p>
095 *
096 * @param ch only character 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 isNot(char ch) {
102 return new CharRange(ch, ch, true);
103 }
104
105 /**
106 * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
107 *
108 * @param start first character, inclusive, in this range
109 * @param end last character, inclusive, in this range
110 * @return the new CharRange object
111 * @see CharRange#CharRange(char, char, boolean)
112 * @since 2.5
113 */
114 public static CharRange isIn(char start, char end) {
115 return new CharRange(start, end, false);
116 }
117
118 /**
119 * <p>Constructs a negated <code>CharRange</code> over a set of characters.</p>
120 *
121 * @param start first character, inclusive, in this range
122 * @param end last character, inclusive, in this range
123 * @return the new CharRange object
124 * @see CharRange#CharRange(char, char, boolean)
125 * @since 2.5
126 */
127 public static CharRange isNotIn(char start, char end) {
128 return new CharRange(start, end, true);
129 }
130
131 // Accessors
132 //-----------------------------------------------------------------------
133 /**
134 * <p>Gets the start character for this character range.</p>
135 *
136 * @return the start char (inclusive)
137 */
138 public char getStart() {
139 return this.start;
140 }
141
142 /**
143 * <p>Gets the end character for this character range.</p>
144 *
145 * @return the end char (inclusive)
146 */
147 public char getEnd() {
148 return this.end;
149 }
150
151 /**
152 * <p>Is this <code>CharRange</code> negated.</p>
153 *
154 * <p>A negated range includes everything except that defined by the
155 * start and end characters.</p>
156 *
157 * @return <code>true</code> is negated
158 */
159 public boolean isNegated() {
160 return negated;
161 }
162
163 // Contains
164 //-----------------------------------------------------------------------
165 /**
166 * <p>Is the character specified contained in this range.</p>
167 *
168 * @param ch the character to check
169 * @return <code>true</code> if this range contains the input character
170 */
171 public boolean contains(char ch) {
172 return (ch >= start && ch <= end) != negated;
173 }
174
175 /**
176 * <p>Are all the characters of the passed in range contained in
177 * this range.</p>
178 *
179 * @param range the range to check against
180 * @return <code>true</code> if this range entirely contains the input range
181 * @throws IllegalArgumentException if <code>null</code> input
182 */
183 public boolean contains(CharRange range) {
184 if (range == null) {
185 throw new IllegalArgumentException("The Range must not be null");
186 }
187 if (negated) {
188 if (range.negated) {
189 return start >= range.start && end <= range.end;
190 }
191 return range.end < start || range.start > end;
192 }
193 if (range.negated) {
194 return start == 0 && end == Character.MAX_VALUE;
195 }
196 return start <= range.start && end >= range.end;
197 }
198
199 // Basics
200 //-----------------------------------------------------------------------
201 /**
202 * <p>Compares two CharRange objects, returning true if they represent
203 * exactly the same range of characters defined in the same way.</p>
204 *
205 * @param obj the object to compare to
206 * @return true if equal
207 */
208 @Override
209 public boolean equals(Object obj) {
210 if (obj == this) {
211 return true;
212 }
213 if (obj instanceof CharRange == false) {
214 return false;
215 }
216 CharRange other = (CharRange) obj;
217 return start == other.start && end == other.end && negated == other.negated;
218 }
219
220 /**
221 * <p>Gets a hashCode compatible with the equals method.</p>
222 *
223 * @return a suitable hashCode
224 */
225 @Override
226 public int hashCode() {
227 return 83 + start + 7 * end + (negated ? 1 : 0);
228 }
229
230 /**
231 * <p>Gets a string representation of the character range.</p>
232 *
233 * @return string representation of this range
234 */
235 @Override
236 public String toString() {
237 if (iToString == null) {
238 StringBuilder buf = new StringBuilder(4);
239 if (isNegated()) {
240 buf.append('^');
241 }
242 buf.append(start);
243 if (start != end) {
244 buf.append('-');
245 buf.append(end);
246 }
247 iToString = buf.toString();
248 }
249 return iToString;
250 }
251
252 // Expansions
253 //-----------------------------------------------------------------------
254 /**
255 * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
256 *
257 * <p>#NotThreadSafe# the iterator is not threadsafe</p>
258 * @return an iterator to the chars represented by this range
259 * @since 2.5
260 */
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(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</code> if the iterator has yet to reach the character date
328 */
329 public boolean hasNext() {
330 return hasNext;
331 }
332
333 /**
334 * Return the next character in the iteration
335 *
336 * @return <code>Character</code> for the next character
337 */
338 public Character next() {
339 if (hasNext == false) {
340 throw new NoSuchElementException();
341 }
342 char cur = current;
343 prepareNext();
344 return Character.valueOf(cur);
345 }
346
347 /**
348 * Always throws UnsupportedOperationException.
349 *
350 * @throws UnsupportedOperationException
351 * @see java.util.Iterator#remove()
352 */
353 public void remove() {
354 throw new UnsupportedOperationException();
355 }
356 }
357 }