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.lang;
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     * @author Apache Software Foundation
029     * @author Chris Feldhacker
030     * @author Gary Gregory
031     * @since 1.0
032     * @version $Id: CharRange.java 906030 2010-02-03 12:25:26Z niallp $
033     */
034    public final class CharRange implements Serializable {
035    
036        /**
037         * Required for serialization support. Lang version 2.0. 
038         * 
039         * @see java.io.Serializable
040         */
041        private static final long serialVersionUID = 8270183163158333422L;
042        
043        /** The first character, inclusive, in the range. */
044        private final char start;
045        /** The last character, inclusive, in the range. */
046        private final char end;
047        /** True if the range is everything except the characters specified. */
048        private final boolean negated;
049        
050        /** Cached toString. */
051        private transient String iToString;
052    
053        // Static
054        //-----------------------------------------------------------------------
055        /**
056         * <p>Constructs a <code>CharRange</code> over a single character.</p>
057         *
058         * @param ch  only character in this range
059         * @return the new CharRange object
060         * @see CharRange#CharRange(char, char, boolean)
061         * @since 2.5
062         */
063        public static CharRange is(char ch) {
064            return new CharRange(ch, ch, false);
065        }
066    
067        /**
068         * <p>Constructs a negated <code>CharRange</code> over a single character.</p>
069         *
070         * @param ch  only character in this range
071         * @return the new CharRange object
072         * @see CharRange#CharRange(char, char, boolean)
073         * @since 2.5
074         */
075        public static CharRange isNot(char ch) {
076            return new CharRange(ch, ch, true);
077        }
078    
079        /**
080         * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
081         *
082         * @param start  first character, inclusive, in this range
083         * @param end  last character, inclusive, in this range
084         * @return the new CharRange object
085         * @see CharRange#CharRange(char, char, boolean)
086         * @since 2.5
087         */
088        public static CharRange isIn(char start, char end) {
089            return new CharRange(start, end, false);
090        }
091    
092        /**
093         * <p>Constructs a negated <code>CharRange</code> over a set of characters.</p>
094         *
095         * @param start  first character, inclusive, in this range
096         * @param end  last character, inclusive, 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 isNotIn(char start, char end) {
102            return new CharRange(start, end, true);
103        }
104    
105        //-----------------------------------------------------------------------
106        /**
107         * <p>Constructs a <code>CharRange</code> over a single character.</p>
108         *
109         * @param ch  only character in this range
110         */
111        public CharRange(char ch) {
112            this(ch, ch, false);
113        }
114    
115        /**
116         * <p>Constructs a <code>CharRange</code> over a single character,
117         * optionally negating the range.</p>
118         *
119         * <p>A negated range includes everything except the specified char.</p>
120         *
121         * @param ch  only character in this range
122         * @param negated  true to express everything except the range
123         */
124        public CharRange(char ch, boolean negated) {
125            this(ch, ch, negated);
126        }
127    
128        /**
129         * <p>Constructs a <code>CharRange</code> over a set of characters.</p>
130         *
131         * @param start  first character, inclusive, in this range
132         * @param end  last character, inclusive, in this range
133         */
134        public CharRange(char start, char end) {
135            this(start, end, false);
136        }
137    
138        /**
139         * <p>Constructs a <code>CharRange</code> over a set of characters,
140         * optionally negating the range.</p>
141         *
142         * <p>A negated range includes everything except that defined by the
143         * start and end characters.</p>
144         * 
145         * <p>If start and end are in the wrong order, they are reversed.
146         * Thus <code>a-e</code> is the same as <code>e-a</code>.</p>
147         *
148         * @param start  first character, inclusive, in this range
149         * @param end  last character, inclusive, in this range
150         * @param negated  true to express everything except the range
151         */
152        public CharRange(char start, char end, boolean negated) {
153            super();
154            if (start > end) {
155                char temp = start;
156                start = end;
157                end = temp;
158            }
159            
160            this.start = start;
161            this.end = end;
162            this.negated = negated;
163        }
164    
165        // Accessors
166        //-----------------------------------------------------------------------
167        /**
168         * <p>Gets the start character for this character range.</p>
169         * 
170         * @return the start char (inclusive)
171         */
172        public char getStart() {
173            return this.start;
174        }
175    
176        /**
177         * <p>Gets the end character for this character range.</p>
178         * 
179         * @return the end char (inclusive)
180         */
181        public char getEnd() {
182            return this.end;
183        }
184    
185        /**
186         * <p>Is this <code>CharRange</code> negated.</p>
187         * 
188         * <p>A negated range includes everything except that defined by the
189         * start and end characters.</p>
190         *
191         * @return <code>true</code> is negated
192         */
193        public boolean isNegated() {
194            return negated;
195        }
196    
197        // Contains
198        //-----------------------------------------------------------------------
199        /**
200         * <p>Is the character specified contained in this range.</p>
201         *
202         * @param ch  the character to check
203         * @return <code>true</code> if this range contains the input character
204         */
205        public boolean contains(char ch) {
206            return (ch >= start && ch <= end) != negated;
207        }
208    
209        /**
210         * <p>Are all the characters of the passed in range contained in
211         * this range.</p>
212         *
213         * @param range  the range to check against
214         * @return <code>true</code> if this range entirely contains the input range
215         * @throws IllegalArgumentException if <code>null</code> input
216         */
217        public boolean contains(CharRange range) {
218            if (range == null) {
219                throw new IllegalArgumentException("The Range must not be null");
220            }
221            if (negated) {
222                if (range.negated) {
223                    return start >= range.start && end <= range.end;
224                }
225                return range.end < start || range.start > end;
226            }
227            if (range.negated) {
228                return start == 0 && end == Character.MAX_VALUE;
229            }
230            return start <= range.start && end >= range.end;
231        }
232    
233        // Basics
234        //-----------------------------------------------------------------------
235        /**
236         * <p>Compares two CharRange objects, returning true if they represent
237         * exactly the same range of characters defined in the same way.</p>
238         * 
239         * @param obj  the object to compare to
240         * @return true if equal
241         */
242        public boolean equals(Object obj) {
243            if (obj == this) {
244                return true;
245            }
246            if (obj instanceof CharRange == false) {
247                return false;
248            }
249            CharRange other = (CharRange) obj;
250            return start == other.start && end == other.end && negated == other.negated;
251        }
252    
253        /**
254         * <p>Gets a hashCode compatible with the equals method.</p>
255         * 
256         * @return a suitable hashCode
257         */
258        public int hashCode() {
259            return 83 + start + 7 * end + (negated ? 1 : 0);
260        }
261        
262        /**
263         * <p>Gets a string representation of the character range.</p>
264         * 
265         * @return string representation of this range
266         */
267        public String toString() {
268            if (iToString == null) {
269                StringBuffer buf = new StringBuffer(4);
270                if (isNegated()) {
271                    buf.append('^');
272                }
273                buf.append(start);
274                if (start != end) {
275                    buf.append('-');
276                    buf.append(end);
277                }
278                iToString = buf.toString();
279            }
280            return iToString;
281        }
282    
283        // Expansions
284        //-----------------------------------------------------------------------
285        /**
286         * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
287         *
288         * @return an iterator to the chars represented by this range
289         * @since 2.5
290         */
291        public Iterator iterator() {
292            return new CharacterIterator(this);
293        }
294    
295        /**
296         * Character {@link Iterator}.
297         */
298        private static class CharacterIterator implements Iterator {
299            /** The current character */
300            private char current;
301    
302            private CharRange range;
303            private boolean hasNext;
304    
305            /**
306             * Construct a new iterator for the character range.
307             *
308             * @param r The character range
309             */
310            private CharacterIterator(CharRange r) {
311                range = r;
312                hasNext = true;
313    
314                if (range.negated) {
315                    if (range.start == 0) {
316                        if (range.end == Character.MAX_VALUE) {
317                            // This range is an empty set
318                            hasNext = false;
319                        } else {
320                            current = (char) (range.end + 1);
321                        }
322                    } else {
323                        current = 0;
324                    }
325                } else {
326                    current = range.start;
327                }
328            }
329    
330            /**
331             * Prepare the next character in the range.
332             */
333            private void prepareNext() {
334                if (range.negated) {
335                    if (current == Character.MAX_VALUE) {
336                        hasNext = false;
337                    } else if (current + 1 == range.start) {
338                        if (range.end == Character.MAX_VALUE) {
339                            hasNext = false;
340                        } else {
341                            current = (char) (range.end + 1);
342                        }
343                    } else {
344                        current = (char) (current + 1);
345                    }
346                } else if (current < range.end) {
347                    current = (char) (current + 1);
348                } else {
349                    hasNext = false;
350                }
351            }
352    
353            /**
354             * Has the iterator not reached the end character yet?
355             *
356             * @return <code>true</code> if the iterator has yet to reach the character date
357             */
358            public boolean hasNext() {
359                return hasNext;
360            }
361    
362            /**
363             * Return the next character in the iteration
364             *
365             * @return <code>Character</code> for the next character
366             */
367            public Object next() {
368                if (hasNext == false) {
369                    throw new NoSuchElementException();
370                }
371                char cur = current;
372                prepareNext();
373                return new Character(cur);
374            }
375    
376            /**
377             * Always throws UnsupportedOperationException.
378             *
379             * @throws UnsupportedOperationException
380             * @see java.util.Iterator#remove()
381             */
382            public void remove() {
383                throw new UnsupportedOperationException();
384            }
385        }
386    }