blob: 2f65d5f5f33637fb4ae0419a0e215bce724a3a7d [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF 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 org.apache.juneau.rest.util;
import static org.apache.juneau.internal.StringUtils.*;
import java.util.*;
import java.util.regex.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.rest.annotation.*;
/**
* A parsed path pattern constructed from a {@link RestMethod#path() @RestMethod(path)} value.
*
* <p>
* Handles aspects of matching and precedence ordering.
*/
@BeanIgnore
public final class UrlPathPattern implements Comparable<UrlPathPattern> {
private static final Pattern VAR_PATTERN = Pattern.compile("\\{([^\\}]+)\\}");
private final String pattern, comparator;
private final String[] parts, vars, varKeys;
private final boolean hasRemainder;
/**
* Constructor.
*
* @param patternString The raw pattern string from the {@link RestMethod#path() @RestMethod(path)} annotation.
*/
public UrlPathPattern(String patternString) {
this.pattern = isEmpty(patternString) ? "/" : patternString.charAt(0) != '/' ? '/' + patternString : patternString;
String c = patternString.replaceAll("\\{[^\\}]+\\}", ".").replaceAll("\\w+", "X").replaceAll("\\.", "W");
if (c.isEmpty())
c = "+";
if (! c.endsWith("/*"))
c = c + "/W";
this.comparator = c;
String[] parts = new UrlPathInfo(pattern).getParts();
this.hasRemainder = parts.length > 0 && "*".equals(parts[parts.length-1]);
parts = hasRemainder ? Arrays.copyOf(parts, parts.length-1) : parts;
this.parts = parts;
this.vars = new String[parts.length];
List<String> vars = new ArrayList<>();
for (int i = 0; i < parts.length; i++) {
Matcher m = VAR_PATTERN.matcher(parts[i]);
if (m.matches()) {
this.vars[i] = m.group(1);
vars.add(this.vars[i]);
}
}
this.varKeys = vars.isEmpty() ? null : vars.toArray(new String[vars.size()]);
}
/**
* Returns a non-<jk>null</jk> value if the specified path matches this pattern.
*
* @param path The path to match against.
* @return
* A pattern match object, or <jk>null</jk> if the path didn't match this pattern.
*/
public UrlPathPatternMatch match(String path) {
return match(new UrlPathInfo(path));
}
/**
* Returns a non-<jk>null</jk> value if the specified path matches this pattern.
*
* @param pathInfo The path to match against.
* @return
* A pattern match object, or <jk>null</jk> if the path didn't match this pattern.
*/
public UrlPathPatternMatch match(UrlPathInfo pathInfo) {
String[] pip = pathInfo.getParts();
if (parts.length != pip.length) {
if (hasRemainder) {
if (pip.length == parts.length - 1 && ! pathInfo.isTrailingSlash())
return null;
else if (pip.length < parts.length)
return null;
} else {
if (pip.length != parts.length + 1)
return null;
if (! pathInfo.isTrailingSlash())
return null;
}
}
for (int i = 0; i < parts.length; i++)
if (vars[i] == null && (pip.length <= i || ! ("*".equals(parts[i]) || pip[i].equals(parts[i]))))
return null;
String[] vals = varKeys == null ? null : new String[varKeys.length];
int j = 0;
if (vals != null)
for (int i = 0; i < parts.length; i++)
if (vars[i] != null)
vals[j++] = pip[i];
return new UrlPathPatternMatch(pathInfo.getPath(), parts.length, varKeys, vals);
}
/**
* Returns the variable names found in the pattern.
*
* @return
* The variable names or an empty array if no variables found.
* <br>Modifying the returned array does not modify this object.
*/
public String[] getVars() {
return varKeys == null ? new String[0] : Arrays.copyOf(varKeys, varKeys.length);
}
/**
* Returns <jk>true</jk> if this path pattern contains variables.
*
* @return <jk>true</jk> if this path pattern contains variables.
*/
public boolean hasVars() {
return varKeys != null;
}
/**
* Comparator for this object.
*
* <p>
* The comparator is designed to order URL pattern from most-specific to least-specific.
* For example, the following patterns would be ordered as follows:
* <ol>
* <li><c>/foo/bar</c>
* <li><c>/foo/bar/*</c>
* <li><c>/foo/{id}/bar</c>
* <li><c>/foo/{id}/bar/*</c>
* <li><c>/foo/{id}</c>
* <li><c>/foo/{id}/*</c>
* <li><c>/foo</c>
* <li><c>/foo/*</c>
* </ol>
*/
@Override /* Comparable */
public int compareTo(UrlPathPattern o) {
return o.comparator.compareTo(comparator);
}
@Override /* Object */
public String toString() {
return pattern.toString();
}
}