From 2a1fea2ccb9cd736b64b7231579c5ece4d42cce2 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Tue, 8 Jan 2019 10:03:06 +0200 Subject: [PATCH] Initial Java Trie --- modules/reitit-core/java-src/reitit/Trie.java | 148 ++++++++++++++++++ modules/reitit-core/src/reitit/segment.cljc | 17 +- 2 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 modules/reitit-core/java-src/reitit/Trie.java diff --git a/modules/reitit-core/java-src/reitit/Trie.java b/modules/reitit-core/java-src/reitit/Trie.java new file mode 100644 index 00000000..2a0219fe --- /dev/null +++ b/modules/reitit-core/java-src/reitit/Trie.java @@ -0,0 +1,148 @@ +package reitit; + +import clojure.lang.Keyword; + +import java.util.*; + +import static java.util.Arrays.asList; + +public class Trie { + + public static class Match { + public 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; + + @Override + public String toString() { + Map m = new HashMap<>(); + m.put(Keyword.intern("childs"), childs); + m.put(Keyword.intern("wilds"), wilds); + m.put(Keyword.intern("catchAll"), catchAll); + m.put(Keyword.intern("data"), data); + return m.toString(); + } + + public static Match lookup(Trie root, String path) { + return lookup(root, new Match(), split(path)); + } + + private static Match lookup(Trie root, Match match, List parts) { + Trie childTrie = null; + if (parts.isEmpty()) { + return match; + } else { + Trie trie = root; + int i = 0; + for (final String part : parts) { + i++; + childTrie = trie.childs.get(part); + if (childTrie != null) { + trie = childTrie; + } else { + for (final Map.Entry e : trie.wilds.entrySet()) { + childTrie = e.getValue(); + match.data = childTrie.data; + Match m = lookup(childTrie, match, parts.subList(i, parts.size())); + if (m != null) { + match.params.put(e.getKey(), part); + return m; + } + } + for (Map.Entry e : trie.catchAll.entrySet()) { + childTrie = e.getValue(); + match.params.put(e.getKey(), String.join("/", parts.subList(i - 1, parts.size()))); + match.data = childTrie.data; + return match; + } + break; + } + } + } + if (childTrie != null) { + match.data = childTrie.data; + return match; + } + return null; + } + + public Trie add(String path, Object data) { + List paths = split(path); + Trie pointer = this; + for (String p : paths) { + if (p.startsWith(":")) { + Keyword k = Keyword.intern(p.substring(1)); + Trie s = pointer.wilds.get(k); + if (s == null) { + s = new Trie(); + pointer.wilds.put(k, s); + } + pointer = s; + } else if (p.startsWith("*")) { + Keyword k = Keyword.intern(p.substring(1)); + Trie s = pointer.catchAll.get(k); + if (s == null) { + s = new Trie(); + pointer.catchAll.put(k, s); + } + break; + } else { + Trie s = pointer.childs.get(p); + if (s == null) { + s = new Trie(); + pointer.childs.put(p, s); + } + pointer = s; + } + } + pointer.data = data; + return this; + } + + public static List split(String path) { + ArrayList strings = new ArrayList<>(asList(path.split("/"))); + strings.remove(0); + return strings; + } + + public static void main(String[] args) { + Trie trie = + new Trie() + .add("/kikka", 1) + .add("/kakka", 2) + .add("/api/ping", 3) + .add("/api/pong", 4) + .add("/api/ipa/ping", 5) + .add("/api/ipa/pong", 6) + .add("/users/:user-id", 7) + .add("/users/:user-id/orders", 8) + .add("/users/:user-id/price", 9) + .add("/orders/:id/price", 10) + .add("/orders/:super", 11) + .add("/orders/:super/hyper/:giga", 12); + + //System.out.println(lookup(trie, split("/kikka"))); + System.out.println(lookup(trie, "/orders/mies/hyper/peikko")); + + System.out.println(lookup( + new Trie().add("/user/:id/profile/:type/", 1), + "/user/1/profile/compat")); + + System.out.println(lookup( + new Trie().add("/user/*path", 1), + "/user/1/profile/compat")); + } +} diff --git a/modules/reitit-core/src/reitit/segment.cljc b/modules/reitit-core/src/reitit/segment.cljc index c96de655..98173c16 100644 --- a/modules/reitit-core/src/reitit/segment.cljc +++ b/modules/reitit-core/src/reitit/segment.cljc @@ -1,7 +1,8 @@ (ns reitit.segment (:refer-clojure :exclude [-lookup]) (:require [reitit.impl :as impl] - [clojure.string :as str])) + [clojure.string :as str]) + #?(:clj (:import (reitit Trie Trie$Match)))) (defrecord Match [data path-params]) @@ -44,13 +45,19 @@ (if catch-all (-catch-all children' catch-all path-params p ps))))))))) (defn insert [root path data] - (-insert (or root (segment)) (impl/segments path) (map->Match {:data data}))) + #?(:cljs (-insert (or root (segment)) (impl/segments path) (map->Match {:data data})) + :clj (.add (or ^Trie root ^Trie (Trie.)) ^String path data))) (defn create [paths] (reduce (fn [segment [p data]] - (insert segment p data)) - nil paths)) + #?(:cljs (insert segment p data) + :clj (.add ^Trie segment ^String p data))) + #?(:cljs nil + :clj (Trie.)) + paths)) (defn lookup [segment path] - (-lookup segment (impl/segments path) {})) + #?(:cljs (-lookup segment (impl/segments path) {}) + :clj (if-let [match ^Trie$Match (Trie/lookup segment path)] + (->Match (.data match) (clojure.lang.PersistentHashMap/create (.params match))))))