org.seasar.mayaa.impl.builder.TemplateBuilderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.seasar.mayaa.impl.builder.TemplateBuilderImpl.java

Source

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

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.builder.SequenceIDGenerator;
import org.seasar.mayaa.builder.TemplateBuilder;
import org.seasar.mayaa.builder.injection.InjectionChain;
import org.seasar.mayaa.builder.injection.InjectionResolver;
import org.seasar.mayaa.builder.library.LibraryManager;
import org.seasar.mayaa.builder.library.ProcessorDefinition;
import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.ApplicationScope;
import org.seasar.mayaa.cycle.script.CompiledScript;
import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.engine.processor.OptimizableProcessor;
import org.seasar.mayaa.engine.processor.ProcessorTreeWalker;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.engine.specification.NodeTreeWalker;
import org.seasar.mayaa.engine.specification.PrefixMapping;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.Specification;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.engine.specification.URI;
import org.seasar.mayaa.impl.builder.injection.DefaultInjectionChain;
import org.seasar.mayaa.impl.builder.parser.AdditionalHandler;
import org.seasar.mayaa.impl.builder.parser.TemplateParser;
import org.seasar.mayaa.impl.builder.parser.TemplateScanner;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.engine.processor.AttributeProcessor;
import org.seasar.mayaa.impl.engine.processor.CharactersProcessor;
import org.seasar.mayaa.impl.engine.processor.CommentProcessor;
import org.seasar.mayaa.impl.engine.processor.DoBodyProcessor;
import org.seasar.mayaa.impl.engine.processor.ElementProcessor;
import org.seasar.mayaa.impl.engine.processor.LiteralCharactersProcessor;
import org.seasar.mayaa.impl.engine.specification.QNameImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.impl.util.xml.XMLReaderPool;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class TemplateBuilderImpl extends SpecificationBuilderImpl implements TemplateBuilder {
    public static final String DEFAULT_CHARSET = "defaultCharset";
    public static final String BALANCE_TAG = "balanceTag";
    public static final String REPLACE_SSI_INCLUDE = "replaceSSIInclude";
    public static final String OPTIMIZE = "optimize";
    public static final String OUTPUT_TEMPLATE_WHITESPACE = "outputTemplateWhitespace";

    private static final Log LOG = LogFactory.getLog(TemplateBuilderImpl.class);
    private static final long serialVersionUID = -1031702086020145692L;

    private List _resolvers = new ArrayList();
    private List _unmodifiableResolvers = Collections.unmodifiableList(_resolvers);
    private HtmlReaderPool _htmlReaderPool = new HtmlReaderPool();
    private transient InjectionChain _chain = new DefaultInjectionChain();
    private boolean _outputTemplateWhitespace = true;
    private boolean _optimize = true;
    private boolean _isSSIIncludeReplacementEnabled = false;

    public void addInjectionResolver(InjectionResolver resolver) {
        if (resolver == null) {
            throw new IllegalArgumentException();
        }
        synchronized (_resolvers) {
            _resolvers.add(resolver);
        }
    }

    protected List getInjectionResolvers() {
        return _unmodifiableResolvers;
    }

    protected boolean isHTML(String mimeType) {
        return mimeType != null && (mimeType.indexOf("html") != -1);
    }

    protected XMLReaderPool getXMLReaderPool(String systemID) {
        ApplicationScope application = CycleUtil.getServiceCycle().getApplicationScope();
        String mimeType = application.getMimeType(systemID);
        if (isHTML(mimeType)) {
            return _htmlReaderPool;
        }
        return super.getXMLReaderPool(systemID);
    }

    protected SpecificationNodeHandler createContentHandler(Specification specification) {
        if (specification instanceof Template == false) {
            throw new IllegalArgumentException();
        }
        TemplateNodeHandler handler = new TemplateNodeHandler((Template) specification);
        handler.setOutputTemplateWhitespace(isOutputTemplateWhitespace());
        handler.setSSIIncludeReplacementEnabled(isSSIIncludeReplacementEnabled());
        return handler;
    }

    protected String getPublicID() {
        return URI_MAYAA + "/template";
    }

    /**
     * SSI include???????m:doRender???
     * ??".inc"(ASP?)
     * @return ?m:doRender?????
     */
    protected String getIncludeExtension() {
        return ".inc";
    }

    protected void afterBuild(Specification specification) {
        if ((specification instanceof Template) == false) {
            throw new IllegalArgumentException();
        }

        Template template = (Template) specification;
        String systemID = specification.getSystemID();
        /*
         * SSI include??????{@link #getIncludeExtension()}?????
         * div???m:doRender????????NekoHTMLParser???
         * ???????(??????)
         */
        if (isSSIIncludeReplacementEnabled() && systemID.endsWith(getIncludeExtension())) {
            int childCount = template.getChildNodeSize();
            if (childCount > 0) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("enclose " + systemID + " with m:doRender.(name=\"\")");
                }

                // ????mime-type??
                ApplicationScope application = CycleUtil.getServiceCycle().getApplicationScope();
                String mimeType = application.getMimeType(systemID);
                if (StringUtil.hasValue(mimeType)) {
                    if (mimeType.indexOf("xhtml") >= 0) {
                        setDefaultNamespaceURI(template, URI_XHTML);
                    } else if (mimeType.indexOf("html") >= 0) {
                        setDefaultNamespaceURI(template, URI_HTML);
                    }
                }

                URI namespace = ((SpecificationNode) template.getChildNode(0)).getDefaultNamespaceURI();
                QName qName = QNameImpl.getInstance(namespace, "div");
                SpecificationNode root = SpecificationUtil.createSpecificationNode(qName, systemID, 0, true,
                        specification.nextSequenceID());
                root.addPrefixMapping("m", URI_MAYAA);
                root.addAttribute(QM_INJECT, "m:doRender");
                for (int i = 0; i < childCount; i++) {
                    root.addChildNode(template.getChildNode(i));
                }
                template.clearChildNodes();
                template.addChildNode(root);
            }
        }

        LOG.debug("built node tree from template. " + systemID);
        doInjection((Template) specification);
        if (isOptimizeEnabled()) {
            nodeOptimize((Template) specification);
        }
        LOG.debug("built processor tree from node tree. " + specification.getSystemID());
    }

    /**
     * {@code template}???????{@code null}????XML???????
     * ???????XML, HTML, XHTML???????????
     * ???{@code namespace}??
     * @param template
     * @param namespace
     */
    protected void setDefaultNamespaceURI(Template template, URI namespace) {
        int size = template.getChildNodeSize();
        if (size > 0) {
            URI firstDefaultNS = ((SpecificationNode) template.getChildNode(0)).getDefaultNamespaceURI();
            if (firstDefaultNS == null || firstDefaultNS.equals(URI_XML)) {
                for (int i = 0; i < size; i++) {
                    setDefaultNamespaceURIToNode((SpecificationNode) template.getChildNode(i), namespace);
                }
            }
        }
    }

    /**
     * parentNode?default namespace?XML, HTML, XHTML????????namespace
     * default namespace???????
     * @param parentNode
     * @param namespace
     */
    protected void setDefaultNamespaceURIToNode(SpecificationNode parentNode, URI namespace) {
        if (parentNode.getDefaultNamespaceURI() == null || parentNode.getDefaultNamespaceURI().equals(URI_XML)
                || parentNode.getDefaultNamespaceURI().equals(URI_HTML)
                || parentNode.getDefaultNamespaceURI().equals(URI_XHTML)) {
            parentNode.setDefaultNamespaceURI(namespace);
        }
        int size = parentNode.getChildNodeSize();
        for (int i = 0; i < size; i++) {
            SpecificationNode node = (SpecificationNode) parentNode.getChildNode(i);
            if (node.getChildNodeSize() > 0) {
                setDefaultNamespaceURIToNode(node, namespace);
            }
        }
    }

    private static class AbsoluteCompareList extends ArrayList {
        private static final long serialVersionUID = 1L;

        protected AbsoluteCompareList() {
            // do nothing.
        }

        public int indexOf(Object elem) {
            for (int i = 0; i < size(); i++) {
                if (get(i) == elem) {
                    return i;
                }
            }
            return -1;
        }
    }

    private static class NodeAndChildren {
        public SpecificationNode _originalNode;
        public List _list = new ArrayList();

        public NodeAndChildren(SpecificationNode originalNode) {
            _originalNode = originalNode;
        }
    }

    protected void nodeOptimize(Template template) {
        SequenceIDGenerator idGenerator = template;
        List children = new ArrayList();
        List usingNodes = new AbsoluteCompareList();
        walkTreeOptimizeNode(idGenerator, template, children, usingNodes);

        SpecificationNode mayaaNode = SpecificationUtil.getMayaaNode(template);
        breakRelationship(template);
        idGenerator.resetSequenceID(1);
        walkTreeNodeChainModify(idGenerator, template, children);
        template.addChildNode(mayaaNode);
    }

    protected void breakRelationship(NodeTreeWalker parent) {
        for (int i = 0; i < parent.getChildNodeSize(); i++) {
            breakRelationship(parent.getChildNode(i));
        }
        parent.clearChildNodes();
    }

    protected void walkTreeNodeChainModify(SequenceIDGenerator idGenerator, NodeTreeWalker parent, List children) {
        parent.clearChildNodes();
        for (int i = 0; i < children.size(); i++) {
            NodeAndChildren nodeAndChildren = (NodeAndChildren) children.get(i);
            SpecificationNode node = nodeAndChildren._originalNode;
            node.setSequenceID(idGenerator.nextSequenceID());
            node.setParentNode(parent);
            parent.addChildNode(node);
            walkTreeNodeChainModify(idGenerator, node, nodeAndChildren._list);
        }
    }

    protected void walkTreeOptimizeNode(SequenceIDGenerator idGenerator, ProcessorTreeWalker parent, List children,
            List usingNodes) {
        for (int i = 0; i < parent.getChildProcessorSize(); i++) {
            ProcessorTreeWalker walker = parent.getChildProcessor(i);
            if (walker instanceof TemplateProcessor) {
                TemplateProcessor processor = (TemplateProcessor) walker;
                SpecificationNode node = processor.getOriginalNode();
                if (processor instanceof LiteralCharactersProcessor) {
                    node = SpecificationUtil.createSpecificationNode(QM_CHARACTERS, node.getSystemID(),
                            node.getLineNumber(), true, idGenerator.nextSequenceID());
                    //node.addAttribute(QM_TEXT, "");   /*don't set text. because memory is not used.*/
                    processor.setOriginalNode(node);

                    SpecificationNode injectedNode = processor.getInjectedNode();
                    injectedNode = SpecificationUtil.createSpecificationNode(QM_LITERALS,
                            injectedNode.getSystemID(), injectedNode.getLineNumber(), false, 1);
                    processor.setInjectedNode(injectedNode);
                } else {
                    if (processor instanceof ElementProcessor == false
                            && processor instanceof AttributeProcessor == false) {
                        //Namespace root = NamespaceImpl.getInstance("/null\n");
                        processor.getOriginalNode().setParentSpace(null);
                        processor.getInjectedNode().setParentSpace(null);
                        //System.out.println(processor.getClass().getName());
                    }
                }
                // to repair for originalNode infinite loop
                if (parent instanceof ElementProcessor && ((ElementProcessor) parent).getOriginalNode() == node) {
                    continue;
                }
                NodeAndChildren nodeAndChildren = new NodeAndChildren(node);
                children.add(nodeAndChildren);
                usingNodes.add(node);
                if (processor.getChildProcessorSize() > 0) {
                    walkTreeOptimizeNode(idGenerator, processor, nodeAndChildren._list, usingNodes);
                }
            }
        }
    }

    protected void saveToCycle(NodeTreeWalker originalNode, NodeTreeWalker injectedNode) {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        cycle.setOriginalNode(originalNode);
        cycle.setInjectedNode(injectedNode);
    }

    protected TemplateProcessor findConnectPoint(TemplateProcessor processor) {
        if (processor instanceof ElementProcessor && ((ElementProcessor) processor).isDuplicated()) {
            // "processor"'s m:replace is true, ripping duplicated element.
            return findConnectPoint((TemplateProcessor) processor.getChildProcessor(0));
        }
        for (int i = 0; i < processor.getChildProcessorSize(); i++) {
            ProcessorTreeWalker child = processor.getChildProcessor(i);
            if (child instanceof CommentProcessor) {
                return null;
            } else if (child instanceof CharactersProcessor) {
                CharactersProcessor charsProc = (CharactersProcessor) child;
                CompiledScript script = charsProc.getText().getValue();
                if (script.isLiteral()) {
                    String value = script.getScriptText();
                    if (StringUtil.hasValue(value.trim())) {
                        // "processor" has child which is not empty.
                        return null;
                    }
                } else {
                    // "processor" has child which is scriptlet.
                    return null;
                }
            } else if (child instanceof AttributeProcessor == false) {
                // "processor" has child which is implicit m:characters or
                // nested child node, but is NOT m:attribute
                return null;
            }
        }
        return processor;
    }

    protected TemplateProcessor createProcessor(SpecificationNode original, SpecificationNode injected) {
        QName name = injected.getQName();
        LibraryManager libraryManager = ProviderUtil.getLibraryManager();
        ProcessorDefinition def = libraryManager.getProcessorDefinition(name);
        if (def != null) {
            TemplateProcessor proc = def.createTemplateProcessor(original, injected);
            proc.setOriginalNode(original);
            proc.setInjectedNode(injected);
            return proc;
        }
        return null;
    }

    protected InjectionChain getDefaultInjectionChain() {
        return _chain;
    }

    protected TemplateProcessor resolveInjectedNode(Template template, Stack stack, SpecificationNode original,
            SpecificationNode injected, Set divided) {
        if (injected == null) {
            throw new IllegalArgumentException();
        }
        saveToCycle(original, injected);
        TemplateProcessor processor = createProcessor(original, injected);
        if (processor == null) {
            PrefixMapping mapping = original.getMappingFromPrefix("", true);
            if (mapping == null) {
                throw new IllegalStateException();
            }
            URI defaultURI = mapping.getNamespaceURI();
            if (defaultURI.equals(injected.getQName().getNamespaceURI())) {
                InjectionChain chain = getDefaultInjectionChain();
                SpecificationNode retry = chain.getNode(injected);
                processor = createProcessor(original, retry);
            }
            if (processor == null) {
                throw new ProcessorNotInjectedException(injected.toString());
            }
        }
        ProcessorTreeWalker parent = (ProcessorTreeWalker) stack.peek();
        parent.addChildProcessor(processor);
        processor.initialize();
        Iterator it = injected.iterateChildNode();
        if (it.hasNext() == false) {
            return processor;
        }
        // "injected" node has children, nested node definition on .mayaa
        stack.push(processor);
        while (it.hasNext()) {
            SpecificationNode childNode = (SpecificationNode) it.next();
            saveToCycle(original, childNode);
            TemplateProcessor childProcessor = resolveInjectedNode(template, stack, original, childNode, divided);
            if (childProcessor instanceof DoBodyProcessor) {
                stack.push(childProcessor);
                walkParsedTree(template, stack, original, divided);
                stack.pop();
            }
        }
        stack.pop();
        saveToCycle(original, injected);
        return findConnectPoint(processor);
    }

    /**
     * ??
     * ?{@link parent}????{@link collector}????
     * ???????????????????
     * ?????{@link divided}??
     * @param idGenerator
     * @param parent ?
     * @param collector ??
     * @param divided ???
     */
    protected void optimizeProcessors(SequenceIDGenerator idGenerator, ProcessorTreeWalker parent, List collector,
            Set divided) {
        List expands = new ArrayList();
        Iterator it = collector.iterator();
        while (it.hasNext()) {
            ProcessorTreeWalker processor = (ProcessorTreeWalker) it.next();
            if (processor == null) {
                throw new IllegalStateException("processor is null");
            }
            if (processor instanceof OptimizableProcessor && divided.contains(processor) == false) {
                ProcessorTreeWalker[] processors = ((OptimizableProcessor) processor).divide(idGenerator);
                for (int i = 0; i < processors.length; i++) {
                    expands.add(processors[i]);
                }
                divided.add(processor);
            } else {
                expands.add(processor);
            }
        }

        List packs = new ArrayList();
        for (int i = 0; i < expands.size(); i++) {
            ProcessorTreeWalker node = (ProcessorTreeWalker) expands.get(i);
            node = convertCharactersProcessor(node, idGenerator);

            if (packs.size() > 0 && node instanceof LiteralCharactersProcessor) {
                Object last = packs.get(packs.size() - 1);
                if (last instanceof LiteralCharactersProcessor) {
                    LiteralCharactersProcessor rawLast = (LiteralCharactersProcessor) last;
                    rawLast.setText(rawLast.getText() + ((LiteralCharactersProcessor) node).getText());
                } else {
                    packs.add(node);
                }
            } else {
                packs.add(node);
            }
        }
        for (int i = 0; i < packs.size(); i++) {
            ProcessorTreeWalker child = (ProcessorTreeWalker) packs.get(i);
            child.setParentProcessor(parent);
            parent.addChildProcessor(child);
        }
    }

    protected ProcessorTreeWalker convertCharactersProcessor(ProcessorTreeWalker processor,
            SequenceIDGenerator idGenerator) {
        if (processor instanceof CharactersProcessor) {
            CharactersProcessor cnode = (CharactersProcessor) processor;
            if (cnode.getText() != null && cnode.getText().getValue() != null
                    && cnode.getText().getValue().isLiteral()) {
                LiteralCharactersProcessor literalProcessor = new LiteralCharactersProcessor(
                        cnode.getText().getValue().getScriptText());
                BuilderUtil.characterProcessorCopy(cnode, literalProcessor, idGenerator);
                processor = literalProcessor;
            }
        }
        return processor;
    }

    protected SpecificationNode resolveOriginalNode(SpecificationNode original, InjectionChain chain) {
        if (original == null || chain == null) {
            throw new IllegalArgumentException();
        }
        if (_resolvers.size() > 0) {
            InjectionChainImpl first = new InjectionChainImpl(chain);
            return first.getNode(original);
        }
        return chain.getNode(original);
    }

    protected void walkParsedTree(Template template, Stack stack, NodeTreeWalker original, Set divided) {
        if (original == null) {
            throw new IllegalArgumentException();
        }
        Iterator it = original.iterateChildNode();
        while (it.hasNext()) {
            SpecificationNode child;
            try {
                child = (SpecificationNode) it.next();
            } catch (ConcurrentModificationException e) {
                LOG.error("original.childNodes ???????", e);
                throw e;
            }
            saveToCycle(child, child);
            if (QM_MAYAA.equals(child.getQName())) {
                continue;
            }
            InjectionChain chain = getDefaultInjectionChain();
            SpecificationNode injected = resolveOriginalNode(child, chain);
            if (injected == null) {
                throw new TemplateNodeNotResolvedException(original.toString());
            }
            saveToCycle(child, injected);
            ProcessorTreeWalker processor = resolveInjectedNode(template, stack, child, injected, divided);
            if (processor != null) {
                stack.push(processor);
                walkParsedTree(template, stack, child, divided);
                stack.pop();
            }
        }
        if (isOptimizeEnabled()) {
            ProcessorTreeWalker parent = (ProcessorTreeWalker) stack.peek();
            int count = parent.getChildProcessorSize();
            if (count > 0) {
                List childProcessors = new ArrayList();
                for (int i = 0; i < count; i++) {
                    childProcessors.add(parent.getChildProcessor(i));
                }
                parent.clearChildProcessors();
                optimizeProcessors(template, parent, childProcessors, divided);
            }
        }
    }

    protected boolean isOutputTemplateWhitespace() {
        return _outputTemplateWhitespace;
    }

    protected void setOutputTemplateWhitespace(boolean outputTemplateWhitespace) {
        _outputTemplateWhitespace = outputTemplateWhitespace;
    }

    protected boolean isSSIIncludeReplacementEnabled() {
        return _isSSIIncludeReplacementEnabled;
    }

    protected void setSSIIncludeReplacementEnabled(boolean isSSIIncludeReplacementEnabled) {
        _isSSIIncludeReplacementEnabled = isSSIIncludeReplacementEnabled;
    }

    protected boolean isOptimizeEnabled() {
        return _optimize;
    }

    protected void setOptimizeEnabled(boolean optimize) {
        _optimize = optimize;
    }

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

    public void setParameter(String name, String value) {
        if (OUTPUT_TEMPLATE_WHITESPACE.equals(name)) {
            setOutputTemplateWhitespace(ObjectUtil.booleanValue(value, true));
        } else if (OPTIMIZE.equals(name)) {
            setOptimizeEnabled(ObjectUtil.booleanValue(value, true));
        } else if (REPLACE_SSI_INCLUDE.equals(name)) {
            setSSIIncludeReplacementEnabled(ObjectUtil.booleanValue(value, false));
        } else if (DEFAULT_CHARSET.equals(name)) {
            try {
                "".getBytes(value);
                _htmlReaderPool.setDefaultCharset(value);
            } catch (UnsupportedEncodingException e) {
                String message = StringUtil.getMessage(TemplateBuilderImpl.class, 0, value);
                LOG.warn(message, e);
            }
        } else if (BALANCE_TAG.equals(name)) {
            _htmlReaderPool.setBalanceTag(ObjectUtil.booleanValue(value, true));
        }
        super.setParameter(name, value);
    }

    protected void doInjection(Template template) {
        if (template == null) {
            throw new IllegalArgumentException();
        }
        saveToCycle(template, template);
        Stack stack = new Stack();
        stack.push(template);
        SpecificationNode mayaa = SpecificationUtil.createSpecificationNode(QM_MAYAA, template.getSystemID(), 0,
                true, 0);
        template.addChildNode(mayaa);

        Set divided = null;
        if (isOptimizeEnabled()) {
            divided = new HashSet();
        }
        walkParsedTree(template, stack, template, divided);
        if (template.equals(stack.peek()) == false) {
            throw new IllegalStateException();
        }
        saveToCycle(template, template);
    }

    // deserialization
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();

        _chain = new DefaultInjectionChain();
    }

    // support class --------------------------------------------------

    protected static class HtmlReaderPool extends XMLReaderPool {

        private static final long serialVersionUID = -5203349759797583368L;
        private String _defaultCharset = TEMPLATE_DEFAULT_CHARSET;
        private boolean _balanceTag = true;

        protected void setDefaultCharset(String charset) {
            _defaultCharset = charset;
        }

        protected void setBalanceTag(boolean balanceTag) {
            _balanceTag = balanceTag;
        }

        protected Object createObject() {
            return new TemplateParser(new TemplateScanner(), _defaultCharset, _balanceTag);
        }

        protected boolean validateObject(Object object) {
            return object instanceof TemplateParser;
        }

        public XMLReader borrowXMLReader(ContentHandler handler, boolean namespaces, boolean validation,
                boolean xmlSchema) {
            XMLReader htmlReader = super.borrowXMLReader(handler, namespaces, validation, xmlSchema);
            if (handler instanceof AdditionalHandler) {
                try {
                    htmlReader.setProperty(AdditionalHandler.ADDITIONAL_HANDLER, handler);
                } catch (SAXException e) {
                    throw new RuntimeException(e);
                }
            }
            return htmlReader;
        }

    }

    protected class InjectionChainImpl implements InjectionChain {

        private int _index;
        private InjectionChain _external;

        public InjectionChainImpl(InjectionChain external) {
            _external = external;
        }

        public SpecificationNode getNode(SpecificationNode original) {
            if (original == null) {
                throw new IllegalArgumentException();
            }
            if (_index < getInjectionResolvers().size()) {
                InjectionResolver resolver = (InjectionResolver) getInjectionResolvers().get(_index);
                _index++;
                InjectionChain chain;
                if (_index == getInjectionResolvers().size()) {
                    chain = _external;
                } else {
                    chain = this;
                }
                return resolver.getNode(original, chain);
            }
            throw new IndexOutOfBoundsException();
        }

    }

}