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