/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.mayaa.impl.engine;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.cycle.Response;
import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.RequestScope;
import org.seasar.mayaa.engine.Engine;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.engine.error.ErrorHandler;
import org.seasar.mayaa.engine.processor.ProcessStatus;
import org.seasar.mayaa.engine.specification.NodeTreeWalker;
import org.seasar.mayaa.engine.specification.Specification;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.engine.specification.SpecificationImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.source.DelaySourceDescriptor;
import org.seasar.mayaa.impl.source.SourceUtil;
import org.seasar.mayaa.impl.util.DateFormatPool;
import org.seasar.mayaa.impl.util.IOUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.source.SourceDescriptor;

/**
 * @author Masataka Kurihara (Gluegent, Inc)
 */
public class EngineImpl extends SpecificationImpl
        implements Engine {

    private static final long serialVersionUID = 643873507172011552L;

    static final Log LOG = LogFactory.getLog(EngineImpl.class);

    public static final String PAGE_CLASS = "pageClass";
    public static final String TEMPLATE_CLASS = "templateClass";
    public static final String PAGE_SERIALIZE = "pageSerialize";
    public static final String SURVIVE_LIMIT = "surviveLimit";
    public static final String FORWARD_LIMIT = "forwardLimit";
    public static final String REQUESTED_SUFFIX_ENABLED = "requestedSuffixEnabled";
    public static final String DUMP_ENABLED = "dumpEnabled";
    public static final String CONVERT_CHARSET = "convertCharset";
    public static final String NO_CACHE_VALUE = "noCacheValue";
    private static final boolean DEFAULT_PAGE_SERIALIZE = true;

    private transient Specification _defaultSpecification;
    private transient ErrorHandler _errorHandler;
    private transient SpecificationCache _specCache;
    /** Engineが破棄されていればtrue。破棄後はサービスを保証しない。 */
    private volatile boolean _destroyed = false;

    // parameters
    private String _defaultSpecificationID = "/default.mayaa";
    private List _templatePathPatterns;
    private Class _pageClass = PageImpl.class;
    private Class _templateClass = TemplateImpl.class;
    private int _surviveLimit = 5;
    private boolean _requestedSuffixEnabled = false;
    private boolean _dumpEnabled = false;
    private int _forwardLimit = 10;
    private String _mayaaExtension = ".mayaa";
    private String _noCacheValue = "no-cache";

    // change on setParameter
    private String _mayaaExtensionName = "mayaa";
    private String _defaultPageName = "/default";
    private boolean _defaultIsMayaa = false;

    public EngineImpl() {
        setSpecificationSerialize(DEFAULT_PAGE_SERIALIZE);
    }

    public void setErrorHandler(ErrorHandler errorHandler) {
        _errorHandler = errorHandler;
    }

    public ErrorHandler getErrorHandler() {
        if (_errorHandler == null) {
            throw new IllegalStateException();
        }
        return _errorHandler;
    }

    public Specification findSpecificationFromCache(String systemID) {
        return getCache().get(systemID);
    }

    protected Page findPageFromCache(String pageName) {
        return (Page) findSpecificationFromCache(pageName + _mayaaExtension);
    }

    public Page getPage(String pageName) {
        if (_defaultIsMayaa && _defaultPageName.equals(pageName)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(StringUtil.getMessage(EngineImpl.class, 2));
            }
            RequestScope request = CycleUtil.getRequestScope();
            throw new PageNotFoundException(pageName, request.getExtension());
        }

        Page page = findPageFromCache(pageName);
        if (page == null) {
            page = createPageInstance(pageName);
        }
        return page;
    }

    public boolean isPageRequested() {
        RequestScope request = CycleUtil.getRequestScope();
        if (_mayaaExtensionName.equals(request.getExtension())) {
            return true;
        }

        return validPath(request.getRequestedPath(), request.getMimeType());
    }

    private boolean validPath(String path, String mimeType) {
        if (_templatePathPatterns != null) {
            for (Iterator it = _templatePathPatterns.iterator();
                    it.hasNext();) {
                PathPattern pattern = (PathPattern) it.next();
                if (pattern.matches(path)) {
                    return pattern.isTemplate();
                }
            }
        }
        return mimeType != null
            && (mimeType.indexOf("html") != -1 || mimeType.indexOf("xml") != -1);
    }

    protected Throwable removeWrapperRuntimeException(Throwable t) {
        Throwable throwable = t;
        while (throwable.getClass().equals(RuntimeException.class)) {
            if (throwable.getCause() == null) {
                break;
            }
            throwable = throwable.getCause();
        }
        return throwable;
    }

    public void handleError(Throwable thrown, boolean pageFlush) {
        Throwable throwable = removeWrapperRuntimeException(thrown);
        if (EngineUtil.isClientAbortException(throwable)) {
            // client abort は出力しようがないので無視
            return;
        }
        if (throwable instanceof CyclicForwardException) {
            // 循環forwardはそのまま投げる
            throw (CyclicForwardException) throwable;
        }
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        try {
            cycle.setHandledError(throwable);
            getErrorHandler().doErrorHandle(throwable, pageFlush);
        } catch (RenderingTerminated ignore) {
            // do nothing.
        } catch (PageForwarded pf) {
            // return page service
            checkCyclicForward(cycle.getRequestScope());
            doPageService(cycle, null, pageFlush);
        } catch (Throwable internalThrown) {
            Throwable internal = removeWrapperRuntimeException(internalThrown);
            if (internal instanceof PageNotFoundException) {
                if (LOG.isInfoEnabled()) {
                    LOG.info(throwable.getMessage());
                }
            } else {
                if (EngineUtil.isClientAbortException(internal)) {
                    // client abort は出力しようがないので無視
                    return;
                }
                if (LOG.isFatalEnabled()) {
                    String fatalMsg = StringUtil.getMessage(
                            EngineImpl.class, 0, internal.getMessage());
                    LOG.fatal(fatalMsg, internal);
                }
            }

            if (throwable instanceof RuntimeException) {
                throw (RuntimeException) throwable;
            }
            throw new RuntimeException(throwable);
        } finally {
            cycle.setHandledError(null);
        }
    }

    protected void checkCyclicForward(RequestScope request) {
        if (_forwardLimit <= 0) {
            return;
        }

        String key = CyclicForwardException.class.getName();
        Map pathMap = (Map) request.getAttribute(key);
        if (pathMap == null) {
            pathMap = new HashMap();
            request.setAttribute(key, pathMap);
        }

        String pageName = getRequestedPageName(request);
        Integer count = (Integer) pathMap.get(pageName);
        int countInt = (count != null) ? count.intValue() + 1 : 1;
        if (countInt > _forwardLimit) {
            throw new CyclicForwardException(pageName);
        }
        pathMap.put(pageName, new Integer(countInt));
    }

    protected String getRequestedPageName(RequestScope request) {
        StringBuffer sb = new StringBuffer();
        sb.append(request.getPageName());
        if (StringUtil.hasValue(request.getRequestedSuffix())) {
            sb.append(getSuffixSeparator());
            sb.append(request.getRequestedSuffix());
        }
        sb.append('.');
        sb.append(request.getExtension());
        return sb.toString();
    }

    protected void saveToCycle() {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        cycle.setOriginalNode(this);
        cycle.setInjectedNode(this);
    }

    public void build() {
        build(true);
    }

    public void build(boolean rebuild) {
        synchronized(this) {
            if (_defaultSpecification != null) {
                if (_defaultSpecification.isDeprecated() == false) {
                    return;
                }
                releaseDefaultSpecification();
            }
            if (StringUtil.hasValue(_defaultSpecificationID)) {
                _defaultSpecification =
                    createDefaultSpecification(_defaultSpecificationID);
            }
        }
    }

    // NodeTreeWalker override

    public void addChildNode(NodeTreeWalker childNode) {
        if (_defaultSpecification != null) {
            _defaultSpecification.addChildNode(childNode);
            return;
        }
        throw new UnsupportedOperationException();
    }

    public NodeTreeWalker getChildNode(int index) {
        if (_defaultSpecification != null) {
            return _defaultSpecification.getChildNode(index);
        }
        throw new UnsupportedOperationException();
    }

    public int getChildNodeSize() {
        if (_defaultSpecification != null) {
            return _defaultSpecification.getChildNodeSize();
        }
        throw new UnsupportedOperationException();
    }

    public void clearChildNodes() {
        if (_defaultSpecification != null) {
            _defaultSpecification.clearChildNodes();
        }
        throw new UnsupportedOperationException();
    }

    protected void releaseDefaultSpecification() {
        _defaultSpecification = null;
    }

    public void insertChildNode(int index, NodeTreeWalker childNode) {
        if (_defaultSpecification != null) {
            _defaultSpecification.insertChildNode(index, childNode);
        }
        throw new UnsupportedOperationException();
    }

    public boolean removeChildNode(NodeTreeWalker node) {
        if (_defaultSpecification != null) {
            _defaultSpecification.removeChildNode(node);
        }
        throw new UnsupportedOperationException();
    }

    public Iterator iterateChildNode() {
        if (_defaultSpecification != null) {
            return _defaultSpecification.iterateChildNode();
        }
        throw new UnsupportedOperationException();
    }

    public Date getTimestamp() {
        if (_defaultSpecification != null) {
            return _defaultSpecification.getTimestamp();
        }
        return null;
    }

    public boolean isDeprecated() {
        if (_defaultSpecification != null) {
            return _defaultSpecification.isDeprecated();
        }
        return false;
    }

    protected void doPageService(
            ServiceCycle cycle, Map pageScopeValues, boolean pageFlush) {
        build();
        try {
            boolean service = true;
            while (service) {
                try {
                    String pageName = null;
                    String extension = null;
                    String requestedSuffix = null;
                    ProcessStatus ret = null;

                    saveToCycle();
                    SpecificationUtil.initScope();
                    SpecificationUtil.startScope(pageScopeValues);
                    try {
                        SpecificationUtil.execEvent(_defaultSpecification, QM_BEFORE_RENDER);
                        RequestScope request = cycle.getRequestScope();
                        pageName = request.getPageName();
                        extension = request.getExtension();
                        if (_requestedSuffixEnabled) {
                            requestedSuffix = request.getRequestedSuffix();
                        }

                        Page page = getPage(pageName);
                        ret = page.doPageRender(requestedSuffix, extension);

                        saveToCycle();
                        SpecificationUtil.execEvent(_defaultSpecification, QM_AFTER_RENDER);
                    } finally {
                        SpecificationUtil.endScope();
                    }
                    Response response = CycleUtil.getResponse();
                    if (ret == null) {
                        if (response.getWriter().isDirty() == false) {
                            throw new RenderNotCompletedException(
                                    pageName, extension);
                        }
                    }
                    if (pageFlush) {
                        response.flush();
                    }
                    service = false;
                } catch (RenderingTerminated rt) {
                    service = false;
                } catch (PageForwarded ignore) {
                    checkCyclicForward(cycle.getRequestScope());
                }
            }
        } catch (Throwable t) {
            cycle.getResponse().clearBuffer();
            SpecificationUtil.initScope();
            handleError(t, pageFlush);
            saveToCycle();
        }
    }

    protected void doResourceService(ServiceCycle cycle) {
        if (cycle == null) {
            throw new IllegalArgumentException();
        }
        String path = cycle.getRequestScope().getRequestedPath();
        SourceDescriptor source = SourceUtil.getSourceDescriptor(path);

        source.setSystemID(path);
        String[] modifiedSinces = (String[])cycle.getRequestScope()
            .getHeaderValues().getAttribute("If-Modified-Since");
        if (modifiedSinces != null && modifiedSinces.length > 0) {
            String modifiedSince = modifiedSinces[0];
            Date sinceDate = null;
            DateFormat format = DateFormatPool.borrowRFC1123Format();
            try {
                sinceDate = format.parse(modifiedSince);
            } catch(ParseException e) {
                DateFormat format2 = DateFormatPool.borrowRFC2822Format();
                try {
                    sinceDate = format2.parse(modifiedSince);
                } catch(ParseException e2) {
                    // give up
                } finally {
                    DateFormatPool.returnFormat(format2);
                }
            } finally {
                DateFormatPool.returnFormat(format);
            }
            Date sourceTimestamp = new Date(
                    source.getTimestamp().getTime() / 1000 * 1000);
            if (sinceDate != null
                    && sourceTimestamp.after(sinceDate) == false) {
                cycle.getResponse().setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                return;
            }
        }

        InputStream stream = source.getInputStream();
        if (stream != null) {
            DateFormat format = DateFormatPool.borrowRFC1123Format();
            try {
                cycle.getResponse().setHeader("Last-Modified",
                        format.format(source.getTimestamp()));
            } finally {
                DateFormatPool.returnFormat(format);
            }
            OutputStream out = cycle.getResponse().getOutputStream();
            try {
                byte[] buffer = new byte[512];
                int readln;
                while ((readln = stream.read(buffer)) != -1) {
                    out.write(buffer, 0, readln);
                }
                out.flush();
            } catch (IOException e) {
                if (EngineUtil.isClientAbortException(e)) {
                    stream = null;
                } else {
                    throw new RuntimeException(e);
                }
            } finally {
                if (stream != null) {
                    IOUtil.close(stream);
                }
            }
        } else {
            cycle.getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    public void doService(Map pageScopeValues, boolean pageFlush) {
        if (_destroyed) {
            throw new IllegalStateException(StringUtil.getMessage(EngineImpl.class, 1));
        }
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        if (isPageRequested()) {
            doPageService(cycle, pageScopeValues, pageFlush);

            if (_dumpEnabled) {
                dump(cycle);
            }
        } else {
            doResourceService(cycle);
        }
    }

    protected void dump(ServiceCycle cycle) {
        ProcessorDump dump = new ProcessorDump();
        dump.setPrintContents(true);
        RequestScope request = cycle.getRequestScope();
        String pageName = request.getPageName();
        Page page = getPage(pageName);
        dump.printSource(page);
    }

    protected SpecificationCache getCache() {
        if (_specCache == null) {
            _specCache = new SpecificationCache(_surviveLimit);
        }
        return _specCache;
    }

    private static interface SpecificationGenerator {
        Class getInstantiator(SourceDescriptor source);
        void initialize(Specification instance);
    }

    protected Specification createSpecificationInstance(String systemID,
            boolean registerCache,
            SpecificationGenerator generator) {
        boolean rebuild = false;
        Specification spec;
        if (isSpecificationSerialize()) {
            spec = deserialize(systemID);
            if (spec != null) {
                if (spec.isDeprecated() == false) {
                    if (registerCache) {
                        getCache().add(spec);
                    }
                    return spec;
                }
                rebuild = true;
            }
        }
        SourceDescriptor source =
            SourceUtil.getSourceDescriptor(systemID);
        Class specClass = generator.getInstantiator(source);
        if (specClass == null) {
            return null;
        }
        spec = (Specification) ObjectUtil.newInstance(specClass);
        generator.initialize(spec);
        if (spec instanceof SpecificationImpl) {
            ((SpecificationImpl)spec).setSpecificationSerialize(
                    isSpecificationSerialize());
        }
        spec.setSource(source);
        spec.setSystemID(systemID);
        try {
            SpecificationUtil.startScope(null);
            try {
                spec.build(rebuild);
            } finally {
                SpecificationUtil.endScope();
            }
        } catch(RuntimeException e) {
            throw e;
        }
        if (registerCache) {
            getCache().add(spec);
        }
        return spec;
    }

    public Specification createDefaultSpecification(final String systemID) {
        return createSpecificationInstance(systemID, false,
                new SpecificationGenerator() {
                    public Class getInstantiator(SourceDescriptor source) {
                        return SpecificationImpl.class;
                    }
                    public void initialize(Specification instance) {
                        // no-op
                    }
                });
    }

    public Page createPageInstance(final String pageName) {
        return (Page) createSpecificationInstance(
                pageName + _mayaaExtension, true,
                new SpecificationGenerator() {
            public Class getInstantiator(SourceDescriptor source) {
                return getPageClass();
            }
            public void initialize(Specification instance) {
                ((Page) instance).initialize(pageName);
            }
        });
    }

    public Template createTemplateInstance(
            final Page page, final String suffix, final String extension) {
        return (Template) createSpecificationInstance(
                getTemplateID(page, suffix, extension), true,
                new SpecificationGenerator() {
            public Class getInstantiator(SourceDescriptor source) {
                if (source.exists() == false) {
                    return null;
                }
                return getTemplateClass();
            }
            public void initialize(Specification instance) {
                ((Template) instance).initialize(page, suffix, extension);
            }
        });
    }

    public String getTemplateID(Page page, String suffix, String extension) {
        StringBuffer name = new StringBuffer(page.getPageName());
        if (StringUtil.hasValue(suffix)) {
            String separator = getSuffixSeparator();
            name.append(separator).append(suffix);
        }
        if (StringUtil.hasValue(extension)) {
            name.append(".").append(extension);
        }
        return name.toString();
    }

    protected String getSuffixSeparator() {
        return EngineUtil.getEngineSetting(SUFFIX_SEPARATOR, "$");
    }

    public synchronized void destroy() {
        _destroyed = true;
        releaseDefaultSpecification();
        if (_specCache != null) {
            _specCache.release();
        }
    }

    protected void finalize() throws Throwable {
        destroy();
        super.finalize();
    }

    // Parameterizable implements ------------------------------------

    public void setParameter(String name, String value) {
        if (DEFAULT_SPECIFICATION.equals(name)) {
            if (StringUtil.hasValue(value)) {
                String systemID;
                if (value.charAt(0) != '/') {
                    systemID = "/" + value;
                } else {
                    systemID = value;
                }
                SourceDescriptor source = new DelaySourceDescriptor();
                source.setSystemID(systemID);
                setSource(source);
                _defaultSpecificationID = systemID;

                setupDefaultIsMayaa();
            }
        } else if (TEMPLATE_PATH_PATTERN.equals(name)) {
            if (StringUtil.hasValue(value)) {
                if (_templatePathPatterns == null) {
                    _templatePathPatterns = new LinkedList();
                }
                PathPattern pathPattern =
                    new PathPattern(Pattern.compile(value), true);
                _templatePathPatterns.add(0, pathPattern);
            }
        } else if (NOT_TEMPLATE_PATH_PATTERN.equals(name)) {
            if (StringUtil.hasValue(value)) {
                if (_templatePathPatterns == null) {
                    _templatePathPatterns = new LinkedList();
                }
                PathPattern pathPattern =
                    new PathPattern(Pattern.compile(value), false);
                _templatePathPatterns.add(0, pathPattern);
            }
        } else if (PAGE_CLASS.equals(name)) {
            if (StringUtil.hasValue(value)) {
                Class pageClass = ObjectUtil.loadClass(value);
                if (Page.class.isAssignableFrom(pageClass)) {
                    _pageClass = pageClass;
                }
            }
        } else if (MAYAA_EXTENSION.equals(name)) {
            if (StringUtil.hasValue(value)) {
                if (value.startsWith(".")) {
                    _mayaaExtension = value;
                    _mayaaExtensionName = value.substring(1);
                } else {
                    _mayaaExtension = "." + value;
                    _mayaaExtensionName = value;
                }

                setupDefaultIsMayaa();
            }
        } else if (TEMPLATE_CLASS.equals(name)) {
            if (StringUtil.hasValue(value)) {
                Class templateClass = ObjectUtil.loadClass(value);
                if (Template.class.isAssignableFrom(templateClass)) {
                    _templateClass = templateClass;
                }
            }
        } else if (NO_CACHE_VALUE.equals(name)) {
            _noCacheValue = value;
        } else if (PAGE_SERIALIZE.equals(name)) {
            setSpecificationSerialize(
                    Boolean.valueOf(value).booleanValue());
        } else if (SURVIVE_LIMIT.equals(name)) {
            _surviveLimit = Integer.parseInt(value);
        } else if (FORWARD_LIMIT.equals(name)) {
            _forwardLimit = Integer.parseInt(value);
        } else if (REQUESTED_SUFFIX_ENABLED.equals(name)) {
            _requestedSuffixEnabled = Boolean.valueOf(value).booleanValue();
        } else if (DUMP_ENABLED.equals(name)) {
            _dumpEnabled = Boolean.valueOf(value).booleanValue();
        } else if (CONVERT_CHARSET.equals(name)) {
            CharsetConverter.setEnabled(Boolean.valueOf(value).booleanValue());
        }
        super.setParameter(name, value);
    }

    private void setupDefaultIsMayaa() {
        _defaultIsMayaa = _defaultSpecificationID.endsWith(_mayaaExtension);
        if (_defaultIsMayaa) {
            _defaultPageName = _defaultSpecificationID.substring(
                    0, _defaultSpecificationID.lastIndexOf('.'));
        }
    }

    public String getParameter(String name) {
        if (DEFAULT_SPECIFICATION.equals(name)) {
            return _defaultSpecificationID;
        } else if (TEMPLATE_PATH_PATTERN.equals(name)) {
            if (_templatePathPatterns == null) {
                return null;
            }
            return patternToString(_templatePathPatterns, true);
        } else if (NOT_TEMPLATE_PATH_PATTERN.equals(name)) {
            if (_templatePathPatterns == null) {
                return null;
            }
            return patternToString(_templatePathPatterns, false);
        } else if (MAYAA_EXTENSION.equals(name)) {
            return _mayaaExtension;
        } else if (PAGE_CLASS.equals(name)) {
            return _pageClass.getName();
        } else if (TEMPLATE_CLASS.equals(name)) {
            return _templateClass.getName();
        } else if (NO_CACHE_VALUE.equals(name)) {
            return _noCacheValue;
        } else if (PAGE_SERIALIZE.equals(name)) {
            return String.valueOf(isSpecificationSerialize());
        } else if (SURVIVE_LIMIT.equals(name)) {
            return String.valueOf(_surviveLimit);
        } else if (FORWARD_LIMIT.equals(name)) {
            return String.valueOf(_forwardLimit);
        } else if (REQUESTED_SUFFIX_ENABLED.equals(name)) {
            return String.valueOf(_requestedSuffixEnabled);
        } else if (DUMP_ENABLED.equals(name)) {
            return String.valueOf(_dumpEnabled);
        }
        return super.getParameter(name);
    }

    private String patternToString(List patterns, boolean result) {
        StringBuffer sb = new StringBuffer();
        for (Iterator it = patterns.iterator(); it.hasNext();) {
            PathPattern pathPattern = (PathPattern) it.next();
            if (pathPattern.isTemplate() == result) {
                sb.append(pathPattern.getPattern());
                sb.append("|");
            }
        }
        return sb.toString();
    }

    protected Class getPageClass() {
        return _pageClass;
    }

    protected Class getTemplateClass() {
        return _templateClass;
    }

    //---- support class

    private static class PathPattern {
        private Pattern _pattern;
        private boolean _result;

        public PathPattern(Pattern pattern, boolean result) {
            _pattern = pattern;
            _result = result;
        }

        public boolean matches(String path) {
            return _pattern.matcher(path).matches();
        }

        public boolean isTemplate() {
            return _result;
        }

        public String getPattern() {
            return _pattern.pattern();
        }
    }

}
