001package org.apache.commons.digester3;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025/**
026 * <p>
027 * Simple regex pattern matching algorithm.
028 * </p>
029 * <p>
030 * This uses just two wildcards:
031 * <ul>
032 * <li><code>*</code> matches any sequence of none, one or more characters
033 * <li><code>?</code> matches any one character
034 * </ul>
035 * Escaping these wildcards is not supported .
036 * </p>
037 * 
038 * @since 1.5
039 */
040public class SimpleRegexMatcher
041    extends RegexMatcher
042{
043
044    // --------------------------------------------------------- Fields
045
046    /** Default log (class wide) */
047    private static final Log BASE_LOG = LogFactory.getLog( SimpleRegexMatcher.class );
048
049    /** Custom log (can be set per object) */
050    private Log log = BASE_LOG;
051
052    // --------------------------------------------------------- Properties
053
054    /**
055     * Gets the <code>Log</code> implementation.
056     *
057     * @return the <code>Log</code> implementation.
058     */
059    public Log getLog()
060    {
061        return log;
062    }
063
064    /**
065     * Sets the current <code>Log</code> implementation used by this class.
066     *
067     * @param log the current <code>Log</code> implementation used by this class.
068     */
069    public void setLog( Log log )
070    {
071        this.log = log;
072    }
073
074    // --------------------------------------------------------- Public Methods
075
076    /**
077     * {@inheritDoc}
078     */
079    @Override
080    public boolean match( String basePattern, String regexPattern )
081    {
082        // check for nulls
083        if ( basePattern == null || regexPattern == null )
084        {
085            return false;
086        }
087        return match( basePattern, regexPattern, 0, 0 );
088    }
089
090    // --------------------------------------------------------- Implementations Methods
091
092    /**
093     * Implementation of regex matching algorithm. This calls itself recursively.
094     *
095     * @param basePattern the standard digester path representing the element
096     * @param regexPattern the regex pattern the path will be tested against
097     * @param baseAt FIXME
098     * @param regexAt FIXME
099     */
100    private boolean match( String basePattern, String regexPattern, int baseAt, int regexAt )
101    {
102        if ( log.isTraceEnabled() )
103        {
104            log.trace( "Base: " + basePattern );
105            log.trace( "Regex: " + regexPattern );
106            log.trace( "Base@" + baseAt );
107            log.trace( "Regex@" + regexAt );
108        }
109
110        // check bounds
111        if ( regexAt >= regexPattern.length() )
112        {
113            // maybe we've got a match
114            if ( baseAt >= basePattern.length() )
115            {
116                // ok!
117                return true;
118            }
119            // run out early
120            return false;
121
122        }
123        if ( baseAt >= basePattern.length() )
124        {
125            // run out early
126            return false;
127        }
128
129        // ok both within bounds
130        char regexCurrent = regexPattern.charAt( regexAt );
131        switch ( regexCurrent )
132        {
133            case '*':
134                // this is the tricky case
135                // check for terminal
136                if ( ++regexAt >= regexPattern.length() )
137                {
138                    // this matches anything let - so return true
139                    return true;
140                }
141                // go through every subsequent apperance of the next character
142                // and so if the rest of the regex matches
143                char nextRegex = regexPattern.charAt( regexAt );
144                if ( log.isTraceEnabled() )
145                {
146                    log.trace( "Searching for next '" + nextRegex + "' char" );
147                }
148                int nextMatch = basePattern.indexOf( nextRegex, baseAt );
149                while ( nextMatch != -1 )
150                {
151                    if ( log.isTraceEnabled() )
152                    {
153                        log.trace( "Trying '*' match@" + nextMatch );
154                    }
155                    if ( match( basePattern, regexPattern, nextMatch, regexAt ) )
156                    {
157                        return true;
158                    }
159                    nextMatch = basePattern.indexOf( nextRegex, nextMatch + 1 );
160                }
161                log.trace( "No matches found." );
162                return false;
163
164            case '?':
165                // this matches anything
166                return match( basePattern, regexPattern, ++baseAt, ++regexAt );
167
168            default:
169                if ( log.isTraceEnabled() )
170                {
171                    log.trace( "Camparing " + regexCurrent + " to " + basePattern.charAt( baseAt ) );
172                }
173                if ( regexCurrent == basePattern.charAt( baseAt ) )
174                {
175                    // still got more to go
176                    return match( basePattern, regexPattern, ++baseAt, ++regexAt );
177                }
178                return false;
179        }
180    }
181
182}