ParserContextImpl.java

/* 
 * 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.esigate.parser;

import java.io.IOException;
import java.util.Stack;

import org.apache.http.HttpResponse;
import org.esigate.HttpErrorPage;
import org.esigate.impl.DriverRequest;

/**
 * 
 * The stack of tags corresponding to the current position in the document
 * 
 * @author Francois-Xavier Bonnet
 * 
 */
class ParserContextImpl implements ParserContext {
    private final RootAdapter root;
    private final DriverRequest httpRequest;
    private final HttpResponse httpResponse;

    private final Stack<ElementInfo> stack = new Stack<>();

    ParserContextImpl(Appendable root, DriverRequest httpRequest, HttpResponse httpResponse) {
        this.root = new RootAdapter(root);
        this.httpRequest = httpRequest;
        this.httpResponse = httpResponse;
    }

    @Override
    public <T> T findAncestor(Class<T> type) {
        T result = null;
        for (int i = stack.size() - 1; i > -1; i--) {
            Element currentElement = stack.elementAt(i).element;
            if (type.isInstance(currentElement)) {
                result = type.cast(currentElement);
                break;
            }
        }
        // try with root
        if (result == null && type.isInstance(root.root)) {
            result = type.cast(root.root);
        }

        return result;
    }

    /** {@inheritDoc} */
    @Override
    public boolean reportError(Exception e) {
        boolean result = false;
        for (int i = stack.size() - 1; i > -1; i--) {
            Element element = stack.elementAt(i).element;
            if (element.onError(e, this)) {
                result = true;
                break;
            }
        }
        return result;
    }

    void startElement(ElementType type, Element element, String tag) throws IOException, HttpErrorPage {
        boolean skipContent = false;
        if (!stack.isEmpty()) {
            // Inherit from parent
            skipContent = stack.peek().skipContent;
        }
        boolean elementDoesNotSkipContent = element.onTagStart(tag, this);
        if (!skipContent) {
            skipContent = !elementDoesNotSkipContent;
        }
        stack.push(new ElementInfo(type, element, skipContent));
    }

    void endElement(String tag) throws IOException, HttpErrorPage {
        ElementInfo elementInfo = stack.pop();
        if (!elementInfo.skipContent) {
            elementInfo.element.onTagEnd(tag, this);
        }
    }

    boolean isCurrentTagEnd(String tag) {
        return !stack.isEmpty() && stack.peek().type.isEndTag(tag);
    }

    /** Writes characters into current writer. */
    public void characters(CharSequence cs) throws IOException {
        characters(cs, 0, cs.length());
    }

    /** Writes characters into current writer. */
    void characters(CharSequence csq, int start, int end) throws IOException {
        getCurrent().characters(csq, start, end);
    }

    @Override
    public Element getCurrent() {
        Element result = root;
        if (!stack.isEmpty()) {
            result = stack.peek().element;
        }
        return result;
    }

    @Override
    public DriverRequest getHttpRequest() {
        return this.httpRequest;
    }

    private static class ElementInfo {
        private final ElementType type;
        private final Element element;
        private final boolean skipContent;

        public ElementInfo(ElementType type, Element element, boolean skipContent) {
            this.type = type;
            this.element = element;
            this.skipContent = skipContent;
        }
    }

    private static class RootAdapter implements Element {
        private final Appendable root;

        public RootAdapter(Appendable root) {
            this.root = root;
        }

        @Override
        public boolean onTagStart(String tag, ParserContext ctx) {
            // Nothing to do, this is the root tag
            return true;
        }

        @Override
        public void onTagEnd(String tag, ParserContext ctx) {
            // Nothing to do, this is the root tag
        }

        @Override
        public boolean onError(Exception e, ParserContext ctx) {
            return false;
        }

        @Override
        public void characters(CharSequence csq, int start, int end) throws IOException {
            this.root.append(csq, start, end);
        }

    }

    @Override
    public HttpResponse getHttpResponse() {
        return this.httpResponse;
    }
}