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 for (String key : complexPaths)
227 {
228 if (match(key, path, false))
229 {
230 return key;
231 }
232 }
233 return null;
234 }
235
236
237 Collection<String> findComplexKeys(final String path, final Set<String> complexPaths)
238 {
239 final List<String> matches = new ArrayList<String>();
240 for (String key : complexPaths)
241 {
242 if (match(key, path, false))
243 {
244 matches.add(key);
245 }
246 }
247 return matches;
248 }
249
250
251 String findDefaultKey(final Map<String, Collection<String>> mappings)
252 {
253 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++)
254 {
255 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i]))
256 {
257 return DefaultPathMapper.DEFAULT_KEYS[i];
258 }
259 }
260 return null;
261 }
262
263
264 Collection<String> findDefaultKeys(final Map<String, Collection<String>> mappings)
265 {
266 final List<String> matches = new ArrayList<String>();
267
268 for (int i = 0; i < DefaultPathMapper.DEFAULT_KEYS.length; i++)
269 {
270 if (mappings.containsKey(DefaultPathMapper.DEFAULT_KEYS[i]))
271 {
272 matches.add(DefaultPathMapper.DEFAULT_KEYS[i]);
273 }
274 }
275
276 return matches;
277 }
278
279 boolean match(final String pattern, final String str, final boolean isCaseSensitive)
280 {
281 final char[] patArr = pattern.toCharArray();
282 final char[] strArr = str.toCharArray();
283 int patIdxStart = 0;
284 int patIdxEnd = patArr.length - 1;
285 int strIdxStart = 0;
286 int strIdxEnd = strArr.length - 1;
287 char ch;
288
289 boolean containsStar = false;
290 for (int i = 0; i < patArr.length; i++)
291 {
292 if (patArr[i] == '*')
293 {
294 containsStar = true;
295 break;
296 }
297 }
298
299 if (!containsStar)
300 {
301
302 if (patIdxEnd != strIdxEnd)
303 {
304 return false;
305 }
306 for (int i = 0; i <= patIdxEnd; i++)
307 {
308 ch = patArr[i];
309 if (ch != '?')
310 {
311 if (isCaseSensitive && (ch != strArr[i]))
312 {
313 return false;
314 }
315 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[i])))
316 {
317 return false;
318 }
319 }
320 }
321 return true;
322 }
323
324 if (patIdxEnd == 0)
325 {
326 return true;
327 }
328
329
330 while (((ch = patArr[patIdxStart]) != '*') && (strIdxStart <= strIdxEnd))
331 {
332 if (ch != '?')
333 {
334 if (isCaseSensitive && (ch != strArr[strIdxStart]))
335 {
336 return false;
337 }
338 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart])))
339 {
340 return false;
341 }
342 }
343 patIdxStart++;
344 strIdxStart++;
345 }
346 if (strIdxStart > strIdxEnd)
347 {
348
349
350 for (int i = patIdxStart; i <= patIdxEnd; i++)
351 {
352 if (patArr[i] != '*')
353 {
354 return false;
355 }
356 }
357 return true;
358 }
359
360
361 while (((ch = patArr[patIdxEnd]) != '*') && (strIdxStart <= strIdxEnd))
362 {
363 if (ch != '?')
364 {
365 if (isCaseSensitive && (ch != strArr[strIdxEnd]))
366 {
367 return false;
368 }
369 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxEnd])))
370 {
371 return false;
372 }
373 }
374 patIdxEnd--;
375 strIdxEnd--;
376 }
377 if (strIdxStart > strIdxEnd)
378 {
379
380
381 for (int i = patIdxStart; i <= patIdxEnd; i++)
382 {
383 if (patArr[i] != '*')
384 {
385 return false;
386 }
387 }
388 return true;
389 }
390
391
392
393 while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd))
394 {
395 int patIdxTmp = -1;
396 for (int i = patIdxStart + 1; i <= patIdxEnd; i++)
397 {
398 if (patArr[i] == '*')
399 {
400 patIdxTmp = i;
401 break;
402 }
403 }
404 if (patIdxTmp == patIdxStart + 1)
405 {
406
407 patIdxStart++;
408 continue;
409 }
410
411
412 final int patLength = (patIdxTmp - patIdxStart - 1);
413 final int strLength = (strIdxEnd - strIdxStart + 1);
414 int foundIdx = -1;
415 strLoop : for (int i = 0; i <= strLength - patLength; i++)
416 {
417 for (int j = 0; j < patLength; j++)
418 {
419 ch = patArr[patIdxStart + j + 1];
420 if (ch != '?')
421 {
422 if (isCaseSensitive && (ch != strArr[strIdxStart + i + j]))
423 {
424 continue strLoop;
425 }
426 if (!isCaseSensitive && (Character.toUpperCase(ch) != Character.toUpperCase(strArr[strIdxStart + i + j])))
427 {
428 continue strLoop;
429 }
430 }
431 }
432
433 foundIdx = strIdxStart + i;
434 break;
435 }
436
437 if (foundIdx == -1)
438 {
439 return false;
440 }
441
442 patIdxStart = patIdxTmp;
443 strIdxStart = foundIdx + patLength;
444 }
445
446
447
448 for (int i = patIdxStart; i <= patIdxEnd; i++)
449 {
450 if (patArr[i] != '*')
451 {
452 return false;
453 }
454 }
455 return true;
456 }
457 }
458 }