From 764134c0a5ef86c6f048f930e3a84a12658629eb Mon Sep 17 00:00:00 2001 From: Hyangtack Lee Date: Wed, 10 Feb 2021 13:24:42 +0900 Subject: [PATCH] Add list builder as method parameter in order to collect Routed instances --- .../linecorp/armeria/server/RoutingTrie.java | 124 ++++++++++++++---- 1 file changed, 98 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/com/linecorp/armeria/server/RoutingTrie.java b/core/src/main/java/com/linecorp/armeria/server/RoutingTrie.java index 35049d52726..a90b9dc811b 100644 --- a/core/src/main/java/com/linecorp/armeria/server/RoutingTrie.java +++ b/core/src/main/java/com/linecorp/armeria/server/RoutingTrie.java @@ -16,6 +16,7 @@ package com.linecorp.armeria.server; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.util.Objects.requireNonNull; @@ -24,6 +25,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Objects; import java.util.function.Function; import javax.annotation.Nullable; @@ -31,6 +33,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; import it.unimi.dsi.fastutil.chars.Char2ObjectMap; import it.unimi.dsi.fastutil.chars.Char2ObjectMaps; @@ -75,31 +78,61 @@ private Node continueWalking() { this.routeResolver = requireNonNull(routeResolver, "routeResolver"); } + // FIXME(hyangtack) Update Javadoc + /** * Returns the list of values which is mapped to the given {@code path}. */ List find(RoutingContext routingCtx) { - final Node node = findNode(routingCtx, false); + final Node node = findNode(routingCtx, false, null); return node == null ? ImmutableList.of() : node.values; } + /** + * Returns the list of {@link Routed}s which are mapped to the given {@link RoutingContext}. + */ + List> findRoutes(RoutingContext routingCtx) { + final ImmutableList.Builder> routeAccumulator = new Builder<>(); + findNode(routingCtx, false, routeAccumulator); + return routeAccumulator.build(); + } + /** * Returns the list of values which is mapped to the given {@code path}. */ List findAll(RoutingContext routingCtx) { - return findAllNodes(routingCtx, false) + return findAllNodes(routingCtx, false, null) .stream() .flatMap(n -> n.values.stream()) .collect(toImmutableList()); } + /** + * Returns the list of {@link Routed}s which are mapped to the given {@link RoutingContext}. + */ + List> findAllRoutes(RoutingContext routingCtx) { + final ImmutableList.Builder> routeAccumulator = new Builder<>(); + findAllNodes(routingCtx, false, routeAccumulator); + return routeAccumulator.build(); + } + /** * Returns a {@link Node} which is mapped to the given {@code path}. */ @Nullable @VisibleForTesting Node findNode(RoutingContext routingCtx) { - return findNode(routingCtx, false); + return findNode(routingCtx, false, null); + } + + /** + * Returns a {@link Node} which is mapped to the given {@code path}. + */ + @Nullable + @VisibleForTesting + Node findNode(RoutingContext routingCtx, + @Nullable ImmutableList.Builder> routeAccumulator) { + return findNode(routingCtx, false, routeAccumulator); } /** @@ -110,7 +143,19 @@ Node findNode(RoutingContext routingCtx) { @VisibleForTesting Node findNode(RoutingContext routingCtx, boolean exact) { requireNonNull(routingCtx, "routingCtx"); - return findFirstNode(root, routingCtx, 0, exact, new IntHolder()); + return findFirstNode(root, routingCtx, 0, exact, new IntHolder(), null); + } + + /** + * Returns a {@link Node} which is mapped to the given {@code path}. + * If {@code exact} is {@code true}, internally-added node may be returned. + */ + @Nullable + @VisibleForTesting + Node findNode(RoutingContext routingCtx, boolean exact, + @Nullable ImmutableList.Builder> routeAccumulator) { + requireNonNull(routingCtx, "routingCtx"); + return findFirstNode(root, routingCtx, 0, exact, new IntHolder(), routeAccumulator); } /** @@ -119,8 +164,9 @@ Node findNode(RoutingContext routingCtx, boolean exact) { */ @Nullable private Node findFirstNode(Node node, RoutingContext routingCtx, int begin, boolean exact, - IntHolder nextHolder) { - final Node checked = checkNode(node, routingCtx, begin, exact, nextHolder); + IntHolder nextHolder, + @Nullable ImmutableList.Builder> routeAccumulator) { + final Node checked = checkNode(node, routingCtx, begin, exact, nextHolder, routeAccumulator); if (checked != continueWalking()) { return checked; } @@ -134,14 +180,14 @@ private Node findFirstNode(Node node, RoutingContext routingCtx, int begin final int next = nextHolder.value; Node child = node.children.get(routingCtx.path().charAt(next)); if (child != null) { - final Node found = findFirstNode(child, routingCtx, next, exact, nextHolder); + final Node found = findFirstNode(child, routingCtx, next, exact, nextHolder, routeAccumulator); if (found != null) { return found; } } child = node.parameterChild; if (child != null) { - final Node found = findFirstNode(child, routingCtx, next, exact, nextHolder); + final Node found = findFirstNode(child, routingCtx, next, exact, nextHolder, routeAccumulator); if (found != null) { return found; } @@ -149,16 +195,18 @@ private Node findFirstNode(Node node, RoutingContext routingCtx, int begin return node.catchAllChild; } - private List> findAllNodes(RoutingContext routingCtx, boolean exact) { + private List> findAllNodes(RoutingContext routingCtx, boolean exact, + @Nullable ImmutableList.Builder> routeAccumulator) { final ImmutableList.Builder> accumulator = ImmutableList.builder(); - findAllNodes(root, routingCtx, 0, exact, accumulator, new IntHolder()); + findAllNodes(root, routingCtx, 0, exact, accumulator, new IntHolder(), routeAccumulator); return accumulator.build(); } private void findAllNodes(Node node, RoutingContext routingCtx, int begin, boolean exact, ImmutableList.Builder> accumulator, - IntHolder nextHolder) { - final Node checked = checkNode(node, routingCtx, begin, exact, nextHolder); + IntHolder nextHolder, + @Nullable ImmutableList.Builder> routeAccumulator) { + final Node checked = checkNode(node, routingCtx, begin, exact, nextHolder, routeAccumulator); if (checked != continueWalking()) { if (checked != null) { accumulator.add(checked); @@ -174,11 +222,11 @@ private void findAllNodes(Node node, RoutingContext routingCtx, int begin, bo } child = node.parameterChild; if (child != null) { - findAllNodes(child, routingCtx, next, exact, accumulator, nextHolder); + findAllNodes(child, routingCtx, next, exact, accumulator, nextHolder, routeAccumulator); } child = node.children.get(routingCtx.path().charAt(next)); if (child != null) { - findAllNodes(child, routingCtx, next, exact, accumulator, nextHolder); + findAllNodes(child, routingCtx, next, exact, accumulator, nextHolder, routeAccumulator); } } @@ -189,7 +237,8 @@ private void findAllNodes(Node node, RoutingContext routingCtx, int begin, bo */ @Nullable private Node checkNode(Node node, RoutingContext routingCtx, int begin, boolean exact, - IntHolder next) { + IntHolder next, + @Nullable ImmutableList.Builder> routeAccumulator) { switch (node.type) { case EXACT: final int len = node.path.length(); @@ -205,16 +254,12 @@ private Node checkNode(Node node, RoutingContext routingCtx, int begin, bo // TODO(hyangtack) How about returning both of RoutingResult and Node so that the caller // doesn't need to resolve RoutingResult again? - // FIXME(hyangtack) isRouteDecorator + // FIXME(hyangtack) Get isRouteDecorator as a parameter? if (!node.values.isEmpty()) { - final boolean existsSuitableRoute = - node.values.stream() - .map(v -> routeResolver.apply(v).apply(routingCtx, false)) - .anyMatch(RoutingResult::isPresent); - return existsSuitableRoute ? node : null; + return filterAcceptableNode(node, routingCtx, false, routeAccumulator); } - // FIXME(hyangtack) Check 'exact' + // FIXME(hyangtack) Need to fully understand what 'exact' means. if (exact || node.catchAllChild == null) { return node; } @@ -227,13 +272,13 @@ private Node checkNode(Node node, RoutingContext routingCtx, int begin, bo // Consume characters until the delimiter '/' as a path variable. final int delim = routingCtx.path().indexOf('/', begin); if (delim < 0) { - // TODO(hyangtack) Apply RoutingContext // No more delimiter. - return node; + return filterAcceptableNode(node, routingCtx, false, routeAccumulator); } if (routingCtx.path().length() == delim + 1) { - final Node trailingSlashNode = node.children.get('/'); - return trailingSlashNode != null ? trailingSlashNode : node; + // Check whether trailing slash node exists or not. + final Node lastNode = firstNonNull(node.children.get('/'), node); + return filterAcceptableNode(lastNode, routingCtx, false, routeAccumulator); } next.value = delim; break; @@ -243,6 +288,33 @@ private Node checkNode(Node node, RoutingContext routingCtx, int begin, bo return continueWalking(); } + @Nullable + private Node filterAcceptableNode(Node node, + RoutingContext routingCtx, + boolean isRouteDecorator, + @Nullable ImmutableList.Builder> routeAccumulator) { + final List> routingResults = + node.values.stream() + // TODO(hyangtack) Add dryRun parameter to Route.apply + // Pass it as true if routeAccumulator is null + // not to set deferred exception to context. + .map(v -> { + final Route route = routeResolver.apply(v); + final RoutingResult result = route.apply(routingCtx, isRouteDecorator); + return result.isPresent() ? Routed.of(route, result, v) : null; + }) + .filter(Objects::nonNull) + .collect(toImmutableList()); + if (routingResults.isEmpty()) { + // No acceptable route. + return null; + } + if (routeAccumulator != null) { + routeAccumulator.addAll(routingResults); + } + return node; + } + void dump(OutputStream output) { // Do not close this writer in order to keep output stream open. final PrintWriter p = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));