1 package com.atlassian.plugin.servlet.util;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.Iterator;
10 import java.util.LinkedHashSet;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.concurrent.locks.ReadWriteLock;
15 import java.util.concurrent.locks.ReentrantReadWriteLock;
16 import java.util.regex.Pattern;
17
18
19
20
21
22
23
24
25
26
27
28
29 public class DefaultPathMapper implements Serializable, PathMapper {
30
31 private static final Pattern REDUNDANT_SLASHES = Pattern.compile("//+");
32 private static final String[] DEFAULT_KEYS = {"/", "*", "/*"};
33
34 private final Map<String, Collection<String>> mappings = new HashMap<>();
35 private final Set<String> complexPaths = new HashSet<>();
36
37 private final KeyMatcher matcher = new KeyMatcher();
38
39
40
41 private final ReadWriteLock lock = new ReentrantReadWriteLock();
42
43 public void put(final String key, final String pattern) {
44 lock.writeLock().lock();
45 try {
46 if (pattern == null) {
47 removeMappingsForKey(key);
48 return;
49 }
50 addMapping(pattern, key);
51 if ((pattern.indexOf('?') > -1) || (pattern.contains("*") && (pattern.length() > 1))) {
52 complexPaths.add(pattern);
53 }
54 } finally {
55 lock.writeLock().unlock();
56 }
57 }
58
59 private void addMapping(String pattern, String key) {
60 mappings.computeIfAbsent(pattern, k -> new LinkedHashSet<>()).add(key);
61 }
62
63
64 private void removeMappingsForKey(final String key) {
65 for (final Iterator<Map.Entry<String, Collection<String>>> it = mappings.entrySet().iterator(); it.hasNext(); ) {
66 final Map.Entry<String, Collection<String>> entry = it.next();
67 if (entry.getValue().remove(key) && entry.getValue().isEmpty()) {
68 complexPaths.remove(entry.getKey());
69 it.remove();
70 }
71 }
72 }
73
74 public String get(String path) {
75 path = removeRedundantSlashes(path);
76 lock.readLock().lock();
77 try {
78 if (path == null) {
79 path = "/";
80 }
81 final String mapped = matcher.findKey(path, mappings, complexPaths);
82 if (mapped == null) {
83 return null;
84 }
85 Collection<String> keys = mappings.get(mapped);
86 if (keys.isEmpty()) {
87 return null;
88 }
89 return keys.iterator().next();
90 } finally {
91 lock.readLock().unlock();
92 }
93 }
94
95
96
97
98 public Collection<String> getAll(String path) {
99 path = removeRedundantSlashes(path);
100 lock.readLock().lock();
101 try {
102 if (path == null) {
103 path = "/";
104 }
105 final Set<String> matches = new LinkedHashSet<>();
106
107 final String exactKey = matcher.findExactKey(path, mappings);
108 if (exactKey != null) {
109 matches.addAll(mappings.get(exactKey));
110 }
111
112 for (final String mapped : matcher.findComplexKeys(path, complexPaths)) {
113 if (mappings.containsKey(mapped)) {
114 matches.addAll(mappings.get(mapped));
115 }
116 }
117
118 for (final String mapped : matcher.findDefaultKeys(mappings)) {
119 matches.addAll(mappings.get(mapped));
120 }
121 return Collections.unmodifiableCollection(matches);
122 } finally {
123 lock.readLock().unlock();
124 }
125 }
126
127
128
129
130
131
132
133
134
135 protected String removeRedundantSlashes(final String path) {
136 return path == null ? null : REDUNDANT_SLASHES.matcher(path).replaceAll("/");
137 }
138
139 public String toString() {
140 final StringBuilder sb = new StringBuilder(30 * (mappings.size() + complexPaths.size()));
141 sb.append("Mappings:\n");
142 for (final String key : mappings.keySet()) {
143 sb.append(key).append("=").append(mappings.get(key)).append("\n");
144 }
145 sb.append("Complex Paths:\n");
146 for (final String path : complexPaths) {
147 sb.append(path).append("\n");
148 }
149
150 return sb.toString();
151 }
152
153 private final class KeyMatcher {
154
155
156
157 String findKey(final String path, final Map<String, Collection<String>> mappings, final Set<String> keys) {
158 String result = findExactKey(path, mappings);
159 if (result == null) {
160 result = findComplexKey(path, keys);
161 }
162 if (result == null) {
163 result = findDefaultKey(mappings);
164 }
165 return result;
166 }
167
168
169
170
171 String findExactKey(final String path, final Map<String, Collection<String>> mappings) {
172 if (mappings.containsKey(path)) {
173 return path;
174 }
175 return null;
176 }
177
178
179
180
181 String findComplexKey(final String path, final Set<String> complexPaths) {
182
183
184 String matchedKey = null;
185 int keyLength = 0;
186 for (String key : complexPaths) {
187 if (match(key, path, false)) {
188 if (key.length() > keyLength) {
189 keyLength = key.length();
190 matchedKey = key;
191 }
192 }
193 }
194 return matchedKey;
195 }
196
197
198
199
200 Collection<String> findComplexKeys(final String path, final Set<String> complexPaths) {
201 final List<String> matches = new ArrayList<>();
202 for (String key : complexPaths) {
203 if (match(key, path, false)) {
204 matches.add(key);
205 }
206 }
207 return matches;
208 }
209
210
211
212
213 String findDefaultKey(final Map<String, Collection<String>> mappings) {
214 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++) {
215 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i])) {
216 return DefaultPathMapper.DEFAULT_KEYS[i];
217 }
218 }
219 return null;
220 }
221
222
223
224
225 Collection<String> findDefaultKeys(final Map<String, Collection<String>> mappings) {
226 final List<String> matches = new ArrayList<>();
227
228 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++) {
229 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i])) {
230 matches.add(DefaultPathMapper.DEFAULT_KEYS[i]);
231 }
232 }
233
234 return matches;
235 }
236
237 boolean match(final String pattern, final String str, final boolean isCaseSensitive) {
238 final char[] patArr = pattern.toCharArray();
239 final char[] strArr = str.toCharArray();
240 int patIdxStart = 0;
241 int patIdxEnd = patArr.length - 1;
242 int strIdxStart = 0;
243 int strIdxEnd = strArr.length - 1;
244 char ch;
245
246 boolean containsStar = false;
247 for (int i = 0; i < patArr.length; i++) {
248 if (patArr[i] == '*') {
249 containsStar = true;
250 break;
251 }
252 }
253
254 if (!containsStar) {
255
256 if (patIdxEnd != strIdxEnd) {
257 return false;
258 }
259 for (int i = 0; i <= patIdxEnd; i++) {
260 ch = patArr[i];
261 if (ch != '?') {
262 if (isCaseSensitive && (ch != strArr[i])) {
263 return false;
264 }
265 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[i]))) {
266 return false;
267 }
268 }
269 }
270 return true;
271 }
272
273 if (patIdxEnd == 0) {
274 return true;
275 }
276
277
278 while (((ch = patArr[patIdxStart]) != '*') && (strIdxStart <= strIdxEnd)) {
279 if (ch != '?') {
280 if (isCaseSensitive && (ch != strArr[strIdxStart])) {
281 return false;
282 }
283 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart]))) {
284 return false;
285 }
286 }
287 patIdxStart++;
288 strIdxStart++;
289 }
290 if (strIdxStart > strIdxEnd) {
291
292
293 for (int i = patIdxStart; i <= patIdxEnd; i++) {
294 if (patArr[i] != '*') {
295 return false;
296 }
297 }
298 return true;
299 }
300
301
302 while (((ch = patArr[patIdxEnd]) != '*') && (strIdxStart <= strIdxEnd)) {
303 if (ch != '?') {
304 if (isCaseSensitive && (ch != strArr[strIdxEnd])) {
305 return false;
306 }
307 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxEnd]))) {
308 return false;
309 }
310 }
311 patIdxEnd--;
312 strIdxEnd--;
313 }
314 if (strIdxStart > strIdxEnd) {
315
316
317 for (int i = patIdxStart; i <= patIdxEnd; i++) {
318 if (patArr[i] != '*') {
319 return false;
320 }
321 }
322 return true;
323 }
324
325
326
327 while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) {
328 int patIdxTmp = -1;
329 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
330 if (patArr[i] == '*') {
331 patIdxTmp = i;
332 break;
333 }
334 }
335 if (patIdxTmp == patIdxStart + 1) {
336
337 patIdxStart++;
338 continue;
339 }
340
341
342 final int patLength = (patIdxTmp - patIdxStart - 1);
343 final int strLength = (strIdxEnd - strIdxStart + 1);
344 int foundIdx = -1;
345 strLoop:
346 for (int i = 0; i <= strLength - patLength; i++) {
347 for (int j = 0; j < patLength; j++) {
348 ch = patArr[patIdxStart + j + 1];
349 if (ch != '?') {
350 if (isCaseSensitive && (ch != strArr[strIdxStart + i + j])) {
351 continue strLoop;
352 }
353 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart + i + j]))) {
354 continue strLoop;
355 }
356 }
357 }
358
359 foundIdx = strIdxStart + i;
360 break;
361 }
362
363 if (foundIdx == -1) {
364 return false;
365 }
366
367 patIdxStart = patIdxTmp;
368 strIdxStart = foundIdx + patLength;
369 }
370
371
372
373 for (int i = patIdxStart; i <= patIdxEnd; i++) {
374 if (patArr[i] != '*') {
375 return false;
376 }
377 }
378 return true;
379 }
380 }
381 }