package org.test4j.spec.util;
import java.util.ArrayList;
import java.util.List;
import org.test4j.spec.util.XmlHelper.MethodNode;
import org.test4j.tools.commons.StringHelper;
public class MethodNodeParser {
private final char[] chars;
private final int total;
private int currIndex;
public MethodNodeParser(String text) {
this.chars = (text + "\0").toCharArray();
this.total = text.length();
this.currIndex = 0;
}
public List<MethodNode> parseMethodNodes() {
List<MethodNode> nodes = new ArrayList<MethodNode>();
StringBuffer textBuff = new StringBuffer();
while (currIndex < total) {
boolean isBngParaElement = this.isBgnParaElement(this.currIndex);
if (isBngParaElement) {
this.addMethodText(nodes, textBuff.toString());
textBuff = new StringBuffer();
MethodNode paraNode = this.parseParameterName(this.currIndex);
if (paraNode == null) {
throw new RuntimeException(
"there must be some wrong, the method parseParameterName should return a MethodNode instance or throw an Exception.");
}
this.parseParameterValue(paraNode, this.currIndex);
nodes.add(paraNode);
} else {
char ch = chars[currIndex];
textBuff.append(ch);
currIndex++;
}
}
this.addMethodText(nodes, textBuff.toString());
return nodes;
}
/**
* 增加文本节点
*
* @param nodes
* @param text
*/
final void addMethodText(List<MethodNode> nodes, String text) {
if (StringHelper.isBlankOrNull(text)) {
return;
}
MethodNode textNode = new MethodNode(false);
textNode.setText(text);
nodes.add(textNode);
}
final char currIndexChar() {
return this.chars[this.currIndex];
}
/**
* 从currIndex位置开始解析参数内容<br>
* 并且将游标移到下一区域的开始
*
* @return
*/
final void parseParameterValue(MethodNode method, int index) {
StringBuffer value = new StringBuffer();
index = this.skipWhitespace(index);
boolean isStartCDATA = this.isStartCDATA(index);
boolean isEndCDATA = false;
if (isStartCDATA) {
index = index + 9;
}
boolean isEndParaNode = false;
while (index < total) {
char ch = this.chars[index];
if (ch == '<') {
isEndParaNode = this.isEndParaElement(index);
if (isEndParaNode) {
break;
}
boolean isStart = this.isBgnParaElement(index);
if (isStart) {
throw new RuntimeException(
"you haven't end with previous para element, but renew a para elemetn at index["
+ this.currIndex + "]");
}
}
if (isEndCDATA) {
throw new RuntimeException("expected end of para element at index[" + index + "].");
}
if (ch == ']') {
isEndCDATA = this.isEndCDATA(index);
if (isEndCDATA) {
index = this.skipWhitespace(index + 3);
continue;
}
}
value.append(ch);
index++;
}
if (isStartCDATA && !isEndCDATA) {
throw new RuntimeException("you have \"<![CDATA[\" begin of para value, but not end with \"]]>\"");
}
if (!isStartCDATA && isEndCDATA) {
throw new RuntimeException("you have \"]]>\" end of para value, but not begin with \"<![CDATA[\"");
}
if (!isEndParaNode) {
throw new RuntimeException("The element type 'para' must be terminated by the matching end-tag '</para>'");
}
method.setText(value.toString());
}
/**
* 判断是否是para元素结尾 </para><br>
* 如果是,则将当前游标指向下一个区域的开始
*
* @param index
* @return
*/
final boolean isEndParaElement(int index) {
char ch = this.chars[index];
if (ch != '<') {
return false;
}
index = this.skipWhitespace(index + 1);
ch = this.chars[index];
if (ch != '/') {
return false;
}
index = this.skipWhitespace(index + 1);
boolean isParaKey = this.isParaKey(index);
if (!isParaKey) {
return false;
}
index = this.skipWhitespace(index + 4);
ch = this.chars[index];
if (ch != '>') {
throw new RuntimeException(String.format(EXPECTED_CHAR_ERROR, '>', index, ch));
}
this.currIndex = index + 1;
return true;
}
/**
* 判断是否是para元素开头 <para<br>
* 如果是,则将当前游标指向name属性
*
* @param index
* @return
*/
final boolean isBgnParaElement(int index) {
char ch = this.chars[index];
if (ch != '<') {
return false;
}
index = this.skipWhitespace(index + 1);
boolean isParaKey = isParaKey(index);
if (isParaKey == false) {
return false;
}
index = this.skipWhitespace(index + 4);
this.currIndex = index;
return true;
}
static final String EXPECTED_CHAR_ERROR = "expected char('%s') at index[%d], but actual is '%s'.";
/**
* 解析参数名称 name="???"><br>
* 将当前游标移到参数内容开始位置,并构造MethodNode(参数)返回
*
* @return
*/
final MethodNode parseParameterName(int index) {
index = this.skipWhitespace(index);
boolean isParaName = this.isParaName(index);
if (isParaName == false) {
throw new RuntimeException("only name property allowed by para element, error at index[" + index + "]");
}
index = this.skipWhitespace(index + 4);
char ch = chars[index];
if (ch != '=') {
throw new RuntimeException(String.format(EXPECTED_CHAR_ERROR, '=', index, ch));
}
index = this.skipWhitespace(index + 1);
ch = chars[index];
if (ch != '"') {
throw new RuntimeException(String.format(EXPECTED_CHAR_ERROR, '"', index, ch));
}
StringBuffer paraName = new StringBuffer();
ch = chars[++index];
while (index < total && ch != '"') {
paraName.append(ch);
ch = chars[++index];
}
index = this.skipWhitespace(index + 1);
ch = chars[index];
if (ch != '>') {
throw new RuntimeException(String.format(EXPECTED_CHAR_ERROR, '>', index, ch));
}
MethodNode paraNode = new MethodNode(true);
paraNode.setParaName(paraName.toString());
if (StringHelper.isBlankOrNull(paraNode.getParaName())) {
throw new RuntimeException("the name of para can't be null! index at " + index);
}
this.currIndex = index + 1;
return paraNode;
}
/**
* 是否是保留字 para
*
* @param index
* @return
*/
private boolean isParaKey(int index) {
boolean isParaKey = this.isKey(index, new char[] { 'p', 'a', 'r', 'a' }, true);
return isParaKey;
}
/**
* 是否是name属性
*
* @param index
* @return
*/
private boolean isParaName(int index) {
boolean isParaName = this.isKey(index, new char[] { 'n', 'a', 'm', 'e' }, true);
return isParaName;
}
/**
* 是否是 <![CDATA[ 关键字
*
* @param index
* @return
*/
private boolean isStartCDATA(int index) {
boolean isCDATA = this.isKey(index, new char[] { '<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[' }, false);
return isCDATA;
}
/**
* 是否是 ]]>关键字
*
* @param index
* @return
*/
private boolean isEndCDATA(int index) {
boolean isCDATA = this.isKey(index, new char[] { ']', ']', '>' }, false);
return isCDATA;
}
/**
* 从index开始的字符串是否是关键字
*
* @param index
* @param key
* @param ignoreCase
* 是否忽略关键字字符的大小写
* @return
*/
private boolean isKey(int index, char[] key, boolean ignoreCase) {
for (char ch : key) {
if (!ignoreCase && chars[index] == ch && index < total) {
index++;
continue;
}
if (ignoreCase && (chars[index] == toLower(ch) || chars[index] == toUpper(ch)) && index < total) {
index++;
continue;
}
return false;
}
return true;
}
char toLower(char ch) {
return String.valueOf(ch).toLowerCase().charAt(0);
}
char toUpper(char ch) {
return String.valueOf(ch).toUpperCase().charAt(0);
}
/**
* 跳过空白字符
*/
final int skipWhitespace(int index) {
char ch = chars[index];
while (StringHelper.isSpace(ch) && index < total) {
index++;
ch = chars[index];
}
return index;
}
}