View Javadoc
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 }