View Javadoc

1   /*
2    * Created by IntelliJ IDEA.
3    * User: Administrator
4    * Date: 19/02/2002
5    * Time: 11:29:22
6    * To change template for new class use
7    * Code Style | Class Templates options (Tools | IDE Options).
8    */
9   
10  /*
11   * Title:        PathMapper
12   * Description:
13   *
14   * This software is published under the terms of the OpenSymphony Software
15   * License version 1.1, of which a copy has been included with this
16   * distribution in the LICENSE.txt file.
17   */
18  package com.atlassian.seraph.util;
19  
20  import java.io.Serializable;
21  import java.util.*;
22  
23  /**
24   * The PathMapper is used to map file patterns to keys, and find an approriate
25   * key for a given file path. The pattern rules are consistent with those defined
26   * in the Servlet 2.3 API on the whole. Wildcard patterns are also supported, using
27   * any combination of * and ?.
28   *
29   * <h3>Example</h3>
30   *
31   * <blockquote><code>
32   * PathMapper pm = new PathMapper();<br>
33   * <br>
34   * pm.put("one","/");<br>
35   * pm.put("two","/mydir/*");<br>
36   * pm.put("three","*.xml");<br>
37   * pm.put("four","/myexactfile.html");<br>
38   * pm.put("five","/*\/admin/*.??ml");<br>
39   * <br>
40   * String result1 = pm.get("/mydir/myfile.xml"); // returns "two";<br>
41   * String result2 = pm.get("/mydir/otherdir/admin/myfile.html"); // returns "five";<br>
42   * </code></blockquote>
43   *
44   * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
45   * @author <a href="mailto:mike@atlassian.com">Mike Cannon-Brookes</a>
46   * @author <a href="mailto:hani@formicary.net">Hani Suleiman</a>
47   * @version $Revision: 654 $
48   */
49  public class PathMapper implements Serializable
50  {
51      private Map mappings = new HashMap();
52      private List complexPaths = new ArrayList();
53      static String[] DEFAULT_KEYS = {"/", "*", "/*"};
54  
55      /** Add a key and appropriate matching pattern. */
56      public void put(String key, String pattern)
57      {
58          if (pattern == null)
59          {
60              removeMappingsForKey(key);
61              return;
62          }
63  
64          mappings.put(pattern, key);
65          if (pattern.indexOf('?') > -1 || (pattern.indexOf("*") > -1 && pattern.length() > 1))
66          {
67              complexPaths.add(pattern);
68          }
69      }
70  
71      private void removeMappingsForKey(String key)
72      {
73          for (Iterator it = mappings.entrySet().iterator(); it.hasNext();)
74          {
75              Map.Entry entry = (Map.Entry) it.next();
76              if (entry.getValue().equals(key))
77              {
78                  complexPaths.remove(entry.getKey());
79                  it.remove();
80              }
81          }
82      }
83  
84  
85      /** Retrieve appropriate key by matching patterns with supplied path. */
86      public String get(String path)
87      {
88          if (path == null) path = "/";
89          String mapped = findKey(path, mappings, complexPaths);
90          if (mapped == null) return null;
91          return (String) mappings.get(mapped);
92      }
93  
94  
95      /** Retrieve all mappings which match a supplied path. */
96      public Collection getAll(String path)
97      {
98          if (path == null) path = "/";
99  
100         List matches = new ArrayList();
101 
102         // find exact keys
103         String exactKey = findExactKey(path, mappings);
104 
105         if (exactKey != null)
106             matches.add(mappings.get(exactKey));
107 
108         // find complex keys
109         for (Iterator iterator = findComplexKeys(path, complexPaths).iterator(); iterator.hasNext();)
110         {
111             String mapped = (String) iterator.next();
112             matches.add(mappings.get(mapped));
113         }
114 
115         // find default keys
116         for (Iterator iterator = findDefaultKeys(mappings).iterator(); iterator.hasNext();)
117         {
118             String mapped = (String) iterator.next();
119             matches.add(mappings.get(mapped));
120         }
121 
122         return matches;
123     }
124 
125 
126     /** Find exact key in mappings. */
127     private static String findKey(String path, Map mappings, List keys)
128     {
129         String result = findExactKey(path, mappings);
130         if (result == null) result = findComplexKey(path, keys);
131         if (result == null) result = findDefaultKey(mappings);
132         return result;
133     }
134 
135 
136     /** Check if path matches exact pattern ( /blah/blah.jsp ). */
137     private static String findExactKey(String path, Map mappings)
138     {
139         if (mappings.containsKey(path)) return path;
140         return null;
141     }
142 
143 
144     /** Find single matching complex key */
145     private static String findComplexKey(String path, List complexPaths)
146     {
147         int size = complexPaths.size();
148         for (int i = 0; i < size; i++)
149         {
150             String key = (String) complexPaths.get(i);
151             if (match(key, path, false))
152             {
153                 return key;
154             }
155         }
156         return null;
157     }
158 
159     /** Find all matching complex keys */
160     private static Collection findComplexKeys(String path, List complexPaths)
161     {
162         int size = complexPaths.size();
163         List matches = new ArrayList();
164         for (int i = 0; i < size; i++)
165         {
166             String key = (String) complexPaths.get(i);
167             if (match(key, path, false))
168             {
169                 matches.add(key);
170             }
171         }
172         return matches;
173     }
174 
175     /** Look for root pattern ( / ). */
176     private static String findDefaultKey(Map mappings)
177     {
178         for (int i = 0; i < DEFAULT_KEYS.length; i++)
179         {
180             if (mappings.containsKey(DEFAULT_KEYS[i])) return DEFAULT_KEYS[i];
181         }
182         return null;
183     }
184 
185     /** Look for root patterns ( / ). */
186     private static Collection findDefaultKeys(Map mappings)
187     {
188         List matches = new ArrayList();
189 
190         for (int i = 0; i < DEFAULT_KEYS.length; i++)
191         {
192             if (mappings.containsKey(DEFAULT_KEYS[i]))
193                 matches.add(DEFAULT_KEYS[i]);
194         }
195 
196         return matches;
197     }
198 
199 
200     private static boolean match(String pattern, String str, boolean isCaseSensitive)
201     {
202         char[] patArr = pattern.toCharArray();
203         char[] strArr = str.toCharArray();
204         int patIdxStart = 0;
205         int patIdxEnd = patArr.length - 1;
206         int strIdxStart = 0;
207         int strIdxEnd = strArr.length - 1;
208         char ch;
209 
210         boolean containsStar = false;
211         for (int i = 0; i < patArr.length; i++)
212         {
213             if (patArr[i] == '*')
214             {
215                 containsStar = true;
216                 break;
217             }
218         }
219 
220         if (!containsStar)
221         {
222             // No '*'s, so we make a shortcut
223             if (patIdxEnd != strIdxEnd)
224             {
225                 return false; // Pattern and string do not have the same size
226             }
227             for (int i = 0; i <= patIdxEnd; i++)
228             {
229                 ch = patArr[i];
230                 if (ch != '?')
231                 {
232                     if (isCaseSensitive && ch != strArr[i])
233                     {
234                         return false;// Character mismatch
235                     }
236                     if (!isCaseSensitive && Character.toUpperCase(ch) !=
237                             Character.toUpperCase(strArr[i]))
238                     {
239                         return false; // Character mismatch
240                     }
241                 }
242             }
243             return true; // String matches against pattern
244         }
245 
246         if (patIdxEnd == 0)
247         {
248             return true; // Pattern contains only '*', which matches anything
249         }
250 
251         // Process characters before first star
252         while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd)
253         {
254             if (ch != '?')
255             {
256                 if (isCaseSensitive && ch != strArr[strIdxStart])
257                 {
258                     return false;// Character mismatch
259                 }
260                 if (!isCaseSensitive && Character.toUpperCase(ch) !=
261                         Character.toUpperCase(strArr[strIdxStart]))
262                 {
263                     return false;// Character mismatch
264                 }
265             }
266             patIdxStart++;
267             strIdxStart++;
268         }
269         if (strIdxStart > strIdxEnd)
270         {
271             // All characters in the string are used. Check if only '*'s are
272             // left in the pattern. If so, we succeeded. Otherwise failure.
273             for (int i = patIdxStart; i <= patIdxEnd; i++)
274             {
275                 if (patArr[i] != '*')
276                 {
277                     return false;
278                 }
279             }
280             return true;
281         }
282 
283         // Process characters after last star
284         while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd)
285         {
286             if (ch != '?')
287             {
288                 if (isCaseSensitive && ch != strArr[strIdxEnd])
289                 {
290                     return false;// Character mismatch
291                 }
292                 if (!isCaseSensitive && Character.toUpperCase(ch) !=
293                         Character.toUpperCase(strArr[strIdxEnd]))
294                 {
295                     return false;// Character mismatch
296                 }
297             }
298             patIdxEnd--;
299             strIdxEnd--;
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 pattern between stars. padIdxStart and patIdxEnd point
316         // always to a '*'.
317         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd)
318         {
319             int patIdxTmp = -1;
320             for (int i = patIdxStart + 1; i <= patIdxEnd; i++)
321             {
322                 if (patArr[i] == '*')
323                 {
324                     patIdxTmp = i;
325                     break;
326                 }
327             }
328             if (patIdxTmp == patIdxStart + 1)
329             {
330                 // Two stars next to each other, skip the first one.
331                 patIdxStart++;
332                 continue;
333             }
334             // Find the pattern between padIdxStart & padIdxTmp in str between
335             // strIdxStart & strIdxEnd
336             int patLength = (patIdxTmp - patIdxStart - 1);
337             int strLength = (strIdxEnd - strIdxStart + 1);
338             int foundIdx = -1;
339             strLoop:
340             for (int i = 0; i <= strLength - patLength; i++)
341             {
342                 for (int j = 0; j < patLength; j++)
343                 {
344                     ch = patArr[patIdxStart + j + 1];
345                     if (ch != '?')
346                     {
347                         if (isCaseSensitive && ch != strArr[strIdxStart + i + j])
348                         {
349                             continue strLoop;
350                         }
351                         if (!isCaseSensitive && Character.toUpperCase(ch) !=
352                                 Character.toUpperCase(strArr[strIdxStart + i + j]))
353                         {
354                             continue strLoop;
355                         }
356                     }
357                 }
358 
359                 foundIdx = strIdxStart + i;
360                 break;
361             }
362 
363             if (foundIdx == -1)
364             {
365                 return false;
366             }
367 
368             patIdxStart = patIdxTmp;
369             strIdxStart = foundIdx + patLength;
370         }
371 
372         // All characters in the string are used. Check if only '*'s are left
373         // in the pattern. If so, we succeeded. Otherwise failure.
374         for (int i = patIdxStart; i <= patIdxEnd; i++)
375         {
376             if (patArr[i] != '*')
377             {
378                 return false;
379             }
380         }
381         return true;
382     }
383 
384     public String toString()
385     {
386         StringBuffer sb = new StringBuffer();
387         sb.append("Mappings:\n");
388         for (Iterator iterator = mappings.keySet().iterator(); iterator.hasNext();)
389         {
390             String key = (String) iterator.next();
391             sb.append(key + "=" + (String) mappings.get(key) + "\n");
392         }
393         sb.append("Complex Paths:\n");
394         for (Iterator iterator = complexPaths.iterator(); iterator.hasNext();)
395         {
396             String path = (String) iterator.next();
397             sb.append(path + "\n");
398         }
399 
400         return sb.toString();
401     }
402 }