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