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 */
017package org.apache.logging.log4j.core.lookup;
018
019import java.util.Arrays;
020
021import org.apache.logging.log4j.util.Chars;
022import org.apache.logging.log4j.util.Strings;
023
024/**
025 * A matcher class that can be queried to determine if a character array
026 * portion matches.
027 * <p>
028 * This class comes complete with various factory methods.
029 * If these do not suffice, you can subclass and implement your own matcher.
030 */
031public abstract class StrMatcher {
032
033    /**
034     * Matches the comma character.
035     */
036    private static final StrMatcher COMMA_MATCHER = new CharMatcher(',');
037    /**
038     * Matches the tab character.
039     */
040    private static final StrMatcher TAB_MATCHER = new CharMatcher(Chars.TAB);
041    /**
042     * Matches the space character.
043     */
044    private static final StrMatcher SPACE_MATCHER = new CharMatcher(Chars.SPACE);
045    /**
046     * Matches the same characters as StringTokenizer,
047     * namely space, tab, newline, formfeed.
048     */
049    private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray());
050    /**
051     * Matches the String trim() whitespace characters.
052     */
053    private static final StrMatcher TRIM_MATCHER = new TrimMatcher();
054    /**
055     * Matches the double quote character.
056     */
057    private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher(Chars.QUOTE);
058    /**
059     * Matches the double quote character.
060     */
061    private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher(Chars.DQUOTE);
062    /**
063     * Matches the single or double quote character.
064     */
065    private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray());
066    /**
067     * Matches no characters.
068     */
069    private static final StrMatcher NONE_MATCHER = new NoMatcher();
070
071    /**
072     * Constructor.
073     */
074    protected StrMatcher() {
075    }
076
077    /**
078     * Returns a matcher which matches the comma character.
079     *
080     * @return a matcher for a comma
081     */
082    public static StrMatcher commaMatcher() {
083        return COMMA_MATCHER;
084    }
085
086    /**
087     * Returns a matcher which matches the tab character.
088     *
089     * @return a matcher for a tab
090     */
091    public static StrMatcher tabMatcher() {
092        return TAB_MATCHER;
093    }
094
095    /**
096     * Returns a matcher which matches the space character.
097     *
098     * @return a matcher for a space
099     */
100    public static StrMatcher spaceMatcher() {
101        return SPACE_MATCHER;
102    }
103
104    /**
105     * Matches the same characters as StringTokenizer,
106     * namely space, tab, newline and formfeed.
107     *
108     * @return the split matcher
109     */
110    public static StrMatcher splitMatcher() {
111        return SPLIT_MATCHER;
112    }
113
114    /**
115     * Matches the String trim() whitespace characters.
116     *
117     * @return the trim matcher
118     */
119    public static StrMatcher trimMatcher() {
120        return TRIM_MATCHER;
121    }
122
123    /**
124     * Returns a matcher which matches the single quote character.
125     *
126     * @return a matcher for a single quote
127     */
128    public static StrMatcher singleQuoteMatcher() {
129        return SINGLE_QUOTE_MATCHER;
130    }
131
132    /**
133     * Returns a matcher which matches the double quote character.
134     *
135     * @return a matcher for a double quote
136     */
137    public static StrMatcher doubleQuoteMatcher() {
138        return DOUBLE_QUOTE_MATCHER;
139    }
140
141    /**
142     * Returns a matcher which matches the single or double quote character.
143     *
144     * @return a matcher for a single or double quote
145     */
146    public static StrMatcher quoteMatcher() {
147        return QUOTE_MATCHER;
148    }
149
150    /**
151     * Matches no characters.
152     *
153     * @return a matcher that matches nothing
154     */
155    public static StrMatcher noneMatcher() {
156        return NONE_MATCHER;
157    }
158
159    /**
160     * Constructor that creates a matcher from a character.
161     *
162     * @param ch  the character to match, must not be null
163     * @return a new Matcher for the given char
164     */
165    public static StrMatcher charMatcher(final char ch) {
166        return new CharMatcher(ch);
167    }
168
169    /**
170     * Constructor that creates a matcher from a set of characters.
171     *
172     * @param chars  the characters to match, null or empty matches nothing
173     * @return a new matcher for the given char[]
174     */
175    public static StrMatcher charSetMatcher(final char[] chars) {
176        if (chars == null || chars.length == 0) {
177            return NONE_MATCHER;
178        }
179        if (chars.length == 1) {
180            return new CharMatcher(chars[0]);
181        }
182        return new CharSetMatcher(chars);
183    }
184
185    /**
186     * Constructor that creates a matcher from a string representing a set of characters.
187     *
188     * @param chars  the characters to match, null or empty matches nothing
189     * @return a new Matcher for the given characters
190     */
191    public static StrMatcher charSetMatcher(final String chars) {
192        if (Strings.isEmpty(chars)) {
193            return NONE_MATCHER;
194        }
195        if (chars.length() == 1) {
196            return new CharMatcher(chars.charAt(0));
197        }
198        return new CharSetMatcher(chars.toCharArray());
199    }
200
201    /**
202     * Constructor that creates a matcher from a string.
203     *
204     * @param str  the string to match, null or empty matches nothing
205     * @return a new Matcher for the given String
206     */
207    public static StrMatcher stringMatcher(final String str) {
208        if (Strings.isEmpty(str)) {
209            return NONE_MATCHER;
210        }
211        return new StringMatcher(str);
212    }
213
214    /**
215     * Returns the number of matching characters, zero for no match.
216     * <p>
217     * This method is called to check for a match.
218     * The parameter <code>pos</code> represents the current position to be
219     * checked in the string <code>buffer</code> (a character array which must
220     * not be changed).
221     * The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
222     * <p>
223     * The character array may be larger than the active area to be matched.
224     * Only values in the buffer between the specified indices may be accessed.
225     * <p>
226     * The matching code may check one character or many.
227     * It may check characters preceding <code>pos</code> as well as those
228     * after, so long as no checks exceed the bounds specified.
229     * <p>
230     * It must return zero for no match, or a positive number if a match was found.
231     * The number indicates the number of characters that matched.
232     *
233     * @param buffer  the text content to match against, do not change
234     * @param pos  the starting position for the match, valid for buffer
235     * @param bufferStart  the first active index in the buffer, valid for buffer
236     * @param bufferEnd  the end index (exclusive) of the active buffer, valid for buffer
237     * @return the number of matching characters, zero for no match
238     */
239    public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
240
241    /**
242     * Returns the number of matching characters, zero for no match.
243     * <p>
244     * This method is called to check for a match.
245     * The parameter <code>pos</code> represents the current position to be
246     * checked in the string <code>buffer</code> (a character array which must
247     * not be changed).
248     * The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
249     * <p>
250     * The matching code may check one character or many.
251     * It may check characters preceding <code>pos</code> as well as those after.
252     * <p>
253     * It must return zero for no match, or a positive number if a match was found.
254     * The number indicates the number of characters that matched.
255     *
256     * @param buffer  the text content to match against, do not change
257     * @param pos  the starting position for the match, valid for buffer
258     * @return the number of matching characters, zero for no match
259     * @since 2.4
260     */
261    public int isMatch(final char[] buffer, final int pos) {
262        return isMatch(buffer, pos, 0, buffer.length);
263    }
264
265    //-----------------------------------------------------------------------
266    /**
267     * Class used to define a set of characters for matching purposes.
268     */
269    static final class CharSetMatcher extends StrMatcher {
270        /** The set of characters to match. */
271        private final char[] chars;
272
273        /**
274         * Constructor that creates a matcher from a character array.
275         *
276         * @param chars  the characters to match, must not be null
277         */
278        CharSetMatcher(final char[] chars) {
279            super();
280            this.chars = chars.clone();
281            Arrays.sort(this.chars);
282        }
283
284        /**
285         * Returns whether or not the given character matches.
286         *
287         * @param buffer  the text content to match against, do not change
288         * @param pos  the starting position for the match, valid for buffer
289         * @param bufferStart  the first active index in the buffer, valid for buffer
290         * @param bufferEnd  the end index of the active buffer, valid for buffer
291         * @return the number of matching characters, zero for no match
292         */
293        @Override
294        public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
295            return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
296        }
297    }
298
299    //-----------------------------------------------------------------------
300    /**
301     * Class used to define a character for matching purposes.
302     */
303    static final class CharMatcher extends StrMatcher {
304        /** The character to match. */
305        private final char ch;
306
307        /**
308         * Constructor that creates a matcher that matches a single character.
309         *
310         * @param ch  the character to match
311         */
312        CharMatcher(final char ch) {
313            super();
314            this.ch = ch;
315        }
316
317        /**
318         * Returns whether or not the given character matches.
319         *
320         * @param buffer  the text content to match against, do not change
321         * @param pos  the starting position for the match, valid for buffer
322         * @param bufferStart  the first active index in the buffer, valid for buffer
323         * @param bufferEnd  the end index of the active buffer, valid for buffer
324         * @return the number of matching characters, zero for no match
325         */
326        @Override
327        public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
328            return ch == buffer[pos] ? 1 : 0;
329        }
330    }
331
332    //-----------------------------------------------------------------------
333    /**
334     * Class used to define a set of characters for matching purposes.
335     */
336    static final class StringMatcher extends StrMatcher {
337        /** The string to match, as a character array. */
338        private final char[] chars;
339
340        /**
341         * Constructor that creates a matcher from a String.
342         *
343         * @param str  the string to match, must not be null
344         */
345        StringMatcher(final String str) {
346            super();
347            chars = str.toCharArray();
348        }
349
350        /**
351         * Returns whether or not the given text matches the stored string.
352         *
353         * @param buffer  the text content to match against, do not change
354         * @param pos  the starting position for the match, valid for buffer
355         * @param bufferStart  the first active index in the buffer, valid for buffer
356         * @param bufferEnd  the end index of the active buffer, valid for buffer
357         * @return the number of matching characters, zero for no match
358         */
359        @Override
360        public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
361            final int len = chars.length;
362            if (pos + len > bufferEnd) {
363                return 0;
364            }
365            for (int i = 0; i < chars.length; i++, pos++) {
366                if (chars[i] != buffer[pos]) {
367                    return 0;
368                }
369            }
370            return len;
371        }
372    }
373
374    //-----------------------------------------------------------------------
375    /**
376     * Class used to match no characters.
377     */
378    static final class NoMatcher extends StrMatcher {
379
380        /**
381         * Constructs a new instance of <code>NoMatcher</code>.
382         */
383        NoMatcher() {
384            super();
385        }
386
387        /**
388         * Always returns {@code false}.
389         *
390         * @param buffer  the text content to match against, do not change
391         * @param pos  the starting position for the match, valid for buffer
392         * @param bufferStart  the first active index in the buffer, valid for buffer
393         * @param bufferEnd  the end index of the active buffer, valid for buffer
394         * @return the number of matching characters, zero for no match
395         */
396        @Override
397        public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
398            return 0;
399        }
400    }
401
402    //-----------------------------------------------------------------------
403    /**
404     * Class used to match whitespace as per trim().
405     */
406    static final class TrimMatcher extends StrMatcher {
407
408        /**
409         * Constructs a new instance of <code>TrimMatcher</code>.
410         */
411        TrimMatcher() {
412            super();
413        }
414
415        /**
416         * Returns whether or not the given character matches.
417         *
418         * @param buffer  the text content to match against, do not change
419         * @param pos  the starting position for the match, valid for buffer
420         * @param bufferStart  the first active index in the buffer, valid for buffer
421         * @param bufferEnd  the end index of the active buffer, valid for buffer
422         * @return the number of matching characters, zero for no match
423         */
424        @Override
425        public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
426            return buffer[pos] <= ' ' ? 1 : 0;
427        }
428    }
429
430}