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