jetbrick.web.mvc.router.RestfulMatcher Maven / Gradle / Ivy
/**
* Copyright 2013-2014 Guoqiang Chen, Shanghai, China. All rights reserved.
*
* Email: [email protected]
* URL: http://subchen.github.io/
*
* 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 jetbrick.web.mvc.router;
import java.util.*;
import jetbrick.commons.collections.SoftHashMap;
import jetbrick.commons.collections.multimap.MultiValueHashMap;
import jetbrick.commons.collections.multimap.MultiValueMap;
import jetbrick.commons.lang.StringUtils;
import jetbrick.web.mvc.RouteInfo;
import jetbrick.web.mvc.action.ActionInfo;
import jetbrick.web.mvc.action.PathVariables;
/*
* 分组匹配算法
*
* - 按照 HttpMethod 分组
* - 按照静态/动态 URL 分组(cache)
* - 动态 URL 先按照 path 长度分组,再按照 group 分组
*
*/
final class RestfulMatcher {
private static final int MAX_PATH_PARTS = 20;
private Map staticUrls = new HashMap(128);
private Map cachedUrls = new SoftHashMap(256);
private OneByOneMatcher[] matchers = new OneByOneMatcher[MAX_PATH_PARTS]; // 按照长度分组
public void register(ActionInfo action, String url) {
if (url.indexOf('{') == -1) {
staticUrls.put(url, new RouteInfo(action));
} else {
String[] urlSegments = StringUtils.split(url.substring(1), '/');
if (urlSegments.length >= MAX_PATH_PARTS) {
throw new IllegalStateException("exceed max url parts: " + url);
}
OneByOneMatcher matcher = matchers[urlSegments.length];
if (matcher == null) {
matcher = new OneByOneMatcher();
matchers[urlSegments.length] = matcher;
}
matcher.register(action, urlSegments);
}
}
public RouteInfo lookup(String url) {
// 1. 查询静态路由
RouteInfo info = staticUrls.get(url);
if (info != null) return info;
// 2. 查询动态路由缓存
info = cachedUrls.get(url);
if (info != null) {
return info;
}
// 3. 开始执行动态路由匹配 (分组匹配)
String[] urlSegments = StringUtils.split(url.substring(1), '/');
if (urlSegments.length >= MAX_PATH_PARTS) {
throw new IllegalStateException("exceed max url parts: " + url);
}
OneByOneMatcher matcher = matchers[urlSegments.length];
if (matcher != null) {
info = matcher.lookup(urlSegments);
}
// 4. 加入缓存
if (info == null) {
info = RouteInfo.NOT_FOUND;
}
cachedUrls.put(url, info);
// 5. 返回
return info;
}
// 动态路由匹配(逐个匹配)
static final class OneByOneMatcher {
private final MultiValueMap groups = new MultiValueHashMap(256);
private final List ungroupList = new ArrayList(32);
// 添加路由信息(按照 URL 前缀分组)
public void register(ActionInfo action, String[] urlSegments) {
String group = urlSegments[0];
if (group.indexOf('{') == -1) {
groups.put(group, action);
} else {
ungroupList.add(action);
}
}
public RouteInfo lookup(String[] urlSegments) {
// 1. 先 new 出一个存放 URL PathVariables 的对象
PathVariables pathVariables = new PathVariables();
// 2. 分组查询
String group = urlSegments[0];
List actions = groups.getList(group);
if (actions != null) {
RouteInfo info = doLookup(actions, urlSegments, pathVariables);
if (info != null) {
return info;
}
}
// 3. 查找未分组的内容
return doLookup(ungroupList, urlSegments, pathVariables);
}
private RouteInfo doLookup(List actions, String[] urlSegments, PathVariables pathVariables) {
for (ActionInfo action : actions) {
if (action.match(urlSegments, pathVariables)) {
return new RouteInfo(action, pathVariables);
}
}
return null;
}
}
}