This project has retired. For details please refer to its Attic page.
WildcardHelper xref
View Javadoc

1   /*
2    * $Id: WildcardHelper.java 795343 2009-07-18 11:26:09Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.tiles.util;
22  
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  /***
29   * This class is an utility class that perform wilcard-patterns matching and
30   * isolation taken from Apache Struts that is taken, in turn, from Apache
31   * Struts.
32   *
33   * @version $Rev: 795343 $ $Date: 2009-07-18 13:26:09 +0200 (sab, 18 lug 2009) $
34   * @since 2.1.0
35   */
36  public class WildcardHelper {
37      /***
38       * The int representing '*' in the pattern <code>int []</code>.
39       *
40       * @since 2.1.0
41       */
42      protected static final int MATCH_FILE = -1;
43  
44      /***
45       * The int representing '**' in the pattern <code>int []</code>.
46       *
47       * @since 2.1.0
48       */
49      protected static final int MATCH_PATH = -2;
50  
51      /***
52       * The int representing begin in the pattern <code>int []</code>.
53       *
54       * @since 2.1.0
55       */
56      protected static final int MATCH_BEGIN = -4;
57  
58      /***
59       * The int representing end in pattern <code>int []</code>.
60       *
61       * @since 2.1.0
62       */
63      protected static final int MATCH_THEEND = -5;
64  
65      /***
66       * The int value that terminates the pattern <code>int []</code>.
67       *
68       * @since 2.1.0
69       */
70      protected static final int MATCH_END = -3;
71  
72      /***
73       * The length of the placeholder.
74       *
75       * @since 2.1.0
76       */
77      private static final int PLACEHOLDER_LENGTH = 3;
78  
79      /***
80       * <p>
81       * Translate the given <code>String</code> into a <code>int []</code>
82       * representing the pattern matchable by this class. <br>
83       * This function translates a <code>String</code> into an int array
84       * converting the special '*' and '\' characters. <br>
85       * Here is how the conversion algorithm works:
86       * </p>
87       *
88       * <ul>
89       *
90       * <li>The '*' character is converted to MATCH_FILE, meaning that zero or
91       * more characters (excluding the path separator '/') are to be matched.</li>
92       *
93       * <li>The '**' sequence is converted to MATCH_PATH, meaning that zero or
94       * more characters (including the path separator '/') are to be matched.</li>
95       *
96       * <li>The '\' character is used as an escape sequence ('\*' is translated
97       * in '*', not in MATCH_FILE). If an exact '\' character is to be matched
98       * the source string must contain a '//'. sequence.</li>
99       *
100      * </ul>
101      *
102      * <p>
103      * When more than two '*' characters, not separated by another character,
104      * are found their value is considered as '**' (MATCH_PATH). <br>
105      * The array is always terminated by a special value (MATCH_END). <br>
106      * All MATCH* values are less than zero, while normal characters are equal
107      * or greater.
108      * </p>
109      *
110      * @param data The string to translate.
111      * @return The encoded string as an int array, terminated by the MATCH_END
112      * value (don't consider the array length).
113      * @throws NullPointerException If data is null.
114      * @since 2.1.0
115      */
116     public int[] compilePattern(String data) {
117         // Prepare the arrays
118         int[] expr = new int[data.length() + 2];
119         char[] buff = data.toCharArray();
120 
121         // Prepare variables for the translation loop
122         int y = 0;
123         boolean slash = false;
124 
125         // Must start from beginning
126         expr[y++] = MATCH_BEGIN;
127 
128         if (buff.length > 0) {
129             if (buff[0] == '//') {
130                 slash = true;
131             } else if (buff[0] == '*') {
132                 expr[y++] = MATCH_FILE;
133             } else {
134                 expr[y++] = buff[0];
135             }
136 
137             // Main translation loop
138             for (int x = 1; x < buff.length; x++) {
139                 // If the previous char was '\' simply copy this char.
140                 if (slash) {
141                     expr[y++] = buff[x];
142                     slash = false;
143 
144                     // If the previous char was not '\' we have to do a bunch of
145                     // checks
146                 } else {
147                     // If this char is '\' declare that and continue
148                     if (buff[x] == '//') {
149                         slash = true;
150 
151                         // If this char is '*' check the previous one
152                     } else if (buff[x] == '*') {
153                         // If the previous character als was '*' match a path
154                         if (expr[y - 1] <= MATCH_FILE) {
155                             expr[y - 1] = MATCH_PATH;
156                         } else {
157                             expr[y++] = MATCH_FILE;
158                         }
159                     } else {
160                         expr[y++] = buff[x];
161                     }
162                 }
163             }
164         }
165 
166         // Must match end at the end
167         expr[y] = MATCH_THEEND;
168 
169         return expr;
170     }
171 
172     /***
173      * Match a pattern agains a string and isolates wildcard replacement into a
174      * <code>Stack</code>.
175      *
176      * @param map The map to store matched values
177      * @param data The string to match
178      * @param expr The compiled wildcard expression
179      * @return True if a match
180      * @throws NullPointerException If any parameters are null
181      * @since 2.1.0
182      * @deprecated Use {@link #match(List, String, int[])}.
183      */
184     public boolean match(Map<Integer, String> map, String data, int[] expr) {
185         if (map == null) {
186             throw new NullPointerException("No map provided");
187         }
188         List<String> varsValues = match(data, expr);
189         int i = 0;
190         for (String value : varsValues) {
191             map.put(i, value);
192             i++;
193         }
194         return varsValues != null;
195     }
196 
197     /***
198      * Match a pattern agains a string and isolates wildcard replacement into a
199      * <code>Stack</code>.
200      *
201      * @param data The string to match
202      * @param expr The compiled wildcard expression
203      * @return The list of matched variables, or <code>null</code> if it does not match.
204      * @throws NullPointerException If any parameters are null
205      * @since 2.2.0
206      */
207     public List<String> match(String data, int[] expr) {
208         List<String> varsValues = null;
209 
210         if (data == null) {
211             throw new NullPointerException("No data provided");
212         }
213 
214         if (expr == null) {
215             throw new NullPointerException("No pattern expression provided");
216         }
217 
218         char[] buff = data.toCharArray();
219 
220         // Allocate the result buffer
221         char[] rslt = new char[expr.length + buff.length];
222 
223         // The previous and current position of the expression character
224         // (MATCH_*)
225         int charpos = 0;
226 
227         // The position in the expression, input, translation and result arrays
228         int exprpos = 0;
229         int buffpos = 0;
230         int rsltpos = 0;
231         int offset = -1;
232 
233         // First check for MATCH_BEGIN
234         boolean matchBegin = false;
235 
236         if (expr[charpos] == MATCH_BEGIN) {
237             matchBegin = true;
238             exprpos = ++charpos;
239         }
240 
241         // Search the fist expression character (except MATCH_BEGIN - already
242         // skipped)
243         while (expr[charpos] >= 0) {
244             charpos++;
245         }
246 
247         // The expression charater (MATCH_*)
248         int exprchr = expr[charpos];
249 
250         while (true) {
251             // Check if the data in the expression array before the current
252             // expression character matches the data in the input buffer
253             if (matchBegin) {
254                 if (!matchArray(expr, exprpos, charpos, buff, buffpos)) {
255                     return null;
256                 }
257 
258                 matchBegin = false;
259             } else {
260                 offset = indexOfArray(expr, exprpos, charpos, buff, buffpos);
261 
262                 if (offset < 0) {
263                     return null;
264                 }
265             }
266 
267             // Check for MATCH_BEGIN
268             if (matchBegin) {
269                 if (offset != 0) {
270                     return null;
271                 }
272 
273                 matchBegin = false;
274             }
275 
276             // Advance buffpos
277             buffpos += (charpos - exprpos);
278 
279             // Check for END's
280             if (exprchr == MATCH_END) {
281                 if (rsltpos > 0) {
282                     varsValues = addAndCreateList(varsValues, new String(rslt,
283                             0, rsltpos));
284                 }
285 
286                 // Don't care about rest of input buffer
287                 varsValues = addElementOnTop(varsValues, data);
288                 return varsValues;
289             } else if (exprchr == MATCH_THEEND) {
290                 if (rsltpos > 0) {
291                     varsValues = addAndCreateList(varsValues, new String(rslt,
292                             0, rsltpos));
293                 }
294 
295                 // Check that we reach buffer's end
296                 if (buffpos == buff.length) {
297                     addElementOnTop(varsValues, data);
298                     return varsValues;
299                 }
300                 return null;
301             }
302 
303             // Search the next expression character
304             exprpos = ++charpos;
305 
306             while (expr[charpos] >= 0) {
307                 charpos++;
308             }
309 
310             int prevchr = exprchr;
311 
312             exprchr = expr[charpos];
313 
314             // We have here prevchr == * or **.
315             offset = (prevchr == MATCH_FILE) ? indexOfArray(expr, exprpos,
316                     charpos, buff, buffpos) : lastIndexOfArray(expr, exprpos,
317                     charpos, buff, buffpos);
318 
319             if (offset < 0) {
320                 return null;
321             }
322 
323             // Copy the data from the source buffer into the result buffer
324             // to substitute the expression character
325             if (prevchr == MATCH_PATH) {
326                 while (buffpos < offset) {
327                     rslt[rsltpos++] = buff[buffpos++];
328                 }
329             } else {
330                 // Matching file, don't copy '/'
331                 while (buffpos < offset) {
332                     if (buff[buffpos] == '/') {
333                         return null;
334                     }
335 
336                     rslt[rsltpos++] = buff[buffpos++];
337                 }
338             }
339 
340             varsValues = addAndCreateList(varsValues, new String(rslt, 0,
341                     rsltpos));
342             rsltpos = 0;
343         }
344     }
345 
346     /***
347      * Get the offset of a part of an int array within a char array. <br>
348      * This method return the index in d of the first occurrence after dpos of
349      * that part of array specified by r, starting at rpos and terminating at
350      * rend.
351      *
352      * @param r The array containing the data that need to be matched in d.
353      * @param rpos The index of the first character in r to look for.
354      * @param rend The index of the last character in r to look for plus 1.
355      * @param d The array of char that should contain a part of r.
356      * @param dpos The starting offset in d for the matching.
357      * @return The offset in d of the part of r matched in d or -1 if that was
358      * not found.
359      * @since 2.1.0
360      */
361     protected int indexOfArray(int[] r, int rpos, int rend, char[] d, int dpos) {
362         // Check if pos and len are legal
363         if (rend < rpos) {
364             throw new IllegalArgumentException("rend < rpos");
365         }
366 
367         // If we need to match a zero length string return current dpos
368         if (rend == rpos) {
369             return (d.length); // ?? dpos?
370         }
371 
372         // If we need to match a 1 char length string do it simply
373         if ((rend - rpos) == 1) {
374             // Search for the specified character
375             for (int x = dpos; x < d.length; x++) {
376                 if (r[rpos] == d[x]) {
377                     return (x);
378                 }
379             }
380         }
381 
382         // Main string matching loop. It gets executed if the characters to
383         // match are less then the characters left in the d buffer
384         while (((dpos + rend) - rpos) <= d.length) {
385             // Set current startpoint in d
386             int y = dpos;
387 
388             // Check every character in d for equity. If the string is matched
389             // return dpos
390             for (int x = rpos; x <= rend; x++) {
391                 if (x == rend) {
392                     return (dpos);
393                 }
394 
395                 if (r[x] != d[y++]) {
396                     break;
397                 }
398             }
399 
400             // Increase dpos to search for the same string at next offset
401             dpos++;
402         }
403 
404         // The remaining chars in d buffer were not enough or the string
405         // wasn't matched
406         return (-1);
407     }
408 
409     /***
410      * Get the offset of a last occurance of an int array within a char array.
411      * <br>
412      * This method return the index in d of the last occurrence after dpos of
413      * that part of array specified by r, starting at rpos and terminating at
414      * rend.
415      *
416      * @param r The array containing the data that need to be matched in d.
417      * @param rpos The index of the first character in r to look for.
418      * @param rend The index of the last character in r to look for plus 1.
419      * @param d The array of char that should contain a part of r.
420      * @param dpos The starting offset in d for the matching.
421      * @return The offset in d of the last part of r matched in d or -1 if that
422      * was not found.
423      * @since 2.1.0
424      */
425     protected int lastIndexOfArray(int[] r, int rpos, int rend, char[] d,
426             int dpos) {
427         // Check if pos and len are legal
428         if (rend < rpos) {
429             throw new IllegalArgumentException("rend < rpos");
430         }
431 
432         // If we need to match a zero length string return current dpos
433         if (rend == rpos) {
434             return (d.length); // ?? dpos?
435         }
436 
437         // If we need to match a 1 char length string do it simply
438         if ((rend - rpos) == 1) {
439             // Search for the specified character
440             for (int x = d.length - 1; x > dpos; x--) {
441                 if (r[rpos] == d[x]) {
442                     return (x);
443                 }
444             }
445         }
446 
447         // Main string matching loop. It gets executed if the characters to
448         // match are less then the characters left in the d buffer
449         int l = d.length - (rend - rpos);
450 
451         while (l >= dpos) {
452             // Set current startpoint in d
453             int y = l;
454 
455             // Check every character in d for equity. If the string is matched
456             // return dpos
457             for (int x = rpos; x <= rend; x++) {
458                 if (x == rend) {
459                     return (l);
460                 }
461 
462                 if (r[x] != d[y++]) {
463                     break;
464                 }
465             }
466 
467             // Decrease l to search for the same string at next offset
468             l--;
469         }
470 
471         // The remaining chars in d buffer were not enough or the string
472         // wasn't matched
473         return (-1);
474     }
475 
476     /***
477      * Matches elements of array r from rpos to rend with array d, starting from
478      * dpos. <br>
479      * This method return true if elements of array r from rpos to rend equals
480      * elements of array d starting from dpos to dpos+(rend-rpos).
481      *
482      * @param r The array containing the data that need to be matched in d.
483      * @param rpos The index of the first character in r to look for.
484      * @param rend The index of the last character in r to look for.
485      * @param d The array of char that should start from a part of r.
486      * @param dpos The starting offset in d for the matching.
487      * @return true if array d starts from portion of array r.
488      * @since 2.1.0
489      */
490     protected boolean matchArray(int[] r, int rpos, int rend, char[] d, int dpos) {
491         if ((d.length - dpos) < (rend - rpos)) {
492             return (false);
493         }
494 
495         for (int i = rpos; i < rend; i++) {
496             if (r[i] != d[dpos++]) {
497                 return (false);
498             }
499         }
500 
501         return (true);
502     }
503 
504     /***
505      * <p>
506      * Inserts into a value wildcard-matched strings where specified.
507      * </p>
508      *
509      * @param val The value to convert
510      * @param vars A Map of wildcard-matched strings
511      * @return The new value
512      * @since 2.1.0
513      */
514     public static String convertParam(String val, Map<Integer, String> vars) {
515         if (val == null) {
516             return null;
517         } else if (val.indexOf("{") == -1) {
518             return val;
519         }
520 
521         Map.Entry<Integer, String> entry;
522         StringBuffer key = new StringBuffer("{0}");
523         StringBuffer ret = new StringBuffer(val);
524         String keyTmp;
525         int x;
526 
527         for (Iterator<Map.Entry<Integer, String>> i = vars.entrySet()
528                 .iterator(); i.hasNext();) {
529             entry = i.next();
530             key.setCharAt(1, entry.getKey().toString().charAt(0));
531             keyTmp = key.toString();
532 
533             // Replace all instances of the placeholder
534             while ((x = ret.toString().indexOf(keyTmp)) > -1) {
535                 ret.replace(x, x + PLACEHOLDER_LENGTH, entry.getValue());
536             }
537         }
538 
539         return ret.toString();
540     }
541 
542     /***
543      * Adds and object to a list. If the list is null, it creates it.
544      *
545      * @param <T> The type of the element.
546      * @param list The list.
547      * @param data The data to add.
548      * @return The list itself, or a new one if it is <code>null</code>.
549      */
550     private <T> List<T> addAndCreateList(List<T> list, T data) {
551         if (list == null) {
552             list = new ArrayList<T>();
553         }
554         list.add(data);
555         return list;
556     }
557 
558     /***
559      * Adds and object on top of a list. If the list is null, it creates it.
560      *
561      * @param <T> The type of the element.
562      * @param list The list.
563      * @param data The data to add.
564      * @return The list itself, or a new one if it is <code>null</code>.
565      */
566     private <T> List<T> addElementOnTop(List<T> list, T data) {
567         if (list == null) {
568             list = new ArrayList<T>();
569         }
570         list.add(0, data);
571         return list;
572     }
573 }