src/main/java/de/uapcore/lightpit/PathPattern.java

changeset 130
7ef369744fd1
--- /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;
+    }
+}

mercurial