Merge from existing repo
This commit is contained in:
parent
649512ba39
commit
99c7704f83
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
classes/*
|
||||
doc/*
|
30
LICENSE
Normal file
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2009,2011 Christopher Ramey
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither the name of the picotemplate nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
112
README.md
Normal file
112
README.md
Normal file
@ -0,0 +1,112 @@
|
||||
picotemplate
|
||||
============
|
||||
|
||||
`picotemplate` is a simple, tiny C-Template workalike written in java
|
||||
with the intention of being as high performance as possible.
|
||||
`picotemplate` is very small, only three class files and is BSD licensed.
|
||||
|
||||
basic usage
|
||||
-----------
|
||||
|
||||
First, create your template:
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
This is my template. My favorite food is {{FOOD}}.
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Import the required classes:
|
||||
```java
|
||||
import com.binarythought.picotemplate.Template;
|
||||
import com.binarythought.picotemplate.TemplateDictionary;
|
||||
```
|
||||
|
||||
|
||||
Create your template and template dictionary:
|
||||
```java
|
||||
Template template = new Template(new File("mytemplate.tpl"));
|
||||
TemplateDictionary dict = new TemplateDictionary();
|
||||
```
|
||||
|
||||
|
||||
Assign a value to the "food" variable (Unassigned variables are not shown):
|
||||
```java
|
||||
dict.put("food", "cookies");
|
||||
```
|
||||
|
||||
|
||||
And parse your template:
|
||||
```java
|
||||
String result = template.parse(dict);
|
||||
```
|
||||
|
||||
|
||||
And the result:
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
This is my template. My favorite food is cookies.
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
advanced usage
|
||||
--------------
|
||||
|
||||
picotemplate can selectively show areas of static content, called "sections".
|
||||
It can also loop over these sections using child dictionaries. Consider the
|
||||
following example:
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
{{FAVORITE_SHOW}} is probably my favorite show.
|
||||
{{#GOODSHOWS}}
|
||||
{{SHOW}} is pretty good, too..
|
||||
{{/GOODSHOWS}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Create your template and template dictionary as usual:
|
||||
```java
|
||||
Template template = new Template(new File("mytemplate.tpl"));
|
||||
TemplateDictionary dict = new TemplateDictionary();
|
||||
```
|
||||
|
||||
|
||||
Define our favorite show:
|
||||
```java
|
||||
dict.put("favorite_show", "Happy Days");
|
||||
```
|
||||
|
||||
Now show the section called "goodshows" (Sections are by default hidden, and
|
||||
must be explicitly told to be shown):
|
||||
```java
|
||||
dict.show("goodshows");
|
||||
```
|
||||
|
||||
And add some shows for it to loop over:
|
||||
```java
|
||||
TemplateDictionary child1 = dict.createChild("goodshows");
|
||||
child1.put("show", "M.A.S.H");
|
||||
TemplateDictionary child2 = dict.createChild("goodshows");
|
||||
child2.put("show", "A-Team");
|
||||
```
|
||||
|
||||
|
||||
And the result:
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
Happy Days is probably my favorite show.
|
||||
|
||||
M.A.S.H is pretty good, too..
|
||||
|
||||
A-Team is pretty good, too..
|
||||
</body>
|
||||
</html>
|
||||
```
|
46
build.xml
Normal file
46
build.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="picotemplate" default="compile">
|
||||
<property name="version.num" value="1.0" />
|
||||
|
||||
<target name="clean">
|
||||
<delete file="${ant.project.name}-${version.num}.jar" />
|
||||
<delete includeemptydirs="true">
|
||||
<fileset dir="doc" includes="**/*" />
|
||||
<fileset dir="classes" includes="**/*" />
|
||||
</delete>
|
||||
</target>
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="classes" />
|
||||
<javac includeantruntime="false" target="1.5"
|
||||
srcdir="src" destdir="classes" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<delete file="${ant.project.name}-${version.num}.jar" />
|
||||
<jar destfile="${ant.project.name}-${version.num}.jar">
|
||||
<fileset dir="classes">
|
||||
<exclude name="**/example/**" />
|
||||
</fileset>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="run" depends="compile">
|
||||
<java classname="com.binarythought.picotemplate.example.TemplateExample"
|
||||
fork="true" maxmemory="64M">
|
||||
<classpath>
|
||||
<dirset dir="classes" />
|
||||
</classpath>
|
||||
</java>
|
||||
</target>
|
||||
|
||||
<target name="doc">
|
||||
<mkdir dir="doc" />
|
||||
<javadoc destdir="doc">
|
||||
<fileset dir="src">
|
||||
<exclude name="**/example/**" />
|
||||
</fileset>
|
||||
</javadoc>
|
||||
</target>
|
||||
|
||||
</project>
|
10
res/test.html
Normal file
10
res/test.html
Normal file
@ -0,0 +1,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Pico Template</title>
|
||||
</head>
|
||||
<body>
|
||||
This is my {{OBJECT1}}, this is my gun.<br/>
|
||||
{{#SECTION}} This is for {{OBJECT2}}!<br/>
|
||||
{{/SECTION}}
|
||||
</body>
|
||||
</html>
|
184
src/com/binarythought/picotemplate/Template.java
Normal file
184
src/com/binarythought/picotemplate/Template.java
Normal file
@ -0,0 +1,184 @@
|
||||
package com.binarythought.picotemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
import java.io.FileReader;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Template is the base class for picotemplate. It it used to generate
|
||||
* html or text from a supplied template and dictionary.
|
||||
* <p>Basic usage:
|
||||
* <p><code>Template template = new Template("I like {{FOOD}}.");<br>
|
||||
* TemplateDictionary dictionary = new TemplateDictionary();<br>
|
||||
* dictionary.put("food", "cookies");<br>
|
||||
* String result = template.parse(dictionary);</code>
|
||||
* <p>Value of result : <code>I like cookies.</code>
|
||||
*/
|
||||
public class Template {
|
||||
private static final Pattern pattern = Pattern.compile(
|
||||
"\\{\\{(#|/){0,1}(\\w+)\\}\\}", Pattern.CANON_EQ
|
||||
);
|
||||
|
||||
private TemplateNode parsedTemplate[];
|
||||
private String originalTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate and compile a new template with the template provided.
|
||||
* @param template String containing the template.
|
||||
*/
|
||||
public Template(String template) throws Exception
|
||||
{
|
||||
this.originalTemplate = template;
|
||||
this.parsedTemplate = initTemplate(template);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate and compile a new template with the template provided.
|
||||
* @param file File containing the template.
|
||||
*/
|
||||
public Template(File file) throws Exception
|
||||
{
|
||||
if(file == null || !file.canRead()){
|
||||
throw new Exception("Cannot read "+file.getName());
|
||||
}
|
||||
|
||||
FileReader reader = new FileReader(file);
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
char buf[] = new char[1024];
|
||||
for(int i=0; (i = reader.read(buf)) != -1;){
|
||||
b.append(buf, 0, i);
|
||||
}
|
||||
|
||||
reader.close();
|
||||
this.originalTemplate = b.toString();
|
||||
this.parsedTemplate = initTemplate(this.originalTemplate);
|
||||
}
|
||||
|
||||
|
||||
private static TemplateNode[] initTemplate(String template) throws Exception
|
||||
{
|
||||
ArrayList<TemplateNode> parsedTemplate = new ArrayList<TemplateNode>();
|
||||
Stack<String> sections = new Stack<String>();
|
||||
Matcher match = pattern.matcher(template);
|
||||
int lastpos = 0;
|
||||
|
||||
while(match.find()){
|
||||
String segment = template.substring(lastpos,match.start());
|
||||
if(segment.length() > 0){
|
||||
parsedTemplate.add(new TemplateNode(TemplateNode.PLAIN, segment));
|
||||
}
|
||||
|
||||
if("#".equals(match.group(1))){
|
||||
sections.push(match.group(2));
|
||||
parsedTemplate.add(
|
||||
new TemplateNode(TemplateNode.SECTION_START, match.group(2))
|
||||
);
|
||||
} else if("/".equals(match.group(1))){
|
||||
parsedTemplate.add(
|
||||
new TemplateNode(TemplateNode.SECTION_END, match.group(2))
|
||||
);
|
||||
if(sections.empty() || !sections.pop().equals(match.group(2))){
|
||||
throw new Exception("Out of turn section termation at "+match.start());
|
||||
}
|
||||
} else {
|
||||
parsedTemplate.add(
|
||||
new TemplateNode(TemplateNode.VARIABLE, match.group(2))
|
||||
);
|
||||
}
|
||||
lastpos = match.end();
|
||||
}
|
||||
|
||||
String segment = template.substring(lastpos, template.length());
|
||||
if(segment.length() > 0){
|
||||
parsedTemplate.add(new TemplateNode(TemplateNode.PLAIN, segment));
|
||||
}
|
||||
|
||||
if(!sections.empty()){
|
||||
throw new Exception("Unterminated section in template");
|
||||
}
|
||||
|
||||
return parsedTemplate.toArray(new TemplateNode[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse this template with the provided dictionary and output it to a string.
|
||||
* <p><b>This method is threadsafe.</b>
|
||||
* @param dict Template dictionary to parse against.
|
||||
* @return String containing the parsed result of the template.
|
||||
*/
|
||||
public String parse(TemplateDictionary dict)
|
||||
{
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
if(dict != null){ parseTemplate(dict, b, parsedTemplate, 0); }
|
||||
else { parseTemplate(b, parsedTemplate); }
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
||||
private static int parseTemplate(StringBuilder output, TemplateNode template[])
|
||||
{
|
||||
for(int i = 0; i < template.length; i++){
|
||||
if(template[i].getNodeType() == TemplateNode.PLAIN){
|
||||
output.append(template[i].getContent());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private static int parseTemplate(TemplateDictionary dict, StringBuilder output, TemplateNode template[], int i)
|
||||
{
|
||||
for(;i < template.length; i++){
|
||||
switch(template[i].getNodeType()){
|
||||
case TemplateNode.PLAIN:
|
||||
output.append(template[i].getContent());
|
||||
break;
|
||||
|
||||
case TemplateNode.VARIABLE:
|
||||
output.append(dict.get(template[i].getContent()));
|
||||
break;
|
||||
|
||||
case TemplateNode.SECTION_START:
|
||||
if(dict.isShown(template[i].getContent())){
|
||||
List<TemplateDictionary> l = (
|
||||
dict.getParent() == null ? dict.getChild(template[i].getContent()) :
|
||||
dict.getParent().getChild(template[i].getContent())
|
||||
);
|
||||
|
||||
if(l == null){
|
||||
i = parseTemplate(dict, output, template, i+1);
|
||||
} else {
|
||||
int last = 0;
|
||||
for(TemplateDictionary d : l){
|
||||
last = parseTemplate(d, output, template, i+1);
|
||||
}
|
||||
i = last;
|
||||
}
|
||||
} else {
|
||||
for(int c=1; c > 0;){
|
||||
i++;
|
||||
switch(template[i].getNodeType()){
|
||||
case TemplateNode.SECTION_START: c++; break;
|
||||
case TemplateNode.SECTION_END: c--; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TemplateNode.SECTION_END: return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
162
src/com/binarythought/picotemplate/TemplateDictionary.java
Normal file
162
src/com/binarythought/picotemplate/TemplateDictionary.java
Normal file
@ -0,0 +1,162 @@
|
||||
package com.binarythought.picotemplate;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Collections;
|
||||
|
||||
|
||||
/**
|
||||
* TemplateDictionary is used to assign variables and show sections
|
||||
* inside templates. It is functionally similar to a HashMap, although
|
||||
* not a decendent. <b>All methods that use identifying keys under this object
|
||||
* are case-insensitive.</b> This means that "MY_VAR" is the same as "my_var"
|
||||
* for both section names and variable names.
|
||||
* <p>At the most basic level, TemplateDictionary can be used with
|
||||
* Template to create simple fill-in-the-blank style templates.
|
||||
* <p>More advanced functionality is available, allowing developers
|
||||
* to toggle shown sections and loop over sections using child dictionaries.
|
||||
* @see Template
|
||||
*/
|
||||
public class TemplateDictionary
|
||||
{
|
||||
private Map<String,String> dictionary;
|
||||
private Map<String,List<TemplateDictionary>> children;
|
||||
private Set<String> sections;
|
||||
private TemplateDictionary parent;
|
||||
|
||||
/**
|
||||
* Instantiate an empty TemplateDictionary.
|
||||
*/
|
||||
public TemplateDictionary(){ this(null); }
|
||||
|
||||
|
||||
private TemplateDictionary(TemplateDictionary parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
|
||||
dictionary = new HashMap<String,String>();
|
||||
children = new HashMap<String,List<TemplateDictionary>>();
|
||||
sections = new HashSet<String>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the parent dictionary to this one, or null if one does not exist.
|
||||
* @return This dictionary's parent dictionary.
|
||||
*/
|
||||
public TemplateDictionary getParent(){ return parent; }
|
||||
|
||||
|
||||
/**
|
||||
* Show a specific section. This method is case-insensitive.
|
||||
* @param name The section to show.
|
||||
*/
|
||||
public void show(String name){ sections.add(name.toUpperCase()); }
|
||||
|
||||
|
||||
/**
|
||||
* Hide a specific section. This method is case-insensitive.
|
||||
* @param name The section to hide.
|
||||
*/
|
||||
public void hide(String name){ sections.remove(name.toUpperCase()); }
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a section is hidden. This method is case-insensitive.
|
||||
* @param name The section name checking against.
|
||||
* @return True if a section is hidden, false if it is not.
|
||||
*/
|
||||
public boolean isHidden(String name){ return !sections.contains(name.toUpperCase()); }
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a section is shown. This method is case-insensitive.
|
||||
* @param name The section name checking against.
|
||||
* @return True if this section is shown, false if it is not.
|
||||
*/
|
||||
public boolean isShown(String name){ return sections.contains(name.toUpperCase()); }
|
||||
|
||||
|
||||
/**
|
||||
* Provides a list of children identified by a case-insensitive section name.
|
||||
* @param name The section name checking against.
|
||||
* @return List of children dictionaries identified by specific section name or null if there are none.
|
||||
*/
|
||||
public List<TemplateDictionary> getChild(String name){
|
||||
if(children.containsKey(name.toUpperCase())){
|
||||
return children.get(name.toUpperCase());
|
||||
} else { return null; }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all of the child dictionaries under this one identified by
|
||||
* a specific section name.
|
||||
* @param name Case-insensitive section name's children to remove.
|
||||
*/
|
||||
public void removeChildren(String name){
|
||||
children.remove(name.toUpperCase());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a child dictionary underneath this one for use with a specific
|
||||
* section.
|
||||
* @param name Case-insensitive section name for the new dictionary.
|
||||
* @return TemplateDictionary for specific section.
|
||||
*/
|
||||
public TemplateDictionary createChild(String name){
|
||||
TemplateDictionary child = new TemplateDictionary(this);
|
||||
|
||||
if(!children.containsKey(name.toUpperCase())){
|
||||
List<TemplateDictionary> list = new LinkedList<TemplateDictionary>();
|
||||
children.put(name.toUpperCase(), list);
|
||||
}
|
||||
|
||||
children.get(name.toUpperCase()).add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a variable by key. This method also ascends into parent
|
||||
* dictionaries looking for the key.
|
||||
* @param key Case-insensitive Key to look for.
|
||||
* @return Value of key as string, or an empty string if the key is not found.
|
||||
*/
|
||||
public String get(String key)
|
||||
{
|
||||
if(dictionary.containsKey(key.toUpperCase())){
|
||||
return dictionary.get(key.toUpperCase());
|
||||
} else if(parent != null){
|
||||
return parent.get(key);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of a variable.
|
||||
* @param key Case-insensitive key that identifies this variable.
|
||||
* @param val Value of this variable.
|
||||
*/
|
||||
public void put(String key, String val)
|
||||
{
|
||||
dictionary.put(key.toUpperCase(), val);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a variable.
|
||||
* @param key Case-insensitive key to remove.
|
||||
*/
|
||||
public void remove(String key)
|
||||
{
|
||||
dictionary.remove(key.toUpperCase());
|
||||
}
|
||||
}
|
48
src/com/binarythought/picotemplate/TemplateNode.java
Normal file
48
src/com/binarythought/picotemplate/TemplateNode.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.binarythought.picotemplate;
|
||||
|
||||
|
||||
/**
|
||||
* Groups of TemplateNodes compose a template once it's compiled.
|
||||
* This class shouldn't be used directly.
|
||||
* @see Template
|
||||
*/
|
||||
public class TemplateNode
|
||||
{
|
||||
/** Plain node type */
|
||||
public static final int PLAIN = 0;
|
||||
/** Variable node type */
|
||||
public static final int VARIABLE = 1;
|
||||
/** Start of section node type */
|
||||
public static final int SECTION_START = 2;
|
||||
/** End of section node type */
|
||||
public static final int SECTION_END = 3;
|
||||
|
||||
private int nodeType;
|
||||
private String content;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate new node.
|
||||
* @param nodeType Type of node
|
||||
* @param content Content of this node.
|
||||
*/
|
||||
public TemplateNode(int nodeType, String content)
|
||||
{
|
||||
this.nodeType = nodeType;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the type of this node
|
||||
* @return Type of this node.
|
||||
*/
|
||||
public int getNodeType(){ return nodeType; }
|
||||
|
||||
|
||||
/**
|
||||
* Get the content of this node.
|
||||
* @return Content of this node.
|
||||
*/
|
||||
public String getContent(){ return content; }
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.binarythought.picotemplate.example;
|
||||
|
||||
import com.binarythought.picotemplate.Template;
|
||||
import com.binarythought.picotemplate.TemplateDictionary;
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class TemplateExample {
|
||||
public static void main(String args[]) throws Exception
|
||||
{
|
||||
Template template = new Template(new File("res/test.html"));
|
||||
|
||||
TemplateDictionary dict = new TemplateDictionary();
|
||||
dict.put("object1", "rifle");
|
||||
|
||||
dict.show("section");
|
||||
TemplateDictionary d1 = dict.createChild("section");
|
||||
d1.put("object2", "fighting");
|
||||
TemplateDictionary d2 = dict.createChild("section");
|
||||
d2.put("object2", "fun");
|
||||
|
||||
System.out.println("*** Parsed Template: ");
|
||||
System.out.println(template.parse(dict));
|
||||
|
||||
long t = System.currentTimeMillis();
|
||||
template.parse(dict);
|
||||
System.out.println("1 Template: "+(System.currentTimeMillis()-t)+"ms");
|
||||
|
||||
t = System.currentTimeMillis();
|
||||
for(int i=0;i < 1000; i++){
|
||||
template.parse(dict);
|
||||
}
|
||||
System.out.println("1000 Templates: "+(System.currentTimeMillis()-t)+"ms");
|
||||
|
||||
t = System.currentTimeMillis();
|
||||
for(int i=0;i < 10000; i++){
|
||||
template.parse(dict);
|
||||
}
|
||||
System.out.println("10000 Templates: "+(System.currentTimeMillis()-t)+"ms");
|
||||
|
||||
t = System.currentTimeMillis();
|
||||
for(int i=0;i < 100000; i++){
|
||||
template.parse(dict);
|
||||
}
|
||||
System.out.println("100000 Templates: "+(System.currentTimeMillis()-t)+"ms");
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user