class PathMatcher implements Matcher {
    private final Set<String> includedPaths = new HashSet<>();
    @Getter
    private final Set<String> excludedPaths = new HashSet<>();
    @Getter
    private final Set<Pattern> excludedPatterns = new HashSet<>();

    private static boolean warnedRegexp;
    private static boolean warnedInclude;

    /**
     * <p>Constructor for PathMatcher.</p>
     */
    public PathMatcher() {}

    /**
     * Any path exactly matching this string will be excluded. Use this method if you are excluding a specific path.
     *
     * @param path the path to be excluded
     * @return this path matcher
     */
    public PathMatcher excludePath(final String path) {
        validatePath(path);
        excludedPaths.add(path);
        return this;
    }

    /**
     * <p>excludePaths.</p>
     *
     * @param paths a {@link String} object
     * @return a {@link PathMatcher} object
     */
    public PathMatcher excludePaths(final String... paths) {
        if (paths != null && paths.length > 0) {
            for (val path : paths) {
                excludePath(path);
            }
        }
        return this;
    }

    /**
     * <p>includePath.</p>
     *
     * @param path a {@link String} object
     * @return a {@link PathMatcher} object
     */
    public PathMatcher includePath(final String path) {
        warnInclude();
        validatePath(path);
        includedPaths.add(path);
        return this;
    }

    /**
     * <p>includePaths.</p>
     *
     * @param paths a {@link String} object
     * @return a {@link PathMatcher} object
     */
    public PathMatcher includePaths(final String... paths) {
        if (paths != null && paths.length > 0) {
            for (val path : paths) {
                includePath(path);
            }
        }
        return this;
    }

    /**
     * Convenience method for excluding all paths starting with a prefix e.g. "/foo" would exclude "/foo", "/foo/bar", etc.
     *
     * @param path the prefix for the paths to be excluded
     * @return this path matcher
     */
    public PathMatcher excludeBranch(final String path) {
        warnRegexp();
        validatePath(path);
        excludedPatterns.add(Pattern.compile("^" + path + "(/.*)?$"));
        return this;
    }

    /**
     * Any path matching this regex will be excluded.
     *
     * @param regex the regular expression matching the paths to be excluded
     * @return this path matcher
     */
    public PathMatcher excludeRegex(final String regex) {
        warnRegexp();
        CommonHelper.assertNotBlank("regex", regex);

        if (!regex.startsWith("^") || !regex.endsWith("$")) {
            throw new TechnicalException("Your regular expression: '" + regex + "' must start with a ^ and end with a $ " +
                "to define a full path matching");
        }

        excludedPatterns.add(Pattern.compile(regex));
        return this;
    }

    /**
     * <p>warnRegexp.</p>
     */
    protected void warnRegexp() {<FILL_FUNCTION_BODY>}

    /**
     * <p>warnInclude.</p>
     */
    protected void warnInclude() {
        if (!warnedInclude) {
            LOGGER.warn("Be careful when using the 'includePath' or 'includePaths' methods. "
                + "The security will only apply on these paths. It could not be secure enough.");
            warnedInclude = true;
        }
    }

    /** {@inheritDoc} */
    @Override
    public boolean matches(final CallContext ctx) {
        return matches(ctx.webContext().getPath());
    }

    // Returns true if a path should be authenticated, false to skip authentication.
    boolean matches(final String requestPath) {

        LOGGER.debug("request path to match: {}", requestPath);

        if (!includedPaths.isEmpty()) {
            for (val path : includedPaths) {
                // accepts any request path starting with the included path
                if (requestPath != null && requestPath.startsWith(path)) {
                    return true;
                }
            }

            return false;
        }

        // just exclude the exact matching request path
        if (excludedPaths.contains(requestPath)) {
            return false;
        }

        for (val pattern : excludedPatterns) {
            if (pattern.matcher(requestPath).matches()) {
                return false;
            }
        }

        return true;
    }

    /**
     * <p>Setter for the field <code>excludedPaths</code>.</p>
     *
     * @param paths a {@link java.util.Collection} object
     */
    public void setExcludedPaths(Iterable<String> paths) {
        excludedPaths.clear();
        paths.forEach(this::excludePath);
    }

    /**
     * <p>Setter for the field <code>excludedPatterns</code>.</p>
     *
     * @param regularExpressions a {@link java.util.Collection} object
     */
    public void setExcludedPatterns(Iterable<String> regularExpressions) {
        excludedPatterns.clear();
        regularExpressions.forEach(this::excludeRegex);
    }

    /**
     * <p>setExcludedPath.</p>
     *
     * @param path a {@link String} object
     */
    public void setExcludedPath(final String path) {
        excludedPaths.clear();
        excludePath(path);
    }

    /**
     * <p>setExcludedPattern.</p>
     *
     * @param regularExpression a {@link String} object
     */
    public void setExcludedPattern(final String regularExpression) {
        excludedPatterns.clear();
        excludeRegex(regularExpression);
    }

    private static void validatePath(String path) {
        CommonHelper.assertNotBlank("path", path);
        if (!path.startsWith("/")) {
            throw new TechnicalException("Excluded path must begin with a /");
        }
    }
}

        if (!warnedRegexp) {
            LOGGER.warn("Be careful when using the 'excludeBranch' or 'excludeRegex' methods. "
                + "They use regular expressions and their definitions may be error prone. You could exclude more URLs than expected.");
            warnedRegexp = true;
        }
    
<no_super_class>