package org.etk.core.rest.impl.uri;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UriPattern {
/**
* Sort the templates according to the string comparison of the template
* regular expressions.
* <p>
* JSR-311 specification: "Sort the set of matching resource classes using the
* number of characters in the regular expression not resulting from template
* variables as the primary key and the number of matching groups as a
* secondary key"
* </p>
*/
public static final Comparator<UriPattern> URIPATTERN_COMPARATOR = new UriPatternComparator();
/**
* URI pattern comparator.
*/
private static final class UriPatternComparator implements Comparator<UriPattern> {
/**
* {@inheritDoc}
*/
public int compare(UriPattern o1, UriPattern o2) {
if (o1 == null & o2 == null)
return 0;
if (o1 == null)
return 1;
if (o2 == null)
return -1;
if ("".equals(o1.getTemplate()) && "".equals(o2.getTemplate()))
return 0;
if ("".equals(o1.getTemplate()))
return 1;
if ("".equals(o2.getTemplate()))
return -1;
if (o1.getNumberOfLiteralCharacters() < o2.getNumberOfLiteralCharacters())
return 1;
if (o1.getNumberOfLiteralCharacters() > o2.getNumberOfLiteralCharacters())
return -1;
// pattern with two variables less the pattern with four variables
if (o1.getParameterNames().size() < o2.getParameterNames().size())
return 1;
if (o1.getParameterNames().size() > o2.getParameterNames().size())
return -1;
return o1.getRegex().compareTo(o2.getRegex());
}
}
/**
* Should be added in URI pattern regular expression.
*/
private static final String URI_PATTERN_TAIL = "(/.*)?";
//
/**
* List of names for URI template variables.
*/
private final List<String> parameterNames;
/**
* URI template.
*/
private final String template;
/**
* Number of characters in URI template NOT resulting from template variable
* substitution.
*/
private final int numberOfCharacters;
/**
* Compiled URI pattern.
*/
private final Pattern pattern;
/**
* Regular expressions for URI pattern.
*/
private final String regex;
/**
* Regex capturing group indexes.
*/
private final int[] groupIndexes;
//
/**
* Constructs UriPattern.
*
* @param template the source template
* @see {@link javax.ws.rs.Path}
*/
public UriPattern(String template) {
if (template.length() > 0 && template.charAt(0) != '/')
template = "/" + template;
UriTemplateParser parser = new UriTemplateParser(template);
this.template = parser.getTemplate();
this.parameterNames = Collections.unmodifiableList(parser.getParameterNames());
this.numberOfCharacters = parser.getNumberOfLiteralCharacters();
int[] indxs = parser.getGroupIndexes();
if (indxs != null) {
this.groupIndexes = new int[indxs.length + 1];
System.arraycopy(indxs, 0, this.groupIndexes, 0, indxs.length);
// Add one more index for URI_PATTERN_TAIL
this.groupIndexes[groupIndexes.length - 1] = indxs[indxs.length - 1] + 1;
} else {
this.groupIndexes = null;
}
String regex = parser.getRegex();
if (regex.endsWith("/"))
regex = regex.substring(0, regex.length() - 1);
this.regex = regex + URI_PATTERN_TAIL;
this.pattern = Pattern.compile(this.regex);
}
/**
* {@inheritDoc}
*/
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj.getClass() != getClass())
return false;
return getRegex().equals(((UriPattern) obj).getRegex());
}
/**
* {@inheritDoc}
*/
public int hashCode() {
return template.hashCode() + regex.hashCode();
}
/**
* Get the regex pattern.
*
* @return the regex pattern
*/
public Pattern getPattern() {
return pattern;
}
/**
* Get the URI template as a String.
*
* @return the URI template
*/
public String getTemplate() {
return template;
}
/**
* Get the regular expression.
*
* @return the regular expression
*/
public String getRegex() {
return regex;
}
/**
* Get the number of literal characters in the template.
*
* @return number of literal characters in the template
*/
public int getNumberOfLiteralCharacters() {
return numberOfCharacters;
}
/**
* @return list of names
*/
public List<String> getParameterNames() {
return parameterNames;
}
/**
* Check is URI string match to pattern. If it is then fill given list by
* parameter value. Before coping value list is cleared. List will be 1
* greater then number of keys. It can be used for check is resource is
* matching to requested. If resource is match the last element in list must
* be '/' or null.
*
* @param uri the URI string
* @param parameters target list
* @return true if URI string is match to pattern, false otherwise
*/
public boolean match(String uri, List<String> parameters) {
if (parameters == null)
throw new IllegalArgumentException("list is null");
if (uri == null || uri.length() == 0) {
if (pattern == null)
return true;
return false;
} else if (pattern == null) {
return false;
}
Matcher m = pattern.matcher(uri);
if (!m.matches())
return false;
parameters.clear();
if (groupIndexes == null) {
for (int i = 1; i <= m.groupCount(); i++)
parameters.add(m.group(i));
} else {
for (int i = 0; i < groupIndexes.length - 1; i++)
parameters.add(m.group(groupIndexes[i]));
}
return true;
}
/**
* {@inheritDoc}
*/
public String toString() {
return regex;
}
/**
* Create URI from URI part. Each URI part can contains templates.
*
* @param schema the schema URI part
* @param userInfo the user info URI part
* @param host the host name URI part
* @param port the port number URI part
* @param path the path URI part
* @param query the query string URI part
* @param fragment the fragment URI part
* @param values the values which must be used instead templates parameters
* @param encode if true then encode value before add it in URI, otherwise
* value must be validate to legal characters
* @return the URI string
*/
public static String createUriWithValues(String schema,
String userInfo,
String host,
int port,
String path,
String query,
String fragment,
Map<String, ? extends Object> values,
boolean encode) {
StringBuffer sb = new StringBuffer();
if (schema != null) {
appendUriPart(sb, schema, UriComponent.SCHEME, values, false);
sb.append(':');
}
if (userInfo != null || host != null || port != -1) {
sb.append('/').append('/');
if (userInfo != null && userInfo.length() > 0) {
appendUriPart(sb, userInfo, UriComponent.USER_INFO, values, encode);
sb.append('@');
}
if (host != null)
appendUriPart(sb, host, UriComponent.HOST, values, encode);
if (port != -1) {
sb.append(':');
appendUriPart(sb, "" + port, UriComponent.PORT, values, encode);
}
}
if (path != null) {
if (sb.length() > 0 && path.charAt(0) != '/')
sb.append('/');
appendUriPart(sb, path, UriComponent.PATH, values, encode);
}
if (query != null && query.length() > 0) {
sb.append('?');
appendUriPart(sb, query, UriComponent.QUERY, values, encode);
}
if (fragment != null && fragment.length() > 0) {
sb.append('#');
appendUriPart(sb, fragment, UriComponent.FRAGMENT, values, encode);
}
return sb.toString();
}
/**
* Create URI from URI part. Each URI part can contains templates.
*
* @param schema the schema URI part
* @param userInfo the user info URI part
* @param host the host name URI part
* @param port the port number URI part
* @param path the path URI part
* @param query the query string URI part
* @param fragment the fragment URI part
* @param values the values which must be used instead templates parameters
* @param encode if true then encode value before add it in URI, otherwise
* value must be validate to legal characters
* @return the URI string
*/
public static String createUriWithValues(String schema,
String userInfo,
String host,
int port,
String path,
String query,
String fragment,
Object[] values,
boolean encode) {
Map<String, String> m = new HashMap<String, String>();
StringBuffer sb = new StringBuffer();
int p = 0;
if (schema != null) {
p = appendUriPart(sb, schema, UriComponent.SCHEME, values, p, m, false);
sb.append(':');
}
if (userInfo != null || host != null || port != -1) {
sb.append('/').append('/');
if (userInfo != null && userInfo.length() > 0) {
p = appendUriPart(sb, userInfo, UriComponent.USER_INFO, values, p, m, encode);
sb.append('@');
}
if (host != null)
p = appendUriPart(sb, host, UriComponent.HOST, values, p, m, encode);
if (port != -1) {
sb.append(':');
p = appendUriPart(sb, "" + port, UriComponent.PORT, values, p, m, encode);
}
}
if (path != null) {
if (sb.length() > 0 && path.charAt(0) != '/')
sb.append('/');
p = appendUriPart(sb, path, UriComponent.PATH, values, p, m, encode);
}
if (query != null && query.length() > 0) {
sb.append('?');
p = appendUriPart(sb, query, UriComponent.QUERY, values, p, m, encode);
}
if (fragment != null && fragment.length() > 0) {
sb.append('#');
p = appendUriPart(sb, fragment, UriComponent.FRAGMENT, values, p, m, encode);
}
return sb.toString();
}
/**
* @param sb the StringBuffer for appending URI part
* @param uriPart URI part
* @param component the URI component
* @param values values map
* @param encode if true then encode value before add it in URI, otherwise
* value must be validate to legal characters
*/
private static void appendUriPart(StringBuffer sb,
String uriPart,
int component,
Map<String, ? extends Object> values,
boolean encode) {
if (!hasUriTemplates(uriPart)) {
sb.append(uriPart);
return;
}
Matcher m = UriTemplateParser.URI_PARAMETERS_PATTERN.matcher(uriPart);
int start = 0;
while (m.find()) {
sb.append(uriPart, start, m.start()); // 'static' part
String param = uriPart.substring(m.start() + 1, m.end() - 1);
Object o = values.get(param);
if (o == null)
throw new IllegalArgumentException("Not found corresponding value for parameter " + param);
String value = o.toString();
sb.append(encode ? UriComponent.encode(value, component, true)
: UriComponent.recognizeEncode(value, component, true));
start = m.end();
}
// copy the last part or uriPart
sb.append(uriPart, start, uriPart.length());
}
/**
* @param sb the StringBuffer for appending URI part
* @param uriPart URI part
* @param component the URI component
* @param sourceValues the source array of values
* @param offset the offset in array
* @param values values map, keep parameter/value pair which have been already
* found. From java docs:
* <p>
* All instances of the same template parameter will be replaced by
* the same value that corresponds to the position of the first
* instance of the template parameter. e.g. the template
* "{a}/{b}/{a}" with values {"x", "y", "z"} will result in the the
* URI "x/y/x", <i>not</i> "x/y/z".
* </p>
* @param encode if true then encode value before add it in URI, otherwise
* value must be validate to legal characters
* @return offset
*/
private static int appendUriPart(StringBuffer sb,
String uriPart,
int component,
Object[] sourceValues,
int offset,
Map<String, String> values,
boolean encode) {
if (!hasUriTemplates(uriPart)) {
sb.append(uriPart);
return offset;
}
Matcher m = UriTemplateParser.URI_PARAMETERS_PATTERN.matcher(uriPart);
int start = 0;
while (m.find()) {
sb.append(uriPart, start, m.start()); // 'static' part
String param = uriPart.substring(m.start() + 1, m.end() - 1);
String value = values.get(param);
if (value != null) {
// Value already known, then don't take new one from array. Value from
// map is already validate or encoded, so do nothing about it
sb.append(value);
} else {
// Value is unknown, we met it first time, then process it and keep in
// map. Value will be encoded (or validate)before putting in map.
if (offset < sourceValues.length)
value = sourceValues[offset++].toString();
if (value != null) {
value = encode ? UriComponent.encode(value, component, true)
: UriComponent.recognizeEncode(value, component, true);
values.put(param, value);
sb.append(value);
} else
throw new IllegalArgumentException("Not found corresponding value for parameter " + param);
}
start = m.end();
}
// copy the last part or uriPart
sb.append(uriPart, start, uriPart.length());
return offset;
}
/**
* Check does given URI string has templates.
*
* @param uri the URI which must be checked
* @return true if URI has templates false otherwise
*/
private static boolean hasUriTemplates(String uri) {
return uri.indexOf('{') != -1;
}
}