package org.esmerilprogramming.overtown.server.handlers;
import io.undertow.UndertowMessages;
import io.undertow.util.CopyOnWriteMap;
import io.undertow.util.PathTemplateMatch;
import java.util.*;
public class CustomPathTemplateMatcher<T> {
/**
* Map of path template stem to the path templates that share the same base.
*/
private Map<String, Set<PathTemplateHolder>> pathTemplateMap = new CopyOnWriteMap<>();
/**
* lengths of all registered paths
*/
private volatile int[] lengths = {};
public PathMatchResult<T> match(final String path) {
final Map<String, String> params = new HashMap<>();
int length = path.length();
final int[] lengths = this.lengths;
for (int i = 0; i < lengths.length; ++i) {
int pathLength = lengths[i];
if (pathLength == length) {
Set<PathTemplateHolder> entry = pathTemplateMap.get(path);
if (entry != null) {
PathMatchResult<T> res = handleStemMatch(entry, path, params);
if (res != null) {
return res;
}
}
} else if (pathLength < length) {
char c = path.charAt(pathLength);
if (c == '/') {
String part = path.substring(0, pathLength);
Set<PathTemplateHolder> entry = pathTemplateMap.get(part);
if (entry != null) {
PathMatchResult<T> res = handleStemMatch(entry, path, params);
if (res != null) {
return res;
}
}
}
}
}
return null;
}
private PathMatchResult<T> handleStemMatch(Set<PathTemplateHolder> entry, final String path, final Map<String, String> params) {
for (PathTemplateHolder val : entry) {
if (val.template.matches(path, params)) {
return new PathMatchResult<>(params, val.template.getTemplateString(), val.value);
} else {
params.clear();
}
}
return null;
}
public synchronized CustomPathTemplateMatcher<T> add(final CustomPathTemplate template, final T value) {
Set<PathTemplateHolder> values = pathTemplateMap.get( trimBase(template) );
Set<PathTemplateHolder> newValues;
if (values == null) {
newValues = new TreeSet<>();
} else {
newValues = new TreeSet<>(values);
}
PathTemplateHolder holder = new PathTemplateHolder(value, template);
if (newValues.contains(holder)) {
CustomPathTemplate equivalent = null;
for (PathTemplateHolder item : newValues) {
if (item.compareTo(holder) == 0) {
equivalent = item.template;
break;
}
}
throw UndertowMessages.MESSAGES.matcherAlreadyContainsTemplate(template.getTemplateString(), equivalent.getTemplateString());
}
newValues.add(holder);
pathTemplateMap.put(trimBase(template), newValues);
buildLengths();
return this;
}
private String trimBase(CustomPathTemplate template) {
if (template.getBase().endsWith("/")) {
return template.getBase().substring(0, template.getBase().length() - 1);
}
return template.getBase();
}
private void buildLengths() {
final Set<Integer> lengths = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -o1.compareTo(o2);
}
});
for (String p : pathTemplateMap.keySet()) {
lengths.add(p.length());
}
int[] lengthArray = new int[lengths.size()];
int pos = 0;
for (int i : lengths) {
lengthArray[pos++] = i; //-1 because the base paths end with a /
}
this.lengths = lengthArray;
}
public synchronized CustomPathTemplateMatcher<T> add(final String pathTemplate, final T value) {
final CustomPathTemplate template = CustomPathTemplate.create(pathTemplate);
return add(template, value);
}
public synchronized CustomPathTemplateMatcher<T> addAll(CustomPathTemplateMatcher<T> pathTemplateMatcher) {
for (Map.Entry<String, Set<PathTemplateHolder>> entry : pathTemplateMatcher.getPathTemplateMap().entrySet()) {
for (PathTemplateHolder pathTemplateHolder : entry.getValue()) {
add(pathTemplateHolder.template, pathTemplateHolder.value);
}
}
return this;
}
Map<String, Set<PathTemplateHolder>> getPathTemplateMap() {
return pathTemplateMap;
}
public Set<CustomPathTemplate> getPathTemplates() {
Set<CustomPathTemplate> templates = new HashSet<>();
for (Set<PathTemplateHolder> holders : pathTemplateMap.values()) {
for (PathTemplateHolder holder: holders) {
templates.add(holder.template);
}
}
return templates;
}
public synchronized CustomPathTemplateMatcher<T> remove(final String pathTemplate) {
final CustomPathTemplate template = CustomPathTemplate.create(pathTemplate);
return remove(template);
}
private synchronized CustomPathTemplateMatcher<T> remove(CustomPathTemplate template) {
Set<PathTemplateHolder> values = pathTemplateMap.get(trimBase(template));
Set<PathTemplateHolder> newValues;
if (values == null) {
return this;
} else {
newValues = new TreeSet<>(values);
}
Iterator<PathTemplateHolder> it = newValues.iterator();
while (it.hasNext()) {
PathTemplateHolder next = it.next();
if (next.template.getTemplateString().equals(template.getTemplateString())) {
it.remove();
break;
}
}
if (newValues.size() == 0) {
pathTemplateMap.remove(trimBase(template));
} else {
pathTemplateMap.put(trimBase(template), newValues);
}
buildLengths();
return this;
}
public synchronized T get(String template) {
CustomPathTemplate pathTemplate = CustomPathTemplate.create(template);
Set<PathTemplateHolder> values = pathTemplateMap.get(trimBase(pathTemplate));
if(values == null) {
return null;
}
for (PathTemplateHolder next : values) {
if (next.template.getTemplateString().equals(template)) {
return next.value;
}
}
return null;
}
public static class PathMatchResult<T> extends PathTemplateMatch {
private final T value;
public PathMatchResult(Map<String, String> parameters, String matchedTemplate, T value) {
super(matchedTemplate, parameters);
this.value = value;
}
public T getValue() {
return value;
}
}
private final class PathTemplateHolder implements Comparable<PathTemplateHolder> {
final T value;
final CustomPathTemplate template;
private PathTemplateHolder(T value, CustomPathTemplate template) {
this.value = value;
this.template = template;
}
@Override
public int compareTo(PathTemplateHolder o) {
return template.compareTo(o.template);
}
}
}