View Javadoc
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 }