// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.application; import java.net.URI; import java.util.regex.Matcher; import java.util.regex.Pattern; /** *
This class holds a regular expression designed so that it only matches certain {@link URI}s. The constructor of * this class accepts a simplified pattern string, and turns that into something that can be used to quickly match * against URIs. This class also implements {@link Comparable} in such a way that stricter patterns order before looser * patterns.
* *Here are some examples of ordering:
*http://host/path
evaluated before *://host/path
http://host/path
evaluated before http://*/path
http://a.host/path
evaluated before http://*.host/path
http://*.host/path
evaluated before http://host/path
http://host.a/path
evaluated before http://host.*/path
http://host.*/path
evaluated before http://host/path
http://host:80/path
evaluated before http://host:*/path
http://host/path
evaluated before http://host/*
http://host/path/*
evaluated before http://host/path
Creates a new instance of this class that represents the given pattern string, with a priority of 0
.
* The input string must be on the form <scheme>://<host>[:<port>]<path>
, where
* '*' can be used as a wildcard character at any position.
Creates a new instance of this class that represents the given pattern string, with the given priority. The
* input string must be on the form <scheme>://<host>[:<port>]<path>
, where
* '*' can be used as a wildcard character at any position.
Attempts to match the given {@link URI} to this pattern. Note that only the scheme, host, port, and path * components of the URI are used. Any query or fragment part is simply ignored.
* * @param uri The URI to match. * @return A {@link Match} object describing the match found, or null if not found. */ public Match match(URI uri) { // Performance optimization: Match path first since scheme and host are often the same in a given binding repository. String uriPath = resolveUriComponent(uri.getPath()); GlobPattern.Match pathMatch = path.match(uriPath, uriPath.startsWith("/") ? 1 : 0); if (pathMatch == null) { return null; } if (port > 0 && port != resolvePortComponent(uri)) { return null; } // Match scheme before host because it has a higher chance of differing (e.g. http versus https) GlobPattern.Match schemeMatch = scheme.match(resolveUriComponent(uri.getScheme())); if (schemeMatch == null) { return null; } GlobPattern.Match hostMatch = host.match(resolveUriComponent(uri.getHost())); if (hostMatch == null) { return null; } return new Match(schemeMatch, hostMatch, port > 0 ? 0 : uri.getPort(), pathMatch); } @Override public int hashCode() { return pattern.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof UriPattern && pattern.equals(((UriPattern)obj).pattern); } @Override public String toString() { return pattern; } @Override public int compareTo(UriPattern rhs) { int cmp; cmp = rhs.priority - priority; if (cmp != 0) { return cmp; } cmp = scheme.compareTo(rhs.scheme); if (cmp != 0) { return cmp; } cmp = host.compareTo(rhs.host); if (cmp != 0) { return cmp; } cmp = path.compareTo(rhs.path); if (cmp != 0) { return cmp; } cmp = rhs.port - port; if (cmp != 0) { return cmp; } return 0; } private static String resolveUriComponent(String str) { return str != null ? str : ""; } private static String resolvePatternComponent(String val) { return val != null ? val : "*"; } private static int resolvePortPattern(String str) { if (str == null || str.equals("*")) { return 0; } return Integer.parseInt(str); } private static int resolvePortComponent(URI uri) { int rawPort = uri.getPort(); return rawPort != -1 ? rawPort : resolvePortFromScheme(uri.getScheme()); } private static int resolvePortFromScheme(String scheme) { if (scheme == null) return -1; switch (scheme) { case "http": return 80; case "https": return 443; default: return -1; } } /** *This class holds the result of a {@link UriPattern#match(URI)} operation. It contains methods to inspect the * groups captured during matching, where a group is defined as a sequence of characters matches by a * wildcard in the {@link UriPattern}.
*/ public static class Match { private final GlobPattern.Match scheme; private final GlobPattern.Match host; private final int port; private final GlobPattern.Match path; private Match(GlobPattern.Match scheme, GlobPattern.Match host, int port, GlobPattern.Match path) { this.scheme = scheme; this.host = host; this.port = port; this.path = path; } /** *Returns the number of captured groups of this match. Any non-negative integer smaller than the value * returned by this method is a valid group index for this match.
* * @return The number of captured groups. */ public int groupCount() { return scheme.groupCount() + host.groupCount() + (port > 0 ? 1 : 0) + path.groupCount(); } /** *Returns the input subsequence captured by the given group by this match. Groups are indexed from left to * right, starting at zero. Note that some groups may match an empty string, in which case this method returns * the empty string. This method never returns null.
* * @param idx The index of the group to return. * @return The (possibly empty) substring captured by the group during matching, nevernull
.
* @throws IndexOutOfBoundsException If there is no group in the match with the given index.
*/
public String group(int idx) {
int len = scheme.groupCount();
if (idx < len) {
return scheme.group(idx);
}
idx = idx - len;
len = host.groupCount();
if (idx < len) {
return host.group(idx);
}
idx = idx - len;
len = port > 0 ? 1 : 0;
if (idx < len) {
return String.valueOf(port);
}
idx = idx - len;
return path.group(idx);
}
}
}