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 }