|
1 package de.uapcore.lightpit; |
|
2 |
|
3 import java.util.ArrayList; |
|
4 import java.util.List; |
|
5 |
|
6 public final class PathPattern { |
|
7 |
|
8 private final List<String> nodePatterns; |
|
9 private final boolean collection; |
|
10 |
|
11 /** |
|
12 * Constructs a new path pattern. |
|
13 * The special directories . and .. are disallowed in the pattern. |
|
14 * |
|
15 * @param pattern |
|
16 */ |
|
17 public PathPattern(String pattern) { |
|
18 nodePatterns = parse(pattern); |
|
19 collection = pattern.endsWith("/"); |
|
20 } |
|
21 |
|
22 private List<String> parse(String pattern) { |
|
23 |
|
24 var nodes = new ArrayList<String>(); |
|
25 var parts = pattern.split("/"); |
|
26 |
|
27 for (var part : parts) { |
|
28 if (part.isBlank()) continue; |
|
29 if (part.equals(".") || part.equals("..")) |
|
30 throw new IllegalArgumentException("Path must not contain '.' or '..' nodes."); |
|
31 nodes.add(part); |
|
32 } |
|
33 |
|
34 return nodes; |
|
35 } |
|
36 |
|
37 /** |
|
38 * Matches a path against this pattern. |
|
39 * The path must be canonical in the sense that no . or .. parts occur. |
|
40 * |
|
41 * @param path the path to match |
|
42 * @return true if the path matches the pattern, false otherwise |
|
43 */ |
|
44 public boolean matches(String path) { |
|
45 if (collection ^ path.endsWith("/")) |
|
46 return false; |
|
47 |
|
48 var nodes = parse(path); |
|
49 if (nodePatterns.size() != nodes.size()) |
|
50 return false; |
|
51 |
|
52 for (int i = 0 ; i < nodePatterns.size() ; i++) { |
|
53 var pattern = nodePatterns.get(i); |
|
54 var node = nodes.get(i); |
|
55 if (pattern.startsWith("$")) |
|
56 continue; |
|
57 if (!pattern.equals(node)) |
|
58 return false; |
|
59 } |
|
60 |
|
61 return true; |
|
62 } |
|
63 |
|
64 /** |
|
65 * Returns the path parameters found in the specified path using this pattern. |
|
66 * The return value of this method is undefined, if the patter does not match. |
|
67 * |
|
68 * @param path the path |
|
69 * @return the path parameters, if any, or an empty map |
|
70 * @see #matches(String) |
|
71 */ |
|
72 public PathParameters obtainPathParameters(String path) { |
|
73 var params = new PathParameters(); |
|
74 |
|
75 var nodes = parse(path); |
|
76 |
|
77 for (int i = 0 ; i < Math.min(nodes.size(), nodePatterns.size()) ; i++) { |
|
78 var pattern = nodePatterns.get(i); |
|
79 var node = nodes.get(i); |
|
80 if (pattern.startsWith("$")) { |
|
81 params.put(pattern.substring(1), node); |
|
82 } |
|
83 } |
|
84 |
|
85 return params; |
|
86 } |
|
87 |
|
88 @Override |
|
89 public int hashCode() { |
|
90 var str = new StringBuilder(); |
|
91 for (var node : nodePatterns) { |
|
92 if (node.startsWith("$")) { |
|
93 str.append("/$"); |
|
94 } else { |
|
95 str.append('/'); |
|
96 str.append(node); |
|
97 } |
|
98 } |
|
99 if (collection) |
|
100 str.append('/'); |
|
101 |
|
102 return str.toString().hashCode(); |
|
103 } |
|
104 |
|
105 @Override |
|
106 public boolean equals(Object obj) { |
|
107 if (!obj.getClass().equals(PathPattern.class)) |
|
108 return false; |
|
109 |
|
110 var other = (PathPattern) obj; |
|
111 if (collection ^ other.collection || nodePatterns.size() != other.nodePatterns.size()) |
|
112 return false; |
|
113 |
|
114 for (int i = 0 ; i < nodePatterns.size() ; i++) { |
|
115 var left = nodePatterns.get(i); |
|
116 var right = other.nodePatterns.get(i); |
|
117 if (left.startsWith("$") && right.startsWith("$")) |
|
118 continue; |
|
119 if (!left.equals(right)) |
|
120 return false; |
|
121 } |
|
122 |
|
123 return true; |
|
124 } |
|
125 } |