/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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.civilian.resource;
import org.civilian.provider.PathParamProvider;
import org.civilian.response.UriEncoder;
import org.civilian.util.ArrayUtil;
import org.civilian.util.Check;
import org.civilian.util.ClassUtil;
import org.civilian.util.StringUtil;
/**
* Route presents a route from a resource root down to a single resource.
* A route can be converted into a path string, given values for all path parameter contained
* in the Route.
*/
public abstract class Route
{
private static final Route ROOT = new RootRoute();
/**
* Returns a Route which represents the root path "/".
*/
public static final Route root()
{
return ROOT;
}
/**
* Returns a Route for a constant path or url.
*/
public static final Route constant(String path)
{
return (path == null) || (path.length() == 0) || path.equals("/") ?
ROOT :
new ConstantRoute(path);
}
/**
* Returns if this Route represents the root path.
*/
public boolean isRoot()
{
return false;
}
/**
* Returns a Route which represents this route + the given path.
*/
public Route add(String path)
{
path = Path.norm(path);
return path.length() == 0 ? this : addNormed(path);
}
protected Route addNormed(String path)
{
return add(new ConstantRoute(path));
}
/**
* Returns a Route which represents this route + the path schema defined
* by the PathParam.
*/
public <T> Route add(PathParam<T> pathParam)
{
return add(new PathParamRoute<>(pathParam, getPathParamCount()));
}
/**
* Returns the number of constant and variable (PathParam) parts contained in the route.
*/
int size()
{
return 1;
}
protected abstract Route add(Route route);
/**
* Returns the number of PathParams contained in this route.
*/
public abstract int getPathParamCount();
/**
* Returns the i-th PathParam contained in this route.
*/
public abstract PathParam<?> getPathParam(int index);
/**
* Returns the index of the PathParam within this route.
*/
public abstract int indexOf(PathParam<?> pathParam);
/**
* Extracts the value of all PathParams of this Route from the
* PathParamProvider (e.g. a request)
* and puts them in the value array. If the request does not contain
* an parameter contained in the route, then the value will be null.
* @param provider the request containing path param values
* @param pathParamValues receives the PathParam values. The length should
* equal {@link #getPathParamCount()}.
*/
public abstract void extractPathParams(PathParamProvider provider, Object[] pathParamValues);
/**
* Builds a path string from the route, using the given PathParam values and UriEncoder.
* @param pathParamValues contains the values for all path parameters of the route. The length should
* equal {@link #getPathParamCount()}.
* @return the path
*/
public String build(Object[] pathParamValues, UriEncoder encoder)
{
StringBuilder s = new StringBuilder();
build(pathParamValues, encoder, s);
return s.toString();
}
/**
* Builds a path string from the route, using the given PathParam values and UriEncoder.
* @param s receives the constructed path
*/
public abstract void build(Object[] pathParams, UriEncoder encoder, StringBuilder s);
/**
* Returns if the last character in the StringBuilder is a slash.
*/
protected void removeLastSlash(StringBuilder s)
{
int length = s.length();
if ((length > 0) && (s.charAt(length - 1) == '/'))
s.setLength(length - 1);
}
/**
* Returns a debug representation of the Route.
*/
@Override public abstract String toString();
}
/**
* The root route.
*/
class RootRoute extends Route
{
@Override public boolean isRoot()
{
return true;
}
@Override protected Route add(Route route)
{
return route;
}
@Override public int getPathParamCount()
{
return 0;
}
@Override public PathParam<?> getPathParam(int index)
{
return null;
}
@Override public int indexOf(PathParam<?> pattern)
{
return -1;
}
@Override public void extractPathParams(PathParamProvider provider, Object[] pathParams)
{
}
@Override public void build(Object[] pathParams, UriEncoder encoder, StringBuilder s)
{
if (s.length() == 0)
s.append('/');
}
@Override public String toString()
{
return "/";
}
}
class ConstantRoute extends Route
{
/**
* @param path a normed path
*/
protected ConstantRoute(String path)
{
path_ = path;
}
@Override public int getPathParamCount()
{
return 0;
}
@Override public PathParam<?> getPathParam(int index)
{
return null;
}
@Override public int indexOf(PathParam<?> pattern)
{
return -1;
}
@Override public void extractPathParams(PathParamProvider provider, Object[] pathParams)
{
}
@Override protected Route addNormed(String path)
{
return new ConstantRoute(StringUtil.cutRight(path_, "/") + path);
}
@Override protected Route add(Route route)
{
return new RouteList(this, route);
}
@Override public void build(Object[] pathParams, UriEncoder encoder, StringBuilder s)
{
removeLastSlash(s);
s.append(path_);
}
@Override public String toString()
{
return path_;
}
private String path_;
}
class PathParamRoute<T> extends Route
{
public PathParamRoute(PathParam<T> pathParam, int paramIndex)
{
pathParam_ = pathParam;
ppIndex_ = paramIndex;
}
@Override public int getPathParamCount()
{
return 1;
}
@Override public PathParam<?> getPathParam(int index)
{
return index == ppIndex_ ? pathParam_ : null;
}
@Override public int indexOf(PathParam<?> pattern)
{
return pathParam_ == pattern ? ppIndex_ : -1;
}
@Override public void extractPathParams(PathParamProvider provider, Object[] pathParams)
{
pathParams[ppIndex_] = provider.getPathParam(pathParam_);
}
@Override protected Route add(Route route)
{
return new RouteList(this, route);
}
@SuppressWarnings("unchecked")
@Override public void build(Object[] pathParams, UriEncoder encoder, StringBuilder s)
{
Check.notNull(pathParams, "pathParams");
Object value = pathParams[ppIndex_];
if (value == null)
throw new IllegalStateException("no value was set for path parameter '" + pathParam_ + "'");
if (!ClassUtil.isA(value, pathParam_.getType()))
throw new IllegalArgumentException("path parameter '" + value + " does not match the type of path parameter '" + pathParam_.toDetailedString() + "'");
removeLastSlash(s);
pathParam_.buildPath((T)value, encoder, s);
}
@Override public String toString()
{
return pathParam_.toString();
}
private final int ppIndex_;
private final PathParam<T> pathParam_;
}
class RouteList extends Route
{
public RouteList(Route... list)
{
list_ = list;
int count = 0;
for (Route route : list)
count += route.getPathParamCount();
pathParamCount_ = count;
}
public RouteList(int pathParamCount, Route... list)
{
list_ = list;
pathParamCount_ = pathParamCount;
}
@Override public int getPathParamCount()
{
return pathParamCount_;
}
@Override int size()
{
return list_.length;
}
@Override public PathParam<?> getPathParam(int index)
{
for (int i=0; i<list_.length; i++)
{
PathParam<?> pattern = list_[i].getPathParam(index);
if (pattern != null)
return pattern;
}
return null;
}
@Override public int indexOf(PathParam<?> pattern)
{
for (int i=0; i<list_.length; i++)
{
int index = list_[i].indexOf(pattern);
if (index >= 0)
return index;
}
return -1;
}
@Override public void extractPathParams(PathParamProvider provider, Object[] pathParams)
{
for (int i=0; i<list_.length; i++)
list_[i].extractPathParams(provider, pathParams);
}
@Override protected Route addNormed(String path)
{
Route last = list_[list_.length - 1];
if (last instanceof ConstantRoute)
{
Route[] newList = list_.clone();
newList[newList.length - 1] = last.addNormed(path);
return new RouteList(pathParamCount_, newList);
}
else
return super.addNormed( path);
}
@Override protected Route add(Route route)
{
Route[] newList = ArrayUtil.addLast(list_, route);
return new RouteList(pathParamCount_ + route.getPathParamCount(), newList);
}
@Override public void build(Object[] pathParams, UriEncoder encoder, StringBuilder s)
{
for (int i=0; i<list_.length; i++)
list_[i].build(pathParams, encoder, s);
}
@Override public String toString()
{
StringBuilder s = new StringBuilder();
for (int i=0; i<list_.length; i++)
s.append(list_[i]).toString();
return s.toString();
}
private final Route[] list_;
private final int pathParamCount_;
}