Coverage Report - org.apache.commons.lang3.CharRange
 
Classes in this File Line Coverage Branch Coverage Complexity
CharRange
100%
44/44
98%
49/50
2,737
CharRange$1
N/A
N/A
2,737
CharRange$CharacterIterator
100%
31/31
100%
18/18
2,737
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.lang3;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.util.Iterator;
 21  
 import java.util.NoSuchElementException;
 22  
 
 23  
 /**
 24  
  * <p>A contiguous range of characters, optionally negated.</p>
 25  
  *
 26  
  * <p>Instances are immutable.</p>
 27  
  *
 28  
  * <p>#ThreadSafe#</p>
 29  
  * @since 1.0
 30  
  */
 31  
 // TODO: This is no longer public and will be removed later as CharSet is moved
 32  
 // to depend on Range.
 33  131102
 final class CharRange implements Iterable<Character>, Serializable {
 34  
 
 35  
     /**
 36  
      * Required for serialization support. Lang version 2.0.
 37  
      *
 38  
      * @see java.io.Serializable
 39  
      */
 40  
     private static final long serialVersionUID = 8270183163158333422L;
 41  
 
 42  
     /** The first character, inclusive, in the range. */
 43  
     private final char start;
 44  
     /** The last character, inclusive, in the range. */
 45  
     private final char end;
 46  
     /** True if the range is everything except the characters specified. */
 47  
     private final boolean negated;
 48  
 
 49  
     /** Cached toString. */
 50  
     private transient String iToString;
 51  
 
 52  
     /**
 53  
      * <p>Constructs a {@code CharRange} over a set of characters,
 54  
      * optionally negating the range.</p>
 55  
      *
 56  
      * <p>A negated range includes everything except that defined by the
 57  
      * start and end characters.</p>
 58  
      *
 59  
      * <p>If start and end are in the wrong order, they are reversed.
 60  
      * Thus {@code a-e} is the same as {@code e-a}.</p>
 61  
      *
 62  
      * @param start  first character, inclusive, in this range
 63  
      * @param end  last character, inclusive, in this range
 64  
      * @param negated  true to express everything except the range
 65  
      */
 66  
     private CharRange(char start, char end, final boolean negated) {
 67  303
         super();
 68  303
         if (start > end) {
 69  19
             final char temp = start;
 70  19
             start = end;
 71  19
             end = temp;
 72  
         }
 73  
 
 74  303
         this.start = start;
 75  303
         this.end = end;
 76  303
         this.negated = negated;
 77  303
     }
 78  
 
 79  
     /**
 80  
      * <p>Constructs a {@code CharRange} over a single character.</p>
 81  
      *
 82  
      * @param ch  only character in this range
 83  
      * @return the new CharRange object
 84  
      * @see CharRange#CharRange(char, char, boolean)
 85  
      * @since 2.5
 86  
      */
 87  
     public static CharRange is(final char ch) {
 88  145
         return new CharRange(ch, ch, false);
 89  
     }
 90  
 
 91  
     /**
 92  
      * <p>Constructs a negated {@code CharRange} over a single character.</p>
 93  
      *
 94  
      * @param ch  only character in this range
 95  
      * @return the new CharRange object
 96  
      * @see CharRange#CharRange(char, char, boolean)
 97  
      * @since 2.5
 98  
      */
 99  
     public static CharRange isNot(final char ch) {
 100  32
         return new CharRange(ch, ch, true);
 101  
     }
 102  
 
 103  
     /**
 104  
      * <p>Constructs a {@code CharRange} over a set of characters.</p>
 105  
      *
 106  
      * @param start  first character, inclusive, in this range
 107  
      * @param end  last character, inclusive, in this range
 108  
      * @return the new CharRange object
 109  
      * @see CharRange#CharRange(char, char, boolean)
 110  
      * @since 2.5
 111  
      */
 112  
     public static CharRange isIn(final char start, final char end) {
 113  90
         return new CharRange(start, end, false);
 114  
     }
 115  
 
 116  
     /**
 117  
      * <p>Constructs a negated {@code CharRange} over a set of characters.</p>
 118  
      *
 119  
      * @param start  first character, inclusive, in this range
 120  
      * @param end  last character, inclusive, in this range
 121  
      * @return the new CharRange object
 122  
      * @see CharRange#CharRange(char, char, boolean)
 123  
      * @since 2.5
 124  
      */
 125  
     public static CharRange isNotIn(final char start, final char end) {
 126  36
         return new CharRange(start, end, true);
 127  
     }
 128  
 
 129  
     // Accessors
 130  
     //-----------------------------------------------------------------------
 131  
     /**
 132  
      * <p>Gets the start character for this character range.</p>
 133  
      *
 134  
      * @return the start char (inclusive)
 135  
      */
 136  
     public char getStart() {
 137  8
         return this.start;
 138  
     }
 139  
 
 140  
     /**
 141  
      * <p>Gets the end character for this character range.</p>
 142  
      *
 143  
      * @return the end char (inclusive)
 144  
      */
 145  
     public char getEnd() {
 146  8
         return this.end;
 147  
     }
 148  
 
 149  
     /**
 150  
      * <p>Is this {@code CharRange} negated.</p>
 151  
      *
 152  
      * <p>A negated range includes everything except that defined by the
 153  
      * start and end characters.</p>
 154  
      *
 155  
      * @return {@code true} if negated
 156  
      */
 157  
     public boolean isNegated() {
 158  22
         return negated;
 159  
     }
 160  
 
 161  
     // Contains
 162  
     //-----------------------------------------------------------------------
 163  
     /**
 164  
      * <p>Is the character specified contained in this range.</p>
 165  
      *
 166  
      * @param ch  the character to check
 167  
      * @return {@code true} if this range contains the input character
 168  
      */
 169  
     public boolean contains(final char ch) {
 170  305
         return (ch >= start && ch <= end) != negated;
 171  
     }
 172  
 
 173  
     /**
 174  
      * <p>Are all the characters of the passed in range contained in
 175  
      * this range.</p>
 176  
      *
 177  
      * @param range  the range to check against
 178  
      * @return {@code true} if this range entirely contains the input range
 179  
      * @throws IllegalArgumentException if {@code null} input
 180  
      */
 181  
     public boolean contains(final CharRange range) {
 182  70
         Validate.isTrue(range != null, "The Range must not be null");
 183  69
         if (negated) {
 184  44
             if (range.negated) {
 185  17
                 return start >= range.start && end <= range.end;
 186  
             }
 187  27
             return range.end < start || range.start > end;
 188  
         }
 189  25
         if (range.negated) {
 190  6
             return start == 0 && end == Character.MAX_VALUE;
 191  
         }
 192  19
         return start <= range.start && end >= range.end;
 193  
     }
 194  
 
 195  
     // Basics
 196  
     //-----------------------------------------------------------------------
 197  
     /**
 198  
      * <p>Compares two CharRange objects, returning true if they represent
 199  
      * exactly the same range of characters defined in the same way.</p>
 200  
      *
 201  
      * @param obj  the object to compare to
 202  
      * @return true if equal
 203  
      */
 204  
     @Override
 205  
     public boolean equals(final Object obj) {
 206  134
         if (obj == this) {
 207  3
             return true;
 208  
         }
 209  131
         if (obj instanceof CharRange == false) {
 210  1
             return false;
 211  
         }
 212  130
         final CharRange other = (CharRange) obj;
 213  130
         return start == other.start && end == other.end && negated == other.negated;
 214  
     }
 215  
 
 216  
     /**
 217  
      * <p>Gets a hashCode compatible with the equals method.</p>
 218  
      *
 219  
      * @return a suitable hashCode
 220  
      */
 221  
     @Override
 222  
     public int hashCode() {
 223  239
         return 83 + start + 7 * end + (negated ? 1 : 0);
 224  
     }
 225  
 
 226  
     /**
 227  
      * <p>Gets a string representation of the character range.</p>
 228  
      *
 229  
      * @return string representation of this range
 230  
      */
 231  
     @Override
 232  
     public String toString() {
 233  18
         if (iToString == null) {
 234  14
             final StringBuilder buf = new StringBuilder(4);
 235  14
             if (isNegated()) {
 236  6
                 buf.append('^');
 237  
             }
 238  14
             buf.append(start);
 239  14
             if (start != end) {
 240  8
                 buf.append('-');
 241  8
                 buf.append(end);
 242  
             }
 243  14
             iToString = buf.toString();
 244  
         }
 245  18
         return iToString;
 246  
     }
 247  
 
 248  
     // Expansions
 249  
     //-----------------------------------------------------------------------
 250  
     /**
 251  
      * <p>Returns an iterator which can be used to walk through the characters described by this range.</p>
 252  
      *
 253  
      * <p>#NotThreadSafe# the iterator is not thread-safe</p>
 254  
      * @return an iterator to the chars represented by this range
 255  
      * @since 2.5
 256  
      */
 257  
     @Override
 258  
     public Iterator<Character> iterator() {
 259  7
         return new CharacterIterator(this);
 260  
     }
 261  
 
 262  
     /**
 263  
      * Character {@link Iterator}.
 264  
      * <p>#NotThreadSafe#</p>
 265  
      */
 266  65552
     private static class CharacterIterator implements Iterator<Character> {
 267  
         /** The current character */
 268  
         private char current;
 269  
 
 270  
         private final CharRange range;
 271  
         private boolean hasNext;
 272  
 
 273  
         /**
 274  
          * Construct a new iterator for the character range.
 275  
          *
 276  
          * @param r The character range
 277  
          */
 278  7
         private CharacterIterator(final CharRange r) {
 279  7
             range = r;
 280  7
             hasNext = true;
 281  
 
 282  7
             if (range.negated) {
 283  4
                 if (range.start == 0) {
 284  2
                     if (range.end == Character.MAX_VALUE) {
 285  
                         // This range is an empty set
 286  1
                         hasNext = false;
 287  
                     } else {
 288  1
                         current = (char) (range.end + 1);
 289  
                     }
 290  
                 } else {
 291  2
                     current = 0;
 292  
                 }
 293  
             } else {
 294  3
                 current = range.start;
 295  
             }
 296  7
         }
 297  
 
 298  
         /**
 299  
          * Prepare the next character in the range.
 300  
          */
 301  
         private void prepareNext() {
 302  65542
             if (range.negated) {
 303  65537
                 if (current == Character.MAX_VALUE) {
 304  2
                     hasNext = false;
 305  65535
                 } else if (current + 1 == range.start) {
 306  2
                     if (range.end == Character.MAX_VALUE) {
 307  1
                         hasNext = false;
 308  
                     } else {
 309  1
                         current = (char) (range.end + 1);
 310  
                     }
 311  
                 } else {
 312  65533
                     current = (char) (current + 1);
 313  
                 }
 314  5
             } else if (current < range.end) {
 315  3
                 current = (char) (current + 1);
 316  
             } else {
 317  2
                 hasNext = false;
 318  
             }
 319  65542
         }
 320  
 
 321  
         /**
 322  
          * Has the iterator not reached the end character yet?
 323  
          *
 324  
          * @return {@code true} if the iterator has yet to reach the character date
 325  
          */
 326  
         @Override
 327  
         public boolean hasNext() {
 328  65546
             return hasNext;
 329  
         }
 330  
 
 331  
         /**
 332  
          * Return the next character in the iteration
 333  
          *
 334  
          * @return {@code Character} for the next character
 335  
          */
 336  
         @Override
 337  
         public Character next() {
 338  65545
             if (hasNext == false) {
 339  3
                 throw new NoSuchElementException();
 340  
             }
 341  65542
             final char cur = current;
 342  65542
             prepareNext();
 343  65542
             return Character.valueOf(cur);
 344  
         }
 345  
 
 346  
         /**
 347  
          * Always throws UnsupportedOperationException.
 348  
          *
 349  
          * @throws UnsupportedOperationException
 350  
          * @see java.util.Iterator#remove()
 351  
          */
 352  
         @Override
 353  
         public void remove() {
 354  1
             throw new UnsupportedOperationException();
 355  
         }
 356  
     }
 357  
 }