/* * Copyright 2015 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.http.router; import io.netty.util.internal.ObjectUtil; import java.util.Map; /** * The path can contain constants or placeholders, example: * {@code constant1/:placeholder1/constant2/:*}. * {@code :*} is a special placeholder to catch the rest of the path * (may include slashes). If exists, it must appear at the end of the path. * * The path must not contain URL query, example: * {@code constant1/constant2?foo=bar}. * * The path will be broken to tokens, example: * {@code ["constant1", ":variable", "constant2", ":*"]} */ final class Path { public static String removeSlashesAtBothEnds(String path) { ObjectUtil.checkNotNull(path, "path"); if (path.isEmpty()) { return path; } int beginIndex = 0; while (beginIndex < path.length() && path.charAt(beginIndex) == '/') { beginIndex++; } if (beginIndex == path.length()) { return ""; } int endIndex = path.length() - 1; while (endIndex > beginIndex && path.charAt(endIndex) == '/') { endIndex--; } return path.substring(beginIndex, endIndex + 1); } //-------------------------------------------------------------------------- private final String path; private final String[] tokens; /** * The path must not contain URL query, example: * {@code constant1/constant2?foo=bar}. * * The path will be stored without slashes at both ends. */ public Path(String path) { this.path = removeSlashesAtBothEnds(ObjectUtil.checkNotNull(path, "path")); this.tokens = this.path.split("/"); } /** Returns the path given at the constructor, without slashes at both ends. */ public String path() { return path; } /** * Returns the path given at the constructor, without slashes at both ends, * and split by {@code '/'}. */ public String[] tokens() { return tokens; } //-------------------------------------------------------------------------- // Need these so that Paths can be conveniently used as Map keys. @Override public int hashCode() { return path.hashCode(); } @Override public boolean equals(Object o) { if (o == null) { return false; } return ((Path) o).path.equals(path); } //-------------------------------------------------------------------------- /** * {@code params} will be updated with params embedded in the path. * * This method signature is designed so that {@code pathTokens} and {@code params} * can be created only once then reused, to optimize for performance when a * large number of paths need to be matched. * * @return {@code false} if not matched; in this case params should be reset */ public boolean match(String[] requestPathTokens, Map<String, String> params) { if (tokens.length == requestPathTokens.length) { for (int i = 0; i < tokens.length; i++) { String key = tokens[i]; String value = requestPathTokens[i]; if (key.length() > 0 && key.charAt(0) == ':') { // This is a placeholder params.put(key.substring(1), value); } else if (!key.equals(value)) { // This is a constant return false; } } return true; } if (tokens.length > 0 && tokens[tokens.length - 1].equals(":*") && tokens.length <= requestPathTokens.length) { // The first part for (int i = 0; i < tokens.length - 2; i++) { String key = tokens[i]; String value = requestPathTokens[i]; if (key.length() > 0 && key.charAt(0) == ':') { // This is a placeholder params.put(key.substring(1), value); } else if (!key.equals(value)) { // This is a constant return false; } } // The last :* part StringBuilder b = new StringBuilder(requestPathTokens[tokens.length - 1]); for (int i = tokens.length; i < requestPathTokens.length; i++) { b.append('/'); b.append(requestPathTokens[i]); } params.put("*", b.toString()); return true; } return false; } }