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