reitit/modules/reitit-core/java-src/reitit/Trie.java

304 lines
7.4 KiB
Java
Raw Normal View History

2019-01-26 14:03:44 +00:00
package reitit;
// https://www.codeproject.com/Tips/1190293/Iteration-Over-Java-Collections-with-High-Performa
2019-01-27 16:57:29 +00:00
import clojure.lang.IPersistentMap;
2019-01-26 14:03:44 +00:00
import clojure.lang.Keyword;
2019-01-27 16:57:29 +00:00
import clojure.lang.PersistentArrayMap;
2019-01-26 14:03:44 +00:00
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
public class Trie {
2019-02-10 11:17:34 +00:00
private static String decode(String s, boolean hasPercent, boolean hasPlus) {
2019-01-26 14:03:44 +00:00
try {
if (hasPercent) {
return URLDecoder.decode(hasPlus ? s.replace("+", "%2B") : s, "UTF-8");
}
} catch (UnsupportedEncodingException ignored) {
}
return s;
}
2019-02-02 18:13:45 +00:00
private static String decode(char[] chars, int begin, int end) {
2019-01-27 20:10:46 +00:00
boolean hasPercent = false;
boolean hasPlus = false;
2019-02-02 17:41:35 +00:00
for (int j = begin; j < end; j++) {
2019-02-05 06:51:21 +00:00
switch (chars[j]) {
case '%':
hasPercent = true;
break;
case '+':
hasPlus = true;
break;
2019-01-27 20:10:46 +00:00
}
}
2019-02-10 11:17:34 +00:00
return decode(new String(chars, begin, end - begin), hasPercent, hasPlus);
2019-01-27 20:10:46 +00:00
}
2019-04-23 11:50:50 +00:00
public static class Match {
public IPersistentMap params;
2019-03-03 18:19:27 +00:00
public final Object data;
2019-01-26 14:03:44 +00:00
2019-04-23 11:50:50 +00:00
public Match(IPersistentMap params, Object data) {
this.params = params;
this.data = data;
}
2019-01-26 14:03:44 +00:00
@Override
public String toString() {
Map<Object, Object> m = new HashMap<>();
m.put(Keyword.intern("data"), data);
2019-04-23 11:50:50 +00:00
m.put(Keyword.intern("params"), params);
2019-01-26 14:03:44 +00:00
return m.toString();
}
}
public interface Matcher {
Match match(int i, int max, char[] path);
2019-01-29 19:25:33 +00:00
int depth();
int length();
2019-01-26 14:03:44 +00:00
}
public static StaticMatcher staticMatcher(String path, Matcher child) {
return new StaticMatcher(path, child);
}
2019-03-03 18:19:27 +00:00
static final class StaticMatcher implements Matcher {
2019-01-26 14:03:44 +00:00
private final Matcher child;
private final char[] path;
private final int size;
StaticMatcher(String path, Matcher child) {
this.path = path.toCharArray();
this.size = path.length();
this.child = child;
}
@Override
public Match match(int i, int max, char[] path) {
2019-02-02 17:41:35 +00:00
if (max < i + size) {
2019-01-26 14:03:44 +00:00
return null;
}
for (int j = 0; j < size; j++) {
2019-02-02 18:13:45 +00:00
if (path[j + i] != this.path[j]) {
2019-01-26 14:03:44 +00:00
return null;
}
}
return child.match(i + size, max, path);
2019-01-26 14:03:44 +00:00
}
2019-01-29 19:25:33 +00:00
@Override
public int depth() {
return child.depth() + 1;
}
@Override
public int length() {
return path.length;
}
2019-01-26 14:03:44 +00:00
@Override
public String toString() {
return "[\"" + new String(path) + "\" " + child + "]";
}
}
public static DataMatcher dataMatcher(IPersistentMap params, Object data) {
return new DataMatcher(params, data);
2019-01-26 14:03:44 +00:00
}
static final class DataMatcher implements Matcher {
2019-05-22 13:39:37 +00:00
private final IPersistentMap params;
private final Object data;
2019-01-26 14:03:44 +00:00
DataMatcher(IPersistentMap params, Object data) {
2019-05-22 13:39:37 +00:00
this.params = params;
this.data = data;
2019-01-26 14:03:44 +00:00
}
@Override
public Match match(int i, int max, char[] path) {
2019-02-02 17:41:35 +00:00
if (i == max) {
2019-05-22 13:39:37 +00:00
return new Match(params, data);
2019-01-26 14:03:44 +00:00
}
return null;
}
2019-01-29 19:25:33 +00:00
@Override
public int depth() {
return 1;
}
@Override
public int length() {
return 0;
}
2019-01-26 14:03:44 +00:00
@Override
public String toString() {
2019-05-22 13:39:37 +00:00
return (data != null ? data.toString() : "nil");
2019-01-26 14:03:44 +00:00
}
}
public static WildMatcher wildMatcher(Keyword parameter, char end, Matcher child) {
return new WildMatcher(parameter, end, child);
2019-01-26 14:03:44 +00:00
}
static final class WildMatcher implements Matcher {
private final Keyword key;
private final char end;
2019-01-26 14:03:44 +00:00
private final Matcher child;
WildMatcher(Keyword key, char end, Matcher child) {
2019-01-26 14:03:44 +00:00
this.key = key;
this.end = end;
2019-01-26 14:03:44 +00:00
this.child = child;
}
@Override
public Match match(int i, int max, char[] path) {
2019-03-07 06:17:17 +00:00
boolean hasPercent = false;
boolean hasPlus = false;
if (i < max && path[i] != end) {
int stop = max;
2019-02-02 17:41:35 +00:00
for (int j = i; j < max; j++) {
2019-02-02 18:13:45 +00:00
final char c = path[j];
2019-03-07 06:17:17 +00:00
hasPercent = hasPercent || c == '%';
hasPlus = hasPlus || c == '+';
if (c == end) {
stop = j;
break;
2019-01-26 14:03:44 +00:00
}
}
final Match m = child.match(stop, max, path);
2019-01-27 16:57:29 +00:00
if (m != null) {
2019-04-23 11:50:50 +00:00
m.params = m.params.assoc(key, decode(new String(path, i, stop - i), hasPercent, hasPlus));
2019-01-26 14:03:44 +00:00
}
2019-01-27 16:57:29 +00:00
return m;
2019-01-26 14:03:44 +00:00
}
return null;
}
2019-01-29 19:25:33 +00:00
@Override
public int depth() {
return child.depth() + 1;
}
@Override
public int length() {
return 0;
}
2019-01-26 14:03:44 +00:00
@Override
public String toString() {
return "[" + key + " " + child + "]";
}
}
public static CatchAllMatcher catchAllMatcher(Keyword parameter, IPersistentMap params, Object data) {
return new CatchAllMatcher(parameter, params, data);
2019-01-27 20:10:46 +00:00
}
static final class CatchAllMatcher implements Matcher {
private final Keyword parameter;
2019-04-23 11:50:50 +00:00
private final IPersistentMap params;
private final Object data;
2019-01-27 20:10:46 +00:00
CatchAllMatcher(Keyword parameter, IPersistentMap params, Object data) {
2019-01-27 20:10:46 +00:00
this.parameter = parameter;
2019-04-23 11:50:50 +00:00
this.params = params;
this.data = data;
2019-01-27 20:10:46 +00:00
}
@Override
public Match match(int i, int max, char[] path) {
if (i <= max) {
2019-04-23 11:50:50 +00:00
return new Match(params.assoc(parameter, decode(path, i, max)), data);
2019-01-27 20:10:46 +00:00
}
return null;
}
2019-01-29 19:25:33 +00:00
@Override
public int depth() {
return 1;
}
@Override
public int length() {
return 0;
}
2019-01-27 20:10:46 +00:00
@Override
public String toString() {
2019-04-23 11:50:50 +00:00
return "[" + parameter + " " + new DataMatcher(null, data) + "]";
2019-01-27 20:10:46 +00:00
}
}
public static LinearMatcher linearMatcher(List<Matcher> childs, boolean inOrder) {
return new LinearMatcher(childs, inOrder);
2019-01-26 14:03:44 +00:00
}
static final class LinearMatcher implements Matcher {
private final Matcher[] childs;
private final int size;
LinearMatcher(List<Matcher> childs, boolean inOrder) {
2019-01-26 14:03:44 +00:00
this.childs = childs.toArray(new Matcher[0]);
if (!inOrder) {
Arrays.sort(this.childs, Comparator.comparing(Matcher::depth).thenComparing(Matcher::length).reversed());
}
2019-01-26 14:03:44 +00:00
this.size = childs.size();
}
@Override
public Match match(int i, int max, char[] path) {
2019-01-26 14:03:44 +00:00
for (int j = 0; j < size; j++) {
final Match m = childs[j].match(i, max, path);
2019-01-26 14:03:44 +00:00
if (m != null) {
return m;
}
}
return null;
}
2019-01-29 19:25:33 +00:00
@Override
public int depth() {
2019-02-28 07:53:50 +00:00
return Arrays.stream(childs).mapToInt(Matcher::depth).max().orElseThrow(NoSuchElementException::new) + 1;
2019-01-29 19:25:33 +00:00
}
@Override
public int length() {
return 0;
}
2019-01-26 14:03:44 +00:00
@Override
public String toString() {
return Arrays.toString(childs);
}
}
public static Object lookup(Matcher matcher, String path) {
return matcher.match(0, path.length(), path.toCharArray());
2019-01-26 14:03:44 +00:00
}
public static void main(String[] args) {
Matcher matcher =
linearMatcher(
Arrays.asList(
staticMatcher("/auth/",
linearMatcher(
Arrays.asList(
2019-04-23 11:33:41 +00:00
staticMatcher("login", dataMatcher(PersistentArrayMap.EMPTY, 1)),
staticMatcher("recovery", dataMatcher(PersistentArrayMap.EMPTY, 2))), true))), true);
2019-01-26 14:03:44 +00:00
System.err.println(matcher);
System.out.println(lookup(matcher, "/auth/login"));
System.out.println(lookup(matcher, "/auth/recovery"));
}
}