📄 template.java
字号:
package ranab.tpl;
import java.io.*;
import java.util.*;
/**
* This class is used to load files into <code>OutputStream</code>
* after parsing the template file. It supports variables,
* for block, if-then-else block and iterator block. It has
* only one public method - <code>loadFile(OutputStream, Map)</code>.
* It also supports vector indexing. It stores the file data in a
* byte array. We may face some problems later due to character
* encoding. But for the time being it works fine.
* <a href="syntax.html">Template syntax.</a>
*
* @author <a href="mailto:rana_b@yahoo.com">Rana Bhattacharyya</a>
*/
public
class Template {
// whitespaces
private static final String mstWhitespaces = " \t\r\n\f";
// keywords
private static final String[] mstKeywords = { "IF",
"FOR",
"ITR",
"VARKEY",
"NULL"
};
// keyword index
private static final int IF = 0;
private static final int FOR = 1;
private static final int ITR = 2;
private static final int VARKEY = 3;
private static final int NULL = 4;
private static final int VAR = 5;
private static final String SIZE = ".size";
private static final String LAST = ".last";
private static final String THRU = "THRU";
private static final String EQ = "==";
private static final String NE = "!=";
private static final String IN = "IN";
private File mFile;
private long mModifiedTime = 0;
private byte[] mbyContent;
/**
* Constructor.
*/
public Template(File file) {
mFile = file;
}
/**
* Read file (if modified) into a byte array.
* This function is <code>Synchronized</code> to make
* this class thread safe.
*/
private synchronized void readFile() throws IOException {
// file check
if (!mFile.exists())
throw new IOException(toString() + " : does not exist.");
if (!mFile.canRead())
throw new IOException(toString() + " : no read permission.");
if (!mFile.isFile())
throw new IOException(toString() + " : not a file.");
// file modified
boolean bNeeded = false;
long modTime = mFile.lastModified();
if (modTime > mModifiedTime) {
mModifiedTime = modTime;
bNeeded = true;
}
if (bNeeded) {
mbyContent = new byte[(int)mFile.length()];
FileInputStream fis = new FileInputStream(mFile);
fis.read(mbyContent);
fis.close();
}
}
/**
* Process block. First find the block type and then process.
*/
private int processBlock(OutputStream out, Map hash, Block block)
throws IOException {
// get keyword type or var
int type = VAR;
for (int i=0; i<mstKeywords.length; i++) {
if ( block.equals(mstKeywords[i]) ) {
type = i;
break;
}
}
return processBlock(out, hash, block, type);
}
/**
* Process block - call respective functions. Each of these
* functions returns the next index from where the next
* processing starts.
*/
private int processBlock(OutputStream out, Map hash,
Block block, int type)
throws IOException {
int index;
switch (type) {
case VAR:
index = processVarBlock(out, hash, block);
break;
case IF:
index = processIfBlock(out, hash, block.nextIndex());
break;
case FOR:
index = processForBlock(out, hash, block.nextIndex());
break;
case VARKEY:
index = processVarkeyBlock(out, block);
break;
case NULL:
index = processNullBlock(out, block);
break;
case ITR:
index = processItrBlock(out, hash, block.nextIndex());
break;
default:
throw new IOException("Unknown keyword index=" + type);
}
return index;
}
/**
* Process null block - returns nothing
*/
private int processNullBlock(OutputStream out, Block block) {
return block.nextIndex();
}
/**
* Process varkey.
* It is used to write ${ in the <code>OutputStream</code>.
*/
private int processVarkeyBlock(OutputStream out, Block block)
throws IOException {
out.write('$');
out.write('{');
return block.nextIndex();
}
/**
* Process normal block. Normal block may contain other variables,
* keywords and other normal blocks. Search for "${". If found,
* get the block and process it.
*/
private int processNormalBlock(OutputStream out, Map hash, Block block)
throws IOException {
int ind = block.miStart;
int finalIndex = ind + block.miLength;
char c, c1;
while (ind < finalIndex) {
c = (char)mbyContent[ind++];
if (c == '$') {
c1 = (char)mbyContent[ind++];
// not a variable
if (c1 != '{') {
out.write(c);
out.write(c1);
continue;
}
// variable and/or keyword
Block nextBlock = getBlock(ind);
ind = processBlock(out, hash, nextBlock);
} else {
out.write(c);
}
}
return block.nextIndex();
}
/**
* Process variable block. Get variable object from the
* <code>Map</code> and write the string representation
* of the object.
*/
private int processVarBlock(OutputStream out, Map hash, Block block)
throws IOException {
// get variable value and send it
String sb = block.toString();
Object val = getVarObject(hash, sb);
if (val != null)
out.write(val.toString().getBytes());
return block.nextIndex();
}
/**
* Get variable object.
* Returns null if not available in the Map
*/
private Object getVarObject(Map hash, String var)
throws IOException {
// get actual variable string
String sb = getActualVarString(hash, var);
// get vector size
if (sb.endsWith(SIZE)) {
return getVarSize(hash, var);
}
// get last element of a vector
if (sb.endsWith(LAST)) {
return getLastVar(hash, var);
}
// get vector element at a particular index
int startIndex = sb.indexOf('[');
int endIndex = sb.indexOf(']');
if (startIndex != -1 && endIndex != -1) {
if (endIndex < endIndex)
throw new IOException("List indexing error");
String indexBlock = sb.substring(startIndex+1, endIndex);
String vectVar = sb.substring(0, startIndex);
Object obj = getVarObject(hash, vectVar);
return getIndexedObject(obj, indexBlock);
}
// null variable
if (sb.equals(mstKeywords[NULL])) {
return null;
}
// return other values
return hash.get(sb);
}
/**
* Get conditional variable object. If not a variable returns
* the string itself. Else returns the equivalent object from
* the hashtable. If it is not available in the hashtable
* returns null.
*/
private Object getCondVarObject(Map hash, String str)
throws IOException{
Object obj = null;
// variable
if (str.charAt(0) == '$' && str.charAt(1) == '{') {
String var1 = str.substring(2, str.length() - 1);
String var2 = getBlock(str, 2);
if (var1.equals(var2))
obj = getVarObject(hash, var1);
else
obj = getActualVarString(hash, str);
} else
obj = getActualVarString(hash, str);
return obj;
}
/**
* Returns the size of a variable as an <code>Integer</code>
* object. In case of Scalar returns 1. If object is null,
* returns 0 and in case of <code>List</code>
* returns the list size.
*/
private Integer getVarSize(Map hash, String sb)
throws IOException {
// get object form hashtable
String vectName = sb.substring(0, sb.length()-SIZE.length());
Object obj = getVarObject(hash, vectName);
if (obj == null)
return new Integer(0);
if (obj instanceof java.util.List)
return new Integer(((List)obj).size());
return new Integer(1);
}
/**
* Returns the last element of a vector. In case of scalar,
* returns the object itself. In case of null returns null
* and in case of vector returns null if size is zero or the
* last element.
*/
private Object getLastVar(Map hash, String sb)
throws IOException {
String vectName = sb.substring(0, sb.length()-LAST.length());
Object obj = getVarObject(hash, vectName);
if (obj == null)
return null;
if (obj instanceof java.util.List) {
List v = (List)obj;
if (v.size() == 0)
return null;
else
return v.get(v.size() - 1);
}
return obj;
}
/**
* Get vector element at a particular index. Tokenize
* <code>indexBlock</code> string and returns the element.
*/
private Object getIndexedObject(Object obj, String indexBlock) {
if (obj == null)
return null;
StringTokenizer st = new StringTokenizer(indexBlock, ",");
while (st.hasMoreTokens()) {
String initVar = st.nextToken().trim();
int ind = Integer.parseInt(initVar);
obj = elementAt(obj, ind);
}
st = null;
return obj;
}
/**
* Returns an element at a particular element. If the object
* is null, returns null. If the object is a <code>List</code>
* returns null if the index is out of range or the element at
* that index.
*/
private Object elementAt(Object obj, int index) {
if (obj == null)
return null;
if (obj instanceof java.util.List) {
int size = ((List)obj).size();
if (index >= size || index < 0)
return null;
return((List)obj).get(index);
}
if (index == 0)
return obj;
return null;
}
/**
* Variable name can be formed dynamically. This function is
* used to get the actual variable name - by resolving inner
* variable name. Keywords will be treated as variables. The
* variable within '{' and '}' will be replaced by its string
* representation.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -