Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
WildcardHelper |
|
| 10.875;10.875 |
1 | /* | |
2 | * $Id: WildcardHelper.java 829574 2009-10-25 14:15:31Z 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: 829574 $ $Date: 2009-10-26 01:15:31 +1100 (Mon, 26 Oct 2009) $ | |
34 | * @since 2.1.0 | |
35 | */ | |
36 | 25 | 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 | 38 | int[] expr = new int[data.length() + 2]; |
119 | 38 | char[] buff = data.toCharArray(); |
120 | ||
121 | // Prepare variables for the translation loop | |
122 | 38 | int y = 0; |
123 | 38 | boolean slash = false; |
124 | ||
125 | // Must start from beginning | |
126 | 38 | expr[y++] = MATCH_BEGIN; |
127 | ||
128 | 38 | if (buff.length > 0) { |
129 | 38 | if (buff[0] == '\\') { |
130 | 0 | slash = true; |
131 | 38 | } else if (buff[0] == '*') { |
132 | 0 | expr[y++] = MATCH_FILE; |
133 | } else { | |
134 | 38 | expr[y++] = buff[0]; |
135 | } | |
136 | ||
137 | // Main translation loop | |
138 | 629 | for (int x = 1; x < buff.length; x++) { |
139 | // If the previous char was '\' simply copy this char. | |
140 | 591 | if (slash) { |
141 | 0 | expr[y++] = buff[x]; |
142 | 0 | 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 | 591 | if (buff[x] == '\\') { |
149 | 0 | slash = true; |
150 | ||
151 | // If this char is '*' check the previous one | |
152 | 591 | } else if (buff[x] == '*') { |
153 | // If the previous character als was '*' match a path | |
154 | 56 | if (expr[y - 1] <= MATCH_FILE) { |
155 | 0 | expr[y - 1] = MATCH_PATH; |
156 | } else { | |
157 | 56 | expr[y++] = MATCH_FILE; |
158 | } | |
159 | } else { | |
160 | 535 | expr[y++] = buff[x]; |
161 | } | |
162 | } | |
163 | } | |
164 | } | |
165 | ||
166 | // Must match end at the end | |
167 | 38 | expr[y] = MATCH_THEEND; |
168 | ||
169 | 38 | 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 data The string to match | |
177 | * @param expr The compiled wildcard expression | |
178 | * @return The list of matched variables, or <code>null</code> if it does not match. | |
179 | * @throws NullPointerException If any parameters are null | |
180 | * @since 2.2.0 | |
181 | */ | |
182 | public List<String> match(String data, int[] expr) { | |
183 | 19 | List<String> varsValues = null; |
184 | ||
185 | 19 | if (data == null) { |
186 | 0 | throw new NullPointerException("No data provided"); |
187 | } | |
188 | ||
189 | 19 | if (expr == null) { |
190 | 0 | throw new NullPointerException("No pattern expression provided"); |
191 | } | |
192 | ||
193 | 19 | char[] buff = data.toCharArray(); |
194 | ||
195 | // Allocate the result buffer | |
196 | 19 | char[] rslt = new char[expr.length + buff.length]; |
197 | ||
198 | // The previous and current position of the expression character | |
199 | // (MATCH_*) | |
200 | 19 | int charpos = 0; |
201 | ||
202 | // The position in the expression, input, translation and result arrays | |
203 | 19 | int exprpos = 0; |
204 | 19 | int buffpos = 0; |
205 | 19 | int rsltpos = 0; |
206 | 19 | int offset = -1; |
207 | ||
208 | // First check for MATCH_BEGIN | |
209 | 19 | boolean matchBegin = false; |
210 | ||
211 | 19 | if (expr[charpos] == MATCH_BEGIN) { |
212 | 19 | matchBegin = true; |
213 | 19 | exprpos = ++charpos; |
214 | } | |
215 | ||
216 | // Search the fist expression character (except MATCH_BEGIN - already | |
217 | // skipped) | |
218 | 188 | while (expr[charpos] >= 0) { |
219 | 169 | charpos++; |
220 | } | |
221 | ||
222 | // The expression charater (MATCH_*) | |
223 | 19 | int exprchr = expr[charpos]; |
224 | ||
225 | while (true) { | |
226 | // Check if the data in the expression array before the current | |
227 | // expression character matches the data in the input buffer | |
228 | 39 | if (matchBegin) { |
229 | 19 | if (!matchArray(expr, exprpos, charpos, buff, buffpos)) { |
230 | 6 | return null; |
231 | } | |
232 | ||
233 | 13 | matchBegin = false; |
234 | } else { | |
235 | 20 | offset = indexOfArray(expr, exprpos, charpos, buff, buffpos); |
236 | ||
237 | 20 | if (offset < 0) { |
238 | 0 | return null; |
239 | } | |
240 | } | |
241 | ||
242 | // Check for MATCH_BEGIN | |
243 | 33 | if (matchBegin) { |
244 | 0 | if (offset != 0) { |
245 | 0 | return null; |
246 | } | |
247 | ||
248 | 0 | matchBegin = false; |
249 | } | |
250 | ||
251 | // Advance buffpos | |
252 | 33 | buffpos += (charpos - exprpos); |
253 | ||
254 | // Check for END's | |
255 | 33 | if (exprchr == MATCH_END) { |
256 | 0 | if (rsltpos > 0) { |
257 | 0 | varsValues = addAndCreateList(varsValues, new String(rslt, |
258 | 0, rsltpos)); | |
259 | } | |
260 | ||
261 | // Don't care about rest of input buffer | |
262 | 0 | varsValues = addElementOnTop(varsValues, data); |
263 | 0 | return varsValues; |
264 | 33 | } else if (exprchr == MATCH_THEEND) { |
265 | 11 | if (rsltpos > 0) { |
266 | 0 | varsValues = addAndCreateList(varsValues, new String(rslt, |
267 | 0, rsltpos)); | |
268 | } | |
269 | ||
270 | // Check that we reach buffer's end | |
271 | 11 | if (buffpos == buff.length) { |
272 | 11 | addElementOnTop(varsValues, data); |
273 | 11 | return varsValues; |
274 | } | |
275 | 0 | return null; |
276 | } | |
277 | ||
278 | // Search the next expression character | |
279 | 22 | exprpos = ++charpos; |
280 | ||
281 | 94 | while (expr[charpos] >= 0) { |
282 | 72 | charpos++; |
283 | } | |
284 | ||
285 | 22 | int prevchr = exprchr; |
286 | ||
287 | 22 | exprchr = expr[charpos]; |
288 | ||
289 | // We have here prevchr == * or **. | |
290 | 22 | offset = (prevchr == MATCH_FILE) ? indexOfArray(expr, exprpos, |
291 | charpos, buff, buffpos) : lastIndexOfArray(expr, exprpos, | |
292 | charpos, buff, buffpos); | |
293 | ||
294 | 22 | if (offset < 0) { |
295 | 2 | return null; |
296 | } | |
297 | ||
298 | // Copy the data from the source buffer into the result buffer | |
299 | // to substitute the expression character | |
300 | 20 | if (prevchr == MATCH_PATH) { |
301 | 0 | while (buffpos < offset) { |
302 | 0 | rslt[rsltpos++] = buff[buffpos++]; |
303 | } | |
304 | } else { | |
305 | // Matching file, don't copy '/' | |
306 | 122 | while (buffpos < offset) { |
307 | 102 | if (buff[buffpos] == '/') { |
308 | 0 | return null; |
309 | } | |
310 | ||
311 | 102 | rslt[rsltpos++] = buff[buffpos++]; |
312 | } | |
313 | } | |
314 | ||
315 | 20 | varsValues = addAndCreateList(varsValues, new String(rslt, 0, |
316 | rsltpos)); | |
317 | 20 | rsltpos = 0; |
318 | 20 | } |
319 | } | |
320 | ||
321 | /** | |
322 | * Get the offset of a part of an int array within a char array. <br> | |
323 | * This method return the index in d of the first occurrence after dpos of | |
324 | * that part of array specified by r, starting at rpos and terminating at | |
325 | * rend. | |
326 | * | |
327 | * @param r The array containing the data that need to be matched in d. | |
328 | * @param rpos The index of the first character in r to look for. | |
329 | * @param rend The index of the last character in r to look for plus 1. | |
330 | * @param d The array of char that should contain a part of r. | |
331 | * @param dpos The starting offset in d for the matching. | |
332 | * @return The offset in d of the part of r matched in d or -1 if that was | |
333 | * not found. | |
334 | * @since 2.1.0 | |
335 | */ | |
336 | protected int indexOfArray(int[] r, int rpos, int rend, char[] d, int dpos) { | |
337 | // Check if pos and len are legal | |
338 | 42 | if (rend < rpos) { |
339 | 0 | throw new IllegalArgumentException("rend < rpos"); |
340 | } | |
341 | ||
342 | // If we need to match a zero length string return current dpos | |
343 | 42 | if (rend == rpos) { |
344 | 18 | return (d.length); // ?? dpos? |
345 | } | |
346 | ||
347 | // If we need to match a 1 char length string do it simply | |
348 | 24 | if ((rend - rpos) == 1) { |
349 | // Search for the specified character | |
350 | 0 | for (int x = dpos; x < d.length; x++) { |
351 | 0 | if (r[rpos] == d[x]) { |
352 | 0 | return (x); |
353 | } | |
354 | } | |
355 | } | |
356 | ||
357 | // Main string matching loop. It gets executed if the characters to | |
358 | // match are less then the characters left in the d buffer | |
359 | 93 | while (((dpos + rend) - rpos) <= d.length) { |
360 | // Set current startpoint in d | |
361 | 91 | int y = dpos; |
362 | ||
363 | // Check every character in d for equity. If the string is matched | |
364 | // return dpos | |
365 | 221 | for (int x = rpos; x <= rend; x++) { |
366 | 221 | if (x == rend) { |
367 | 22 | return (dpos); |
368 | } | |
369 | ||
370 | 199 | if (r[x] != d[y++]) { |
371 | 69 | break; |
372 | } | |
373 | } | |
374 | ||
375 | // Increase dpos to search for the same string at next offset | |
376 | 69 | dpos++; |
377 | 69 | } |
378 | ||
379 | // The remaining chars in d buffer were not enough or the string | |
380 | // wasn't matched | |
381 | 2 | return (-1); |
382 | } | |
383 | ||
384 | /** | |
385 | * Get the offset of a last occurance of an int array within a char array. | |
386 | * <br> | |
387 | * This method return the index in d of the last occurrence after dpos of | |
388 | * that part of array specified by r, starting at rpos and terminating at | |
389 | * rend. | |
390 | * | |
391 | * @param r The array containing the data that need to be matched in d. | |
392 | * @param rpos The index of the first character in r to look for. | |
393 | * @param rend The index of the last character in r to look for plus 1. | |
394 | * @param d The array of char that should contain a part of r. | |
395 | * @param dpos The starting offset in d for the matching. | |
396 | * @return The offset in d of the last part of r matched in d or -1 if that | |
397 | * was not found. | |
398 | * @since 2.1.0 | |
399 | */ | |
400 | protected int lastIndexOfArray(int[] r, int rpos, int rend, char[] d, | |
401 | int dpos) { | |
402 | // Check if pos and len are legal | |
403 | 0 | if (rend < rpos) { |
404 | 0 | throw new IllegalArgumentException("rend < rpos"); |
405 | } | |
406 | ||
407 | // If we need to match a zero length string return current dpos | |
408 | 0 | if (rend == rpos) { |
409 | 0 | return (d.length); // ?? dpos? |
410 | } | |
411 | ||
412 | // If we need to match a 1 char length string do it simply | |
413 | 0 | if ((rend - rpos) == 1) { |
414 | // Search for the specified character | |
415 | 0 | for (int x = d.length - 1; x > dpos; x--) { |
416 | 0 | if (r[rpos] == d[x]) { |
417 | 0 | return (x); |
418 | } | |
419 | } | |
420 | } | |
421 | ||
422 | // Main string matching loop. It gets executed if the characters to | |
423 | // match are less then the characters left in the d buffer | |
424 | 0 | int l = d.length - (rend - rpos); |
425 | ||
426 | 0 | while (l >= dpos) { |
427 | // Set current startpoint in d | |
428 | 0 | int y = l; |
429 | ||
430 | // Check every character in d for equity. If the string is matched | |
431 | // return dpos | |
432 | 0 | for (int x = rpos; x <= rend; x++) { |
433 | 0 | if (x == rend) { |
434 | 0 | return (l); |
435 | } | |
436 | ||
437 | 0 | if (r[x] != d[y++]) { |
438 | 0 | break; |
439 | } | |
440 | } | |
441 | ||
442 | // Decrease l to search for the same string at next offset | |
443 | 0 | l--; |
444 | 0 | } |
445 | ||
446 | // The remaining chars in d buffer were not enough or the string | |
447 | // wasn't matched | |
448 | 0 | return (-1); |
449 | } | |
450 | ||
451 | /** | |
452 | * Matches elements of array r from rpos to rend with array d, starting from | |
453 | * dpos. <br> | |
454 | * This method return true if elements of array r from rpos to rend equals | |
455 | * elements of array d starting from dpos to dpos+(rend-rpos). | |
456 | * | |
457 | * @param r The array containing the data that need to be matched in d. | |
458 | * @param rpos The index of the first character in r to look for. | |
459 | * @param rend The index of the last character in r to look for. | |
460 | * @param d The array of char that should start from a part of r. | |
461 | * @param dpos The starting offset in d for the matching. | |
462 | * @return true if array d starts from portion of array r. | |
463 | * @since 2.1.0 | |
464 | */ | |
465 | protected boolean matchArray(int[] r, int rpos, int rend, char[] d, int dpos) { | |
466 | 19 | if ((d.length - dpos) < (rend - rpos)) { |
467 | 0 | return (false); |
468 | } | |
469 | ||
470 | 170 | for (int i = rpos; i < rend; i++) { |
471 | 157 | if (r[i] != d[dpos++]) { |
472 | 6 | return (false); |
473 | } | |
474 | } | |
475 | ||
476 | 13 | return (true); |
477 | } | |
478 | ||
479 | /** | |
480 | * <p> | |
481 | * Inserts into a value wildcard-matched strings where specified. | |
482 | * </p> | |
483 | * | |
484 | * @param val The value to convert | |
485 | * @param vars A Map of wildcard-matched strings | |
486 | * @return The new value | |
487 | * @since 2.1.0 | |
488 | */ | |
489 | public static String convertParam(String val, Map<Integer, String> vars) { | |
490 | 0 | if (val == null) { |
491 | 0 | return null; |
492 | 0 | } else if (val.indexOf("{") == -1) { |
493 | 0 | return val; |
494 | } | |
495 | ||
496 | Map.Entry<Integer, String> entry; | |
497 | 0 | StringBuffer key = new StringBuffer("{0}"); |
498 | 0 | StringBuffer ret = new StringBuffer(val); |
499 | String keyTmp; | |
500 | int x; | |
501 | ||
502 | 0 | for (Iterator<Map.Entry<Integer, String>> i = vars.entrySet() |
503 | 0 | .iterator(); i.hasNext();) { |
504 | 0 | entry = i.next(); |
505 | 0 | key.setCharAt(1, entry.getKey().toString().charAt(0)); |
506 | 0 | keyTmp = key.toString(); |
507 | ||
508 | // Replace all instances of the placeholder | |
509 | 0 | while ((x = ret.toString().indexOf(keyTmp)) > -1) { |
510 | 0 | ret.replace(x, x + PLACEHOLDER_LENGTH, entry.getValue()); |
511 | } | |
512 | } | |
513 | ||
514 | 0 | return ret.toString(); |
515 | } | |
516 | ||
517 | /** | |
518 | * Adds and object to a list. If the list is null, it creates it. | |
519 | * | |
520 | * @param <T> The type of the element. | |
521 | * @param list The list. | |
522 | * @param data The data to add. | |
523 | * @return The list itself, or a new one if it is <code>null</code>. | |
524 | */ | |
525 | private <T> List<T> addAndCreateList(List<T> list, T data) { | |
526 | 20 | if (list == null) { |
527 | 11 | list = new ArrayList<T>(); |
528 | } | |
529 | 20 | list.add(data); |
530 | 20 | return list; |
531 | } | |
532 | ||
533 | /** | |
534 | * Adds and object on top of a list. If the list is null, it creates it. | |
535 | * | |
536 | * @param <T> The type of the element. | |
537 | * @param list The list. | |
538 | * @param data The data to add. | |
539 | * @return The list itself, or a new one if it is <code>null</code>. | |
540 | */ | |
541 | private <T> List<T> addElementOnTop(List<T> list, T data) { | |
542 | 11 | if (list == null) { |
543 | 0 | list = new ArrayList<T>(); |
544 | } | |
545 | 11 | list.add(0, data); |
546 | 11 | return list; |
547 | } | |
548 | } |