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