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         Validate.isTrue(range != null, "The Range must not be null");
183         if (negated) {
184             if (range.negated) {
185                 return start >= range.start && end <= range.end;
186             }
187             return range.end < start || range.start > end;
188         }
189         if (range.negated) {
190             return start == 0 && end == Character.MAX_VALUE;
191         }
192         return start <= range.start && end >= range.end;
193     }
194 
195     // Basics
196     //-----------------------------------------------------------------------
197     /**
198      * <p>Compares two CharRange objects, returning true if they represent
199      * exactly the same range of characters defined in the same way.</p>
200      *
201      * @param obj  the object to compare to
202      * @return true if equal
203      */
204     @Override
205     public boolean equals(final Object obj) {
206         if (obj == this) {
207             return true;
208         }
209         if (obj instanceof CharRange == false) {
210             return false;
211         }
212         final CharRange other = (CharRange) obj;
213         return start == other.start && end == other.end && negated == other.negated;
214     }
215 
216     /**
217      * <p>Gets a hashCode compatible with the equals method.</p>
218      *
219      * @return a suitable hashCode
220      */
221     @Override
222     public int hashCode() {
223         return 83 + start + 7 * end + (negated ? 1 : 0);
224     }
225 
226     /**
227      * <p>Gets a string representation of the character range.</p>
228      *
229      * @return string representation of this range
230      */
231     @Override
232     public String toString() {
233         if (iToString == null) {
234             final StringBuilder buf = new StringBuilder(4);
235             if (isNegated()) {
236                 buf.append('^');
237             }
238             buf.append(start);
239             if (start != end) {
240                 buf.append('-');
241                 buf.append(end);
242             }
243             iToString = buf.toString();
244         }
245         return iToString;
246     }
247 
248     // Expansions
249     //-----------------------------------------------------------------------
250     /**
251      * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
252      *
253      * <p>#NotThreadSafe# the iterator is not thread-safe</p>
254      * @return an iterator to the chars represented by this range
255      * @since 2.5
256      */
257     @Override
258     public Iterator<Character> iterator() {
259         return new CharacterIterator(this);
260     }
261 
262     /**
263      * Character {@link Iterator}.
264      * <p>#NotThreadSafe#</p>
265      */
266     private static class CharacterIterator implements Iterator<Character> {
267         /** The current character */
268         private char current;
269 
270         private final CharRange range;
271         private boolean hasNext;
272 
273         /**
274          * Construct a new iterator for the character range.
275          *
276          * @param r The character range
277          */
278         private CharacterIterator(final CharRange r) {
279             range = r;
280             hasNext = true;
281 
282             if (range.negated) {
283                 if (range.start == 0) {
284                     if (range.end == Character.MAX_VALUE) {
285                         // This range is an empty set
286                         hasNext = false;
287                     } else {
288                         current = (char) (range.end + 1);
289                     }
290                 } else {
291                     current = 0;
292                 }
293             } else {
294                 current = range.start;
295             }
296         }
297 
298         /**
299          * Prepare the next character in the range.
300          */
301         private void prepareNext() {
302             if (range.negated) {
303                 if (current == Character.MAX_VALUE) {
304                     hasNext = false;
305                 } else if (current + 1 == range.start) {
306                     if (range.end == Character.MAX_VALUE) {
307                         hasNext = false;
308                     } else {
309                         current = (char) (range.end + 1);
310                     }
311                 } else {
312                     current = (char) (current + 1);
313                 }
314             } else if (current < range.end) {
315                 current = (char) (current + 1);
316             } else {
317                 hasNext = false;
318             }
319         }
320 
321         /**
322          * Has the iterator not reached the end character yet?
323          *
324          * @return {@code true} if the iterator has yet to reach the character date
325          */
326         @Override
327         public boolean hasNext() {
328             return hasNext;
329         }
330 
331         /**
332          * Return the next character in the iteration
333          *
334          * @return {@code Character} for the next character
335          */
336         @Override
337         public Character next() {
338             if (hasNext == false) {
339                 throw new NoSuchElementException();
340             }
341             final char cur = current;
342             prepareNext();
343             return Character.valueOf(cur);
344         }
345 
346         /**
347          * Always throws UnsupportedOperationException.
348          *
349          * @throws UnsupportedOperationException
350          * @see java.util.Iterator#remove()
351          */
352         @Override
353         public void remove() {
354             throw new UnsupportedOperationException();
355         }
356     }
357 }