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 * Constructor.
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 }
452