/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.uri;
import org.everrest.core.impl.uri.UriComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UriTemplateParser {
/** Pattern for process URI parameters, for example /a/b/{x}/c . */
public static final Pattern URI_PARAMETERS_PATTERN = Pattern.compile("\\{[^\\}^\\{]+\\}");
/** Regex character, this characters in URI template will be escaped by addition '\'. */
private static final String REGEX_CHARACTERS = ".?()";
/** Should be added in regular expression instead URI parameter. */
private static final String URI_PARAMETER_TEMPLATE = "[^/]+?";
/** URI parameter names. */
private List<String> names = new ArrayList<String>();
/** Regular expression buffer. */
private StringBuilder regex = new StringBuilder();
/** Normalized template, whitespace must be removed. */
private StringBuilder normalizedTemplate = new StringBuilder();
/** Number of explicit characters in URI template, all characters except parameters. */
private int numberOfCharacters = 0;
/** Actual processed parameter name. */
private String name;
/** Indexes regular expression capturing group. */
private List<Integer> groupIndexes = new ArrayList<Integer>();
/**
* @param template
* source URI template
*/
public UriTemplateParser(String template) {
Matcher m = URI_PARAMETERS_PATTERN.matcher(template);
int start = 0;
while (m.find()) {
numberOfCharacters += addCharacter(template, start, m.start());
parseRegex(template.substring(m.start() + 1, m.end() - 1));
start = m.end();
}
numberOfCharacters += addCharacter(template, start, template.length());
}
/**
* Get the number of literal characters in the template.
*
* @return number of literal characters in the template
*/
public final int getNumberOfLiteralCharacters() {
return numberOfCharacters;
}
/** @return list of names */
public final List<String> getParameterNames() {
return names;
}
/**
* Get the regular expression.
*
* @return the regular expression
*/
public final String getRegex() {
return regex.toString();
}
/**
* Get the URI template.
*
* @return the URI template
*/
public final String getTemplate() {
return normalizedTemplate.toString();
}
/** @return indexes of regular expression capturing group */
public final int[] getGroupIndexes() {
if (names.isEmpty()) {
return null;
}
int[] indexes = new int[names.size() + 1];
indexes[0] = 1;
for (int i = 1; i < indexes.length; i++) {
indexes[i] = indexes[i - 1] + groupIndexes.get(i - 1);
}
// Check are groups indexes goes one by one.
for (int i = 0; i < indexes.length; i++) {
if (indexes[i] != i + 1) {
return indexes;
}
}
return null;
}
/**
* Encode set of literal characters. Characters must not e double encoded.
*
* @param literalCharacters
* source string
* @return encoded string
*/
protected String encodeLiteralCharacters(String literalCharacters) {
return UriComponent.recognizeEncode(literalCharacters, UriComponent.PATH, false);
}
/**
* @param str
* part of source template between '{' and '}'
*/
private void parseRegex(String str) {
int length = str.length();
int p = parseName(str, 0, length);
String reg;
if (p == length) {
reg = URI_PARAMETER_TEMPLATE;
addToTemplate(name);
} else {
reg = str.substring(p + 1).trim();
if (reg.length() == 0) {
reg = URI_PARAMETER_TEMPLATE;
addToTemplate(name);
} else {
addToTemplate(name, reg);
}
}
// Count how many groups has current part of template
Pattern gp = Pattern.compile(reg);
Matcher m = gp.matcher("");
groupIndexes.add(m.groupCount() + 1);
regex.append('(').append(reg).append(')');
}
/**
* Add name of parameter to normalized template.
*
* @param name
* parameter name
*/
private void addToTemplate(String name) {
normalizedTemplate.append('{').append(name).append('}');
}
/**
* Add name of parameter and regular expression corresponded to name to
* normalized template.
*
* @param name
* parameter name
* @param reg
* regular expression
*/
private void addToTemplate(String name, String reg) {
normalizedTemplate.append('{').append(name).append('}').append(':').append(reg);
}
/**
* @param str
* Part of source URI template between '{' and '}'.
* @param p
* start position
* @param length
* string length
* @return URI template parameter name
*/
private int parseName(String str, int p, int length) {
StringBuilder sb = new StringBuilder();
for (; p < length; p++) {
char ch = str.charAt(p);
if (Character.isLetterOrDigit(ch) || ch == '-' || ch == '_' || ch == '.') {
sb.append(ch);
} else if (ch == ':') {
break;
} else if (ch != ' ') {
throw new IllegalArgumentException("Wrong character at part " + str);
}
}
name = sb.toString();
// TODO remove restriction that not allowed have few path parameters with
// the same name. This should be allowed but part of URI template also MUST
// be the same. E.g. /a/{x}/b/{x} and /a/{x:\d+}/b/{x:\d+} must be allowed,
// but /a/{x}/b/{x:\d+} is not allowed. This task is not high priority.
if (names.contains(name)) {
throw new IllegalArgumentException("URI template variables name " + name + " already registered.");
}
names.add(name);
return p;
}
/**
* Add explicit characters to normalized URI template.
*
* @param template
* source URI template
* @param start
* start position for reading characters
* @param end
* end position for reading characters
* @return how many characters was red
*/
private int addCharacter(String template, int start, int end) {
String str = encodeLiteralCharacters(template.substring(start, end));
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
// check is character corresponds to regular expression character
if (REGEX_CHARACTERS.indexOf(ch) != -1) {
regex.append('\\');
}
regex.append(ch);
normalizedTemplate.append(ch);
}
return end - start;
}
}