diff --git a/modules/reitit-core/java-src/reitit/SegmentTrie.java b/modules/reitit-core/java-src/reitit/SegmentTrie.java deleted file mode 100644 index d1020b24..00000000 --- a/modules/reitit-core/java-src/reitit/SegmentTrie.java +++ /dev/null @@ -1,319 +0,0 @@ -package reitit; - -import clojure.lang.Keyword; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.*; - -public class SegmentTrie { - - public static ArrayList split(final String path) { - final ArrayList segments = new ArrayList<>(4); - final int size = path.length(); - int start = 1; - for (int i = start; i < size; i++) { - final char c = path.charAt(i); - if (c == '/') { - segments.add(path.substring(start, i)); - start = i + 1; - } - } - if (start <= size) { - segments.add(path.substring(start, size)); - } - return segments; - } - - private static String decode(String s) { - try { - if (s.contains("%")) { - String _s = s; - if (s.contains("+")) { - _s = s.replace("+", "%2B"); - } - return URLDecoder.decode(_s, "UTF-8"); - } - } catch (UnsupportedEncodingException ignored) { - } - return s; - } - - public static class Match { - public final Map params = new HashMap<>(); - public Object data; - - @Override - public String toString() { - Map m = new HashMap<>(); - m.put(Keyword.intern("data"), data); - m.put(Keyword.intern("params"), params); - return m.toString(); - } - } - - private Map childs = new HashMap<>(); - private Map wilds = new HashMap<>(); - private Map catchAll = new HashMap<>(); - private Object data; - - public SegmentTrie add(String path, Object data) { - List paths = split(path); - SegmentTrie pointer = this; - for (String p : paths) { - if (p.startsWith(":")) { - Keyword k = Keyword.intern(p.substring(1)); - SegmentTrie s = pointer.wilds.get(k); - if (s == null) { - s = new SegmentTrie(); - pointer.wilds.put(k, s); - } - pointer = s; - } else if (p.startsWith("*")) { - Keyword k = Keyword.intern(p.substring(1)); - SegmentTrie s = pointer.catchAll.get(k); - if (s == null) { - s = new SegmentTrie(); - pointer.catchAll.put(k, s); - } - pointer = s; - break; - } else { - SegmentTrie s = pointer.childs.get(p); - if (s == null) { - s = new SegmentTrie(); - pointer.childs.put(p, s); - } - pointer = s; - } - } - pointer.data = data; - return this; - } - - private Matcher staticMatcher() { - if (childs.size() == 1) { - return new StaticMatcher(childs.keySet().iterator().next(), childs.values().iterator().next().matcher()); - } else { - Map m = new HashMap<>(); - for (Map.Entry e : childs.entrySet()) { - m.put(e.getKey(), e.getValue().matcher()); - } - return new StaticMapMatcher(m); - } - } - - public Matcher matcher() { - Matcher m; - if (!catchAll.isEmpty()) { - m = new CatchAllMatcher(catchAll.keySet().iterator().next(), catchAll.values().iterator().next().data); - if (data != null) { - m = new LinearMatcher(Arrays.asList(new DataMatcher(data), m)); - } - } else if (!wilds.isEmpty()) { - if (wilds.size() == 1 && data == null && childs.isEmpty()) { - m = new WildMatcher(wilds.keySet().iterator().next(), wilds.values().iterator().next().matcher()); - } else { - List matchers = new ArrayList<>(); - if (data != null) { - matchers.add(new DataMatcher(data)); - } - if (!childs.isEmpty()) { - matchers.add(staticMatcher()); - } - for (Map.Entry e : wilds.entrySet()) { - matchers.add(new WildMatcher(e.getKey(), e.getValue().matcher())); - } - m = new LinearMatcher(matchers); - } - } else if (!childs.isEmpty()) { - m = staticMatcher(); - if (data != null) { - m = new LinearMatcher(Arrays.asList(new DataMatcher(data), m)); - } - } else { - return new DataMatcher(data); - } - return m; - } - - public interface Matcher { - Match match(int i, List segments, Match match); - } - - public static final class StaticMatcher implements Matcher { - private final String segment; - private final Matcher child; - - StaticMatcher(String segment, Matcher child) { - this.segment = segment; - this.child = child; - } - - @Override - public Match match(int i, List segments, Match match) { - if (i < segments.size() && segment.equals(segments.get(i))) { - return child.match(i + 1, segments, match); - } - return null; - } - - @Override - public String toString() { - return "[\"" + segment + "\" " + child + "]"; - } - } - - public static final class WildMatcher implements Matcher { - private final Keyword parameter; - private final Matcher child; - - WildMatcher(Keyword parameter, Matcher child) { - this.parameter = parameter; - this.child = child; - } - - @Override - public Match match(int i, List segments, Match match) { - if (i < segments.size() && !segments.get(i).isEmpty()) { - final Match m = child.match(i + 1, segments, match); - if (m != null) { - m.params.put(parameter, decode(segments.get(i))); - return m; - } - } - return null; - } - - @Override - public String toString() { - return "[" + parameter + " " + child + "]"; - } - } - - public static final class CatchAllMatcher implements Matcher { - private final Keyword parameter; - private final Object data; - - CatchAllMatcher(Keyword parameter, Object data) { - this.parameter = parameter; - this.data = data; - } - - @Override - public Match match(int i, List segments, Match match) { - if (i < segments.size()) { - match.params.put(parameter, decode(String.join("/", segments.subList(i, segments.size())))); - match.data = data; - return match; - } - return null; - } - - @Override - public String toString() { - return "[" + parameter + " " + new DataMatcher(data) + "]"; - } - } - - public static final class StaticMapMatcher implements Matcher { - private final Map map; - - StaticMapMatcher(Map map) { - this.map = map; - } - - @Override - public Match match(int i, List segments, Match match) { - if (i < segments.size()) { - final Matcher child = map.get(segments.get(i)); - if (child != null) { - return child.match(i + 1, segments, match); - } - } - return null; - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("{"); - List keys = new ArrayList<>(map.keySet()); - for (int i = 0; i < keys.size(); i++) { - String path = keys.get(i); - Matcher value = map.get(path); - b.append("\"").append(path).append("\" ").append(value); - if (i < keys.size() - 1) { - b.append(", "); - } - } - b.append("}"); - return b.toString(); - } - } - - public static final class LinearMatcher implements Matcher { - - private final List childs; - - LinearMatcher(List childs) { - this.childs = childs; - } - - @Override - public Match match(int i, List segments, Match match) { - for (Matcher child : childs) { - final Match m = child.match(i, segments, match); - if (m != null) { - return m; - } - } - return null; - } - - @Override - public String toString() { - return childs.toString(); - } - } - - public static final class DataMatcher implements Matcher { - private final Object data; - - DataMatcher(Object data) { - this.data = data; - } - - @Override - public Match match(int i, List segments, Match match) { - if (i == segments.size()) { - match.data = data; - return match; - } - return null; - } - - @Override - public String toString() { - return (data != null ? data.toString() : "nil"); - } - } - - public static Matcher scanner(List matchers) { - return new LinearMatcher(matchers); - } - - public static Match lookup(Matcher matcher, String path) { - return matcher.match(0, split(path), new Match()); - } - - public static void main(String[] args) { - - SegmentTrie trie = new SegmentTrie(); - trie.add("/repos/:owner/:repo/stargazers", 1); - Matcher m = trie.matcher(); - System.err.println(m); - System.err.println(m.getClass()); - System.out.println(lookup(m, "/repos/metosin/reitit/stargazers")); - } -} diff --git a/modules/reitit-core/src/reitit/segment.cljc b/modules/reitit-core/src/reitit/segment.cljc deleted file mode 100644 index a12f06f9..00000000 --- a/modules/reitit-core/src/reitit/segment.cljc +++ /dev/null @@ -1,75 +0,0 @@ -(ns reitit.segment - (:refer-clojure :exclude [-lookup compile]) - (:require [reitit.impl :as impl] - [clojure.string :as str]) - #?(:clj (:import (reitit SegmentTrie SegmentTrie$Match)))) - -(defrecord Match [data path-params]) - -(defprotocol Segment - (-insert [this ps data]) - (-lookup [this ps path-params])) - -(extend-protocol Segment - nil - (-insert [_ _ _]) - (-lookup [_ _ _])) - -(defn- -catch-all [children catch-all path-params p ps] - (-lookup - (impl/fast-get children catch-all) - nil - (assoc path-params catch-all (str/join "/" (cons p ps))))) - -(defn- segment - ([] (segment {} #{} nil nil)) - ([children wilds catch-all match] - (let [children' (impl/fast-map children) - wilds? (seq wilds)] - ^{:type ::segment} - (reify - Segment - (-insert [_ [p & ps] d] - (if-not p - (segment children wilds catch-all d) - (let [[w c] ((juxt impl/wild-param impl/catch-all-param) p) - wilds (if w (conj wilds w) wilds) - catch-all (or c catch-all) - children (update children (or w c p) #(-insert (or % (segment)) ps d))] - (segment children wilds catch-all match)))) - (-lookup [_ [p & ps] path-params] - (if (nil? p) - (when match (assoc match :path-params path-params)) - (or (-lookup (impl/fast-get children' p) ps path-params) - (if (and wilds? (not (str/blank? p))) (some #(-lookup (impl/fast-get children' %) ps (assoc path-params % p)) wilds)) - (if catch-all (-catch-all children' catch-all path-params p ps))))))))) - -;; -;; public api -;; - -(defn insert - "Returns a Segment Trie with path with data inserted into it. Creates the trie if `nil`." - [trie path data] - #?(:cljs (-insert (or trie (segment)) (impl/segments path) (map->Match {:data data})) - :clj (.add (or ^SegmentTrie trie ^SegmentTrie (SegmentTrie.)) ^String path data))) - -(defn compile [trie] - "Compiles the Trie so that [[lookup]] can be used." - #?(:cljs trie - :clj (.matcher (or ^SegmentTrie trie (SegmentTrie.))))) - -(defn scanner [compiled-tries] - "Returns a new compiled trie that does linear scan on the given compiled tries on [[lookup]]." - #?(:cljs (reify - Segment - (-lookup [_ ps params] - (some (fn [trie] (-lookup trie ps params)) compiled-tries))) - :clj (SegmentTrie/scanner compiled-tries))) - -(defn lookup [trie path] - "Looks the path from a Segment Trie. Returns a [[Match]] or `nil`." - #?(:cljs (if-let [match (-lookup trie (impl/segments path) {})] - (assoc match :path-params (impl/url-decode-coll (:path-params match)))) - :clj (if-let [match ^SegmentTrie$Match (SegmentTrie/lookup trie path)] - (->Match (.data match) (clojure.lang.PersistentHashMap/create (.params match)))))) diff --git a/perf-test/clj/reitit/prefix_tree_perf_test.clj b/perf-test/clj/reitit/prefix_tree_perf_test.clj index b56f2c58..aeed5516 100644 --- a/perf-test/clj/reitit/prefix_tree_perf_test.clj +++ b/perf-test/clj/reitit/prefix_tree_perf_test.clj @@ -1,10 +1,8 @@ (ns reitit.prefix-tree-perf-test (:require [clojure.test :refer :all] [io.pedestal.http.route.prefix-tree :as p] - [reitit.segment :as segment] [reitit.trie :as trie] - [criterium.core :as cc]) - (:import (reitit SegmentTrie))) + [criterium.core :as cc])) ;; ;; testing @@ -71,14 +69,6 @@ (p/insert acc p d)) nil routes)) -(def segment-matcher - (.matcher - ^SegmentTrie - (reduce - (fn [acc [p d]] - (segment/insert acc p d)) - nil routes))) - (def trie-matcher (trie/compile (reduce @@ -117,7 +107,7 @@ ;; 0.63µs (Single sweep path paraµs) ;; 0.51µs (Cleanup) ;; 0.30µs (Java) - (cc/quick-bench + #_(cc/quick-bench (segment/lookup segment-matcher "/v1/orgs/1/topics")) ;; 0.32µs (initial) @@ -134,5 +124,5 @@ (comment (p/lookup pedestal-tree "/v1/orgs/1/topics") (trie/lookup trie-matcher "/v1/orgs/1/topics") - (segment/lookup segment-matcher "/v1/orgs/1/topics")) + #_(segment/lookup segment-matcher "/v1/orgs/1/topics")) diff --git a/test/cljc/reitit/segment_test.cljc b/test/cljc/reitit/segment_test.cljc deleted file mode 100644 index 2be592e1..00000000 --- a/test/cljc/reitit/segment_test.cljc +++ /dev/null @@ -1,26 +0,0 @@ -(ns reitit.segment-test - (:require [clojure.test :refer [deftest testing is are]] - [reitit.segment :as s])) - -(deftest tests - (is (= (s/->Match {:a 1} {}) - (-> (s/insert nil "/foo" {:a 1}) - (s/compile) - (s/lookup "/foo")))) - - (is (= (s/->Match {:a 1} {}) - (-> (s/insert nil "/foo" {:a 1}) - (s/insert "/foo/*bar" {:b 1}) - (s/compile) - (s/lookup "/foo")))) - - (is (= (s/->Match {:b 1} {:bar "bar"}) - (-> (s/insert nil "/foo" {:a 1}) - (s/insert "/foo/*bar" {:b 1}) - (s/compile) - (s/lookup "/foo/bar")))) - - (is (= (s/->Match {:a 1} {}) - (-> (s/insert nil "" {:a 1}) - (s/compile) - (s/lookup ""))))) diff --git a/test/cljc/reitit/trie_test.cljc b/test/cljc/reitit/trie_test.cljc new file mode 100644 index 00000000..570c45c5 --- /dev/null +++ b/test/cljc/reitit/trie_test.cljc @@ -0,0 +1,26 @@ +(ns reitit.trie-test + (:require [clojure.test :refer [deftest testing is are]] + [reitit.trie :as rt])) + +(deftest tests + (is (= (rt/->Match {:a 1} {}) + (-> (rt/insert nil "/foo" {:a 1}) + (rt/compile) + (rt/lookup "/foo")))) + + (is (= (rt/->Match {:a 1} {}) + (-> (rt/insert nil "/foo" {:a 1}) + (rt/insert "/foo/*bar" {:b 1}) + (rt/compile) + (rt/lookup "/foo")))) + + (is (= (rt/->Match {:b 1} {:bar "bar"}) + (-> (rt/insert nil "/foo" {:a 1}) + (rt/insert "/foo/*bar" {:b 1}) + (rt/compile) + (rt/lookup "/foo/bar")))) + + (is (= (rt/->Match {:a 1} {}) + (-> (rt/insert nil "" {:a 1}) + (rt/compile) + (rt/lookup "")))))