View Javadoc

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