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