package org.nutz.mvc.impl;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.mvc.ActionContext;
public class MappingNode<T> {
private T obj;
private T asterisk; // 匹配 *
private T remain; // 匹配 **
private MappingNode<T> quesmark; // 匹配 ?
private Map<String, MappingNode<T>> map; // 匹配精确的值
public MappingNode() {
map = new HashMap<String, MappingNode<T>>();
}
private void add(T obj, String[] ss, int off) {
// 还有路径
if (off < ss.length) {
String key = ss[off].toLowerCase();
off++;
// '*'
if ("*".equals(key)) {
if (off < ss.length) {
throw Lang.makeThrow("char '*' should be the last item"
+ " in a Path '../**/%s'",
Lang.concat(off, ss.length - off, "/", ss));
}
asterisk = obj;
}
// '**'
else if ("**".equals(key)) {
if (off < ss.length) {
throw Lang.makeThrow("'**' should be the last item" + " in a Path '../**/%s'",
Lang.concat(off, ss.length - off, "/", ss));
}
remain = obj;
}
// '?'
else if ("?".equals(key)) {
if (quesmark == null) // 也许这个节点之前就已经有值呢
quesmark = new MappingNode<T>();
quesmark.add(obj, ss, off);
}
// 其它节点,加入 map
else {
MappingNode<T> node = map.get(key);
if (null == node) {
node = new MappingNode<T>();
map.put(key, node);
}
node.add(obj, ss, off);
}
}
// 没有路径了
else {
this.obj = obj;
}
}
private T get(ActionContext ac, String[] ss, int off) {
// 路径已经没有内容了,看看本节点是否有一个对象
if (off >= ss.length) {
return obj == null ? (asterisk == null ? remain : asterisk) : obj;
}
String key = ss[off];
// 先在 map 里寻找,
MappingNode<T> node = map.get(key.toLowerCase());
if (null != node) {
// 在子节点中查找
T t = node.get(ac, ss, off + 1);
if (t != null)
return t;
// 找不到的时候, 继续在当前节点找泛匹配(?或者*)
}
// 如果没有看看是否有 '?' 的匹配
if (quesmark != null) {
ac.getPathArgs().add(key);
T t = quesmark.get(ac, ss, off + 1);
if (t != null)
return t;
ac.getPathArgs().remove(ac.getPathArgs().size() - 1);
}
// 还没有则看看是否有 '*' 的匹配
if (null != asterisk) {
List<String> pathArgs = ac.getPathArgs();
while (off < ss.length)
pathArgs.add(ss[off++]);
return asterisk;
}
// 最后看看是不是有 '**' 匹配
if (null != remain) {
String ph = Lang.concat(off, ss.length - off, "/", ss).toString();
if (!Strings.isBlank(ac.getSuffix())) {
HttpServletRequest req = ac.getRequest();
String url = Strings.sBlank(req.getPathInfo(), req.getServletPath());
// 看看有没有必要补一个 "/"
if (url.endsWith("/." + ac.getSuffix())) {
ph += "/";
}
ph += "." + ac.getSuffix();
}
ac.getPathArgs().add(ph);
return remain;
}
return null;
}
/**
* 增加一个映射,将 obj 映射到 path 上,或 path 上的[?,*]
*/
public void add(String path, T obj) {
try {
String[] ss = Strings.splitIgnoreBlank(path, "/");
add(obj, ss, 0);
}
catch (Exception e) {
throw Lang.wrapThrow(e, "Wrong Url path format '%s'", path);
}
}
public T get(ActionContext ac, String path) {
return get(ac, path, null);
}
public T get(ActionContext ac, String path, String suffix) {
ac.setPath(path);
ac.setPathArgs(new LinkedList<String>());
String[] ss = Strings.splitIgnoreBlank(path, "/");
return get(ac, ss, 0);
}
public String toString() {
StringBuilder sb = new StringBuilder();
appendTo(sb, 0);
return sb.toString();
}
private void appendTo(StringBuilder sb, int indent) {
String prefix = Strings.dup(" ", indent);
sb.append(prefix).append('<').append(Strings.sNull(obj, "null")).append('>');
prefix = "\n " + prefix;
if (null != asterisk) {
sb.append(prefix).append(" * : ").append(asterisk.toString());
}
if (null != quesmark) {
sb.append(prefix).append(" ? : ");
quesmark.appendTo(sb, indent + 1);
}
for (String key : map.keySet()) {
sb.append(prefix).append(" '" + key + "' : ");
map.get(key).appendTo(sb, indent + 1);
}
}
}