/*
* Copyright 2004-2010 the Seasar Foundation and the Others.
*
* 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 org.slim3.controller.router;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.slim3.util.WrapRuntimeException;
/**
* A class to route a path.
*
* @author higa
* @since 1.0.0
*
*/
public class Routing {
/**
* The "from" path.
*/
protected String from;
/**
* {@link Pattern} for "from".
*/
protected Pattern fromPattern;
/**
* The list of place holders.
*/
protected List<String> placeHolderList = new ArrayList<String>();
/**
* The "to" path.
*/
protected String to;
/**
* The list of {@link ToFragment}s.
*/
protected List<ToFragment> toFragmentList = new ArrayList<ToFragment>();
/**
* Constructor.
*
* @param from
* the "from" path
* @param to
* the "to" path
* @throws NullPointerException
* if the from parameter is null or if the to parameter is null
*/
public Routing(String from, String to) throws NullPointerException {
setFrom(from);
setTo(to);
}
/**
* Sets the "from" path.
*
* @param from
* the "from" path
* @throws NullPointerException
* if the from parameter is null
*/
protected void setFrom(String from) throws NullPointerException {
if (from == null) {
throw new NullPointerException("The from parameter is null.");
}
this.from = from;
StringBuilder sb = new StringBuilder(50);
char[] chars = from.toCharArray();
int length = chars.length;
int index = -1;
boolean catchAll = false;
for (int i = 0; i < length; i++) {
if (chars[i] == '{') {
if (index < 0) {
index = i;
} else if (catchAll) {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because of \"{\" after \"*\".");
} else {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because \"}\" is missing.");
}
} else if (chars[i] == '}') {
if (index >= 0) {
sb.append("([^/]+)");
placeHolderList.add(from.substring(index + 1, i));
index = -1;
} else if (catchAll) {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because of \"}\" after \"*\".");
} else {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because \"{\" is missing.");
}
} else if (chars[i] == '*') {
if (index < 0) {
index = i;
catchAll = true;
} else if (catchAll) {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because of duplicate \"*\".");
} else {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because \"}\" is missing.");
}
} else if (index < 0) {
sb.append(chars[i]);
}
}
if (catchAll) {
sb.append("([^*]+)");
placeHolderList.add(from.substring(index + 1));
index = -1;
} else if (index >= 0) {
throw new IllegalArgumentException("The from path("
+ from
+ ") is invalid, because \"}\" is missing.");
}
fromPattern = Pattern.compile("^" + sb.toString() + "$");
}
/**
* Sets the "to" path.
*
* @param to
* the "to" path
*/
protected void setTo(String to) {
if (to == null) {
throw new NullPointerException("The to parameter is null.");
}
this.to = to;
StringBuilder sb = new StringBuilder(50);
char[] chars = to.toCharArray();
int length = chars.length;
int index = -1;
for (int i = 0; i < length; i++) {
if (chars[i] == '{') {
if (index < 0) {
toFragmentList.add(new StringFragment(sb.toString()));
sb.setLength(0);
index = i;
} else {
throw new IllegalArgumentException("The to path("
+ to
+ ") is invalid, because \"}\" is missing.");
}
} else if (chars[i] == '}') {
if (index >= 0) {
String name = to.substring(index + 1, i);
if (placeHolderList.indexOf(name) < 0) {
throw new IllegalArgumentException("The to path("
+ to
+ ") is invalid, because the name("
+ name
+ ") is not found in from path("
+ from
+ ").");
}
toFragmentList.add(new PlaceHolderFragment(name));
index = -1;
} else {
throw new IllegalArgumentException("The to path("
+ to
+ ") is invalid, because \"{\" is missing.");
}
} else if (index < 0) {
sb.append(chars[i]);
}
}
if (index >= 0) {
throw new IllegalArgumentException("The to path("
+ to
+ ") is invalid, because \"}\" is missing.");
}
if (sb.length() > 0) {
toFragmentList.add(new StringFragment(sb.toString()));
}
}
/**
* Routes the path.
*
* @param request
* the request
* @param path
* the path
* @return a routed path
* @throws NullPointerException
* if the request parameter is null or if the path parameter is
* null
*/
public String route(HttpServletRequest request, String path)
throws NullPointerException {
if (request == null) {
throw new NullPointerException("The request parameter is null.");
}
if (path == null) {
throw new NullPointerException("The path parameter is null.");
}
Matcher matcher = fromPattern.matcher(path);
if (!matcher.find()) {
return null;
}
Map<String, String> placeHolderValues = new HashMap<String, String>();
int index = 1;
for (String name : placeHolderList) {
placeHolderValues.put(name, matcher.group(index++));
}
StringBuilder to = new StringBuilder(50);
for (ToFragment f : toFragmentList) {
f.append(request, to, placeHolderValues);
}
return to.toString();
}
/**
* A fragment of "to" path.
*
*/
protected static interface ToFragment {
/**
* Appends a part of "to" path.
*
* @param request
* the request
* @param to
* the "to" path
* @param placeHolderValues
* the map of place holders
*/
void append(HttpServletRequest request, StringBuilder to,
Map<String, String> placeHolderValues);
}
/**
* {@link ToFragment} for String.
*
*/
protected static class StringFragment implements ToFragment {
/**
* The value.
*/
protected String value;
/**
* Constructor.
*
* @param value
* the value
* @throws NullPointerException
* if the value parameter is null
*/
public StringFragment(String value) throws NullPointerException {
if (value == null) {
throw new NullPointerException("The value parameter is null");
}
this.value = value;
}
public void append(HttpServletRequest request, StringBuilder to,
Map<String, String> placeHolderValues) {
to.append(value);
}
}
/**
* {@link ToFragment} for String.
*
*/
protected static class PlaceHolderFragment implements ToFragment {
/**
* The name.
*/
protected String name;
/**
* Constructor.
*
* @param name
* the value
* @throws NullPointerException
* if the name parameter is null
*/
public PlaceHolderFragment(String name) throws NullPointerException {
if (name == null) {
throw new NullPointerException("The name parameter is null");
}
this.name = name;
}
public void append(HttpServletRequest request, StringBuilder to,
Map<String, String> placeHolderValues) {
to.append(encode(placeHolderValues.get(name), request
.getCharacterEncoding()));
}
/**
* Encodes the path as a part of {@link URL}.
*
* @param path
* the path
* @param encoding
* the encoding
* @return an encoded path
* @throws WrapRuntimeException
* if {@link UnsupportedEncodingException} occurred
*/
protected String encode(String path, String encoding)
throws WrapRuntimeException {
if (path == null) {
return "";
}
if (encoding == null) {
encoding = "UTF-8";
}
try {
return URLEncoder.encode(path, encoding);
} catch (UnsupportedEncodingException e) {
throw new WrapRuntimeException(e);
}
}
}
}