org.apache.wink.common.internal.uritemplate.BitWorkingUriTemplateProcessor Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* 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.apache.wink.common.internal.uritemplate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.wink.common.internal.i18n.Messages;
import org.apache.wink.common.internal.uri.UriEncoder;
import org.apache.wink.common.internal.utils.StringUtils;
/**
* BitWorking style
* template processor for compiling, matching and expanding URI templates
*/
public class BitWorkingUriTemplateProcessor extends UriTemplateProcessor {
/*
* op = 1ALPHA arg =(reserved / unreserved / pct-encoded) varname = (ALPHA /
* DIGIT)(ALPHA / DIGIT / "." / "_" / "-" ) vardefault =(unreserved /
* pct-encoded) var = varname [ "=" vardefault ] vars = var [("," var) ]
* operator = "-" op "|" arg "|" vars expansion = "{" ( var / operator ) "}"
*/
private static final String HEX = "[0-9A-Fa-f]"; //$NON-NLS-1$
private static final String RESERVED = "[;/?:@&=+$,]"; //$NON-NLS-1$
private static final String UNRESERVED = "[\\w\\.!~*'()-]"; //$NON-NLS-1$
private static final String PCT_ENCONDED = "(?:%" + HEX + HEX + ")"; //$NON-NLS-1$ //$NON-NLS-2$
private static final String ALPHA = "[a-zA-Z]"; //$NON-NLS-1$
private static final String BITWORKING_OP = "(" + ALPHA + "+)"; //$NON-NLS-1$ //$NON-NLS-2$
private static final String BITWORKING_ARG = "((?:" + RESERVED //$NON-NLS-1$
+ "|" //$NON-NLS-1$
+ UNRESERVED
+ "|" //$NON-NLS-1$
+ PCT_ENCONDED
+ ")*)"; //$NON-NLS-1$
private static final String BITWORKING_VARNAME = "\\w[\\w\\.-]*"; //$NON-NLS-1$
private static final String BITWORKING_VARDEFAULT = "((?:" + UNRESERVED //$NON-NLS-1$
+ "|" //$NON-NLS-1$
+ PCT_ENCONDED
+ ")*)"; //$NON-NLS-1$
private static final String BITWORKING_VAR = "(" + BITWORKING_VARNAME //$NON-NLS-1$
+ ")(?:=" //$NON-NLS-1$
+ BITWORKING_VARDEFAULT
+ ")?"; //$NON-NLS-1$
private static final String BITWORKING_VARS = "(" + BITWORKING_VAR //$NON-NLS-1$
+ "(?:," //$NON-NLS-1$
+ BITWORKING_VAR
+ ")*)"; //$NON-NLS-1$
private static final String BITWORKING_OPERATOR = "(?:-" + BITWORKING_OP //$NON-NLS-1$
+ "[|]" //$NON-NLS-1$
+ BITWORKING_ARG
+ "[|]" //$NON-NLS-1$
+ BITWORKING_VARS
+ ")"; //$NON-NLS-1$
private static final String BITWORKING_EXPANSION = "\\{(?:" + BITWORKING_VAR //$NON-NLS-1$
+ "|" //$NON-NLS-1$
+ BITWORKING_OPERATOR
+ ")\\}"; //$NON-NLS-1$
private static final Pattern BITWORKING_VARIABLE_PATTERN =
Pattern
.compile(BITWORKING_EXPANSION);
/**
* Create a processor without a template
*/
public BitWorkingUriTemplateProcessor() {
super();
}
/**
* Create an processor with the provided template. The
* {@link #compile(String)} method is called on the provided template.
*
* @param template the template that this processor is associated with
*/
public BitWorkingUriTemplateProcessor(String template) {
this();
compile(template);
}
@Override
public final void compile(String uriTemplate) {
compile(uriTemplate, new BitWorkingPatternBuilder(this));
}
/**
* Compile the provided uri template and pass compilation events to the
* provided {@link BitWorkingCompilationHandler}.
*
* @param template the template to compile
* @param handler the CompilationHandler that will receive compilation
* events
*/
public static void compile(String template, BitWorkingCompilationHandler handler) {
if (template == null) {
throw new NullPointerException(Messages.getMessage("variableIsNull", "template")); //$NON-NLS-1$ //$NON-NLS-2$
}
if (handler == null) {
throw new NullPointerException(Messages.getMessage("variableIsNull", "handler")); //$NON-NLS-1$ //$NON-NLS-2$
}
int start = 0;
String literal = ""; //$NON-NLS-1$
// fire start
handler.startCompile(template);
Matcher matcher = BITWORKING_VARIABLE_PATTERN.matcher(template);
while (matcher.find()) {
// get the literal part which is the characters up to the variable
literal = template.substring(start, matcher.start());
start = matcher.end();
// fire literal
handler.literal(literal);
// get the different parts according to the following:
// group 1: variable name
// group 2: variable default value
// group 3: operator name
// group 4: operator arg
// group 5: operator vars
// if a variable was matched then all the operator groups will be
// null
// if an operator was matched then all the variable groups will be
// null
String variable = matcher.group(1);
if (variable != null) {
// variable
String defaultValue = matcher.group(2);
// fire variable
handler.variable(variable, defaultValue);
} else {
// operator
String operator = matcher.group(3);
String arg = matcher.group(4);
String vars = matcher.group(5);
// extract the variable names of the operator
String[] arrayVars = StringUtils.fastSplit(vars, ","); //$NON-NLS-1$
Map varsMap = new LinkedHashMap();
for (String var : arrayVars) {
String defaultValue = null;
int index = var.indexOf('=');
// is there a default value?
if (index != -1) {
defaultValue = var.substring(index + 1);
var = var.substring(0, index);
}
varsMap.put(var, defaultValue);
}
// fire operator
handler.operator(operator, arg, varsMap);
}
}
// get the trailing literal part
literal = template.substring(start);
// fire end
handler.endCompile(literal);
}
/**
* Expand the provided template using the provided values. Regular
* expressions of variables in the template are ignored. All variables
* defined in the template must have a value.
*
* @param template the uri template to expand
* @param values a map with the values of the variables
* @return an expanded uri using the supplied variable values
*/
public static String expand(String template, MultivaluedMap values) {
if (template == null) {
return null;
}
StringBuilder result = new StringBuilder();
expand(template, values, result);
return result.toString();
}
/**
* Expand the provided template using the provided values. Regular
* expressions of variables in the template are ignored. All variables
* defined in the template must have a value.
*
* @param template the uri template to expand
* @param values a map with the values of the variables
* @param out the output for the expansion
*/
public static void expand(String template,
MultivaluedMap values,
StringBuilder out) {
if (template == null) {
return;
}
BitWorkingTemplateExpander expander = new BitWorkingTemplateExpander(values, out);
compile(template, expander);
}
/**
* Factory method for normalized uri-templates.
*
* @param uriTemplate uri-template specification
* @return instance representing (normalized) uri-template
* @see UriTemplateProcessor#normalizeUri(String)
*/
public static UriTemplateProcessor newNormalizedInstance(String uriTemplate) {
return new BitWorkingUriTemplateProcessor(UriTemplateProcessor.normalizeUri(uriTemplate));
}
/**
* This interface is used for receiving events during the compilation of a
* uri template.
*
* @see {@link BitWorkingUriTemplateProcessor#compile(String, BitWorkingCompilationHandler)}
*/
public static interface BitWorkingCompilationHandler extends BaseCompilationHandler {
/**
* Variable event.
*
* @param name the name of the variable
* @param defaultValue the default value of the variable, or null
*/
public void variable(String name, String defaultValue);
/**
* Operator event.
*
* @param name the name of the operator
* @param arg the argument of the operator
* @param vars a map of variables and their default values
*/
public void operator(String name, String arg, Map vars);
}
/**
* This compilation handler handles the compilation events for compiling the
* uri template of a processor instance. It creates the regex pattern to use
* for matching and the variables used for matching and expansion, and then
* sets them on the processor instance.
*/
private static class BitWorkingPatternBuilder extends AbstractPatternBuilder implements
BitWorkingCompilationHandler {
public BitWorkingPatternBuilder(BitWorkingUriTemplateProcessor processor) {
super(processor);
}
public void variable(String name, String defaultValue) {
// create a new variable
CapturingGroup variable = createVariable(name, null, defaultValue);
// save it to the map of capturing variables for use during matching
processor.variables.add(name, variable);
// save it for use during expansion
processor.expanders.add(variable);
}
public void operator(String name, String arg, Map vars) {
// get a new operator
BitWorkingOperator operator = BitWorkingOperator.forName(name);
if (operator == null) {
throw new IllegalArgumentException(Messages.getMessage("unsupportedOperator", name)); //$NON-NLS-1$
}
// set the arg and vars of the operator
operator.setArg(arg);
operator.setVars(vars);
// build it into the pattern
operator.build(patternBuilder);
// set its capturing group
++capturingGroupId;
operator.setCapturingGroupId(capturingGroupId);
// save it to the map of capturing variables for use during matching
for (String var : vars.keySet()) {
processor.variables.add(var, operator);
}
// save it for use during expansion
processor.expanders.add(operator);
}
}
/**
* Represents a BitWorking operator
*/
private abstract static class BitWorkingOperator extends CapturingGroup {
protected String name;
protected String arg;
protected Map vars;
protected BitWorkingOperator(String name) {
super();
this.name = name;
this.arg = null;
this.vars = null;
}
public String getName() {
return name;
}
public String getArg() {
return arg;
}
public void setArg(String arg) {
this.arg = arg;
}
public Map getVars() {
return vars;
}
public void setVars(Map vars) {
this.vars = vars;
}
/**
* Return an instance of an operator for the specified name
*
* @param name the name of the operator
* @return an instance of an operator
*/
public static BitWorkingOperator forName(String name) {
if (name == null) {
return null;
}
if (name.equals("neg")) { //$NON-NLS-1$
return new Neg();
}
if (name.equals("opt")) { //$NON-NLS-1$
return new Opt();
}
if (name.equals("prefix")) { //$NON-NLS-1$
return new Prefix();
}
if (name.equals("suffix")) { //$NON-NLS-1$
return new Suffix();
}
if (name.equals("list")) { //$NON-NLS-1$
return new List();
}
if (name.equals("join")) { //$NON-NLS-1$
return new Join();
}
return null;
}
/**
* Represents the "neg" operator
*/
private static class Neg extends BitWorkingOperator {
public Neg() {
super("neg"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
builder.append("("); //$NON-NLS-1$
builder.append(Pattern.quote(arg));
builder.append(")?"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
// do nothing
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
for (String var : vars.keySet()) {
if (values.containsKey(var) && values.get(var).size() > 0) {
return;
}
}
builder.append(arg);
}
}
/**
* Represents the "opt" operator
*/
private static class Opt extends BitWorkingOperator {
public Opt() {
super("opt"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
builder.append("("); //$NON-NLS-1$
builder.append(Pattern.quote(arg));
builder.append(")?"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
// do nothing
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
for (String var : vars.keySet()) {
if (values.containsKey(var) && values.get(var).size() > 0) {
builder.append(arg);
return;
}
}
}
}
/**
* Represents the "prefix" operator
*/
private static class Prefix extends BitWorkingOperator {
public Prefix() {
super("prefix"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
if (vars.size() != 1) {
throw new IllegalArgumentException(Messages
.getMessage("prefixOperatorMustHaveOnlyOneVariable")); //$NON-NLS-1$
}
builder.append("((?:"); //$NON-NLS-1$
builder.append(Pattern.quote(arg));
builder.append(REGEX0);
builder.append(")*)"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
String var = vars.keySet().iterator().next();
if (matched == null || matched.length() == 0) {
values.putSingle(var, null);
indices.putSingle(var, startIndex);
return;
}
if (!matched.startsWith(arg)) {
throw new IllegalArgumentException(Messages
.getMessage("matchedSuffixMustStartWith", arg)); //$NON-NLS-1$
}
// clear the previous values
values.put(var, null);
String[] array = StringUtils.fastSplit(matched, arg);
// array[0] will always contain the empty string, so skip it
for (int i = 1; i < array.length; ++i) {
values.add(var, array[i]);
indices.add(var, startIndex);
}
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
// we have only one var
String var = vars.keySet().iterator().next();
java.util.List varValues = values.get(var);
if (varValues != null) {
for (String value : varValues) {
builder.append(arg);
if (encode) {
value = UriEncoder.encodeString(value);
}
builder.append(value);
}
}
}
}
/**
* Represents the "suffix" operator
*/
private static class Suffix extends BitWorkingOperator {
public Suffix() {
super("suffix"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
if (vars.size() != 1) {
throw new IllegalArgumentException(Messages
.getMessage("suffixOperatorMustOnlyHaveOneVariable")); //$NON-NLS-1$
}
builder.append("((?:"); //$NON-NLS-1$
builder.append(REGEX0);
builder.append(Pattern.quote(arg));
builder.append(")*)"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
String var = vars.keySet().iterator().next();
if (matched == null || matched.length() == 0) {
values.putSingle(var, null);
indices.putSingle(var, startIndex);
return;
}
if (!matched.endsWith(arg)) {
throw new IllegalArgumentException(Messages
.getMessage("matchedSuffixMustEndWith", arg)); //$NON-NLS-1$
}
// clear the previous values
values.put(var, null);
String[] array = StringUtils.fastSplit(matched, arg);
// the last element in the array will always contain the empty
// string, so skip it
for (int i = 0; i < array.length - 1; ++i) {
values.add(var, array[i]);
indices.add(var, startIndex);
}
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
// we have only one var
String var = vars.keySet().iterator().next();
java.util.List varValues = values.get(var);
if (varValues != null) {
for (String value : varValues) {
if (encode) {
value = UriEncoder.encodeString(value);
}
builder.append(value);
builder.append(arg);
}
}
}
}
/**
* Represents the "list" operator
*/
private static class List extends BitWorkingOperator {
public List() {
super("list"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
if (vars.size() != 1) {
throw new IllegalArgumentException(Messages
.getMessage("listOperatorMustHaveOnlyOneVariable")); //$NON-NLS-1$
}
builder.append("("); //$NON-NLS-1$
builder.append(REGEX0);
builder.append("(?:"); //$NON-NLS-1$
builder.append(Pattern.quote(arg));
builder.append(REGEX0);
builder.append(")*)"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
String var = vars.keySet().iterator().next();
if (matched == null || matched.length() == 0) {
values.putSingle(var, ""); //$NON-NLS-1$
indices.putSingle(var, startIndex);
return;
}
// clear the previous values
values.put(var, null);
String[] array = StringUtils.fastSplit(matched, arg);
for (int i = 0; i < array.length; ++i) {
values.add(var, array[i]);
indices.add(var, startIndex);
}
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
// we have only one var
String var = vars.keySet().iterator().next();
String delim = ""; //$NON-NLS-1$
java.util.List varValues = values.get(var);
if (varValues != null) {
for (String value : varValues) {
builder.append(delim);
if (encode) {
value = UriEncoder.encodeString(value);
}
builder.append(value);
delim = arg;
}
}
}
}
/**
* Represents the "join" operator
*/
private static class Join extends BitWorkingOperator {
public Join() {
super("join"); //$NON-NLS-1$
}
public void build(StringBuilder builder) {
String orSign = ""; //$NON-NLS-1$
StringBuilder keysAndValuesPattern = new StringBuilder();
keysAndValuesPattern.append("(?:"); //$NON-NLS-1$
for (String var : vars.keySet()) {
keysAndValuesPattern.append(orSign);
keysAndValuesPattern.append("(?:"); //$NON-NLS-1$
keysAndValuesPattern.append(var);
keysAndValuesPattern.append("="); //$NON-NLS-1$
keysAndValuesPattern.append(REGEX0);
keysAndValuesPattern.append(")"); //$NON-NLS-1$
orSign = "|"; //$NON-NLS-1$
}
keysAndValuesPattern.append(")"); //$NON-NLS-1$
// with a capturing group
builder.append("("); //$NON-NLS-1$
builder.append(keysAndValuesPattern.toString());
builder.append("(?:"); //$NON-NLS-1$
builder.append(Pattern.quote(arg));
builder.append(keysAndValuesPattern.toString());
builder.append(")*)?"); //$NON-NLS-1$
}
@Override
public void onMatch(String matched,
MultivaluedMap values,
int startIndex,
MultivaluedMap indices) {
// extract all the keys and values and prepare a temporary map
Map extractedValues = new HashMap();
if (matched != null) {
String[] array = StringUtils.fastSplit(matched, arg);
for (int i = 0; i < array.length; ++i) {
String var = array[i];
String value = ""; //$NON-NLS-1$
int index = var.indexOf('=');
if (index != -1) {
value = var.substring(index + 1);
var = var.substring(0, index);
}
extractedValues.put(var, value);
}
}
// add all the values to the output multivalued map.
// if a certain key did not have a value, then null will added
for (String key : vars.keySet()) {
values.putSingle(key, extractedValues.get(key));
indices.putSingle(key, startIndex);
}
}
public void expand(MultivaluedMap values,
boolean encode,
StringBuilder builder) {
String delim = ""; //$NON-NLS-1$
for (String var : vars.keySet()) {
java.util.List varValues = values.get(var);
String value = ""; //$NON-NLS-1$
if (varValues != null) {
if (varValues.size() > 1) {
throw new IllegalArgumentException(Messages
.getMessage("variableContainsMoreThanOneValueForJoinOperator", var)); //$NON-NLS-1$
}
if (varValues.size() == 1) {
value = varValues.get(0);
if (value == null) {
continue;
}
builder.append(delim);
builder.append(var);
builder.append("="); //$NON-NLS-1$
if (encode) {
value = UriEncoder.encodeString(value);
}
builder.append(value);
delim = arg;
}
}
}
}
}
}
/**
* This compilation handler is used for creating an expansion string by
* replacing all variables with their values from the provided map.
*/
private static class BitWorkingTemplateExpander extends AbstractTemplateExpander implements
BitWorkingCompilationHandler {
public BitWorkingTemplateExpander(MultivaluedMap values, StringBuilder out) {
super(values, out);
}
public void variable(String name, String defaultValue) {
if (values == null) {
throw new NullPointerException(Messages.getMessage("variableIsNull", name)); //$NON-NLS-1$
}
Variable expander = new Variable(name, null, defaultValue);
expander.expand(values, false, out);
}
public void operator(String name, String arg, Map vars) {
if (values == null) {
throw new NullPointerException(Messages.getMessage("variableIsNull", name)); //$NON-NLS-1$
}
BitWorkingOperator expander = BitWorkingOperator.forName(name);
if (expander == null) {
throw new IllegalArgumentException(Messages.getMessage("unsupportedOperator", name)); //$NON-NLS-1$
}
expander.expand(values, false, out);
}
}
}