Skip to content

Commit a54094f

Browse files
committed
routing: yet another micro optimization for static path
- remove the use of hashmap for single method matching
1 parent 062170f commit a54094f

File tree

1 file changed

+76
-31
lines changed
  • jooby/src/main/java/io/jooby/internal

1 file changed

+76
-31
lines changed

jooby/src/main/java/io/jooby/internal/Chi.java

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import io.jooby.Router;
1111

1212
import java.util.Arrays;
13-
import java.util.Collections;
1413
import java.util.Map;
1514
import java.util.Objects;
1615
import java.util.concurrent.ConcurrentHashMap;
@@ -31,24 +30,71 @@ class Chi implements RouteTree {
3130

3231
private static final int NODE_SIZE = ntCatchAll + 1;
3332

34-
static final StaticRoute NO_MATCH = new StaticRoute(Collections.emptyMap());
35-
3633
static final char ZERO_CHAR = (char) 0;
3734
private MessageEncoder encoder;
3835

39-
static class StaticRoute {
40-
private final Map<String, StaticRouterMatch> methods;
36+
private interface MethodMatcher {
37+
StaticRouterMatch get(String method);
38+
39+
void put(String method, StaticRouterMatch route);
40+
41+
boolean matches(String method);
42+
}
43+
44+
private static class SingleMethodMatcher implements MethodMatcher {
45+
private String method;
46+
private StaticRouterMatch route;
47+
48+
@Override public void put(String method, StaticRouterMatch route) {
49+
this.method = method;
50+
this.route = route;
51+
}
52+
53+
@Override public StaticRouterMatch get(String method) {
54+
return this.method.equals(method) ? route : null;
55+
}
56+
57+
@Override public boolean matches(String method) {
58+
return this.method.equals(method);
59+
}
60+
61+
public void clear() {
62+
this.method = null;
63+
this.route = null;
64+
}
65+
}
66+
67+
private static class MultipleMethodMatcher implements MethodMatcher {
68+
private Map<String, StaticRouterMatch> methods = new ConcurrentHashMap<>();
4169

42-
public StaticRoute(Map<String, StaticRouterMatch> methods) {
43-
this.methods = methods;
70+
public MultipleMethodMatcher(SingleMethodMatcher matcher) {
71+
methods.put(matcher.method, matcher.route);
72+
matcher.clear();
4473
}
4574

46-
public StaticRoute() {
47-
this(newMap(Router.METHODS.size()));
75+
@Override public StaticRouterMatch get(String method) {
76+
return methods.get(method);
4877
}
4978

79+
@Override public void put(String method, StaticRouterMatch route) {
80+
methods.put(method, route);
81+
}
82+
83+
@Override public boolean matches(String method) {
84+
return this.methods.containsKey(method);
85+
}
86+
}
87+
88+
static class StaticRoute {
89+
private MethodMatcher matcher;
90+
5091
public void put(String method, Route route) {
51-
methods.put(method, new StaticRouterMatch(route));
92+
if (matcher == null) {
93+
matcher = new SingleMethodMatcher();
94+
} else if (matcher instanceof SingleMethodMatcher) {
95+
matcher = new MultipleMethodMatcher((SingleMethodMatcher) matcher);
96+
}
97+
matcher.put(method, new StaticRouterMatch(route));
5298
}
5399
}
54100

@@ -555,7 +601,7 @@ Segment patNextSegment(String pattern) {
555601
int ws = pattern.indexOf('*');
556602

557603
if (ps < 0 && ws < 0) {
558-
return new Segment(ntStatic, EMPTY_STRING, (char) 0, 0,
604+
return new Segment(ntStatic, EMPTY_STRING, ZERO_CHAR, 0,
559605
pattern.length()); // we return the entire thing
560606
}
561607

@@ -623,7 +669,7 @@ Segment patNextSegment(String pattern) {
623669
// EDIT: should we panic if there is stuff after the * ???
624670
// We allow naming a wildcard: *path
625671
//String key = ws == pattern.length() - 1 ? "*" : pattern.substring(ws + 1).toString();
626-
return new Segment(ntCatchAll, EMPTY_STRING, (char) 0, ws, pattern.length());
672+
return new Segment(ntCatchAll, EMPTY_STRING, ZERO_CHAR, ws, pattern.length());
627673
}
628674

629675
public void destroy() {
@@ -650,11 +696,7 @@ public void destroy() {
650696
private final Node root = new Node();
651697

652698
/** Not need to use a concurrent map, due we don't allow to add routes after application started. */
653-
private final Map<Object, StaticRoute> staticPaths = newMap(16);
654-
655-
static <K, V> Map<K, V> newMap(int size) {
656-
return new ConcurrentHashMap<>(size);
657-
}
699+
private final Map<Object, StaticRoute> staticPaths = new ConcurrentHashMap<>();
658700

659701
public void insert(String method, String pattern, Route route) {
660702
String baseCatchAll = baseCatchAll(pattern);
@@ -691,24 +733,27 @@ public void destroy() {
691733
}
692734

693735
public boolean exists(String method, String path) {
694-
if (!staticPaths.getOrDefault(path, NO_MATCH).methods.containsKey(method)) {
695-
return root.findRoute(new RouterMatch(), method, path) != null;
696-
}
697-
return true;
736+
return find(method, path) != null;
698737
}
699738

700739
@Override public Router.Match find(String method, String path) {
701-
StaticRouterMatch match = staticPaths.getOrDefault(path, NO_MATCH).methods.get(method);
702-
if (match == null) {
703-
// use radix tree
704-
RouterMatch result = new RouterMatch();
705-
Route route = root.findRoute(result, method, path);
706-
if (route == null) {
707-
return result.missing(method, path, encoder);
708-
}
709-
return result.found(route);
740+
StaticRoute staticRoute = staticPaths.get(path);
741+
if (staticRoute == null) {
742+
return findInternal(method, path);
743+
} else {
744+
StaticRouterMatch match = staticRoute.matcher.get(method);
745+
return match == null ? findInternal(method, path) : match;
746+
}
747+
}
748+
749+
private Router.Match findInternal(String method, String path) {
750+
// use radix tree
751+
RouterMatch result = new RouterMatch();
752+
Route route = root.findRoute(result, method, path);
753+
if (route == null) {
754+
return result.missing(method, path, encoder);
710755
}
711-
return match;
756+
return result.found(route);
712757
}
713758

714759
public void setEncoder(MessageEncoder encoder) {

0 commit comments

Comments
 (0)