org.restlet.util.Template Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.servicemix.bundles.restlet
Show all versions of org.apache.servicemix.bundles.restlet
This OSGi bundle wraps org.restlet, and com.noelios.restlet ${pkgVersion} jar files.
The newest version!
/**
* Copyright 2005-2008 Noelios Technologies.
*
* The contents of this file are subject to the terms of the following open
* source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.gnu.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.sun.com/cddl/cddl.html
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royaltee free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.restlet.Context;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
/**
* String template with a pluggable model. Supports both formatting and parsing.
* The template variables can be inserted using the "{name}" syntax and
* described using the modifiable map of variable descriptors. When no
* descriptor is found for a given variable, the template logic uses its default
* variable property initialized using the default {@link Variable} constructor.
*
* Note that the variable descriptors can be changed before the first parsing or
* matching call. After that point, changes won't be taken into account.
*
* Format and parsing methods are specially available to deal with requests and
* response. See {@link #format(Request, Response)} and
* {@link #parse(String, Request)}.
*
* @see Resolver
* @see URI Template specification
* @author Jerome Louvel
*/
public class Template {
public static final int MODE_EQUALS = 2;
public static final int MODE_STARTS_WITH = 1;
/**
* Appends to a pattern a repeating group of a given content based on a
* class of characters.
*
* @param pattern
* The pattern to append to.
* @param content
* The content of the group.
* @param required
* Indicates if the group is required.
*/
private static void appendClass(StringBuilder pattern, String content,
boolean required) {
pattern.append("(");
if (content.equals(".")) {
// Special case for the TYPE_ALL variable type because the
// dot looses its meaning inside a character class
pattern.append(content);
} else {
pattern.append("[").append(content).append(']');
}
if (required) {
pattern.append("+");
} else {
pattern.append("*");
}
pattern.append(")");
}
/**
* Appends to a pattern a repeating group of a given content based on a
* non-capturing group.
*
* @param pattern
* The pattern to append to.
* @param content
* The content of the group.
* @param required
* Indicates if the group is required.
*/
private static void appendGroup(StringBuilder pattern, String content,
boolean required) {
pattern.append("((?:").append(content).append(')');
if (required) {
pattern.append("+");
} else {
pattern.append("*");
}
pattern.append(")");
}
/**
* Returns the Regex pattern string corresponding to a variable.
*
* @param variable
* The variable.
* @return The Regex pattern string corresponding to a variable.
*/
private static String getVariableRegex(Variable variable) {
String result = null;
if (variable.isFixed()) {
result = "(" + Pattern.quote(variable.getDefaultValue()) + ")";
} else {
// Expressions to create character classes
final String ALL = ".";
final String ALPHA = "a-zA-Z";
final String DIGIT = "\\d";
final String ALPHA_DIGIT = ALPHA + DIGIT;
final String HEXA = DIGIT + "ABCDEFabcdef";
final String URI_UNRESERVED = ALPHA_DIGIT + "\\-\\.\\_\\~";
final String URI_GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@";
final String URI_SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=";
final String URI_RESERVED = URI_GEN_DELIMS + URI_SUB_DELIMS;
final String WORD = "\\w";
// Basic rules expressed by the HTTP rfc.
final String CRLF = "\\r\\n";
final String CTL = "\\p{Cntrl}";
final String LWS = CRLF + "\\ \\t";
final String SEPARATOR = "\\(\\)\\<\\>\\@\\,\\;\\:\\[\\]\"\\/\\\\?\\=\\{\\}\\ \\t";
final String TOKEN = "[^" + SEPARATOR + "]";
final String COMMENT = "[^" + CTL + "]" + "[^\\(\\)]" + LWS;
final String COMMENT_ATTRIBUTE = "[^\\;\\(\\)]";
// Expressions to create non-capturing groups
final String PCT_ENCODED = "\\%[" + HEXA + "][" + HEXA + "]";
// final String PCHAR = "[" + URI_UNRESERVED + "]|(?:" + PCT_ENCODED
// + ")|[" + URI_SUB_DELIMS + "]|\\:|\\@";
final String PCHAR = "[" + URI_UNRESERVED + URI_SUB_DELIMS
+ "\\:\\@]|(?:" + PCT_ENCODED + ")";
final String QUERY = PCHAR + "|\\/|\\?";
final String FRAGMENT = QUERY;
final String URI_PATH = PCHAR + "|\\/";
final String URI_ALL = "[" + URI_RESERVED + URI_UNRESERVED
+ "]|(?:" + PCT_ENCODED + ")";
final StringBuilder coreRegex = new StringBuilder();
switch (variable.getType()) {
case Variable.TYPE_ALL:
appendClass(coreRegex, ALL, variable.isRequired());
break;
case Variable.TYPE_ALPHA:
appendClass(coreRegex, ALPHA, variable.isRequired());
break;
case Variable.TYPE_DIGIT:
appendClass(coreRegex, DIGIT, variable.isRequired());
break;
case Variable.TYPE_ALPHA_DIGIT:
appendClass(coreRegex, ALPHA_DIGIT, variable.isRequired());
break;
case Variable.TYPE_URI_ALL:
appendGroup(coreRegex, URI_ALL, variable.isRequired());
break;
case Variable.TYPE_URI_UNRESERVED:
appendClass(coreRegex, URI_UNRESERVED, variable.isRequired());
break;
case Variable.TYPE_WORD:
appendClass(coreRegex, WORD, variable.isRequired());
break;
case Variable.TYPE_URI_FRAGMENT:
appendGroup(coreRegex, FRAGMENT, variable.isRequired());
break;
case Variable.TYPE_URI_PATH:
appendGroup(coreRegex, URI_PATH, variable.isRequired());
break;
case Variable.TYPE_URI_QUERY:
appendGroup(coreRegex, QUERY, variable.isRequired());
break;
case Variable.TYPE_URI_SEGMENT:
appendGroup(coreRegex, PCHAR, variable.isRequired());
break;
case Variable.TYPE_TOKEN:
appendClass(coreRegex, TOKEN, variable.isRequired());
break;
case Variable.TYPE_COMMENT:
appendClass(coreRegex, COMMENT, variable.isRequired());
break;
case Variable.TYPE_COMMENT_ATTRIBUTE:
appendClass(coreRegex, COMMENT_ATTRIBUTE, variable.isRequired());
break;
}
result = coreRegex.toString();
}
return result;
}
/** The default variable to use when no matching variable descriptor exists. */
private volatile Variable defaultVariable;
/** True if the variables must be encoded when formatting the template. */
private volatile boolean encodeVariables;
/** The logger to use. */
private volatile Logger logger;
/** The matching mode to use when parsing a formatted reference. */
private volatile int matchingMode;
/** The pattern to use for formatting or parsing. */
private volatile String pattern;
/** The internal Regex pattern. */
private volatile Pattern regexPattern;
/** The sequence of Regex variable names as found in the pattern string. */
private volatile List regexVariables;
/** The map of variables associated to the route's template. */
private final Map variables;
/**
* Default constructor. Each variable matches any sequence of characters by
* default. When parsing, the template will attempt to match the whole
* template. When formatting, the variable are replaced by an empty string
* if they don't exist in the model.
*
* @param logger
* The logger to use.
* @param pattern
* The pattern to use for formatting or parsing.
* @deprecated Use the constructor with logger instead. The logger can still
* be set using the {@link #setLogger(Logger)} method.
*/
@Deprecated
public Template(Logger logger, String pattern) {
this(logger, pattern, MODE_EQUALS, Variable.TYPE_ALL, "", true, false);
}
/**
* Constructor.
*
* @param logger
* The logger to use.
* @param pattern
* The pattern to use for formatting or parsing.
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
* @deprecated Use the constructor with logger instead. The logger can still
* be set using the {@link #setLogger(Logger)} method.
*/
@Deprecated
public Template(Logger logger, String pattern, int matchingMode) {
this(logger, pattern, matchingMode, Variable.TYPE_ALL, "", true, false);
}
/**
* Constructor.
*
* @param logger
* The logger to use.
* @param pattern
* The pattern to use for formatting or parsing.
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
* @param defaultType
* The default type of variables with no descriptor.
* @param defaultDefaultValue
* The default value for null variables with no descriptor.
* @param defaultRequired
* The default required flag for variables with no descriptor.
* @param defaultFixed
* The default fixed value for variables with no descriptor.
* @deprecated Use the constructor with logger instead. The logger can still
* be set using the {@link #setLogger(Logger)} method.
*/
@Deprecated
public Template(Logger logger, String pattern, int matchingMode,
int defaultType, String defaultDefaultValue,
boolean defaultRequired, boolean defaultFixed) {
this(logger, pattern, matchingMode, defaultType, defaultDefaultValue,
defaultRequired, defaultFixed, false);
}
/**
* Constructor.
*
* @param logger
* The logger to use.
* @param pattern
* The pattern to use for formatting or parsing.
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
* @param defaultType
* The default type of variables with no descriptor.
* @param defaultDefaultValue
* The default value for null variables with no descriptor.
* @param defaultRequired
* The default required flag for variables with no descriptor.
* @param defaultFixed
* The default fixed value for variables with no descriptor.
* @param encodeVariables
* True if the variables must be encoded when formatting the
* template.
* @deprecated Use the constructor with logger instead. The logger can still
* be set using the {@link #setLogger(Logger)} method.
*/
@Deprecated
public Template(Logger logger, String pattern, int matchingMode,
int defaultType, String defaultDefaultValue,
boolean defaultRequired, boolean defaultFixed,
boolean encodeVariables) {
this.logger = (logger == null) ? Logger.getLogger(getClass()
.getCanonicalName()) : logger;
this.pattern = pattern;
this.defaultVariable = new Variable(defaultType, defaultDefaultValue,
defaultRequired, defaultFixed);
this.matchingMode = matchingMode;
this.variables = new ConcurrentHashMap();
this.regexPattern = null;
this.encodeVariables = encodeVariables;
}
/**
* Default constructor. Each variable matches any sequence of characters by
* default. When parsing, the template will attempt to match the whole
* template. When formatting, the variable are replaced by an empty string
* if they don't exist in the model.
*
* @param pattern
* The pattern to use for formatting or parsing.
*/
public Template(String pattern) {
this(Context.getCurrentLogger(), pattern);
}
/**
* Constructor.
*
* @param pattern
* The pattern to use for formatting or parsing.
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
*/
public Template(String pattern, int matchingMode) {
this(Context.getCurrentLogger(), pattern, matchingMode);
}
/**
* Constructor.
*
* @param pattern
* The pattern to use for formatting or parsing.
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
* @param defaultType
* The default type of variables with no descriptor.
* @param defaultDefaultValue
* The default value for null variables with no descriptor.
* @param defaultRequired
* The default required flag for variables with no descriptor.
* @param defaultFixed
* The default fixed value for variables with no descriptor.
*/
public Template(String pattern, int matchingMode, int defaultType,
String defaultDefaultValue, boolean defaultRequired,
boolean defaultFixed) {
this(Context.getCurrentLogger(), pattern, matchingMode, defaultType,
defaultDefaultValue, defaultRequired, defaultFixed);
}
/**
* Creates a formatted string based on the given map of values.
*
* @param values
* The values to use when formatting.
* @return The formatted string.
* @see Resolver#createResolver(Map)
*/
public String format(Map values) {
return format(Resolver.createResolver(values));
}
/**
* Creates a formatted string based on the given request and response.
*
* @param request
* The request to use as a model.
* @param response
* The response to use as a model.
* @return The formatted string.
* @see Resolver#createResolver(Request, Response)
*/
public String format(Request request, Response response) {
return format(Resolver.createResolver(request, response));
}
/**
* Creates a formatted string based on the given variable resolver.
*
* @param resolver
* The variable resolver to use.
* @return The formatted string.
*/
public String format(Resolver resolver) {
final StringBuilder result = new StringBuilder();
StringBuilder varBuffer = null;
char next;
boolean inVariable = false;
final int patternLength = getPattern().length();
for (int i = 0; i < patternLength; i++) {
next = getPattern().charAt(i);
if (inVariable) {
if (Reference.isUnreserved(next)) {
// Append to the variable name
varBuffer.append(next);
} else if (next == '}') {
// End of variable detected
if (varBuffer.length() == 0) {
getLogger().warning(
"Empty pattern variables are not allowed : "
+ this.regexPattern);
} else {
final String varName = varBuffer.toString();
String varValue = resolver.resolve(varName);
Variable var = getVariables().get(varName);
// Use the default values instead
if (varValue == null) {
if (var == null) {
var = getDefaultVariable();
}
if (var != null) {
varValue = var.getDefaultValue();
}
}
if (this.encodeVariables) {
// In case the values must be encoded.
if (var != null) {
result.append(var.encode(varValue));
} else {
result.append(Reference.encode(varValue));
}
} else {
if ((var != null) && var.isEncodedOnFormat()) {
result.append(Reference.encode(varValue));
} else {
result.append(varValue);
}
}
// Reset the variable name buffer
varBuffer = new StringBuilder();
}
inVariable = false;
} else {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.regexPattern);
}
} else {
if (next == '{') {
inVariable = true;
varBuffer = new StringBuilder();
} else if (next == '}') {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.regexPattern);
} else {
result.append(next);
}
}
}
return result.toString();
}
/**
* Returns the default variable.
*
* @return The default variable.
*/
public Variable getDefaultVariable() {
return this.defaultVariable;
}
/**
* Returns the logger to use.
*
* @return The logger to use.
*/
public Logger getLogger() {
return this.logger;
}
/**
* Returns the matching mode to use when parsing a formatted reference.
*
* @return The matching mode to use when parsing a formatted reference.
*/
public int getMatchingMode() {
return this.matchingMode;
}
/**
* Returns the pattern to use for formatting or parsing.
*
* @return The pattern to use for formatting or parsing.
*/
public String getPattern() {
return this.pattern;
}
/**
* Compiles the URI pattern into a Regex pattern.
*
* @return The Regex pattern.
*/
private Pattern getRegexPattern() {
if (this.regexPattern == null) {
synchronized (this) {
if (this.regexPattern == null) {
getRegexVariables().clear();
final StringBuilder patternBuffer = new StringBuilder();
StringBuilder varBuffer = null;
char next;
boolean inVariable = false;
for (int i = 0; i < getPattern().length(); i++) {
next = getPattern().charAt(i);
if (inVariable) {
if (Reference.isUnreserved(next)) {
// Append to the variable name
varBuffer.append(next);
} else if (next == '}') {
// End of variable detected
if (varBuffer.length() == 0) {
getLogger().warning(
"Empty pattern variables are not allowed : "
+ this.regexPattern);
} else {
final String varName = varBuffer.toString();
final int varIndex = getRegexVariables()
.indexOf(varName);
if (varIndex != -1) {
// The variable is used several times in
// the pattern, ensure that this
// constraint is enforced when parsing.
patternBuffer.append("\\"
+ (varIndex + 1));
} else {
// New variable detected. Insert a
// capturing group.
getRegexVariables().add(varName);
Variable var = getVariables().get(
varName);
if (var == null) {
var = getDefaultVariable();
}
patternBuffer
.append(getVariableRegex(var));
}
// Reset the variable name buffer
varBuffer = new StringBuilder();
}
inVariable = false;
} else {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.regexPattern);
}
} else {
if (next == '{') {
inVariable = true;
varBuffer = new StringBuilder();
} else if (next == '}') {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.regexPattern);
} else {
patternBuffer.append(quote(next));
}
}
}
this.regexPattern = Pattern.compile(patternBuffer
.toString());
}
}
}
return this.regexPattern;
}
/**
* Returns the sequence of Regex variable names as found in the pattern
* string.
*
* @return The sequence of Regex variable names as found in the pattern
* string.
*/
private List getRegexVariables() {
// Lazy initialization with double-check.
List rv = this.regexVariables;
if (rv == null) {
synchronized (this) {
rv = this.regexVariables;
if (rv == null) {
this.regexVariables = rv = new CopyOnWriteArrayList();
}
}
}
return rv;
}
/**
* Returns the list of variable names in the template.
*
* @return The list of variable names.
*/
public List getVariableNames() {
final List result = new ArrayList();
StringBuilder varBuffer = null;
char next;
boolean inVariable = false;
final String pattern = getPattern();
for (int i = 0; i < pattern.length(); i++) {
next = pattern.charAt(i);
if (inVariable) {
if (Reference.isUnreserved(next)) {
// Append to the variable name
varBuffer.append(next);
} else if (next == '}') {
// End of variable detected
if (varBuffer.length() == 0) {
getLogger().warning(
"Empty pattern variables are not allowed : "
+ this.pattern);
} else {
result.add(varBuffer.toString());
// Reset the variable name buffer
varBuffer = new StringBuilder();
}
inVariable = false;
} else {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.pattern);
}
} else {
if (next == '{') {
inVariable = true;
varBuffer = new StringBuilder();
} else if (next == '}') {
getLogger().warning(
"An invalid character was detected inside a pattern variable : "
+ this.pattern);
}
}
}
return result;
}
/**
* Returns the modifiable map of variable descriptors. Creates a new
* instance if no one has been set. Note that those variables are only
* descriptors that can influence the way parsing and formatting is done,
* they don't contain the actual value parsed.
*
* @return The modifiable map of variables.
*/
public synchronized Map getVariables() {
return this.variables;
}
/**
* Indicates if the variables must be encoded when formatting the template.
*
* @return True if the variables must be encoded when formatting the
* template, false otherwise.
*/
public boolean isEncodeVariables() {
return this.encodeVariables;
}
/**
* Indicates if the current pattern matches the given formatted string.
*
* @param formattedString
* The formatted string to match.
* @return The number of matched characters or -1 if the match failed.
*/
public int match(String formattedString) {
int result = -1;
try {
if (formattedString != null) {
final Matcher matcher = getRegexPattern().matcher(
formattedString);
if ((getMatchingMode() == MODE_EQUALS) && matcher.matches()) {
result = matcher.end();
} else if ((getMatchingMode() == MODE_STARTS_WITH)
&& matcher.lookingAt()) {
result = matcher.end();
}
}
} catch (StackOverflowError soe) {
getLogger().warning(
"StackOverflowError exception encountered while matching this string : "
+ formattedString);
}
return result;
}
/**
* Attempts to parse a formatted reference. If the parsing succeeds, the
* given request's attributes are updated.
* Note that the values parsed are directly extracted from the formatted
* reference and are therefore not percent-decoded.
*
* @see Reference#decode(String)
*
* @param formattedString
* The string to parse.
* @param variables
* The map of variables to update.
* @return The number of matched characters or -1 if no character matched.
*/
public int parse(String formattedString, Map variables) {
int result = -1;
if (formattedString != null) {
try {
final Matcher matcher = getRegexPattern().matcher(
formattedString);
final boolean matched = ((getMatchingMode() == MODE_EQUALS) && matcher
.matches())
|| ((getMatchingMode() == MODE_STARTS_WITH) && matcher
.lookingAt());
if (matched) {
// Update the number of matched characters
result = matcher.end();
// Update the attributes with the variables value
String attributeName = null;
String attributeValue = null;
for (int i = 0; i < getRegexVariables().size(); i++) {
attributeName = getRegexVariables().get(i);
attributeValue = matcher.group(i + 1);
final Variable var = getVariables().get(attributeName);
if ((var != null) && var.isDecodedOnParse()) {
variables.put(attributeName, Reference
.decode(attributeValue));
} else {
variables.put(attributeName, attributeValue);
}
}
}
} catch (StackOverflowError soe) {
getLogger().warning(
"StackOverflowError exception encountered while matching this string : "
+ formattedString);
}
}
return result;
}
/**
* Attempts to parse a formatted reference. If the parsing succeeds, the
* given request's attributes are updated.
* Note that the values parsed are directly extracted from the formatted
* reference and are therefore not percent-decoded.
*
* @see Reference#decode(String)
*
* @param formattedString
* The string to parse.
* @param request
* The request to update.
* @return The number of matched characters or -1 if no character matched.
*/
public int parse(String formattedString, Request request) {
return parse(formattedString, request.getAttributes());
}
/**
* Quotes special characters that could be taken for special Regex
* characters.
*
* @param character
* The character to quote if necessary.
* @return The quoted character.
*/
private String quote(char character) {
switch (character) {
case '[':
return "\\[";
case ']':
return "\\]";
case '.':
return "\\.";
case '\\':
return "\\\\";
case '$':
return "\\$";
case '^':
return "\\^";
case '?':
return "\\?";
case '*':
return "\\*";
case '|':
return "\\|";
case '(':
return "\\(";
case ')':
return "\\)";
case ':':
return "\\:";
case '-':
return "\\-";
case '!':
return "\\!";
case '<':
return "\\<";
case '>':
return "\\>";
default:
return Character.toString(character);
}
}
/**
* Sets the variable to use, if no variable is given.
*
* @param defaultVariable
*/
public void setDefaultVariable(Variable defaultVariable) {
this.defaultVariable = defaultVariable;
}
/**
* Indicates if the variables must be encoded when formatting the template.
*
* @param encodeVariables
* True if the variables must be encoded when formatting the
* template.
*/
public void setEncodeVariables(boolean encodeVariables) {
this.encodeVariables = encodeVariables;
}
/**
* Sets the logger to use.
*
* @param logger
* The logger to use.
*/
public void setLogger(Logger logger) {
this.logger = logger;
}
/**
* Sets the matching mode to use when parsing a formatted reference.
*
* @param matchingMode
* The matching mode to use when parsing a formatted reference.
*/
public void setMatchingMode(int matchingMode) {
this.matchingMode = matchingMode;
}
/**
* Sets the pattern to use for formatting or parsing.
*
* @param pattern
* The pattern to use for formatting or parsing.
*/
public void setPattern(String pattern) {
this.pattern = pattern;
this.regexPattern = null;
}
/**
* Sets the modifiable map of variables.
*
* @param variables
* The modifiable map of variables.
*/
public synchronized void setVariables(Map variables) {
this.variables.clear();
this.variables.putAll(variables);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy