Skip to content

Commit

Permalink
Add list builder as method parameter in order to collect Routed insta…
Browse files Browse the repository at this point in the history
…nces
  • Loading branch information
hyangtack committed Feb 10, 2021
1 parent 2fbfc90 commit 764134c
Showing 1 changed file with 98 additions and 26 deletions.
124 changes: 98 additions & 26 deletions core/src/main/java/com/linecorp/armeria/server/RoutingTrie.java
Expand Up @@ -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;

Expand All @@ -24,13 +25,15 @@
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;

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;
Expand Down Expand Up @@ -75,31 +78,61 @@ private Node<V> continueWalking() {
this.routeResolver = requireNonNull(routeResolver, "routeResolver");
}

// FIXME(hyangtack) Update Javadoc

/**
* Returns the list of values which is mapped to the given {@code path}.
*/
List<V> find(RoutingContext routingCtx) {
final Node<V> node = findNode(routingCtx, false);
final Node<V> 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<Routed<V>> findRoutes(RoutingContext routingCtx) {
final ImmutableList.Builder<Routed<V>> routeAccumulator = new Builder<>();
findNode(routingCtx, false, routeAccumulator);
return routeAccumulator.build();
}

/**
* Returns the list of values which is mapped to the given {@code path}.
*/
List<V> 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<Routed<V>> findAllRoutes(RoutingContext routingCtx) {
final ImmutableList.Builder<Routed<V>> 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<V> 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<V> findNode(RoutingContext routingCtx,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
return findNode(routingCtx, false, routeAccumulator);
}

/**
Expand All @@ -110,7 +143,19 @@ Node<V> findNode(RoutingContext routingCtx) {
@VisibleForTesting
Node<V> 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<V> findNode(RoutingContext routingCtx, boolean exact,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
requireNonNull(routingCtx, "routingCtx");
return findFirstNode(root, routingCtx, 0, exact, new IntHolder(), routeAccumulator);
}

/**
Expand All @@ -119,8 +164,9 @@ Node<V> findNode(RoutingContext routingCtx, boolean exact) {
*/
@Nullable
private Node<V> findFirstNode(Node<V> node, RoutingContext routingCtx, int begin, boolean exact,
IntHolder nextHolder) {
final Node<V> checked = checkNode(node, routingCtx, begin, exact, nextHolder);
IntHolder nextHolder,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
final Node<V> checked = checkNode(node, routingCtx, begin, exact, nextHolder, routeAccumulator);
if (checked != continueWalking()) {
return checked;
}
Expand All @@ -134,31 +180,33 @@ private Node<V> findFirstNode(Node<V> node, RoutingContext routingCtx, int begin
final int next = nextHolder.value;
Node<V> child = node.children.get(routingCtx.path().charAt(next));
if (child != null) {
final Node<V> found = findFirstNode(child, routingCtx, next, exact, nextHolder);
final Node<V> found = findFirstNode(child, routingCtx, next, exact, nextHolder, routeAccumulator);
if (found != null) {
return found;
}
}
child = node.parameterChild;
if (child != null) {
final Node<V> found = findFirstNode(child, routingCtx, next, exact, nextHolder);
final Node<V> found = findFirstNode(child, routingCtx, next, exact, nextHolder, routeAccumulator);
if (found != null) {
return found;
}
}
return node.catchAllChild;
}

private List<Node<V>> findAllNodes(RoutingContext routingCtx, boolean exact) {
private List<Node<V>> findAllNodes(RoutingContext routingCtx, boolean exact,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
final ImmutableList.Builder<Node<V>> 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<V> node, RoutingContext routingCtx, int begin, boolean exact,
ImmutableList.Builder<Node<V>> accumulator,
IntHolder nextHolder) {
final Node<V> checked = checkNode(node, routingCtx, begin, exact, nextHolder);
IntHolder nextHolder,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
final Node<V> checked = checkNode(node, routingCtx, begin, exact, nextHolder, routeAccumulator);
if (checked != continueWalking()) {
if (checked != null) {
accumulator.add(checked);
Expand All @@ -174,11 +222,11 @@ private void findAllNodes(Node<V> 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);
}
}

Expand All @@ -189,7 +237,8 @@ private void findAllNodes(Node<V> node, RoutingContext routingCtx, int begin, bo
*/
@Nullable
private Node<V> checkNode(Node<V> node, RoutingContext routingCtx, int begin, boolean exact,
IntHolder next) {
IntHolder next,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
switch (node.type) {
case EXACT:
final int len = node.path.length();
Expand All @@ -205,16 +254,12 @@ private Node<V> checkNode(Node<V> 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;
}
Expand All @@ -227,13 +272,13 @@ private Node<V> checkNode(Node<V> 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<V> trailingSlashNode = node.children.get('/');
return trailingSlashNode != null ? trailingSlashNode : node;
// Check whether trailing slash node exists or not.
final Node<V> lastNode = firstNonNull(node.children.get('/'), node);
return filterAcceptableNode(lastNode, routingCtx, false, routeAccumulator);
}
next.value = delim;
break;
Expand All @@ -243,6 +288,33 @@ private Node<V> checkNode(Node<V> node, RoutingContext routingCtx, int begin, bo
return continueWalking();
}

@Nullable
private Node<V> filterAcceptableNode(Node<V> node,
RoutingContext routingCtx,
boolean isRouteDecorator,
@Nullable ImmutableList.Builder<Routed<V>> routeAccumulator) {
final List<Routed<V>> 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));
Expand Down

0 comments on commit 764134c

Please sign in to comment.