Package com.alibaba.citrus.util.templatelite

Source Code of com.alibaba.citrus.util.templatelite.Template$InputSource

/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.citrus.util.templatelite;

import static com.alibaba.citrus.util.ArrayUtil.*;
import static com.alibaba.citrus.util.Assert.ExceptionType.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.StringEscapeUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static java.lang.Math.*;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.alibaba.citrus.util.FileUtil;
import com.alibaba.citrus.util.ToStringBuilder;
import com.alibaba.citrus.util.ToStringBuilder.MapBuilder;
import net.sf.cglib.reflect.FastClass;

/**
* 一个简易的模板。
* <p>
* 格式如下:
* </p>
* <dl>
* <dt>模板解析参数</dt>
* <dd>
* <p>
* 参数必须定义在模板或子模板的开头,在所有的内容开始之前。
* </p>
* <p/>
* <pre>
* #@charset UTF-8
* </pre>
* <p/>
* </dd>
* <dt>注释</dt>
* <dd>
* <p>
* 注释可以写在任何地方。如果一个注释从行首开始,或者注释之前没有任何可见字符,则整行将被忽略。
* </p>
* <p/>
* <pre>
* ## comment
* </pre>
* <p/>
* </dd>
* <dt>替换变量(placeholder)</dt>
* <dd>
* <p>
* 最简单的写法:
* </p>
* <p/>
* <pre>
* ${placeholder}
* </pre>
* <p/>
* <p>
* 包含一个或多个参数:
* </p>
* <p/>
* <pre>
* ${placeholder: param, param}
* </pre>
* <p/>
* <p>
* 可引用一个或多个子模板作为参数。所引用的子模板将从当前子模板或者上级子模板中查找。
* </p>
* <p/>
* <pre>
* ${placeholder: #subtpl, #subtpl}
* </pre>
* <p/>
* <p>
* 也可引用多级子模板作为参数。
* </p>
* <p/>
* <pre>
* ${placeholder: #tpl1.subtpl2.suptpl3}
* </pre>
* <p/>
* <p>
* 使用.*可达到引用一组模板的作用。
* </p>
* <p/>
* <pre>
* ${placeholder: #tpl1.*}
* </pre>
* <p/>
* </dd>
* <dt>包含子模板</dt>
* <dd>
* <p>
* 直接包含一个子模板,不调用visitor。
* </p>
* <p/>
* <pre>
* $#{subtpl}
* </pre>
* <p/>
* <p>
* 也可包含多级子模板。
* </p>
* <p/>
* <pre>
* $#{tpl.subtpl1.subtpl2}
* </pre>
* <p/>
* </dd>
* <dt>定义子模板</dt>
* <dd>
* <p>
* 子模板必须位于模板或其它子模板的末尾。从最后一行内容到子模板之间的空行将被忽略。子模板可以包含其它子模板。
* </p>
* <p/>
* <pre>
* #subtpl
* #@trimming on
* #@whitespace collapse
* content
* #end
* </pre>
* <p/>
* </dd>
* <dt>导入子模板</dt>
* <dd>
* <p>
* 导入外部文件,作为子模板。这种方法所产生的子模板,和直接定义子模板的效果完全相同。但将子模板定义在外部文件中,有利于整理并缩短模板的长度。
* </p>
* <p/>
* <pre>
* #subtpl(relative_file_name)
* </pre>
* <p/>
* <p>
* 或者:
* </p>
* <p/>
* <pre>
* #subtpl("relative_file_name")
* </pre>
* <p/>
* </dd>
* </dl>
*
* @author Michael Zhou
*/
public final class Template {
    private final static int                   MAX_REDIRECT_DEPTH  = 10;
    private final static Node[]                EMPTY_NODES         = new Node[0];
    private final static Map<String, Template> predefinedTemplates = createHashMap();
    private final String      name;
    final         InputSource source;
    final         Location    location;
    final         Template    ref;
    Node[]                nodes;
    Map<String, Template> subtemplates;
    Map<String, String>   params;

    static {
        // 预定义模板,可供任何模板中直接使用,例如:$#{SPACE}可强制插入空格。
        predefineTemplate("SPACE", " ");
        predefineTemplate("BR", "\n");
    }

    /** 从File中创建template。 */
    public Template(File source) {
        this(new InputSource(source, null), null);
    }

    /** 从URL中创建template。 */
    public Template(URL source) {
        this(new InputSource(source, null), null);
    }

    /** 从输入流中创建template。 */
    public Template(InputStream stream, String systemId) {
        this(new InputSource(stream, systemId), null);
    }

    /** 从输入流中创建template。 */
    public Template(Reader reader, String systemId) {
        this(new InputSource(reader, systemId), null);
    }

    /** 内部构造函数:创建主模板。 */
    private Template(InputSource source, String name) {
        this.name = trimToNull(name);
        this.source = assertNotNull(source, "source");
        this.location = new Location(source.systemId, 0, 0);
        this.ref = null;

        source.reloadIfNecessary(this);
    }

    /** 内部构造函数:创建子模板。 */
    private Template(String name, Node[] nodes, Map<String, String> params, Map<String, Template> subtemplates,
                     Location location) {
        this.name = trimToNull(name);
        this.source = null;
        this.location = assertNotNull(location, "location");
        this.ref = null;

        update(nodes, params, subtemplates);
    }

    /** 内部构造函数:创建template ref。 */
    private Template(Template ref) {
        this.ref = assertNotNull(ref, "template ref");
        this.name = null;
        this.source = null;
        this.location = null;
        this.nodes = null;
        this.subtemplates = null;
        this.params = null;
    }

    private void assertNotRef() {
        assertNull(ref, UNSUPPORTED_OPERATION, "template ref");
    }

    private void update(Node[] nodes, Map<String, String> params, Map<String, Template> subtemplates) {
        assertNotRef();

        this.nodes = defaultIfEmptyArray(nodes, EMPTY_NODES);
        this.params = createArrayHashMap(assertNotNull(params, "params").size());
        this.subtemplates = createArrayHashMap(assertNotNull(subtemplates, "subtemplates").size());

        this.params.putAll(params);
        this.subtemplates.putAll(subtemplates);
    }

    public String getName() {
        if (ref == null) {
            return name;
        } else {
            return ref.name;
        }
    }

    public String getParameter(String name) {
        if (ref == null) {
            return params.get(name);
        } else {
            return ref.params.get(name);
        }
    }

    public Template getSubTemplate(String name) {
        if (ref == null) {
            return subtemplates.get(name);
        } else {
            return ref.subtemplates.get(name);
        }
    }

    /** 将模板渲染成文本。 */
    public String renderToString(TextWriter<? super StringBuilder> writer) {
        writer.setOut(new StringBuilder());
        accept(writer);
        return writer.out().toString();
    }

    /** 渲染模板。 */
    public void accept(Object visitor) throws TemplateRuntimeException {
        if (ref == null) {
            if (source != null) {
                source.reloadIfNecessary(this);
            }

            for (Node node : nodes) {
                invokeVisitor(visitor, node);
            }
        } else {
            // 调用ref,也就是${placeholder: #template}或者$#{template}中,被注入的template对象。
            // 首先调用:visitTemplateName(Template)方法,如果方法不存在,则直接调用被引用的template。
            invokeVisitor(visitor, ref);
        }
    }

    /** 调用visitTemplateName(Template)方法,如果方法不存在,则直接调用被引用的template。 */
    private void invokeVisitor(Object visitor, Template templateRef) throws TemplateRuntimeException {
        invokeVisitor(visitor, templateRef, 0);
    }

    /** 根据node类型,访问visitor相应的方法。 */
    private void invokeVisitor(Object visitor, Node node) throws TemplateRuntimeException {
        assertNotRef();
        invokeVisitor(visitor, node, 0);
    }

    /** 根据node类型,访问visitor相应的方法。 */
    private void invokeVisitor(Object visitor, Object node, int redirectDepth) throws TemplateRuntimeException {
        // 对$#{includeTemplate}直接调用template
        if (node instanceof IncludeTemplate) {
            assertNotNull(((IncludeTemplate) node).includedTemplate).accept(visitor);
            return;
        }

        Class<?> visitorClass = assertNotNull(visitor, "visitor is null").getClass();

        try {
            Method method = null;
            Object[] params = null;

            // 对普通文本调用visitText(String)
            if (node instanceof Text) {
                Text text = (Text) node;
                method = findVisitTextMethod(visitorClass, "visitText");
                params = new Object[] { text.text };
            }

            // 对ref template调用visitTemplateName(Template),如果不存在该方法,则直接调用template
            else if (node instanceof Template) {
                Template ref = (Template) node;
                method = findVisitTemplateMethod(visitorClass, "visit" + trimToEmpty(capitalize(ref.getName())));

                if (method == null) {
                    ref.accept(visitor); // 方法不存在,直接调用ref template
                    return;
                }

                params = new Object[] { ref };
            }

            // 对${placeholder}调用:
            //
            // 1. visitXyz()                                        // 无参数
            // 2. visitXyz(String[])                                // 数组参数
            //    visitXyz(Template[])
            //    visitXyz(Object[])
            // 3. visitXyz(Template, String, String, Template, ...) // 独立参数
            else if (node instanceof Placeholder) {
                Placeholder placeholder = (Placeholder) node;
                int placeholderParamCount = placeholder.params.length;
                String methodName = "visit" + trimToEmpty(capitalize(placeholder.name));

                try {
                    method = findVisitPlaceholderMethod(visitorClass, methodName, placeholder.params);
                    int methodParamCount = method.getParameterTypes().length;

                    if (method.getParameterTypes().length == 0) {
                        params = EMPTY_OBJECT_ARRAY;
                    } else if (method.getParameterTypes()[0].isArray()) {
                        Object[] array;

                        if (method.getParameterTypes()[0].equals(String[].class)) {
                            array = new String[placeholderParamCount];
                        } else if (method.getParameterTypes()[0].equals(Template[].class)) {
                            array = new Template[placeholderParamCount];
                        } else {
                            array = new Object[placeholderParamCount];
                        }

                        params = new Object[] { toPlaceholderParameterValues(placeholder, array) };
                    } else {
                        params = toPlaceholderParameterValues(placeholder, new Object[methodParamCount]);
                    }
                } catch (NoSuchMethodException e) {
                    boolean processed = false;

                    if (visitor instanceof FallbackVisitor) {
                        processed = ((FallbackVisitor) visitor).visitPlaceholder(placeholder.name,
                                                                                 toPlaceholderParameterValues(placeholder, new Object[placeholderParamCount]));
                    }

                    if (!processed) {
                        throw e;
                    }
                }
            } else {
                unreachableCode("Unexpected node type: " + node.getClass().getName());
            }

            if (method != null) {
                Object newVisitor = null;

                try {
                    newVisitor = FastClass.create(visitorClass).getMethod(method).invoke(visitor, params);
                } catch (InvocationTargetException e) {
                    if (visitor instanceof VisitorInvocationErrorHandler) {
                        ((VisitorInvocationErrorHandler) visitor).handleInvocationError(node.toString(), e.getCause());
                    } else {
                        throw new TemplateRuntimeException("Error rendering " + node, e.getCause());
                    }
                }

                // 如果当前visitor返回了一个新的visitor对象,则重定向到新的visitor。
                if (newVisitor != null && visitor != newVisitor) {
                    if (redirectDepth >= MAX_REDIRECT_DEPTH) {
                        throw new TemplateRuntimeException("Redirection out of control (depth>" + MAX_REDIRECT_DEPTH
                                                           + ") in " + method);
                    }

                    invokeVisitor(newVisitor, node, redirectDepth + 1);
                }
            }
        } catch (TemplateRuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new TemplateRuntimeException("Error rendering " + node, e);
        }
    }

    /** 查找visitText方法。 */
    private Method findVisitTextMethod(Class<?> visitorClass, String methodName) throws NoSuchMethodException {
        Method method = null;

        for (Method candidateMethod : visitorClass.getMethods()) {
            if (methodName.equals(candidateMethod.getName())) {
                Class<?>[] paramTypes = candidateMethod.getParameterTypes();
                int paramsCount = paramTypes.length;

                // visitText(String)
                if (paramsCount == 1 && paramTypes[0].equals(String.class)) {
                    method = candidateMethod;
                    break;
                }
            }
        }

        if (method == null) {
            throw new NoSuchMethodException(visitorClass.getSimpleName() + "." + methodName + "(String)");
        }

        return method;
    }

    /** 查找visitTemplate方法。 */
    private Method findVisitTemplateMethod(Class<?> visitorClass, String methodName) throws NoSuchMethodException {
        Method method = null;

        for (Method candidateMethod : visitorClass.getMethods()) {
            if (methodName.equals(candidateMethod.getName())) {
                Class<?>[] paramTypes = candidateMethod.getParameterTypes();
                int paramsCount = paramTypes.length;

                // visitTemplateName(Template)
                if (paramsCount == 1 && paramTypes[0].equals(Template.class)) {
                    method = candidateMethod;
                    break;
                }
            }
        }

        return method;
    }

    /** 查找visitPlaceholder方法。 */
    private Method findVisitPlaceholderMethod(Class<?> visitorClass, String methodName,
                                              PlaceholderParameter[] placeholderParams) throws NoSuchMethodException {
        Method[] methods = visitorClass.getMethods();

        // 统计placeholder参数的类型及数量。
        // Placeholder支持两种类型的参数:String和Template。
        Class<?>[] placeholderParamTypes = new Class[placeholderParams.length];
        int placeholderParamCount = placeholderParamTypes.length;
        int placeholderParamStringCount = 0;
        int placeholderParamTemplateCount = 0;

        for (int i = 0; i < placeholderParamCount; i++) {
            PlaceholderParameter param = placeholderParams[i];

            if (param.isTemplateReference()) {
                placeholderParamTemplateCount++;
                placeholderParamTypes[i] = Template.class;
            } else {
                placeholderParamStringCount++;
                placeholderParamTypes[i] = String.class;
            }
        }

        Method method = null;
        int minIndexWeight = Integer.MAX_VALUE;

        for (Method candidateMethod : methods) {
            if (methodName.equals(candidateMethod.getName())) {
                Class<?>[] methodParamTypes = candidateMethod.getParameterTypes();
                int methodParamCount = methodParamTypes.length;
                boolean paramTypeMatches = false;
                int indexWeight = Integer.MAX_VALUE;

                switch (methodParamCount) {
                    case 0:
                        // visitXyz()
                        paramTypeMatches = true;
                        indexWeight = 100000 * abs(methodParamCount - placeholderParamCount);
                        break;

                    case 1:
                        // 如果placeholder参数全为String,可调用visitXyz(String[])
                        if (placeholderParamStringCount == placeholderParamCount
                            && methodParamTypes[0].equals(String[].class)) {
                            paramTypeMatches = true;
                            indexWeight = 50;
                        }

                        // 如果placeholder参数全为Template,可调用visitXyz(Template[])
                        if (placeholderParamTemplateCount == placeholderParamCount
                            && methodParamTypes[0].equals(Template[].class)) {
                            paramTypeMatches = true;
                            indexWeight = 50;
                        }

                        // 任何placeholder参数,都可调用visitXyz(Object[])
                        if (methodParamTypes[0].equals(Object[].class)) {
                            paramTypeMatches = true;
                            indexWeight = 90;
                        }

                        // no break;

                    default:
                        // 如果placeholder的参数比方法中的参数多,多余的placeholder参数将被忽略。
                        // 如果placeholder的参数比方法中的参数少,不足的参数值将为null。
                        // 访问visitXyz(Template, String, String, Template, ...)
                        if (!paramTypeMatches) {
                            paramTypeMatches = true;

                            for (int i = 0; i < methodParamCount; i++) {
                                if (i < placeholderParamCount) {
                                    if (!methodParamTypes[i].equals(placeholderParamTypes[i])) {
                                        paramTypeMatches = false;
                                        break;
                                    }
                                } else {
                                    if (!methodParamTypes[i].equals(String.class)
                                        && !methodParamTypes[i].equals(Template.class)) {
                                        paramTypeMatches = false;
                                        break;
                                    }
                                }
                            }

                            if (paramTypeMatches) {
                                indexWeight = 100 * abs(methodParamCount - placeholderParamCount);
                            }
                        }

                        break;
                }

                if (paramTypeMatches && indexWeight < minIndexWeight) {
                    minIndexWeight = indexWeight;
                    method = candidateMethod;

                    if (indexWeight == 0) {
                        break;
                    }
                }
            }
        }

        if (method == null) {
            StringBuilder buf = new StringBuilder();
            Formatter format = new Formatter(buf);
            int count = 1;

            buf.append("One of the following method:\n");

            // 方法1. 详细匹配
            format.format("  %d. %s.%s(", count++, visitorClass.getSimpleName(), methodName);

            for (int i = 0; i < placeholderParamCount; i++) {
                if (i > 0) {
                    buf.append(", ");
                }

                buf.append(placeholderParamTypes[i].getSimpleName());
            }

            buf.append(")\n");

            // 方法2. 数组匹配
            format.format("  %d. %s.%s(", count++, visitorClass.getSimpleName(), methodName);

            if (placeholderParamStringCount == placeholderParamCount) {
                buf.append("String");
            } else if (placeholderParamTemplateCount == placeholderParamCount) {
                buf.append("Template");
            } else {
                buf.append("Object");
            }

            buf.append("[])\n");

            // 方法3. 无参数
            if (placeholderParamCount > 0) {
                format.format("  %d. %s.%s()", count++, visitorClass.getSimpleName(), methodName);
            }

            if (buf.charAt(buf.length() - 1) == '\n') {
                buf.setLength(buf.length() - 1);
            }

            throw new NoSuchMethodException(buf.toString());
        }

        return method;
    }

    private Object[] toPlaceholderParameterValues(Placeholder placeholder, Object[] params) {
        for (int i = 0; i < params.length && i < placeholder.params.length; i++) {
            PlaceholderParameter param = placeholder.params[i];

            if (param.isTemplateReference()) {
                params[i] = assertNotNull(param.getTemplateReference());
            } else {
                params[i] = param.getValue();
            }
        }

        return params;
    }

    @Override
    public String toString() {
        if (ref == null) {
            MapBuilder mb = new MapBuilder();

            mb.append("params", params.entrySet());
            mb.append("nodes", nodes);
            mb.append("sub-templates", subtemplates.values());

            return new ToStringBuilder()
                    .format("#%s with %d nodes at %s", name == null ? "(template)" : name, nodes.length, location)
                    .append(mb).toString();
        } else {
            return "ref to " + ref;
        }
    }

    private static void predefineTemplate(String name, String text) {
        Template template = new Template(name, new Node[] { new Text(text, new Location(null, 1, 1)) },
                                         Collections.<String, String>emptyMap(), Collections.<String, Template>emptyMap(), new Location(null,
                                                                                                                                        1, 1));

        predefinedTemplates.put(name, template);
    }

    /** 代表一个结点。 */
    static abstract class Node {
        final Location location;

        public Node(Location location) {
            this.location = assertNotNull(location, "location");
        }

        public abstract String desc();
    }

    /** 代表一个文本结点。 */
    static class Text extends Node {
        final String text;

        public Text(String text, Location location) {
            super(location);
            this.text = assertNotNull(text, "text is null");
        }

        @Override
        public String desc() {
            return "text";
        }

        @Override
        public String toString() {
            String brief;

            if (text.length() < 10) {
                brief = text;
            } else {
                brief = text.substring(0, 10) + "...";
            }

            return String.format("Text with %d characters: %s", text.length(), escapeJava(brief));
        }
    }

    /** 代表一个<code>${var}</code>结点。 */
    static class Placeholder extends Node {
        private final static PlaceholderParameter[] EMPTY_PARAMS = new PlaceholderParameter[0];
        final String name;
        final String paramsString;
        PlaceholderParameter[] params;

        public Placeholder(String name, String paramsString, Location location) {
            super(location);
            this.name = assertNotNull(trimToNull(name), "${missing name}");
            this.paramsString = trimToNull(paramsString);
            this.params = splitParams();
        }

        private PlaceholderParameter[] splitParams() {
            if (paramsString == null) {
                return EMPTY_PARAMS;
            } else {
                String[] paramValues = paramsString.split(",");
                PlaceholderParameter[] params = new PlaceholderParameter[paramValues.length];

                for (int i = 0; i < params.length; i++) {
                    params[i] = new PlaceholderParameter(trimToNull(paramValues[i]));
                }

                return params;
            }
        }

        @Override
        public String desc() {
            if (isEmptyArray(params)) {
                return "${" + name + "}";
            } else {
                return "${" + name + ":" + paramsString + "}";
            }
        }

        @Override
        public String toString() {
            return new ToStringBuilder().format("%s at %s", desc(), location).toString();
        }
    }

    static class PlaceholderParameter {
        private final String   value;
        private       Template templateReference;

        public PlaceholderParameter(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public boolean isTemplateReference() {
            return value != null && value.length() > 1 && value.startsWith("#");
        }

        public String getTemplateName() {
            return isTemplateReference() ? value.substring(1) : null;
        }

        public Template getTemplateReference() {
            return templateReference;
        }

        @Override
        public String toString() {
            return value;
        }
    }

    /** 代表一个<code>$#{template}</code>结点。 */
    static class IncludeTemplate extends Node {
        final String templateName;
        Template includedTemplate;

        public IncludeTemplate(String templateName, Location location) {
            super(location);
            this.templateName = assertNotNull(trimToNull(templateName), "$#{missing template name}");
        }

        @Override
        public String desc() {
            return "$#{" + templateName + "}";
        }

        @Override
        public String toString() {
            return new ToStringBuilder().format("%s at %s", desc(), location).toString();
        }
    }

    static class Location {
        final String systemId;
        final int    lineNumber;
        final int    columnNumber;

        public Location(String systemId, int lineNumber, int columnNumber) {
            this.systemId = trimToNull(systemId);
            this.lineNumber = lineNumber;
            this.columnNumber = columnNumber;
        }

        @Override
        public String toString() {
            return toString(systemId, lineNumber, columnNumber);
        }

        private static String toString(String systemId, int lineNumber, int columnNumber) {
            StringBuilder buf = new StringBuilder();

            if (systemId == null) {
                buf.append("[unknown source]");
            } else {
                buf.append(systemId);
            }

            if (lineNumber > 0) {
                buf.append(": Line ").append(lineNumber);

                if (columnNumber > 0) {
                    buf.append(" Column ").append(columnNumber);
                }
            }

            return buf.toString();
        }
    }

    /** 解析器 */
    private static class Parser {
        private final static Pattern DIRECTIVE_PATTERN = Pattern.compile("\\\\(\\$|\\$#|#|#@|\\\\)" // group 1
                                                                         + "|(\\s*##)" // group 2
                                                                         + "|\\$\\{\\s*([A-Za-z]\\w*)(\\s*:([^\\}]*))?\\s*\\}" // group 3, 4, 5
                                                                         + "|\\$#\\{\\s*([A-Za-z][\\.\\w]*)\\s*\\}" // group 6
                                                                         + "|(\\s*)#([A-Za-z]\\w*)(\\s*\\(\\s*(.*)\\s*\\))?(\\s*(##.*)?)" // group 7, 8, 9, 10, 11, 12
                                                                         + "|(\\s*)#@\\s*([A-Za-z]\\w*)(\\s+(.*?))?(##.*)?$" // group 11, 12, 13, 14, 15
        );

        private final static Set<String> KEYWORDS = createTreeSet("text", "placeholder", "template", "end");

        private final static int INDEX_OF_ESCAPE             = 1;
        private final static int INDEX_OF_COMMENT            = 2;
        private final static int INDEX_OF_PLACEHOLDER        = 3;
        private final static int INDEX_OF_PLACEHOLDER_PARAMS = 5;
        private final static int INDEX_OF_INCLUDE_TEMPLATE   = 6;
        private final static int INDEX_OF_SUBTEMPLATE_PREFIX = 7;
        private final static int INDEX_OF_SUBTEMPLATE        = 8;
        private final static int INDEX_OF_IMPORT_FILE        = 9;
        private final static int INDEX_OF_IMPORT_FILE_NAME   = 10;
        private final static int INDEX_OF_SUBTEMPLATE_SUFFIX = 11;
        private final static int INDEX_OF_PARAM_PREFIX       = 13;
        private final static int INDEX_OF_PARAM              = 14;
        private final static int INDEX_OF_PARAM_VALUE        = 16;

        private final InputSource    source;
        private final BufferedReader reader;
        private final String         systemId;
        private final ParsingTemplateStack stack = new ParsingTemplateStack();
        private final TextBuffer buf;
        private       String     currentLine;
        private int lineNumber = 1;

        public Parser(Reader reader, String systemId, InputSource source) {
            this.source = assertNotNull(source, "input source");
            this.systemId = trimToNull(systemId);

            if (reader instanceof BufferedReader) {
                this.reader = (BufferedReader) reader;
            } else {
                this.reader = new BufferedReader(reader);
            }

            this.buf = new TextBuffer(systemId);
        }

        public ParsingTemplate parse() {
            stack.push(new ParsingTemplate(null, systemId, 0, 0, null));

            for (; nextLine(); lineNumber++) {
                Matcher matcher = DIRECTIVE_PATTERN.matcher(currentLine);
                int index = 0;
                boolean appendNewLine = true;

                while (matcher.find()) {
                    buf.append(currentLine, index, matcher.start(), lineNumber, index + 1);
                    index = matcher.end();

                    // Escaped Char: \x
                    if (matcher.group(INDEX_OF_ESCAPE) != null) {
                        buf.append(matcher.group(INDEX_OF_ESCAPE), lineNumber, matcher.start(INDEX_OF_ESCAPE));
                    }

                    // Comment: ## xxx
                    else if (matcher.group(INDEX_OF_COMMENT) != null) {
                        index = currentLine.length(); // 忽略当前行后面所有内容

                        if (matcher.start(INDEX_OF_COMMENT) == 0) {
                            appendNewLine = false; // 如果注释是从行首开始,则忽略掉整行
                        }

                        break; // ignore the rest of line
                    }

                    // Param: #@param value
                    else if (matcher.group(INDEX_OF_PARAM) != null) {
                        pushTextNode();

                        String name = matcher.group(INDEX_OF_PARAM);

                        // #@前面只允许有空白(只允许从行首开始定义),否则报错。
                        if (matcher.start() > 0) {
                            throw new TemplateParseException("#@" + name
                                                             + " should start at new line, which is now at "
                                                             + Location.toString(systemId, lineNumber, matcher.end(INDEX_OF_PARAM_PREFIX) + 1));
                        }

                        String value = trimToEmpty(matcher.group(INDEX_OF_PARAM_VALUE));

                        stack.peek().addParam(name, value, lineNumber, matcher.end(INDEX_OF_PARAM_PREFIX) + 1);

                        // 忽略本行
                        appendNewLine = false;
                    }

                    // Placeholder: ${var}
                    else if (matcher.group(INDEX_OF_PLACEHOLDER) != null) {
                        pushTextNode();

                        String name = matcher.group(INDEX_OF_PLACEHOLDER);
                        String paramsString = matcher.group(INDEX_OF_PLACEHOLDER_PARAMS);
                        Location location = new Location(systemId, lineNumber, matcher.start() + 1);

                        checkName(name, location);

                        stack.peek().addNode(new Placeholder(name, paramsString, location));
                    }

                    // Include template: $#{template}
                    else if (matcher.group(INDEX_OF_INCLUDE_TEMPLATE) != null) {
                        pushTextNode();

                        String templateName = matcher.group(INDEX_OF_INCLUDE_TEMPLATE);
                        Location location = new Location(systemId, lineNumber, matcher.start() + 1);

                        stack.peek().addNode(new IncludeTemplate(templateName, location));
                    }

                    // Sub-template: #template
                    else if (matcher.group(INDEX_OF_SUBTEMPLATE) != null) {
                        String name = matcher.group(INDEX_OF_SUBTEMPLATE);

                        // #前面只允许有空白(只允许从行首开始定义),否则报错。
                        if (matcher.start() > 0) {
                            throw new TemplateParseException("#"
                                                             + name
                                                             + " should start at new line, which is now at "
                                                             + Location.toString(systemId, lineNumber,
                                                                                 matcher.end(INDEX_OF_SUBTEMPLATE_PREFIX) + 1));
                        }

                        // #xxx必须独占一行
                        if (matcher.end(INDEX_OF_SUBTEMPLATE_SUFFIX) < currentLine.length()) {
                            throw new TemplateParseException("Invalid content followed after #"
                                                             + name
                                                             + " at "
                                                             + Location.toString(systemId, lineNumber,
                                                                                 matcher.end(INDEX_OF_SUBTEMPLATE_SUFFIX) + 1));
                        }

                        pushTextNode();

                        // #end of sub-template
                        if ("end".equals(name)) {
                            // #end后跟()
                            if (matcher.group(INDEX_OF_IMPORT_FILE) != null) {
                                throw new TemplateParseException("Invalid character '(' after #end tag at "
                                                                 + Location.toString(systemId, lineNumber, matcher.start(INDEX_OF_IMPORT_FILE)
                                                                                                           + matcher.group(INDEX_OF_IMPORT_FILE).indexOf("(") + 1));
                            }

                            // #end没有对应的#template
                            else if (stack.size() <= 1) {
                                throw new TemplateParseException("Unmatched #end tag at "
                                                                 + Location.toString(systemId, lineNumber,
                                                                                     matcher.end(INDEX_OF_SUBTEMPLATE_PREFIX) + 1));
                            }

                            // #end
                            else {
                                Template subTemplate = stack.pop();
                                stack.peek().addSubTemplate(subTemplate);
                            }
                        }

                        // start sub-template
                        else {
                            int columnNumber = matcher.end(INDEX_OF_SUBTEMPLATE_PREFIX) + 1;

                            checkName(name, new Location(systemId, lineNumber, columnNumber));

                            // 从另一个文件中读取子模板
                            if (matcher.group(INDEX_OF_IMPORT_FILE) != null) {
                                String importedFileName = trimToNull(trim(
                                        trimToEmpty(matcher.group(INDEX_OF_IMPORT_FILE_NAME)), "\""));

                                int importedFileColumnNumber = matcher.start(INDEX_OF_IMPORT_FILE_NAME) + 1;

                                if (importedFileName == null) {
                                    throw new TemplateParseException("Import file name is not specified at "
                                                                     + Location.toString(systemId, lineNumber, importedFileColumnNumber));
                                }

                                InputSource importedSource = null;
                                Exception e = null;

                                try {
                                    importedSource = source.getRelative(importedFileName);
                                } catch (Exception ee) {
                                    e = ee;
                                }

                                if (importedSource == null || e != null) {
                                    throw new TemplateParseException("Could not import template file \""
                                                                     + importedFileName + "\" at "
                                                                     + Location.toString(systemId, lineNumber, importedFileColumnNumber), e);
                                }

                                Template importedTemplate;

                                try {
                                    importedTemplate = new Template(importedSource, name);
                                } catch (Exception ee) {
                                    throw new TemplateParseException("Could not import template file \""
                                                                     + importedFileName + "\" at "
                                                                     + Location.toString(systemId, lineNumber, importedFileColumnNumber), ee);
                                }

                                stack.peek().addSubTemplate(importedTemplate);
                            }

                            // 开始一个新模板
                            else {
                                stack.push(new ParsingTemplate(name, systemId, lineNumber, columnNumber,
                                                               stack.peek().params));
                            }
                        }

                        // 忽略本行
                        appendNewLine = false;
                    } else {
                        unreachableCode();
                    }
                }

                buf.append(currentLine, index, currentLine.length(), lineNumber, index + 1);

                if (appendNewLine) {
                    buf.newLine();
                }
            }

            pushTextNode();

            if (stack.size() > 1) {
                StringBuilder buf = new StringBuilder("Unclosed tags: ");

                while (stack.size() > 1) {
                    buf.append("#").append(stack.pop().getName());

                    if (stack.size() > 1) {
                        buf.append(", ");
                    }
                }

                buf.append(" at ").append(Location.toString(systemId, lineNumber, 0));

                throw new TemplateParseException(buf.toString());
            }

            assertTrue(stack.size() == 1);

            ParsingTemplate parsingTemplate = stack.peek();

            postProcessParsingTemplate(parsingTemplate);

            return parsingTemplate;
        }

        private void pushTextNode() {
            Text node = buf.toText();
            buf.clear();

            if (node != null) {
                stack.peek().addNode(node);
            }
        }

        private boolean nextLine() {
            try {
                currentLine = reader.readLine();
            } catch (IOException e) {
                throw new TemplateParseException("Reading error at " + Location.toString(systemId, lineNumber, 0), e);
            }

            return currentLine != null;
        }

        private void checkName(String name, Object location) {
            if (KEYWORDS.contains(name.toLowerCase())) {
                throw new TemplateParseException("Reserved name: " + name + " at " + location);
            }
        }

        /**
         * 后期处理。
         * <ol>
         * <li>检查include template。</li>
         * <li>检查placeholder中的template reference。</li>
         * <li>根据trimming和whitespace参数的值,处理模板中的空白和换行。</li>
         * </ol>
         */
        private void postProcessParsingTemplate(ParsingTemplate parsingTemplate) {
            LinkedList<Map<String, Template>> templateStack = createLinkedList();

            templateStack.addFirst(parsingTemplate.subtemplates);

            for (Node node : parsingTemplate.nodes) {
                postProcessNode(node, templateStack);
            }

            chomp(parsingTemplate.nodes);
            trimIfNeccessary(parsingTemplate.nodes, parsingTemplate.params);
            collapseWhitespacesIfNeccessary(parsingTemplate.nodes, parsingTemplate.params);

            for (Template subTemplate : parsingTemplate.subtemplates.values()) {
                postProcessTemplate(subTemplate, templateStack);
            }

            templateStack.removeFirst();
        }

        private void postProcessTemplate(Template template, LinkedList<Map<String, Template>> templateStack) {
            templateStack.addFirst(template.subtemplates);

            // 处理nodes
            for (Node node : template.nodes) {
                postProcessNode(node, templateStack);
            }

            LinkedList<Node> nodes = createLinkedList(template.nodes);

            chomp(nodes);
            trimIfNeccessary(nodes, template.params);
            collapseWhitespacesIfNeccessary(nodes, template.params);

            template.nodes = nodes.toArray(new Node[nodes.size()]);

            // 递归处理子模板
            for (Template subTemplate : template.subtemplates.values()) {
                postProcessTemplate(subTemplate, templateStack);
            }

            templateStack.removeFirst();
        }

        private void postProcessNode(Node node, LinkedList<Map<String, Template>> templateStack) {
            // $#{includeTemplate}
            if (node instanceof IncludeTemplate) {
                ((IncludeTemplate) node).includedTemplate = new Template(findTemplate(
                        ((IncludeTemplate) node).templateName, templateStack, node.location, "Included")); // create template ref
            }

            // ${placeholder: #templateRef}
            if (node instanceof Placeholder && !isEmptyArray(((Placeholder) node).params)) {
                List<PlaceholderParameter> expandedParameters = createLinkedList();

                for (PlaceholderParameter param : ((Placeholder) node).params) {
                    if (param.isTemplateReference()) {
                        String templateName = param.getTemplateName();
                        String parentName;

                        if (templateName.equals("*") || templateName.endsWith(".*")) {
                            Map<String, Template> subtemplates;

                            if (templateName.equals("*")) {
                                subtemplates = templateStack.getFirst();
                                parentName = EMPTY_STRING;
                            } else {
                                String parentTemplateName = templateName.substring(0,
                                                                                   templateName.length() - ".*".length());

                                Template parentTemplate = findTemplate(parentTemplateName, templateStack,
                                                                       ((Placeholder) node).location, "Referenced");

                                subtemplates = parentTemplate.subtemplates;
                                parentName = parentTemplateName + ".";
                            }

                            for (Template template : subtemplates.values()) {
                                PlaceholderParameter newParam = new PlaceholderParameter("#" + parentName
                                                                                         + template.getName());
                                newParam.templateReference = new Template(template); // create template ref
                                expandedParameters.add(newParam);
                            }
                        } else {
                            param.templateReference = new Template(findTemplate(templateName, templateStack,
                                                                                ((Placeholder) node).location, "Referenced")); // create template ref

                            expandedParameters.add(param);
                        }
                    } else {
                        expandedParameters.add(param);
                    }
                }

                ((Placeholder) node).params = expandedParameters.toArray(new PlaceholderParameter[expandedParameters
                        .size()]);
            }
        }

        private Template findTemplate(String templateName, LinkedList<Map<String, Template>> templateStack,
                                      Location location, String messagePrefix) {
            String[] parts = split(templateName, ".");
            Template template = null;

            if (parts.length >= 1) {
                for (Map<String, Template> templates : templateStack) {
                    template = templates.get(parts[0]);

                    if (template != null) {
                        break;
                    }
                }

                // 在predefined templates中找
                if (template == null) {
                    template = predefinedTemplates.get(parts[0]);
                }

                // 取子模板
                for (int i = 1; i < parts.length && template != null; i++) {
                    template = template.getSubTemplate(parts[i]);
                }
            }

            if (template == null) {
                throw new TemplateParseException(messagePrefix + " template " + templateName
                                                 + " is not found in the context around " + location);
            }

            return template;
        }

        /** 除去template结尾的换行。 */
        private void chomp(LinkedList<Node> nodes) {
            if (!nodes.isEmpty() && nodes.getLast() instanceof Text) {
                Text node = (Text) nodes.getLast();
                String text = node.text;

                if (text.endsWith("\n")) {
                    text = text.substring(0, text.length() - 1);
                }

                if (isEmpty(text)) {
                    nodes.removeLast();
                } else {
                    nodes.set(nodes.size() - 1, new Text(text, node.location));
                }
            }
        }

        /** 去除每行的首尾空白,去除模板首尾的空白和换行符。 */
        private boolean trimIfNeccessary(LinkedList<Node> nodes, Map<String, String> params) {
            boolean trimming = getBoolean(params.get("trimming"), false, "on", "yes", "true");

            if (!trimming) {
                return false;
            }

            // 去除模板开头的空白。
            if (!nodes.isEmpty() && nodes.getFirst() instanceof Text) {
                Text firstNode = (Text) nodes.getFirst();
                String text = trimStart(firstNode.text);

                if (isEmpty(text)) {
                    nodes.removeFirst();
                } else {
                    nodes.set(0, new Text(text, firstNode.location));
                }
            }

            // 去除模板末尾的空白。
            if (!nodes.isEmpty() && nodes.getLast() instanceof Text) {
                Text lastNode = (Text) nodes.getLast();
                String text = trimEnd(lastNode.text);

                if (isEmpty(text)) {
                    nodes.removeLast();
                } else {
                    nodes.set(nodes.size() - 1, new Text(text, lastNode.location));
                }
            }

            // 去除每行首尾的空白
            boolean startOfLine = true;
            boolean endOfLine;

            for (ListIterator<Node> i = nodes.listIterator(); i.hasNext(); ) {
                Node node = i.next();
                endOfLine = !i.hasNext();

                if (!(node instanceof Text)) {
                    startOfLine = false;
                    continue;
                }

                String text = ((Text) node).text;
                StringBuilder buf = new StringBuilder(text.length());

                int start = 0;
                for (int pos = text.indexOf("\n"); pos >= 0 && pos < text.length(); start = pos + 1, pos = text
                        .indexOf("\n", start)) {
                    String line = text.substring(start, pos);

                    if (startOfLine) {
                        line = trim(line);
                    } else {
                        line = trimEnd(line);
                    }

                    buf.append(line).append("\n");
                    startOfLine = true;
                }

                String line = text.substring(start);

                if (startOfLine && endOfLine) {
                    line = trim(line);
                } else if (startOfLine) {
                    line = trimStart(line);
                } else if (endOfLine) {
                    line = trimEnd(line);
                }

                buf.append(line);

                i.set(new Text(buf.toString(), node.location));
            }

            return true;
        }

        /** 将多个空白变成一个空白;如果多个空白中包含换行符,则转换成一个换行符。 */
        private boolean collapseWhitespacesIfNeccessary(LinkedList<Node> nodes, Map<String, String> params) {
            boolean collapseWhitespaces = getBoolean(params.get("whitespace"), false, "collapse");

            if (!collapseWhitespaces) {
                return false;
            }

            for (ListIterator<Node> i = nodes.listIterator(); i.hasNext(); ) {
                Node node = i.next();

                if (node instanceof Text) {
                    char[] cs = ((Text) node).text.toCharArray();
                    StringBuilder buf = new StringBuilder(cs.length);
                    boolean ws = false;
                    boolean newline = false;

                    for (char c : cs) {
                        if (c == '\n') {
                            newline = true;
                        } else if (Character.isWhitespace(c)) {
                            ws = true;
                        } else {
                            if (newline) {
                                buf.append('\n');
                            } else if (ws) {
                                buf.append(' ');
                            }

                            ws = false;
                            newline = false;
                            buf.append(c);
                        }
                    }

                    if (newline) {
                        buf.append('\n');
                    } else if (ws) {
                        buf.append(' ');
                    }

                    i.set(new Text(buf.toString(), node.location));
                }
            }

            return true;
        }

        private boolean getBoolean(String value, boolean defaultValue, String... specificValues) {
            if (!isEmpty(value) && !isEmptyArray(specificValues)) {
                for (String specificValue : specificValues) {
                    if (value.equalsIgnoreCase(specificValue)) {
                        return !defaultValue;
                    }
                }
            }

            return defaultValue;
        }
    }

    /** 保存纯文本内容,并记录第一个非空字符的行列号。 */
    private static class TextBuffer {
        private final StringBuilder buf = new StringBuilder();
        private final String systemId;
        private int lineNumber   = -1;
        private int columnNumber = -1;

        public TextBuffer(String systemId) {
            this.systemId = systemId;
        }

        public void append(String s, int lineNumber, int columnNumber) {
            append(s, 0, s.length(), lineNumber, columnNumber);
        }

        public void append(CharSequence s, int start, int end, int lineNumber, int columnNumber) {
            buf.append(s, start, end);

            if (this.lineNumber == -1 || this.columnNumber == -1) {
                for (int i = start; i < end; i++) {
                    char c = s.charAt(i);

                    if (Character.isWhitespace(c)) {
                        columnNumber++;
                    } else {
                        this.lineNumber = lineNumber;
                        this.columnNumber = columnNumber;
                        break;
                    }
                }
            }
        }

        public void newLine() {
            buf.append("\n");
        }

        public void clear() {
            buf.setLength(0);
            lineNumber = -1;
            columnNumber = -1;
        }

        public Text toText() {
            if (buf.length() > 0) {
                return new Text(buf.toString(), new Location(systemId, lineNumber, columnNumber));
            }

            return null;
        }
    }

    private static class ParsingTemplateStack {
        private final LinkedList<ParsingTemplate> stack = createLinkedList();

        public void push(ParsingTemplate pt) {
            ParsingTemplate currentParsingTemplate = peek();

            if (currentParsingTemplate != null) {
                Template duplicatedTemplate = currentParsingTemplate.getSubTemplate(pt.name);

                if (duplicatedTemplate != null) {
                    throw new TemplateParseException("Duplicated template name #" + pt.name + " at "
                                                     + Location.toString(pt.systemId, pt.lineNumber, pt.columnNumber)
                                                     + ".  Another template with the same name is located in " + duplicatedTemplate.location);
                }
            }

            stack.addLast(pt);
        }

        public ParsingTemplate peek() {
            if (stack.isEmpty()) {
                return null;
            } else {
                return stack.getLast();
            }
        }

        public int size() {
            return stack.size();
        }

        public Template pop() {
            return stack.removeLast().toTemplate();
        }

        @Override
        public String toString() {
            return new ToStringBuilder().append(stack).toString();
        }
    }

    private static class ParsingTemplate {
        private final String systemId;
        private final int    lineNumber;
        private final int    columnNumber;
        private final String name;
        private final LinkedList<Node>      nodes        = createLinkedList();
        private final Map<String, String>   params       = createTreeMap();
        private final Map<String, Template> subtemplates = createArrayHashMap();

        public ParsingTemplate(String name, String systemId, int lineNumber, int columnNumber,
                               Map<String, String> parentParams) {
            this.name = name;
            this.systemId = systemId;
            this.lineNumber = lineNumber;
            this.columnNumber = columnNumber;

            if (parentParams != null) {
                this.params.putAll(parentParams);
            }
        }

        public void addNode(Node node) {
            if (node != null) {
                if (!subtemplates.isEmpty()) {
                    // 在sub templates被定义以后,不能再增加任何nodes,否则报错。
                    // 特殊情况:对于text node,如果未包含可见字符,则不报错,只是安静地丢弃。
                    if (node.location.lineNumber > 0) {
                        throw new TemplateParseException("Invalid " + node.desc() + " here at " + node.location);
                    }
                } else {
                    if (node instanceof Text && !nodes.isEmpty() && nodes.getLast() instanceof Text) {
                        // 合并text node
                        Text lastNode = (Text) nodes.removeLast();
                        Text thisNode = (Text) node;
                        Location location;

                        if (lastNode.location.lineNumber > 0) {
                            location = lastNode.location;
                        } else {
                            location = node.location;
                        }

                        nodes.add(new Text(lastNode.text + thisNode.text, location));
                    } else {
                        nodes.add(node);
                    }
                }
            }
        }

        public void addParam(String name, String value, int lineNumber, int columnNumber) {
            if (!subtemplates.isEmpty() || hasNonEmptyNode()) {
                throw new TemplateParseException("Invalid #@" + name + " here at "
                                                 + Location.toString(systemId, lineNumber, columnNumber));
            }

            params.put(name, trimToEmpty(value));
        }

        public void addSubTemplate(Template template) {
            subtemplates.put(template.getName(), template);
        }

        public Template getSubTemplate(String name) {
            return subtemplates.get(name);
        }

        private boolean hasNonEmptyNode() {
            for (Node node : nodes) {
                if (node.location.lineNumber > 0) {
                    return true;
                }
            }

            return false;
        }

        public Template toTemplate() {
            return new Template(name, nodes.toArray(new Node[nodes.size()]), params, subtemplates, new Location(
                    systemId, lineNumber, columnNumber));
        }

        public void updateTemplate(Template template) {
            template.update(nodes.toArray(new Node[nodes.size()]), params, subtemplates);
        }

        @Override
        public String toString() {
            MapBuilder mb = new MapBuilder();

            mb.append("name", name);
            mb.append("systemId", systemId);
            mb.append("lineNumber", lineNumber);
            mb.append("columnNumber", columnNumber);
            mb.append("nodes", nodes);
            mb.append("params", params);
            mb.append("sub-templates", subtemplates);

            return new ToStringBuilder().append("Template").append(mb).toString();
        }
    }

    /** 保存文件来源,必要时重装模板。 */
    static class InputSource {
        private final static Pattern CHARSET_DETECTIVE_PATTERN = Pattern.compile("^(\\s*##)" // comment
                                                                                 + "|^(\\s*)#@\\s*(\\w+)\\s+(.*?)\\s*(##.*)?$" // charset或其它参数
                                                                                 + "|^\\s*$" // empty line
        );

        private long lastModified = 0;
        final String systemId;
        Object source;

        public InputSource(File source) {
            this(source, null);
        }

        public InputSource(URL source) {
            this(source, null);
        }

        public InputSource(InputStream source, String systemId) {
            this((Object) source, systemId);
        }

        public InputSource(Reader source, String systemId) {
            this((Object) source, systemId);
        }

        private InputSource(Object source, String systemId) {
            assertNotNull(source, "source");

            if (source instanceof URL) {
                try {
                    this.source = new File(((URL) source).toURI().normalize()); // convert URL to File
                } catch (Exception e) {
                    this.source = source;
                }
            } else if (source instanceof File) {
                this.source = new File(((File) source).toURI().normalize()); // remove ../
            } else {
                this.source = source;
            }

            if (this.source instanceof URL) {
                this.systemId = ((URL) this.source).toExternalForm();
            } else if (this.source instanceof File) {
                this.systemId = ((File) this.source).toURI().toString();
            } else {
                this.systemId = trimToNull(systemId);
            }
        }

        private void reloadIfNecessary(Template template) {
            assertNotNull(template, "template");
            assertTrue(template.source == this);

            boolean doLoad = false;

            if (template.nodes == null) {
                doLoad = true;
            } else if (source instanceof File && ((File) source).lastModified() != this.lastModified) {
                doLoad = true;
            }

            if (doLoad) {
                Reader reader;

                try {
                    reader = getReader();
                } catch (IOException e) {
                    throw new TemplateParseException(e);
                }

                new Parser(reader, systemId, this).parse().updateTemplate(template);

                if (source instanceof File) {
                    this.lastModified = ((File) source).lastModified();
                }
            }
        }

        /** 根据指定的相对于当前source的路径,取得input source。 */
        InputSource getRelative(String relativePath) throws Exception {
            relativePath = trimToNull(relativePath);

            if (relativePath != null) {
                String sourceURI = null;

                if (source instanceof File) {
                    sourceURI = ((File) source).toURI().toString();
                } else if (source instanceof URL) {
                    sourceURI = ((URL) source).toExternalForm();
                }

                if (sourceURI != null) {
                    return new InputSource(new URL(FileUtil.resolve(sourceURI, relativePath)));
                }
            }

            return null;
        }

        Reader getReader() throws IOException {
            Reader reader;

            if (source instanceof Reader) {
                reader = (Reader) source;
                source = null; // clear source
            } else {
                BufferedInputStream istream = null;

                if (source instanceof File) {
                    istream = new BufferedInputStream(new FileInputStream((File) source));
                } else if (source instanceof URL) {
                    try {
                        source = new File(((URL) source).toURI());
                        istream = new BufferedInputStream(new FileInputStream((File) source));
                    } catch (IllegalArgumentException e) {
                    } catch (URISyntaxException e) {
                    }

                    if (istream == null) {
                        istream = new BufferedInputStream(((URL) source).openStream());
                    }
                } else if (source instanceof InputStream) {
                    istream = new BufferedInputStream((InputStream) source);
                    source = null; // clear source
                } else {
                    throw new IllegalStateException("Unknown source: " + source);
                }

                String charset = detectCharset(istream, "UTF-8");
                reader = new InputStreamReader(istream, charset);
            }

            return reader;
        }

        /** 读取输入流的前几行,查找<code>#@charset</code>参数。如果未找到,则返回指定默认值。 */
        static String detectCharset(BufferedInputStream istream, String defaultCharset) throws IOException {
            int readlimit = 1024 * 4;
            istream.mark(readlimit);

            StringBuilder buf = new StringBuilder(readlimit);

            try {
                int c;
                for (int i = 0; i < readlimit && (c = istream.read()) != -1; i++) {
                    if (c == '\r' || c == '\n') {
                        String line = buf.toString();
                        buf.setLength(0);

                        Matcher matcher = CHARSET_DETECTIVE_PATTERN.matcher(line);

                        if (matcher.find()) {
                            String charset = null;

                            if ("charset".equals(matcher.group(3))) {
                                charset = matcher.group(4);
                            }

                            // 如果找到#@charset则返回。
                            // 忽略charset前的空行、注释和其它参数。
                            if (charset != null) {
                                return charset;
                            }
                        } else {
                            // 碰到第一行非注释、非空行、非#@param,则立即返回。
                            break;
                        }
                    } else {
                        buf.append((char) c);
                    }
                }
            } finally {
                istream.reset();
            }

            return defaultCharset;
        }
    }
}
TOP

Related Classes of com.alibaba.citrus.util.templatelite.Template$InputSource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.