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
32 private static final Pattern REDUNDANT_SLASHES = Pattern.compile("//+");
33 private static final String[] DEFAULT_KEYS = { "/", "*", "/*" };
34
35 private final Map<String, Collection<String>> mappings = new HashMap<String, Collection<String>>();
36 private final Set<String> complexPaths = new HashSet<String>();
37
38 private final KeyMatcher matcher = new KeyMatcher();
39
40
41
42 private final ReadWriteLock lock = new ReentrantReadWriteLock();
43
44 public void put(final String key, final String pattern)
45 {
46 lock.writeLock().lock();
47 try
48 {
49 if (pattern == null)
50 {
51 removeMappingsForKey(key);
52 return;
53 }
54 addMapping(pattern, key);
55 if ((pattern.indexOf('?') > -1) || ((pattern.indexOf("*") > -1) && (pattern.length() > 1)))
56 {
57 complexPaths.add(pattern);
58 }
59 }
60 finally
61 {
62 lock.writeLock().unlock();
63 }
64 }
65
66 private void addMapping(String pattern, String key)
67 {
68 Collection<String> keys = mappings.get(pattern);
69 if (keys == null)
70 {
71 keys = new LinkedHashSet<String>();
72 mappings.put(pattern, keys);
73 }
74 keys.add(key);
75 }
76
77
78 private void removeMappingsForKey(final String key)
79 {
80 for (final Iterator<Map.Entry<String, Collection<String>>> it = mappings.entrySet().iterator(); it.hasNext();)
81 {
82 final Map.Entry<String, Collection<String>> entry = it.next();
83 if (entry.getValue().remove(key) && entry.getValue().isEmpty())
84 {
85 complexPaths.remove(entry.getKey());
86 it.remove();
87 }
88 }
89 }
90
91 public String get(String path)
92 {
93 path = removeRedundantSlashes(path);
94 lock.readLock().lock();
95 try
96 {
97 if (path == null)
98 {
99 path = "/";
100 }
101 final String mapped = matcher.findKey(path, mappings, complexPaths);
102 if (mapped == null)
103 {
104 return null;
105 }
106 Collection<String> keys = mappings.get(mapped);
107 if (keys.isEmpty())
108 {
109 return null;
110 }
111 return keys.iterator().next();
112 }
113 finally
114 {
115 lock.readLock().unlock();
116 }
117 }
118
119
120
121
122 public Collection<String> getAll(String path)
123 {
124 path = removeRedundantSlashes(path);
125 lock.readLock().lock();
126 try
127 {
128 if (path == null)
129 {
130 path = "/";
131 }
132 final Set<String> matches = new LinkedHashSet<String>();
133
134 final String exactKey = matcher.findExactKey(path, mappings);
135 if (exactKey != null)
136 {
137 matches.addAll(mappings.get(exactKey));
138 }
139
140 for (final Iterator<String> iterator = matcher.findComplexKeys(path, complexPaths).iterator(); iterator.hasNext();)
141 {
142 final String mapped = iterator.next();
143 if (mappings.containsKey(mapped))
144 {
145 matches.addAll(mappings.get(mapped));
146 }
147 }
148
149 for (final Iterator<String> iterator = matcher.findDefaultKeys(mappings).iterator(); iterator.hasNext();)
150 {
151 final String mapped = iterator.next();
152 matches.addAll(mappings.get(mapped));
153 }
154 return Collections.unmodifiableCollection(matches);
155 }
156 finally
157 {
158 lock.readLock().unlock();
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171
172 protected String removeRedundantSlashes(final String path)
173 {
174 return path == null ? null : REDUNDANT_SLASHES.matcher(path).replaceAll("/");
175 }
176
177 public String toString()
178 {
179 final StringBuffer sb = new StringBuffer(30 * (mappings.size() + complexPaths.size()));
180 sb.append("Mappings:\n");
181 for (final Iterator<String> iterator = mappings.keySet().iterator(); iterator.hasNext();)
182 {
183 final String key = (String) iterator.next();
184 sb.append(key).append("=").append(mappings.get(key)).append("\n");
185 }
186 sb.append("Complex Paths:\n");
187 for (final Iterator<String> iterator = complexPaths.iterator(); iterator.hasNext();)
188 {
189 final String path = (String) iterator.next();
190 sb.append(path).append("\n");
191 }
192
193 return sb.toString();
194 }
195
196 private final class KeyMatcher
197 {
198
199 String findKey(final String path, final Map<String, Collection<String>> mappings, final Set<String> keys)
200 {
201 String result = findExactKey(path, mappings);
202 if (result == null)
203 {
204 result = findComplexKey(path, keys);
205 }
206 if (result == null)
207 {
208 result = findDefaultKey(mappings);
209 }
210 return result;
211 }
212
213
214 String findExactKey(final String path, final Map<String, Collection<String>> mappings)
215 {
216 if (mappings.containsKey(path))
217 {
218 return path;
219 }
220 return null;
221 }
222
223
224 String findComplexKey(final String path, final Set<String> complexPaths)
225 {
226
227
228 String matchedKey = null;
229 int keyLength = 0;
230 for (String key : complexPaths)
231 {
232 if (match(key, path, false))
233 {
234 if (key.length() > keyLength)
235 {
236 keyLength = key.length();
237 matchedKey = key;
238 }
239 }
240 }
241 return matchedKey;
242 }
243
244
245 Collection<String> findComplexKeys(final String path, final Set<String> complexPaths)
246 {
247 final List<String> matches = new ArrayList<String>();
248 for (String key : complexPaths)
249 {
250 if (match(key, path, false))
251 {
252 matches.add(key);
253 }
254 }
255 return matches;
256 }
257
258
259 String findDefaultKey(final Map<String, Collection<String>> mappings)
260 {
261 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++)
262 {
263 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i]))
264 {
265 return DefaultPathMapper.DEFAULT_KEYS[i];
266 }
267 }
268 return null;
269 }
270
271
272 Collection<String> findDefaultKeys(final Map<String, Collection<String>> mappings)
273 {
274 final List<String> matches = new ArrayList<String>();
275
276 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++)
277 {
278 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i]))
279 {
280 matches.add(DefaultPathMapper.DEFAULT_KEYS[i]);
281 }
282 }
283
284 return matches;
285 }
286
287 boolean match(final String pattern, final String str, final boolean isCaseSensitive)
288 {
289 final char[] patArr = pattern.toCharArray();
290 final char[] strArr = str.toCharArray();
291 int patIdxStart = 0;
292 int patIdxEnd = patArr.length - 1;
293 int strIdxStart = 0;
294 int strIdxEnd = strArr.length - 1;
295 char ch;
296
297 boolean containsStar = false;
298 for (int i = 0; i < patArr.length; i++)
299 {
300 if (patArr[i] == '*')
301 {
302 containsStar = true;
303 break;
304 }
305 }
306
307 if (!containsStar)
308 {
309
310 if (patIdxEnd != strIdxEnd)
311 {
312 return false;
313 }
314 for (int i = 0; i <= patIdxEnd; i++)
315 {
316 ch = patArr[i];
317 if (ch != '?')
318 {
319 if (isCaseSensitive && (ch != strArr[i]))
320 {
321 return false;
322 }
323 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[i])))
324 {
325 return false;
326 }
327 }
328 }
329 return true;
330 }
331
332 if (patIdxEnd == 0)
333 {
334 return true;
335 }
336
337
338 while (((ch = patArr[patIdxStart]) != '*') && (strIdxStart <= strIdxEnd))
339 {
340 if (ch != '?')
341 {
342 if (isCaseSensitive && (ch != strArr[strIdxStart]))
343 {
344 return false;
345 }
346 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart])))
347 {
348 return false;
349 }
350 }
351 patIdxStart++;
352 strIdxStart++;
353 }
354 if (strIdxStart > strIdxEnd)
355 {
356
357
358 for (int i = patIdxStart; i <= patIdxEnd; i++)
359 {
360 if (patArr[i] != '*')
361 {
362 return false;
363 }
364 }
365 return true;
366 }
367
368
369 while (((ch = patArr[patIdxEnd]) != '*') && (strIdxStart <= strIdxEnd))
370 {
371 if (ch != '?')
372 {
373 if (isCaseSensitive && (ch != strArr[strIdxEnd]))
374 {
375 return false;
376 }
377 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxEnd])))
378 {
379 return false;
380 }
381 }
382 patIdxEnd--;
383 strIdxEnd--;
384 }
385 if (strIdxStart > strIdxEnd)
386 {
387
388
389 for (int i = patIdxStart; i <= patIdxEnd; i++)
390 {
391 if (patArr[i] != '*')
392 {
393 return false;
394 }
395 }
396 return true;
397 }
398
399
400
401 while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd))
402 {
403 int patIdxTmp = -1;
404 for (int i = patIdxStart + 1; i <= patIdxEnd; i++)
405 {
406 if (patArr[i] == '*')
407 {
408 patIdxTmp = i;
409 break;
410 }
411 }
412 if (patIdxTmp == patIdxStart + 1)
413 {
414
415 patIdxStart++;
416 continue;
417 }
418
419
420 final int patLength = (patIdxTmp - patIdxStart - 1);
421 final int strLength = (strIdxEnd - strIdxStart + 1);
422 int foundIdx = -1;
423 strLoop : for (int i = 0; i <= strLength - patLength; i++)
424 {
425 for (int j = 0; j < patLength; j++)
426 {
427 ch = patArr[patIdxStart + j + 1];
428 if (ch != '?')
429 {
430 if (isCaseSensitive && (ch != strArr[strIdxStart + i + j]))
431 {
432 continue strLoop;
433 }
434 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart + i + j])))
435 {
436 continue strLoop;
437 }
438 }
439 }
440
441 foundIdx = strIdxStart + i;
442 break;
443 }
444
445 if (foundIdx == -1)
446 {
447 return false;
448 }
449
450 patIdxStart = patIdxTmp;
451 strIdxStart = foundIdx + patLength;
452 }
453
454
455
456 for (int i = patIdxStart; i <= patIdxEnd; i++)
457 {
458 if (patArr[i] != '*')
459 {
460 return false;
461 }
462 }
463 return true;
464 }
465 }
466 }