/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.util.filter;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * A filter which uses a regular expression to match Strings. Invalid regular
 * expressions will match nothing.
 * <p>
 * Because regular expression matching is slow, and a common usage is to match
 * some sort of relative file path, the regular expression is checked to see if
 * it can be evaluated using much faster calls to
 * {@link String#endsWith(String)}.
 */
public class RegexStringFilter implements Filter<String> {
    /**
     * Matches regular expressions begin with an optional {@code ^}, then
     * {@code .*}, then a literal path, with an optional file extension, and
     * finally an optional {@code $} at the end. The {@code .} in the extension
     * may or may not be preceded by a {@code \} escape. The literal path
     * portion is determine by the absence of any of the following characters:
     * <code>\ [ ( . * ? + | { $</code>
     *
     * There are two capturing groups in the expression. The first is for the
     * literal path. The second is for the file extension, without the escaping.
     * The concatenation of these two captures creates the {@link String} which
     * can be used with {@link String#endsWith(String)}.
     *
     * For ease of reference, the non-Java escaped form of this pattern is:
     * <code>\^?\.\*([^\\\[\(\.\*\?\+\|\{\$]+)(?:\\?(\.\w+))?\$?</code>
     */
    private static final Pattern ENDS_WITH = Pattern
            .compile("\\^?\\.\\*([^\\\\\\[\\(\\.\\*\\?\\+\\|\\{\\$]+)(?:\\\\?(\\.\\w+))?\\$?");

    protected String regex;
    protected Pattern pattern;
    protected String endsWith;

    public RegexStringFilter(String regex) {
        this.regex = regex;
        optimize();
    }

    public String getRegex() {
        return this.regex;
    }

    public String getEndsWith() {
        return this.endsWith;
    }

    protected void optimize() {
        final Matcher matcher = ENDS_WITH.matcher(this.regex);
        if (matcher.matches()) {
            final String literalPath = matcher.group(1);
            final String fileExtension = matcher.group(2);
            if (fileExtension != null) {
                this.endsWith = literalPath + fileExtension;
            } else {
                this.endsWith = literalPath;
            }
        } else {
            try {
                this.pattern = Pattern.compile(this.regex);
            } catch (PatternSyntaxException ignored) {
                // If the regular expression is invalid, then pattern will be
                // null.
            }
        }
    }

    @Override
    public boolean filter(String obj) {
        if (this.endsWith != null) {
            return obj.endsWith(this.endsWith);
        } else if (this.pattern != null) {
            return this.pattern.matcher(obj).matches();
        } else {
            // The regular expression must have been bad, so it will match
            // nothing.
            return false;
        }
    }

    @Override
    public String toString() {
        return "matches " + this.regex;
    }
}
