001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.lang3;
018    
019    import java.io.Serializable;
020    import java.util.Iterator;
021    import java.util.NoSuchElementException;
022    
023    /**
024     * <p>A contiguous range of characters, optionally negated.</p>
025     * 
026     * <p>Instances are immutable.</p>
027     *
028     * <p>#ThreadSafe#</p>
029     * @author Apache Software Foundation
030     * @author Chris Feldhacker
031     * @author Gary Gregory
032     * @since 1.0
033     * @version $Id: CharRange.java 918868 2010-03-04 06:22:16Z bayard $
034     */
035    public final class CharRange implements Iterable<Character>, Serializable {
036    
037        /**
038         * Required for serialization support. Lang version 2.0. 
039         * 
040         * @see java.io.Serializable
041         */
042        private static final long serialVersionUID = 8270183163158333422L;
043        
044        /** The first character, inclusive, in the range. */
045        private final char start;
046        /** The last character, inclusive, in the range. */
047        private final char end;
048        /** True if the range is everything except the characters specified. */
049        private final boolean negated;
050        
051        /** Cached toString. */
052        private transient String iToString;
053    
054        /**
055         * <p>Constructs a <code>CharRange</code> over a set of characters,
056         * optionally negating the range.</p>
057         *
058         * <p>A negated range includes everything except that defined by the
059         * start and end characters.</p>
060         * 
061         * <p>If start and end are in the wrong order, they are reversed.
062         * Thus <code>a-e</code> is the same as <code>e-a</code>.</p>
063         *
064         * @param start  first character, inclusive, in this range
065         * @param end  last character, inclusive, in this range
066         * @param negated  true to express everything except the range
067         */
068        private CharRange(char start, char end, boolean negated) {
069            super();
070            if (start > end) {
071                char temp = start;
072                start = end;
073                end = temp;
074            }
075            
076            this.start = start;
077            this.end = end;
078            this.negated = negated;
079        }
080    
081        /**
082         * <p>Constructs a <code>CharRange</code> over a single character.</p>
083         *
084         * @param ch  only character in this range
085         * @return the new CharRange object
086         * @see CharRange#CharRange(char, char, boolean)
087         * @since 2.5
088         */
089        public static CharRange is(char ch) {
090            return new CharRange(ch, ch, false);
091        }
092    
093        /**
094         * <p>Constructs a negated <code>CharRange</code> over a single character.</p>
095         *
096         * @param ch  only character in this range
097         * @return the new CharRange object
098         * @see CharRange#CharRange(char, char, boolean)
099         * @since 2.5
100         */
101        public static CharRange isNot(char ch) {
102            return new CharRange(ch, ch, true);
103        }
104    
105        /**
106         * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
107         *
108         * @param start  first character, inclusive, in this range
109         * @param end  last character, inclusive, in this range
110         * @return the new CharRange object
111         * @see CharRange#CharRange(char, char, boolean)
112         * @since 2.5
113         */
114        public static CharRange isIn(char start, char end) {
115            return new CharRange(start, end, false);
116        }
117    
118        /**
119         * <p>Constructs a negated <code>CharRange</code> over a set of characters.</p>
120         *
121         * @param start  first character, inclusive, in this range
122         * @param end  last character, inclusive, in this range
123         * @return the new CharRange object
124         * @see CharRange#CharRange(char, char, boolean)
125         * @since 2.5
126         */
127        public static CharRange isNotIn(char start, char end) {
128            return new CharRange(start, end, true);
129        }
130    
131        // Accessors
132        //-----------------------------------------------------------------------
133        /**
134         * <p>Gets the start character for this character range.</p>
135         * 
136         * @return the start char (inclusive)
137         */
138        public char getStart() {
139            return this.start;
140        }
141    
142        /**
143         * <p>Gets the end character for this character range.</p>
144         * 
145         * @return the end char (inclusive)
146         */
147        public char getEnd() {
148            return this.end;
149        }
150    
151        /**
152         * <p>Is this <code>CharRange</code> negated.</p>
153         * 
154         * <p>A negated range includes everything except that defined by the
155         * start and end characters.</p>
156         *
157         * @return <code>true</code> is negated
158         */
159        public boolean isNegated() {
160            return negated;
161        }
162    
163        // Contains
164        //-----------------------------------------------------------------------
165        /**
166         * <p>Is the character specified contained in this range.</p>
167         *
168         * @param ch  the character to check
169         * @return <code>true</code> if this range contains the input character
170         */
171        public boolean contains(char ch) {
172            return (ch >= start && ch <= end) != negated;
173        }
174    
175        /**
176         * <p>Are all the characters of the passed in range contained in
177         * this range.</p>
178         *
179         * @param range  the range to check against
180         * @return <code>true</code> if this range entirely contains the input range
181         * @throws IllegalArgumentException if <code>null</code> input
182         */
183        public boolean contains(CharRange range) {
184            if (range == null) {
185                throw new IllegalArgumentException("The Range must not be null");
186            }
187            if (negated) {
188                if (range.negated) {
189                    return start >= range.start && end <= range.end;
190                }
191                return range.end < start || range.start > end;
192            }
193            if (range.negated) {
194                return start == 0 && end == Character.MAX_VALUE;
195            }
196            return start <= range.start && end >= range.end;
197        }
198    
199        // Basics
200        //-----------------------------------------------------------------------
201        /**
202         * <p>Compares two CharRange objects, returning true if they represent
203         * exactly the same range of characters defined in the same way.</p>
204         * 
205         * @param obj  the object to compare to
206         * @return true if equal
207         */
208        @Override
209        public boolean equals(Object obj) {
210            if (obj == this) {
211                return true;
212            }
213            if (obj instanceof CharRange == false) {
214                return false;
215            }
216            CharRange other = (CharRange) obj;
217            return start == other.start && end == other.end && negated == other.negated;
218        }
219    
220        /**
221         * <p>Gets a hashCode compatible with the equals method.</p>
222         * 
223         * @return a suitable hashCode
224         */
225        @Override
226        public int hashCode() {
227            return 83 + start + 7 * end + (negated ? 1 : 0);
228        }
229        
230        /**
231         * <p>Gets a string representation of the character range.</p>
232         * 
233         * @return string representation of this range
234         */
235        @Override
236        public String toString() {
237            if (iToString == null) {
238                StringBuilder buf = new StringBuilder(4);
239                if (isNegated()) {
240                    buf.append('^');
241                }
242                buf.append(start);
243                if (start != end) {
244                    buf.append('-');
245                    buf.append(end);
246                }
247                iToString = buf.toString();
248            }
249            return iToString;
250        }
251    
252        // Expansions
253        //-----------------------------------------------------------------------
254        /**
255         * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
256         *
257         * <p>#NotThreadSafe# the iterator is not threadsafe</p>
258         * @return an iterator to the chars represented by this range
259         * @since 2.5
260         */
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(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</code> if the iterator has yet to reach the character date
328             */
329            public boolean hasNext() {
330                return hasNext;
331            }
332    
333            /**
334             * Return the next character in the iteration
335             *
336             * @return <code>Character</code> for the next character
337             */
338            public Character next() {
339                if (hasNext == false) {
340                    throw new NoSuchElementException();
341                }
342                char cur = current;
343                prepareNext();
344                return Character.valueOf(cur);
345            }
346    
347            /**
348             * Always throws UnsupportedOperationException.
349             *
350             * @throws UnsupportedOperationException
351             * @see java.util.Iterator#remove()
352             */
353            public void remove() {
354                throw new UnsupportedOperationException();
355            }
356        }
357    }