/*
* Copyright (C) 2010 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.web.controller.router;
//import javanet.staxutils.IndentingXMLStreamWriter;
import org.exoplatform.web.controller.QualifiedName;
import org.exoplatform.web.controller.metadata.PathParamDescriptor;
import org.exoplatform.web.controller.metadata.RequestParamDescriptor;
import org.exoplatform.web.controller.metadata.RouteDescriptor;
import org.exoplatform.web.controller.metadata.RouteParamDescriptor;
import org.gatein.common.util.Tools;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* The implementation of the routing algorithm.
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
class Route
{
void writeTo(XMLStreamWriter writer) throws XMLStreamException
{
if (this instanceof SegmentRoute)
{
writer.writeStartElement("segment");
writer.writeAttribute("path", "/" + ((SegmentRoute)this).name);
writer.writeAttribute("terminal", "" + terminal);
}
else if (this instanceof PatternRoute)
{
PatternRoute pr = (PatternRoute)this;
StringBuilder path = new StringBuilder("/");
for (int i = 0;i < pr.params.length;i++)
{
path.append(pr.chunks[i]).append("{").append(pr.params[i].name.getValue()).append("}");
}
path.append(pr.chunks[pr.chunks.length - 1]);
writer.writeStartElement("pattern");
writer.writeAttribute("path", path.toString());
writer.writeAttribute("terminal", Boolean.toString(terminal));
for (PathParam param : pr.params)
{
writer.writeStartElement("path-param");
writer.writeAttribute("qname", param.name.getValue());
writer.writeAttribute("encodingMode", param.encodingMode.toString());
writer.writeAttribute("pattern", param.matchingRegex.toString());
writer.writeEndElement();
}
}
else
{
writer.writeStartElement("route");
}
//
for (RouteParam routeParam : routeParamArray)
{
writer.writeStartElement("route-param");
writer.writeAttribute("qname", routeParam.name.getValue());
writer.writeAttribute("value", routeParam.value);
writer.writeEndElement();
}
//
for (RequestParam requestParam : requestParamArray)
{
writer.writeStartElement("request-param");
writer.writeAttribute("qname", requestParam.name.getValue());
writer.writeAttribute("name", requestParam.matchName);
if (requestParam.matchPattern != null)
{
writer.writeAttribute("value", requestParam.matchPattern.getPattern());
}
writer.writeEndElement();
}
//
/*
for (Map.Entry<String, SegmentRoute[]> entry : segments.entrySet())
{
writer.writeStartElement("segment");
writer.writeAttribute("name", entry.getKey());
for (SegmentRoute segment : entry.getValue())
{
segment.writeTo(writer);
}
writer.writeEndElement();
}
//
for (PatternRoute pattern : patterns)
{
pattern.writeTo(writer);
}
*/
//
writer.writeEndElement();
}
@Override
public String toString()
{
try
{
XMLOutputFactory factory = XMLOutputFactory.newInstance();
StringWriter sw = new StringWriter();
XMLStreamWriter xmlWriter = factory.createXMLStreamWriter(sw);
// xmlWriter = new IndentingXMLStreamWriter(xmlWriter);
writeTo(xmlWriter);
return sw.toString();
}
catch (XMLStreamException e)
{
throw new AssertionError(e);
}
}
/** . */
private static final Route[] EMPTY_ROUTE_ARRAY = new Route[0];
/** . */
private static final RouteParam[] EMPTY_ROUTE_PARAM_ARRAY = new RouteParam[0];
/** . */
private static final RequestParam[] EMPTY_REQUEST_PARAM_ARRAY = new RequestParam[0];
/** . */
private final Router router;
/** . */
private Route parent;
/** . */
private boolean terminal;
/** . */
private Route[] children;
/** . */
private Map<QualifiedName, RouteParam> routeParamMap;
/** . */
private RouteParam[] routeParamArray;
/** . */
private Map<String, RequestParam> requestParamMap;
/** . */
private RequestParam[] requestParamArray;
Route(Router router)
{
this.router = router;
this.parent = null;
this.terminal = true;
this.children = EMPTY_ROUTE_ARRAY;
this.routeParamMap = Collections.emptyMap();
this.routeParamArray = EMPTY_ROUTE_PARAM_ARRAY;
this.requestParamMap = Collections.emptyMap();
this.requestParamArray = EMPTY_REQUEST_PARAM_ARRAY;
}
final boolean isTerminal()
{
return terminal;
}
/*
* Ok, so this is not the fastest way to do it, but for now it's OK, it's what is needed, we'll find
* a way to optimize it later with some precompilation.
*/
final void render(RenderContext context, URIWriter writer) throws IOException
{
RouteMatch r = find(context);
// We found a route we need to render it now
if (r != null)
{
r.render(writer);
}
}
static class RouteMatch
{
/** The matched route.*/
final Route route;
/** The matched parameters. */
final Map<QualifiedName, String> matches;
/** . */
final RenderContext context;
RouteMatch(RenderContext context, Route route, Map<QualifiedName, String> matches)
{
this.context = context;
this.route = route;
this.matches = matches;
}
private void render(URIWriter writer) throws IOException
{
// Append path first
renderPath(route, writer, false);
// Append query parameters after
renderQueryString(route, writer);
}
private boolean renderPath(Route route, URIWriter writer, boolean hasChildren) throws IOException
{
boolean endWithSlash;
if (route.parent != null)
{
endWithSlash = renderPath(route.parent, writer, true);
}
else
{
endWithSlash = false;
}
//
if (route instanceof SegmentRoute)
{
SegmentRoute sr = (SegmentRoute)route;
if (!endWithSlash)
{
writer.append('/');
endWithSlash = true;
}
String name = sr.encodedName;
writer.append(name);
if (name.length() > 0)
{
endWithSlash = false;
}
}
else if (route instanceof PatternRoute)
{
PatternRoute pr = (PatternRoute)route;
if (!endWithSlash)
{
writer.append('/');
endWithSlash = true;
}
int i = 0;
int count = 0;
while (i < pr.params.length)
{
writer.append(pr.encodedChunks[i]);
count += pr.chunks[i].length();
//
PathParam def = pr.params[i];
String value = matches.get(def.name);
count += value.length();
// Write value
for (int len = value.length(), j = 0;j < len;j++)
{
char c = value.charAt(j);
if (c == route.router.separatorEscape)
{
if (def.encodingMode == EncodingMode.PRESERVE_PATH)
{
writer.append('_');
} else
{
writer.append('%');
writer.append(route.router.separatorEscapeNible1);
writer.append(route.router.separatorEscapeNible2);
}
}
else if (c == '/')
{
writer.append(def.encodingMode == EncodingMode.PRESERVE_PATH ? '/' : route.router.separatorEscape);
}
else
{
writer.appendSegment(c);
}
}
//
i++;
}
writer.append(pr.encodedChunks[i]);
count += pr.chunks[i].length();
if (count > 0)
{
endWithSlash = false;
}
}
else
{
if (!hasChildren)
{
writer.append('/');
endWithSlash = true;
}
}
//
return endWithSlash;
}
private void renderQueryString(Route route, URIWriter writer) throws IOException
{
if (route.parent != null)
{
renderQueryString(route.parent, writer);
}
//
for (RequestParam requestParamDef : route.requestParamArray)
{
String s = matches.get(requestParamDef.name);
switch (requestParamDef.valueMapping)
{
case CANONICAL:
break;
case NEVER_EMPTY:
if (s != null && s.length() == 0)
{
s = null;
}
break;
case NEVER_NULL:
if (s == null)
{
s = "";
}
break;
}
if (s != null)
{
writer.appendQueryParameter(requestParamDef.matchName, s);
}
}
}
}
final RouteMatch find(RenderContext context)
{
context.enter();
RouteMatch route = _find(context);
context.leave();
return route;
}
private RouteMatch _find(RenderContext context)
{
// Match first the static parameteters
for (RouteParam param : routeParamArray)
{
RenderContext.Parameter entry = context.getParameter(param.name);
if (entry != null && !entry.isMatched() && param.value.equals(entry.getValue()))
{
entry.remove(entry.getValue());
}
else
{
return null;
}
}
// Match any request parameter
for (RequestParam requestParamDef : requestParamArray)
{
RenderContext.Parameter entry = context.getParameter(requestParamDef.name);
boolean matched = false;
if (entry != null && !entry.isMatched())
{
if (requestParamDef.matchPattern == null || context.matcher(requestParamDef.matchPattern).matches(entry.getValue()))
{
matched = true;
}
}
if (matched)
{
entry.remove(entry.getValue());
}
else
{
switch (requestParamDef.controlMode)
{
case OPTIONAL:
// Do nothing
break;
case REQUIRED:
return null;
default:
throw new AssertionError();
}
}
}
// Match any pattern parameter
if (this instanceof PatternRoute)
{
PatternRoute prt = (PatternRoute)this;
for (int i = 0;i < prt.params.length;i++)
{
PathParam param = prt.params[i];
RenderContext.Parameter s = context.getParameter(param.name);
String matched = null;
if (s != null && !s.isMatched())
{
switch (param.encodingMode)
{
case FORM:
case PRESERVE_PATH:
for (int j = 0;j < param.matchingRegex.length;j++)
{
Regex renderingRegex = param.matchingRegex[j];
if (context.matcher(renderingRegex).matches(s.getValue()))
{
matched = param.templatePrefixes[j] + s.getValue() + param.templateSuffixes[j];
break;
}
}
break;
default:
throw new AssertionError();
}
}
if (matched != null)
{
s.remove(matched);
}
else
{
return null;
}
}
}
//
if (context.isEmpty() && terminal)
{
Map<QualifiedName, String> matches = Collections.emptyMap();
for (QualifiedName name : context.getNames())
{
RenderContext.Parameter parameter = context.getParameter(name);
if (matches.isEmpty())
{
matches = new HashMap<QualifiedName, String>();
}
String match = parameter.getMatch();
matches.put(name, match);
}
return new RouteMatch(context, this, matches);
}
//
for (Route route : children)
{
RouteMatch a = route.find(context);
if (a != null)
{
return a;
}
}
//
return null;
}
/**
* Create a route matcher for the a request.
*
* @param path the path
* @param requestParams the query parameters
* @return the route matcher
*/
final RouteMatcher route(String path, Map<String, String[]> requestParams)
{
return new RouteMatcher(this, Path.parse(path), requestParams);
}
static class RouteFrame
{
/**
* Defines the status of a frame.
*/
static enum Status
{
BEGIN,
MATCHED_PARAMS,
PROCESS_CHILDREN,
MATCHED,
END
}
/** . */
private final RouteFrame parent;
/** . */
private final Route route;
/** . */
private final Path path;
/** . */
private Status status;
/** The matches. */
private Map<QualifiedName, String> matches;
/** The index when iterating child in {@link org.exoplatform.web.controller.router.Route.RouteFrame.Status#PROCESS_CHILDREN} status. */
private int childIndex;
private RouteFrame(RouteFrame parent, Route route, Path path)
{
this.parent = parent;
this.route = route;
this.path = path;
this.status = Status.BEGIN;
this.childIndex = 0;
}
private RouteFrame(Route route, Path path)
{
this(null, route, path);
}
Map<QualifiedName, String> getParameters()
{
Map<QualifiedName, String> parameters = null;
for (RouteFrame frame = this;frame != null;frame = frame.parent)
{
if (frame.matches != null)
{
if (parameters == null)
{
parameters = new HashMap<QualifiedName, String>();
}
parameters.putAll(frame.matches);
}
for (RouteParam param : frame.route.routeParamArray)
{
if (parameters == null)
{
parameters = new HashMap<QualifiedName, String>();
}
parameters.put(param.name, param.value);
}
}
return parameters != null ? parameters : Collections.<QualifiedName, String>emptyMap();
}
}
static class RouteMatcher implements Iterator<Map<QualifiedName, String>>
{
/** . */
private final Map<String, String[]> requestParams;
/** . */
private RouteFrame frame;
/** . */
private RouteFrame next;
RouteMatcher(Route route, Path path, Map<String, String[]> requestParams)
{
this.frame = new RouteFrame(route, path);
this.requestParams = requestParams;
}
public boolean hasNext()
{
if (next == null)
{
if (frame != null)
{
frame = route(frame, requestParams);
}
if (frame != null && frame.status == RouteFrame.Status.MATCHED)
{
next = frame;
}
}
return next != null;
}
public Map<QualifiedName, String> next()
{
if (!hasNext())
{
throw new NoSuchElementException();
}
Map<QualifiedName, String> parameters = next.getParameters();
next = null;
return parameters;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
private static RouteFrame route(RouteFrame root, Map<String, String[]> requestParams)
{
RouteFrame current = root;
//
if (root.status == RouteFrame.Status.MATCHED)
{
if (root.parent != null)
{
current = root.parent;
}
else
{
return null;
}
}
else if (root.status != RouteFrame.Status.BEGIN)
{
throw new AssertionError("Unexpected status " + root.status);
}
//
while (true)
{
if (current.status == RouteFrame.Status.BEGIN)
{
boolean matched = true;
// We enter a frame
for (RequestParam requestParamDef : current.route.requestParamArray)
{
String value = null;
String[] values = requestParams.get(requestParamDef.matchName);
if (values != null && values.length > 0 && values[0] != null)
{
value = values[0];
}
if (value == null)
{
switch (requestParamDef.controlMode)
{
case OPTIONAL:
// Do nothing
break;
case REQUIRED:
matched = false;
break;
}
}
else if (!requestParamDef.matchValue(value))
{
matched = false;
break;
}
switch (requestParamDef.valueMapping)
{
case CANONICAL:
break;
case NEVER_EMPTY:
if (value != null && value.length() == 0)
{
value = null;
}
break;
case NEVER_NULL:
if (value == null)
{
value = "";
}
break;
}
if (value != null)
{
if (current.matches == null)
{
current.matches = new HashMap<QualifiedName, String>();
}
current.matches.put(requestParamDef.name, value);
}
}
//
if (matched)
{
// We enter next state
current.status = RouteFrame.Status.MATCHED_PARAMS;
}
else
{
current.status = RouteFrame.Status.END;
}
}
else if (current.status == RouteFrame.Status.MATCHED_PARAMS)
{
RouteFrame.Status next;
// Anything that does not begin with '/' returns null
if (current.path.length() > 0 && current.path.charAt(0) == '/')
{
// The '/' means the current controller if any, otherwise it may be processed by the pattern matching
if (current.path.length() == 1 && current.route.terminal)
{
next = RouteFrame.Status.MATCHED;
}
else
{
next = RouteFrame.Status.PROCESS_CHILDREN;
}
}
else
{
next = RouteFrame.Status.END;
}
//
current.status = next;
}
else if (current.status == RouteFrame.Status.PROCESS_CHILDREN)
{
if (current.childIndex < current.route.children.length)
{
Route child = current.route.children[current.childIndex++];
// The next frame
RouteFrame next;
//
if (child instanceof SegmentRoute)
{
SegmentRoute segmentRoute = (SegmentRoute)child;
//
if (segmentRoute.name.length() == 0)
{
// Delegate the process to the next route
next = new RouteFrame(current, segmentRoute, current.path);
}
else
{
// Find the next '/' for determining the segment and next path
// JULIEN : this can be computed multiple times
int pos = current.path.indexOf('/', 1);
if (pos == -1)
{
pos = current.path.length();
}
String segment = current.path.getValue().substring(1, pos);
// Determine next path
if (segmentRoute.name.equals(segment))
{
// Lazy create next segment path
// JULIEN : this can be computed multiple times
Path nextSegmentPath;
if (pos == current.path.length())
{
// todo make a constant
nextSegmentPath = Path.SLASH;
}
else
{
nextSegmentPath = current.path.subPath(pos);
}
// Delegate the process to the next route
next = new RouteFrame(current, segmentRoute, nextSegmentPath);
}
else
{
next = null;
}
}
}
else if (child instanceof PatternRoute)
{
PatternRoute patternRoute = (PatternRoute)child;
//
Regex.Match[] matches = patternRoute.pattern.matcher().find(current.path.getValue());
// We match
if (matches.length > 0)
{
// Build next controller context
int nextPos = matches[0].getEnd();
Path nextPath;
if (current.path.length() == nextPos)
{
nextPath = Path.SLASH;
}
else
{
if (nextPos > 0 && current.path.charAt(nextPos - 1) == '/')
{
nextPos--;
}
//
nextPath = current.path.subPath(nextPos);
}
// Delegate to next patternRoute
next = new RouteFrame(current, patternRoute, nextPath);
// JULIEN : this can be done lazily
// Append parameters
int index = 1;
for (int i = 0;i < patternRoute.params.length;i++)
{
PathParam param = patternRoute.params[i];
for (int j = 0;j < param.matchingRegex.length;j++)
{
Regex.Match match = matches[index + j];
if (match.getEnd() != -1)
{
String value;
if (param.encodingMode == EncodingMode.FORM)
{
StringBuilder sb = new StringBuilder();
for (int from = match.getStart();from < match.getEnd();from++)
{
char c = current.path.charAt(from);
if (c == child.router.separatorEscape && current.path.getRawLength(from) == 1)
{
c = '/';
}
sb.append(c);
}
value = sb.toString();
}
else
{
value = match.getValue();
}
if (next.matches == null)
{
next.matches = new HashMap<QualifiedName, String>();
}
next.matches.put(param.name, value);
break;
}
else
{
// It can be the match of a particular disjunction
// or an optional parameter
}
}
index += param.matchingRegex.length;
}
}
else
{
next = null;
}
}
else
{
throw new AssertionError();
}
//
if (next != null)
{
current = next;
}
}
else
{
current.status = RouteFrame.Status.END;
}
}
else if (current.status == RouteFrame.Status.MATCHED)
{
// We found a solution
break;
}
else if (current.status == RouteFrame.Status.END)
{
if (current.parent != null)
{
current = current.parent;
}
else
{
// The end of the search
break;
}
}
else
{
throw new AssertionError();
}
}
//
return current;
}
final <R extends Route> R add(R route) throws MalformedRouteException
{
if (route == null)
{
throw new NullPointerException("No null route accepted");
}
if (((Route)route).parent != null)
{
throw new IllegalArgumentException("No route with an existing parent can be accepted");
}
//
LinkedList<Param> ancestorParams = new LinkedList<Param>();
findAncestorOrSelfParams(ancestorParams);
LinkedList<Param> descendantParams = new LinkedList<Param>();
for (Param param : ancestorParams)
{
((Route)route).findDescendantOrSelfParams(param.name, descendantParams);
if (descendantParams.size() > 0)
{
throw new MalformedRouteException("Duplicate parameter " + param.name);
}
}
//
if (route instanceof PatternRoute || route instanceof SegmentRoute)
{
children = Tools.appendTo(children, route);
terminal = false;
((Route)route).parent = this;
}
else
{
throw new IllegalArgumentException("Only accept segment or pattern routes");
}
//
return route;
}
final Set<String> getSegmentNames()
{
Set<String> names = new HashSet<String>();
for (Route child : children)
{
if (child instanceof SegmentRoute)
{
SegmentRoute childSegment = (SegmentRoute)child;
names.add(childSegment.name);
}
}
return names;
}
final int getSegmentSize(String segmentName)
{
int size = 0;
for (Route child : children)
{
if (child instanceof SegmentRoute)
{
SegmentRoute childSegment = (SegmentRoute)child;
if (segmentName.equals(childSegment.name))
{
size++;
}
}
}
return size;
}
final SegmentRoute getSegment(String segmentName, int index)
{
for (Route child : children)
{
if (child instanceof SegmentRoute)
{
SegmentRoute childSegment = (SegmentRoute)child;
if (segmentName.equals(childSegment.name))
{
if (index == 0)
{
return childSegment;
}
else
{
index--;
}
}
}
}
return null;
}
final int getPatternSize()
{
int size = 0;
for (Route route : children)
{
if (route instanceof PatternRoute)
{
size++;
}
}
return size;
}
final PatternRoute getPattern(int index)
{
for (Route route : children)
{
if (route instanceof PatternRoute)
{
if (index == 0)
{
return (PatternRoute)route;
}
else
{
index--;
}
}
}
return null;
}
final Route append(RouteDescriptor descriptor) throws MalformedRouteException
{
Route route = append(descriptor.getPathParams(), descriptor.getPath());
//
for (RouteParamDescriptor routeParamDesc : descriptor.getRouteParams())
{
route.add(RouteParam.create(routeParamDesc));
}
//
for (RequestParamDescriptor requestParamDesc : descriptor.getRequestParams())
{
route.add(RequestParam.create(requestParamDesc, router));
}
//
for (RouteDescriptor childDescriptor : descriptor.getChildren())
{
route.append(childDescriptor);
}
//
return route;
}
final Route add(RouteParam param) throws MalformedRouteException
{
Param existing = findParam(param.name);
if (existing != null)
{
throw new MalformedRouteException("Duplicate parameter " + param.name);
}
if (routeParamArray.length == 0)
{
routeParamMap = new HashMap<QualifiedName, RouteParam>();
}
routeParamMap.put(param.name, param);
routeParamArray = Tools.appendTo(routeParamArray, param);
return this;
}
final Route add(RequestParam param) throws MalformedRouteException
{
Param existing = findParam(param.name);
if (existing != null)
{
throw new MalformedRouteException("Duplicate parameter " + param.name);
}
if (requestParamArray.length == 0)
{
requestParamMap = new HashMap<String, RequestParam>();
}
requestParamMap.put(param.matchName, param);
requestParamArray = Tools.appendTo(requestParamArray, param);
return this;
}
/**
* Append a path, creates the necessary routes and returns the last route added.
*
* @param pathParamDescriptors the path param descriptors
* @param path the path to append
* @return the last route added
*/
private Route append(Map<QualifiedName, PathParamDescriptor> pathParamDescriptors, String path) throws MalformedRouteException
{
if (path.length() == 0 || path.charAt(0) != '/')
{
throw new MalformedRouteException();
}
//
int pos = path.length();
int level = 0;
List<Integer> start = new ArrayList<Integer>();
List<Integer> end = new ArrayList<Integer>();
for (int i = 1;i < path.length();i++)
{
char c = path.charAt(i);
if (c == '{')
{
if (level++ == 0)
{
start.add(i);
}
}
else if (c == '}')
{
if (--level == 0)
{
end.add(i);
}
}
else if (c == '/')
{
if (level == 0)
{
pos = i;
break;
}
}
}
//
Route next;
if (start.isEmpty())
{
String segment = path.substring(1, pos);
SegmentRoute route = new SegmentRoute(router, segment);
add(route);
next = route;
}
else
{
if (start.size() == end.size())
{
PatternBuilder builder = new PatternBuilder();
builder.expr("^").expr('/');
List<String> chunks = new ArrayList<String>();
List<PathParam> parameterPatterns = new ArrayList<PathParam>();
//
int previous = 1;
for (int i = 0;i < start.size();i++)
{
builder.litteral(path, previous, start.get(i));
chunks.add(path.substring(previous, start.get(i)));
String parameterName = path.substring(start.get(i) + 1, end.get(i));
//
QualifiedName parameterQName = QualifiedName.parse(parameterName);
// Now get path param metadata
PathParamDescriptor parameterDescriptor = pathParamDescriptors.get(parameterQName);
//
PathParam param;
if (parameterDescriptor != null)
{
param = PathParam.create(parameterDescriptor, router);
}
else
{
param = PathParam.create(parameterQName, router);
}
// Append routing regex to the route regex surrounded by a non capturing regex
// to isolate routingRegex like a|b or a(.)b
builder.expr("(?:").expr(param.routingRegex).expr(")");
// Add the path param with the rendering regex
parameterPatterns.add(param);
previous = end.get(i) + 1;
}
//
builder.litteral(path, previous, pos);
// We want to satisfy one of the following conditions
// - the next char after the matched expression is '/'
// - the expression matched until the end
// - the match expression is the '/' expression
builder.expr("(?:(?<=^/)|(?=/)|$)");
//
chunks.add(path.substring(previous, pos));
PatternRoute route = new PatternRoute(router, router.compile(builder.build()), parameterPatterns, chunks);
// Wire
add(route);
//
next = route;
}
else
{
throw new UnsupportedOperationException("Report error");
}
}
//
if (pos < path.length())
{
return next.append(pathParamDescriptors, path.substring(pos));
}
else
{
return next;
}
}
private Param getParam(QualifiedName name)
{
Param param = routeParamMap.get(name);
if (param == null)
{
for (RequestParam requestParam : requestParamArray)
{
if (requestParam.name.equals(name))
{
param = requestParam;
break;
}
}
if (param == null && this instanceof PatternRoute)
{
for (PathParam pathParam : ((PatternRoute)this).params)
{
if (pathParam.name.equals(name))
{
param = pathParam;
break;
}
}
}
}
return param;
}
private Param findParam(QualifiedName name)
{
Param param = getParam(name);
if (param == null && parent != null)
{
param = parent.findParam(name);
}
return param;
}
private void findParams(List<Param> params)
{
Collections.addAll(params, routeParamArray);
for (RequestParam param : requestParamArray)
{
params.add(param);
}
if (this instanceof PatternRoute)
{
Collections.addAll(params, ((PatternRoute)this).params);
}
}
private void findAncestorOrSelfParams(List<Param> params)
{
findParams(params);
if (parent != null)
{
parent.findAncestorOrSelfParams(params);
}
}
/**
* Find the params having the specified <code>name</code> among this route or its descendants.
*
* @param name the name
* @param params the list collecting the found params
*/
private void findDescendantOrSelfParams(QualifiedName name, List<Param> params)
{
Param param = getParam(name);
if (param != null)
{
params.add(param);
}
}
}