1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
118 int[] expr = new int[data.length() + 2];
119 char[] buff = data.toCharArray();
120
121
122 int y = 0;
123 boolean slash = false;
124
125
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
138 for (int x = 1; x < buff.length; x++) {
139
140 if (slash) {
141 expr[y++] = buff[x];
142 slash = false;
143
144
145
146 } else {
147
148 if (buff[x] == '//') {
149 slash = true;
150
151
152 } else if (buff[x] == '*') {
153
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
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
221 char[] rslt = new char[expr.length + buff.length];
222
223
224
225 int charpos = 0;
226
227
228 int exprpos = 0;
229 int buffpos = 0;
230 int rsltpos = 0;
231 int offset = -1;
232
233
234 boolean matchBegin = false;
235
236 if (expr[charpos] == MATCH_BEGIN) {
237 matchBegin = true;
238 exprpos = ++charpos;
239 }
240
241
242
243 while (expr[charpos] >= 0) {
244 charpos++;
245 }
246
247
248 int exprchr = expr[charpos];
249
250 while (true) {
251
252
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
268 if (matchBegin) {
269 if (offset != 0) {
270 return null;
271 }
272
273 matchBegin = false;
274 }
275
276
277 buffpos += (charpos - exprpos);
278
279
280 if (exprchr == MATCH_END) {
281 if (rsltpos > 0) {
282 varsValues = addAndCreateList(varsValues, new String(rslt,
283 0, rsltpos));
284 }
285
286
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
296 if (buffpos == buff.length) {
297 addElementOnTop(varsValues, data);
298 return varsValues;
299 }
300 return null;
301 }
302
303
304 exprpos = ++charpos;
305
306 while (expr[charpos] >= 0) {
307 charpos++;
308 }
309
310 int prevchr = exprchr;
311
312 exprchr = expr[charpos];
313
314
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
324
325 if (prevchr == MATCH_PATH) {
326 while (buffpos < offset) {
327 rslt[rsltpos++] = buff[buffpos++];
328 }
329 } else {
330
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
363 if (rend < rpos) {
364 throw new IllegalArgumentException("rend < rpos");
365 }
366
367
368 if (rend == rpos) {
369 return (d.length);
370 }
371
372
373 if ((rend - rpos) == 1) {
374
375 for (int x = dpos; x < d.length; x++) {
376 if (r[rpos] == d[x]) {
377 return (x);
378 }
379 }
380 }
381
382
383
384 while (((dpos + rend) - rpos) <= d.length) {
385
386 int y = dpos;
387
388
389
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
401 dpos++;
402 }
403
404
405
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
428 if (rend < rpos) {
429 throw new IllegalArgumentException("rend < rpos");
430 }
431
432
433 if (rend == rpos) {
434 return (d.length);
435 }
436
437
438 if ((rend - rpos) == 1) {
439
440 for (int x = d.length - 1; x > dpos; x--) {
441 if (r[rpos] == d[x]) {
442 return (x);
443 }
444 }
445 }
446
447
448
449 int l = d.length - (rend - rpos);
450
451 while (l >= dpos) {
452
453 int y = l;
454
455
456
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
468 l--;
469 }
470
471
472
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
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 }