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