    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    }