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