/*
* 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;
}
}
}