/*
* 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.generictype.introspect;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import java.util.List;
import com.alibaba.citrus.util.internal.StringUtil;
/**
* 代表一个<code>PropertyPath</code>,其格式为:
* <p/>
* <pre>
* ( "."? propertyName | [index] | [key] )
* ( "." propertyName | [index] | [key] )*
* </pre>
* <p>
* 其中,<code>index</code>为整数,<code>key</code>为单引号或双引号包围的字符串。<br>
* 在<code>key</code>中,可使用Java字符串转义符,例如:<code>"\n\u1234"</code>。
* </p>
* <p>
* <code>PropertyPath</code>支持事件模式(类似SAX)和DOM两种模式。
* </p>
*
* @author Michael Zhou
*/
public class PropertyPath {
private final Node[] nodes;
/** 创建一个<code>PropertyPath</code>结构。 */
private PropertyPath(List<Node> nodes) {
this.nodes = nodes.toArray(new Node[nodes.size()]);
}
/** 解析字符串并生成<code>PropertyPath</code>结构。 */
public static PropertyPath parse(String propertyPath) {
Parser parser = new Parser(propertyPath);
parser.parse();
return new PropertyPath(parser.nodes);
}
/** 解析<code>PropertyPath</code>字符串,并访问指定visitor。 */
public static void parse(String propertyPath, Visitor visitor) {
new Parser(propertyPath, visitor).parse();
}
/** 取得所有结点。 */
public Node[] getNodes() {
return nodes.clone();
}
/** 访问visitor。 */
public void accept(Visitor visitor) {
int length = nodes.length;
Node node = null;
for (int i = 0; i < length; i++) {
if (node == null) {
if (i < nodes.length - 1) {
node = nodes[i];
} else {
nodes[i].accept(visitor);
}
} else {
Node lookAhead = nodes[i];
if (node.accept(visitor, lookAhead)) {
node = null;
} else {
node.accept(visitor);
node = lookAhead;
}
}
}
if (node != null) {
node.accept(visitor);
node = null;
}
}
/** 转换成字符串。 */
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("PropertyPath {\n");
for (Node node : nodes) {
buf.append(" ").append(node).append("\n");
}
buf.append("}");
return buf.toString();
}
/** 用来访问<code>PropertyPath</code>的visitor。 */
public static interface Visitor {
/** 访问property[index]。 */
boolean visitIndexedProperty(String propertyName, int index, String displayName, boolean last);
/** 访问property[key]。 */
boolean visitMappedProperty(String propertyName, String key, String displayName, boolean last);
/** 访问property。 */
void visitSimpleProperty(String propertyName, String displayName, boolean last);
/** 访问[index]。 */
void visitIndex(int index, String displayName, boolean last);
/** 访问[key]。 */
void visitKey(String key, String displayName, boolean last);
}
/** 代表<code>PropertyPath</code>中的一个结点。 */
public static interface Node {
boolean accept(Visitor visitor, Node lookAhead);
void accept(Visitor visitor);
}
/** 代表<code>PropertyPath</code>中的一个property name结点。 */
public static final class PropertyName implements Node {
private final String propertyName;
private final String displayName;
private final boolean last;
public PropertyName(String propertyName, String displayName, boolean last) {
this.propertyName = propertyName;
this.displayName = displayName;
this.last = last;
}
public String getPropertyName() {
return propertyName;
}
public String getDisplayName() {
return displayName;
}
public boolean accept(Visitor visitor, Node lookAhead) {
if (lookAhead instanceof Index) {
Index key = (Index) lookAhead;
return visitor.visitIndexedProperty(propertyName, key.index, key.displayName, key.last);
} else if (lookAhead instanceof Key) {
Key key = (Key) lookAhead;
return visitor.visitMappedProperty(propertyName, key.key, key.displayName, key.last);
} else {
return false;
}
}
public void accept(Visitor visitor) {
visitor.visitSimpleProperty(propertyName, displayName, last);
}
@Override
public String toString() {
return propertyName;
}
}
/** 代表<code>PropertyPath</code>中的一个indexed key结点。 */
public static final class Index implements Node {
private final int index;
private final String displayName;
private final boolean last;
public Index(int index, String displayName, boolean last) {
this.index = index;
this.displayName = displayName;
this.last = last;
}
public int getIndex() {
return index;
}
public String getDisplayName() {
return displayName;
}
public boolean accept(Visitor visitor, Node lookAhead) {
return false;
}
public void accept(Visitor visitor) {
visitor.visitIndex(index, displayName, last);
}
@Override
public String toString() {
return String.format("[%d]", index);
}
}
/** 代表<code>PropertyPath</code>中的一个mapped key结点。 */
public static final class Key implements Node {
private final String key;
private final String displayName;
private final boolean last;
public Key(String key, String displayName, boolean last) {
this.key = key;
this.displayName = displayName;
this.last = last;
}
public String getKey() {
return key;
}
public String getDisplayName() {
return displayName;
}
public boolean accept(Visitor visitor, Node lookAhead) {
return false;
}
public void accept(Visitor visitor) {
visitor.visitKey(key, displayName, last);
}
@Override
public String toString() {
return String.format("[\"%s\"]", StringUtil.escapeJava(key));
}
}
/** <code>PropertyPath</code>词法分析器。 */
private abstract static class Tokenizer {
private final static char EOF = '\0';
private final static int KEY_STATE_NORMAL = 0;
private final static int KEY_STATE_ESCAPE = 1;
private final static int KEY_STATE_UNICODE = 2;
protected final String propertyPath;
private final int lastIndex;
// Parser的状态
private int i = -1;
private char ch = EOF;
private String propertyName = null;
private int index = -1;
private String key = null;
public Tokenizer(String propertyPath) {
this.propertyPath = propertyPath;
this.lastIndex = propertyPath.length() - 1;
next();
skipSpaces();
}
public final void parse() {
boolean firstToken = true;
while (ch != EOF) {
if (Character.isLetter(ch) && firstToken) {
propertyName();
propertyName(propertyName, propertyPath.substring(0, i));
} else if (ch == '.') {
next();
skipSpaces();
if (Character.isLetter(ch)) {
propertyName();
} else {
syntaxError();
}
propertyName(propertyName, propertyPath.substring(0, i));
} else if (ch == '[') {
next();
skipSpaces();
indexOrKey();
skipSpaces();
if (ch != ']') {
syntaxError();
}
next();
if (index != -1) {
index(index, propertyPath.substring(0, i));
} else { // key != null
key(key, propertyPath.substring(0, i));
}
} else {
syntaxError();
}
skipSpaces();
firstToken = false;
}
done();
}
private void propertyName() {
int beginIndex = i;
while (Character.isLetterOrDigit(ch)) {
next();
}
propertyName = propertyPath.substring(beginIndex, i);
}
private void indexOrKey() {
if ('0' <= ch && ch <= '9') {
index();
} else if (ch == '\"' || ch == '\'') {
char quote = ch;
next();
key(quote);
next();
}
}
private void index() {
int beginIndex = i;
while ('0' <= ch && ch <= '9') {
next();
}
index = Integer.parseInt(propertyPath.substring(beginIndex, i));
key = null;
}
private void key(char quote) {
StringBuilder buf = new StringBuilder();
StringBuilder unicode = new StringBuilder(4);
int keyState = KEY_STATE_NORMAL;
for (; ch != EOF && (ch != quote || keyState != KEY_STATE_NORMAL); next()) {
switch (keyState) {
case KEY_STATE_UNICODE:
unicode.append(ch);
if (unicode.length() == 4) {
String unicodeStr = unicode.toString();
try {
int value = Integer.parseInt(unicodeStr, 16);
buf.append((char) value);
} catch (NumberFormatException e) {
buf.append("\\u" + unicodeStr);
}
unicode.setLength(0);
keyState = KEY_STATE_NORMAL;
}
break;
case KEY_STATE_ESCAPE:
keyState = KEY_STATE_NORMAL;
switch (ch) {
case '\\':
buf.append('\\');
break;
case '\'':
buf.append('\'');
break;
case '\"':
buf.append('"');
break;
case 'r':
buf.append('\r');
break;
case 'f':
buf.append('\f');
break;
case 't':
buf.append('\t');
break;
case 'n':
buf.append('\n');
break;
case 'b':
buf.append('\b');
break;
case 'u': {
keyState = KEY_STATE_UNICODE;
break;
}
default:
buf.append(ch);
break;
}
break;
case KEY_STATE_NORMAL:
if (ch == '\\') {
keyState = KEY_STATE_ESCAPE;
continue;
}
buf.append(ch);
break;
default:
unreachableCode();
}
}
if (ch != quote) {
syntaxError();
}
index = -1;
key = buf.toString();
}
protected abstract void propertyName(String propertyName, String displayName);
protected abstract void index(int index, String displayName);
protected abstract void key(String key, String displayName);
protected abstract void done();
private void syntaxError() {
throw new InvalidPropertyPathException(propertyPath, i);
}
private void skipSpaces() {
while (Character.isWhitespace(ch)) {
next();
}
}
private char next() {
if (i >= lastIndex) {
ch = EOF;
i = lastIndex + 1;
} else {
ch = propertyPath.charAt(++i);
}
return ch;
}
}
/** <code>PropertyPath</code>解析器。 */
private static class Parser extends Tokenizer implements Visitor {
private final List<Node> nodes;
private final Visitor visitor;
private String lastPropertyName = null;
private String lastPropertyDisplayName = null;
private int lastIndex = -1;
private String lastKey = null;
private String lastKeyDisplayName = null;
public Parser(String propertyPath) {
this(propertyPath, null);
}
public Parser(String propertyPath, Visitor visitor) {
super(propertyPath);
if (visitor == null) {
this.visitor = this;
this.nodes = createLinkedList();
} else {
this.visitor = visitor;
this.nodes = null;
}
}
@Override
protected void propertyName(String propertyName, String displayName) {
if (lastPropertyName == null && lastIndex == -1 && lastKey == null) {
lastPropertyName = propertyName;
lastPropertyDisplayName = displayName;
} else {
visitLast(false);
lastPropertyName = propertyName;
lastPropertyDisplayName = displayName;
}
}
@Override
protected void index(int index, String displayName) {
if (lastIndex == -1 && lastKey == null) {
lastIndex = index;
lastKeyDisplayName = displayName;
} else {
visitLast(false);
lastIndex = index;
lastKeyDisplayName = displayName;
}
}
@Override
protected void key(String key, String displayName) {
if (lastIndex == -1 && lastKey == null) {
lastKey = key;
lastKeyDisplayName = displayName;
} else {
visitLast(false);
lastKey = key;
lastKeyDisplayName = displayName;
}
}
@Override
protected void done() {
visitLast(true);
}
private void visitLast(boolean last) {
if (lastPropertyName == null && lastIndex == -1 && lastKey == null) {
return;
}
if (lastPropertyName == null) {
if (lastIndex != -1) {
visitor.visitIndex(lastIndex, lastKeyDisplayName, last);
} else { // lastKey != null
visitor.visitKey(lastKey, lastKeyDisplayName, last);
}
} else {
if (lastIndex != -1) {
if (!visitor.visitIndexedProperty(lastPropertyName, lastIndex, lastKeyDisplayName, last)) {
visitor.visitSimpleProperty(lastPropertyName, lastPropertyDisplayName, false);
visitor.visitIndex(lastIndex, lastKeyDisplayName, last);
}
} else if (lastKey != null) {
if (!visitor.visitMappedProperty(lastPropertyName, lastKey, lastKeyDisplayName, last)) {
visitor.visitSimpleProperty(lastPropertyName, lastPropertyDisplayName, false);
visitor.visitKey(lastKey, lastKeyDisplayName, last);
}
} else {
visitor.visitSimpleProperty(lastPropertyName, lastPropertyDisplayName, last);
}
}
lastPropertyName = null;
lastPropertyDisplayName = null;
lastIndex = -1;
lastKey = null;
lastKeyDisplayName = null;
}
/** 访问property[index]。 */
public boolean visitIndexedProperty(String propertyName, int index, String displayName, boolean last) {
return false;
}
/** 访问property[key]。 */
public boolean visitMappedProperty(String propertyName, String key, String displayName, boolean last) {
return false;
}
/** 访问property。 */
public void visitSimpleProperty(String propertyName, String displayName, boolean last) {
nodes.add(new PropertyName(propertyName, displayName, last));
}
/** 访问[index]。 */
public void visitIndex(int index, String displayName, boolean last) {
nodes.add(new Index(index, displayName, last));
}
/** 访问[key]。 */
public void visitKey(String key, String displayName, boolean last) {
nodes.add(new Key(key, displayName, last));
}
}
}