1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/main/java/de/uapcore/lightpit/PathPattern.java Thu Oct 15 18:36:05 2020 +0200 1.3 @@ -0,0 +1,125 @@ 1.4 +package de.uapcore.lightpit; 1.5 + 1.6 +import java.util.ArrayList; 1.7 +import java.util.List; 1.8 + 1.9 +public final class PathPattern { 1.10 + 1.11 + private final List<String> nodePatterns; 1.12 + private final boolean collection; 1.13 + 1.14 + /** 1.15 + * Constructs a new path pattern. 1.16 + * The special directories . and .. are disallowed in the pattern. 1.17 + * 1.18 + * @param pattern 1.19 + */ 1.20 + public PathPattern(String pattern) { 1.21 + nodePatterns = parse(pattern); 1.22 + collection = pattern.endsWith("/"); 1.23 + } 1.24 + 1.25 + private List<String> parse(String pattern) { 1.26 + 1.27 + var nodes = new ArrayList<String>(); 1.28 + var parts = pattern.split("/"); 1.29 + 1.30 + for (var part : parts) { 1.31 + if (part.isBlank()) continue; 1.32 + if (part.equals(".") || part.equals("..")) 1.33 + throw new IllegalArgumentException("Path must not contain '.' or '..' nodes."); 1.34 + nodes.add(part); 1.35 + } 1.36 + 1.37 + return nodes; 1.38 + } 1.39 + 1.40 + /** 1.41 + * Matches a path against this pattern. 1.42 + * The path must be canonical in the sense that no . or .. parts occur. 1.43 + * 1.44 + * @param path the path to match 1.45 + * @return true if the path matches the pattern, false otherwise 1.46 + */ 1.47 + public boolean matches(String path) { 1.48 + if (collection ^ path.endsWith("/")) 1.49 + return false; 1.50 + 1.51 + var nodes = parse(path); 1.52 + if (nodePatterns.size() != nodes.size()) 1.53 + return false; 1.54 + 1.55 + for (int i = 0 ; i < nodePatterns.size() ; i++) { 1.56 + var pattern = nodePatterns.get(i); 1.57 + var node = nodes.get(i); 1.58 + if (pattern.startsWith("$")) 1.59 + continue; 1.60 + if (!pattern.equals(node)) 1.61 + return false; 1.62 + } 1.63 + 1.64 + return true; 1.65 + } 1.66 + 1.67 + /** 1.68 + * Returns the path parameters found in the specified path using this pattern. 1.69 + * The return value of this method is undefined, if the patter does not match. 1.70 + * 1.71 + * @param path the path 1.72 + * @return the path parameters, if any, or an empty map 1.73 + * @see #matches(String) 1.74 + */ 1.75 + public PathParameters obtainPathParameters(String path) { 1.76 + var params = new PathParameters(); 1.77 + 1.78 + var nodes = parse(path); 1.79 + 1.80 + for (int i = 0 ; i < Math.min(nodes.size(), nodePatterns.size()) ; i++) { 1.81 + var pattern = nodePatterns.get(i); 1.82 + var node = nodes.get(i); 1.83 + if (pattern.startsWith("$")) { 1.84 + params.put(pattern.substring(1), node); 1.85 + } 1.86 + } 1.87 + 1.88 + return params; 1.89 + } 1.90 + 1.91 + @Override 1.92 + public int hashCode() { 1.93 + var str = new StringBuilder(); 1.94 + for (var node : nodePatterns) { 1.95 + if (node.startsWith("$")) { 1.96 + str.append("/$"); 1.97 + } else { 1.98 + str.append('/'); 1.99 + str.append(node); 1.100 + } 1.101 + } 1.102 + if (collection) 1.103 + str.append('/'); 1.104 + 1.105 + return str.toString().hashCode(); 1.106 + } 1.107 + 1.108 + @Override 1.109 + public boolean equals(Object obj) { 1.110 + if (!obj.getClass().equals(PathPattern.class)) 1.111 + return false; 1.112 + 1.113 + var other = (PathPattern) obj; 1.114 + if (collection ^ other.collection || nodePatterns.size() != other.nodePatterns.size()) 1.115 + return false; 1.116 + 1.117 + for (int i = 0 ; i < nodePatterns.size() ; i++) { 1.118 + var left = nodePatterns.get(i); 1.119 + var right = other.nodePatterns.get(i); 1.120 + if (left.startsWith("$") && right.startsWith("$")) 1.121 + continue; 1.122 + if (!left.equals(right)) 1.123 + return false; 1.124 + } 1.125 + 1.126 + return true; 1.127 + } 1.128 +}