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 18 package org.apache.commons.text.matcher; 19 20 import java.util.Arrays; 21 22 /** 23 * A matcher that determines if a character array portion matches. 24 * <p> 25 * Thread=safe. 26 * </p> 27 * 28 * @since 1.3 29 */ 30 abstract class AbstractStringMatcher implements StringMatcher { 31 32 /** 33 * Matches all of the given matchers in order. 34 * 35 * @since 1.9 36 */ 37 static final class AndStringMatcher extends AbstractStringMatcher { 38 39 /** 40 * Matchers in order. 41 */ 42 private final StringMatcher[] stringMatchers; 43 44 /** 45 * Constructs a new initialized instance. 46 * 47 * @param stringMatchers Matchers in order. Never null since the {@link StringMatcherFactory} uses the 48 * {@link NoneMatcher} instead. 49 */ 50 AndStringMatcher(final StringMatcher... stringMatchers) { 51 this.stringMatchers = stringMatchers.clone(); 52 } 53 54 @Override 55 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 56 int total = 0; 57 int curStart = start; 58 for (final StringMatcher stringMatcher : stringMatchers) { 59 if (stringMatcher != null) { 60 final int len = stringMatcher.isMatch(buffer, curStart, bufferStart, bufferEnd); 61 if (len == 0) { 62 return 0; 63 } 64 total += len; 65 curStart += len; 66 } 67 } 68 return total; 69 } 70 71 @Override 72 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 73 int total = 0; 74 int curStart = start; 75 for (final StringMatcher stringMatcher : stringMatchers) { 76 if (stringMatcher != null) { 77 final int len = stringMatcher.isMatch(buffer, curStart, bufferStart, bufferEnd); 78 if (len == 0) { 79 return 0; 80 } 81 total += len; 82 curStart += len; 83 } 84 } 85 return total; 86 } 87 88 @Override 89 public int size() { 90 int total = 0; 91 for (final StringMatcher stringMatcher : stringMatchers) { 92 if (stringMatcher != null) { 93 total += stringMatcher.size(); 94 } 95 } 96 return total; 97 } 98 } 99 100 /** 101 * Matches out of a set of characters. 102 * <p> 103 * Thread=safe. 104 * </p> 105 */ 106 static final class CharArrayMatcher extends AbstractStringMatcher { 107 108 /** The string to match, as a character array, implementation treats as immutable. */ 109 private final char[] chars; 110 111 /** The string to match. */ 112 private final String string; 113 114 /** 115 * Constructs a matcher from a String. 116 * 117 * @param chars the string to match, must not be null 118 */ 119 CharArrayMatcher(final char... chars) { 120 this.string = String.valueOf(chars); 121 this.chars = chars.clone(); 122 } 123 124 /** 125 * Returns the number of matching characters, {@code 0} if there is no match. 126 * 127 * @param buffer the text content to match against, do not change 128 * @param start the starting position for the match, valid for buffer 129 * @param bufferStart unused 130 * @param bufferEnd the end index of the active buffer, valid for buffer 131 * @return The number of matching characters, zero for no match 132 */ 133 @Override 134 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 135 final int len = size(); 136 if (start + len > bufferEnd) { 137 return 0; 138 } 139 int j = start; 140 for (int i = 0; i < len; i++, j++) { 141 if (chars[i] != buffer[j]) { 142 return 0; 143 } 144 } 145 return len; 146 } 147 148 /** 149 * Returns the number of matching characters, {@code 0} if there is no match. 150 * 151 * @param buffer the text content to match against, do not change 152 * @param start the starting position for the match, valid for buffer 153 * @param bufferStart unused 154 * @param bufferEnd the end index of the active buffer, valid for buffer 155 * @return The number of matching characters, zero for no match 156 */ 157 @Override 158 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 159 final int len = size(); 160 if (start + len > bufferEnd) { 161 return 0; 162 } 163 int j = start; 164 for (int i = 0; i < len; i++, j++) { 165 if (chars[i] != buffer.charAt(j)) { 166 return 0; 167 } 168 } 169 return len; 170 } 171 172 /** 173 * Returns the size of the string to match given in the constructor. 174 * 175 * @since 1.9 176 */ 177 @Override 178 public int size() { 179 return chars.length; 180 } 181 182 @Override 183 public String toString() { 184 return super.toString() + "[\"" + string + "\"]"; 185 } 186 187 } 188 189 /** 190 * Matches a character. 191 * <p> 192 * Thread=safe. 193 * </p> 194 */ 195 static final class CharMatcher extends AbstractStringMatcher { 196 197 /** The character to match. */ 198 private final char ch; 199 200 /** 201 * Constructs a matcher for a single character. 202 * 203 * @param ch the character to match 204 */ 205 CharMatcher(final char ch) { 206 this.ch = ch; 207 } 208 209 /** 210 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 211 * 212 * @param buffer the text content to match against, do not change 213 * @param start the starting position for the match, valid for buffer 214 * @param bufferStart unused 215 * @param bufferEnd unused 216 * @return The number of matching characters, zero for no match 217 */ 218 @Override 219 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 220 return ch == buffer[start] ? 1 : 0; 221 } 222 223 /** 224 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 225 * 226 * @param buffer the text content to match against, do not change 227 * @param start the starting position for the match, valid for buffer 228 * @param bufferStart unused 229 * @param bufferEnd unused 230 * @return The number of matching characters, zero for no match 231 */ 232 @Override 233 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 234 return ch == buffer.charAt(start) ? 1 : 0; 235 } 236 237 /** 238 * Returns 1. 239 * 240 * @since 1.9 241 */ 242 @Override 243 public int size() { 244 return 1; 245 } 246 247 @Override 248 public String toString() { 249 return super.toString() + "['" + ch + "']"; 250 } 251 } 252 253 /** 254 * Matches a set of characters. 255 * <p> 256 * Thread=safe. 257 * </p> 258 */ 259 static final class CharSetMatcher extends AbstractStringMatcher { 260 261 /** The set of characters to match. */ 262 private final char[] chars; 263 264 /** 265 * Constructs a matcher from a character array. 266 * 267 * @param chars the characters to match, must not be null 268 */ 269 CharSetMatcher(final char[] chars) { 270 this.chars = chars.clone(); 271 Arrays.sort(this.chars); 272 } 273 274 /** 275 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 276 * 277 * @param buffer the text content to match against, do not change 278 * @param start the starting position for the match, valid for buffer 279 * @param bufferStart unused 280 * @param bufferEnd unused 281 * @return The number of matching characters, zero for no match 282 */ 283 @Override 284 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 285 return Arrays.binarySearch(chars, buffer[start]) >= 0 ? 1 : 0; 286 } 287 288 /** 289 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 290 * 291 * @param buffer the text content to match against, do not change 292 * @param start the starting position for the match, valid for buffer 293 * @param bufferStart unused 294 * @param bufferEnd unused 295 * @return The number of matching characters, zero for no match 296 */ 297 @Override 298 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 299 return Arrays.binarySearch(chars, buffer.charAt(start)) >= 0 ? 1 : 0; 300 } 301 302 /** 303 * Returns 1. 304 * 305 * @since 1.9 306 */ 307 @Override 308 public int size() { 309 return 1; 310 } 311 312 @Override 313 public String toString() { 314 return super.toString() + Arrays.toString(chars); 315 } 316 317 } 318 319 /** 320 * Matches nothing. 321 * <p> 322 * Thread=safe. 323 * </p> 324 */ 325 static final class NoneMatcher extends AbstractStringMatcher { 326 327 /** 328 * Constructs a new instance of {@code NoMatcher}. 329 */ 330 NoneMatcher() { 331 } 332 333 /** 334 * Always returns {@code 0}. 335 * 336 * @param buffer unused 337 * @param start unused 338 * @param bufferStart unused 339 * @param bufferEnd unused 340 * @return The number of matching characters, zero for no match 341 */ 342 @Override 343 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 344 return 0; 345 } 346 347 /** 348 * Always returns {@code 0}. 349 * 350 * @param buffer unused 351 * @param start unused 352 * @param bufferStart unused 353 * @param bufferEnd unused 354 * @return The number of matching characters, zero for no match 355 */ 356 @Override 357 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 358 return 0; 359 } 360 361 /** 362 * Returns 0. 363 * 364 * @since 1.9 365 */ 366 @Override 367 public int size() { 368 return 0; 369 } 370 371 } 372 373 /** 374 * Matches whitespace as per trim(). 375 * <p> 376 * Thread=safe. 377 * </p> 378 */ 379 static final class TrimMatcher extends AbstractStringMatcher { 380 381 /** 382 * The space character. 383 */ 384 private static final int SPACE_INT = 32; 385 386 /** 387 * Constructs a new instance of {@code TrimMatcher}. 388 */ 389 TrimMatcher() { 390 } 391 392 /** 393 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 394 * 395 * @param buffer the text content to match against, do not change 396 * @param start the starting position for the match, valid for buffer 397 * @param bufferStart unused 398 * @param bufferEnd unused 399 * @return The number of matching characters, zero for no match 400 */ 401 @Override 402 public int isMatch(final char[] buffer, final int start, final int bufferStart, final int bufferEnd) { 403 return buffer[start] <= SPACE_INT ? 1 : 0; 404 } 405 406 /** 407 * Returns {@code 1} if there is a match, or {@code 0} if there is no match. 408 * 409 * @param buffer the text content to match against, do not change 410 * @param start the starting position for the match, valid for buffer 411 * @param bufferStart unused 412 * @param bufferEnd unused 413 * @return The number of matching characters, zero for no match 414 */ 415 @Override 416 public int isMatch(final CharSequence buffer, final int start, final int bufferStart, final int bufferEnd) { 417 return buffer.charAt(start) <= SPACE_INT ? 1 : 0; 418 } 419 420 /** 421 * Returns 1. 422 * 423 * @since 1.9 424 */ 425 @Override 426 public int size() { 427 return 1; 428 } 429 } 430 431 /** 432 * Constructs a new instance. 433 */ 434 protected AbstractStringMatcher() { 435 } 436 437 // /** 438 // * Validates indices for {@code bufferStart <= start < bufferEnd}. 439 // * 440 // * @param start the starting position for the match, valid in {@code buffer}. 441 // * @param bufferStart the first active index in the buffer, valid in {@code buffer}. 442 // * @param bufferEnd the end index (exclusive) of the active buffer, valid in {@code buffer}. 443 // */ 444 // void validate(final int start, final int bufferStart, final int bufferEnd) { 445 // if (((bufferStart > start) || (start >= bufferEnd))) { 446 // throw new IndexOutOfBoundsException( 447 // String.format("bufferStart(%,d) <= start(%,d) < bufferEnd(%,d)", bufferStart, start, bufferEnd)); 448 // } 449 // } 450 451 }