/*
* Copyright 2012 Jason Miller
*
* 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 jj.http.server.uri;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jj.http.server.uri.Parameter.Type;
/**
* @author jason
*
*/
class ParamNode extends TrieNode {
private static final Pattern PARSER = Pattern.compile("^([:*])([\\w\\d$_]+)(?:\\((.+)\\))?$");
private SeparatorNode child;
final Parameter parameter;
static String makeValue(final Route route) {
return makeValue(route.uri(), route.index());
}
static String makeValue(final String uri, final int index) {
int end = uri.indexOf(PATH_SEPARATOR_STRING, index);
if (end == -1) { end = uri.indexOf(EXTENSION_SEPARATOR_CHAR, index); }
if (end == -1) { end = uri.length(); }
return uri.substring(index, end);
}
private static Pattern makeParameterPattern(String input) {
Pattern result = null;
if (input != null) {
if (!input.startsWith("^")) {
input = "^" + input;
}
if (!input.endsWith("$")) {
input = input + "$";
}
result = Pattern.compile(input);
}
return result;
}
ParamNode(Route route) {
String value = makeValue(route);
Matcher m = PARSER.matcher(value);
if (!m.matches()) { throw new AssertionError("[" + value + "] is not a valid parameter definition. validate routes first!"); }
parameter = new Parameter(
m.group(2),
route.index(),
route.index() + value.length(),
parseType(m.group(1)),
makeParameterPattern(m.group(3))
);
route.addParam(parameter);
}
private Type parseType(String value) {
switch (value.charAt(0)) {
case '*':
return Type.Splat;
case ':':
return Type.Param;
default:
throw new AssertionError();
}
}
@Override
void doAddChild(Route route) {
char current = route.currentChar();
// this is an assertion because it's a trie construction error
assert current == PATH_SEPARATOR_CHAR || current == EXTENSION_SEPARATOR_CHAR;
assert !terminal : "only one node type is allowed past an extension!";
if (parameter.type == Type.Splat && current != EXTENSION_SEPARATOR_CHAR) {
throw new AssertionError("only extensions can follow a splat parameter");
}
child = child == null ? new SeparatorNode(current) : child;
child.terminal = current == EXTENSION_SEPARATOR_CHAR;
child.addRoute(route.advanceIndex());
}
private String matchParam(String uri, int index) {
String value = makeValue(uri, index);
value = (parameter.pattern != null && !parameter.pattern.matcher(value).find()) ? "" : value;
return value;
}
@Override
boolean findGoal(RouteFinderContext context, String uri, int index) {
// first, see if we match
String matchValue = "";
switch (parameter.type) {
case Param:
matchValue = matchParam(uri, index);
break;
case Splat:
int end = uri.length();
if (child != null) {
assert child.terminal : "splat followed by non-terminal child!";
end = uri.lastIndexOf('.');
// if we're expecting an extension an none exist, we don't match.
// is returning here correct?
if (end == -1) return false;
}
matchValue = uri.substring(index, end);
break;
}
if (!matchValue.isEmpty()) {
context.addParam(parameter.name, matchValue);
}
if (index + matchValue.length() == uri.length()) {
if (goal != null) {
context.setGoal(goal);
return true;
}
return false;
}
return child != null && child.findGoal(context, uri, index + matchValue.length() + 1);
}
@Override
void doCompress() {
if (child != null) {
child.compress();
}
}
@Override
void describeChildren(int indent, StringBuilder sb) {
if (child != null) {
addIndentation(indent, sb.append("\n")).append(child.separator).append(" = ");
child.describe(indent, sb);
}
}
@Override
StringBuilder describe(int indent, StringBuilder sb) {
sb.append(getClass().getSimpleName()).append(" - ").append("parameter: ").append(parameter).append(", goal: ").append(goal);
describeChildren(indent + 2, sb);
return sb;
}
}