--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/PathPattern.java Thu Oct 15 18:36:05 2020 +0200 @@ -0,0 +1,125 @@ +package de.uapcore.lightpit; + +import java.util.ArrayList; +import java.util.List; + +public final class PathPattern { + + private final List<String> nodePatterns; + private final boolean collection; + + /** + * Constructs a new path pattern. + * The special directories . and .. are disallowed in the pattern. + * + * @param pattern + */ + public PathPattern(String pattern) { + nodePatterns = parse(pattern); + collection = pattern.endsWith("/"); + } + + private List<String> parse(String pattern) { + + var nodes = new ArrayList<String>(); + var parts = pattern.split("/"); + + for (var part : parts) { + if (part.isBlank()) continue; + if (part.equals(".") || part.equals("..")) + throw new IllegalArgumentException("Path must not contain '.' or '..' nodes."); + nodes.add(part); + } + + return nodes; + } + + /** + * Matches a path against this pattern. + * The path must be canonical in the sense that no . or .. parts occur. + * + * @param path the path to match + * @return true if the path matches the pattern, false otherwise + */ + public boolean matches(String path) { + if (collection ^ path.endsWith("/")) + return false; + + var nodes = parse(path); + if (nodePatterns.size() != nodes.size()) + return false; + + for (int i = 0 ; i < nodePatterns.size() ; i++) { + var pattern = nodePatterns.get(i); + var node = nodes.get(i); + if (pattern.startsWith("$")) + continue; + if (!pattern.equals(node)) + return false; + } + + return true; + } + + /** + * Returns the path parameters found in the specified path using this pattern. + * The return value of this method is undefined, if the patter does not match. + * + * @param path the path + * @return the path parameters, if any, or an empty map + * @see #matches(String) + */ + public PathParameters obtainPathParameters(String path) { + var params = new PathParameters(); + + var nodes = parse(path); + + for (int i = 0 ; i < Math.min(nodes.size(), nodePatterns.size()) ; i++) { + var pattern = nodePatterns.get(i); + var node = nodes.get(i); + if (pattern.startsWith("$")) { + params.put(pattern.substring(1), node); + } + } + + return params; + } + + @Override + public int hashCode() { + var str = new StringBuilder(); + for (var node : nodePatterns) { + if (node.startsWith("$")) { + str.append("/$"); + } else { + str.append('/'); + str.append(node); + } + } + if (collection) + str.append('/'); + + return str.toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!obj.getClass().equals(PathPattern.class)) + return false; + + var other = (PathPattern) obj; + if (collection ^ other.collection || nodePatterns.size() != other.nodePatterns.size()) + return false; + + for (int i = 0 ; i < nodePatterns.size() ; i++) { + var left = nodePatterns.get(i); + var right = other.nodePatterns.get(i); + if (left.startsWith("$") && right.startsWith("$")) + continue; + if (!left.equals(right)) + return false; + } + + return true; + } +}