    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    }