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