All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restlet.util.Template Maven / Gradle / Ivy

Go to download

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