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