KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > dialogs > SearchPattern


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.ui.dialogs;
12
13 import org.eclipse.ui.internal.misc.StringMatcher;
14
15 /**
16  * A search pattern defines how search results are found.
17  *
18  * <p>
19  * This class is intended to be subclassed by clients. A default behavior is
20  * provided for each of the methods above, that clients can ovveride if they
21  * wish.
22  * </p>
23  *
24  * @since 3.3
25  */

26 public class SearchPattern {
27
28     // Rules for pattern matching: (exact, prefix, pattern) [ | case sensitive]
29
/**
30      * Match rule: The search pattern matches exactly the search result, that
31      * is, the source of the search result equals the search pattern. Search pattern
32      * should start from lowerCase char.
33      */

34     public static final int RULE_EXACT_MATCH = 0;
35
36     /**
37      * Match rule: The search pattern is a prefix of the search result.
38      */

39     public static final int RULE_PREFIX_MATCH = 0x0001;
40
41     /**
42      * Match rule: The search pattern contains one or more wild cards ('*' or
43      * '?'). A '*' wild-card can replace 0 or more characters in the search
44      * result. A '?' wild-card replaces exactly 1 character in the search
45      * result.
46      */

47     public static final int RULE_PATTERN_MATCH = 0x0002;
48
49     /**
50      * Match rule: The search pattern matches the search result only if cases
51      * are the same. Can be combined to previous rules, e.g.
52      * {@link #RULE_EXACT_MATCH} | {@link #RULE_CASE_SENSITIVE}
53      */

54     public static final int RULE_CASE_SENSITIVE = 0x0008;
55
56     /**
57      * Match rule: The search pattern is blank.
58      */

59     public static final int RULE_BLANK_MATCH = 0x0020;
60
61     /**
62      * Match rule: The search pattern contains a Camel Case expression. <br>
63      * Examples:
64      * <ul>
65      * <li><code>NPE</code> type string pattern will match
66      * <code>NullPointerException</code> and
67      * <code>NpPermissionException</code> types,</li>
68      * <li><code>NuPoEx</code> type string pattern will only match
69      * <code>NullPointerException</code> type.</li>
70      * </ul>
71      *
72      *
73      * <br>
74      * Can be combined to {@link #RULE_PREFIX_MATCH} match rule. For example,
75      * when prefix match rule is combined with Camel Case match rule,
76      * <code>"nPE"</code> pattern will match <code>nPException</code>. <br>
77      * Match rule {@link #RULE_PATTERN_MATCH} may also be combined but both
78      * rules will not be used simultaneously as they are mutually exclusive.
79      * Used match rule depends on whether string pattern contains specific
80      * pattern characters (e.g. '*' or '?') or not. If it does, then only
81      * Pattern match rule will be used, otherwise only Camel Case match will be
82      * used. For example, with <code>"NPE"</code> string pattern, search will
83      * only use Camel Case match rule, but with <code>N*P*E*</code> string
84      * pattern, it will use only Pattern match rule.
85      *
86      */

87     public static final int RULE_CAMELCASE_MATCH = 0x0080;
88
89     private int matchRule;
90
91     private String JavaDoc stringPattern;
92
93     private String JavaDoc initialPattern;
94
95     private StringMatcher stringMatcher;
96
97     private static final char END_SYMBOL = '<';
98
99     private static final char ANY_STRING = '*';
100
101     private static final char BLANK = ' ';
102
103     private int allowedRules;
104
105     /**
106      * Creates new instance of SearchPattern Default allowedRules for it is
107      * result of belong logic operation: ( RULE_EXACT_MATCH | RULE_PREFIX_MATCH |
108      * RULE_PATTERN_MATCH | RULE_CAMELCASE_MATCH )
109      *
110      */

111     public SearchPattern() {
112         this(RULE_EXACT_MATCH | RULE_PREFIX_MATCH | RULE_PATTERN_MATCH
113                 | RULE_CAMELCASE_MATCH | RULE_BLANK_MATCH);
114     }
115
116     /**
117      * Creates a search pattern with the rule to apply for matching index keys.
118      * It can be exact match, prefix match, pattern match or camelCase match.
119      * Rule can also be combined with a case sensitivity flag.
120      *
121      * @param allowedRules
122      * one of {@link #RULE_EXACT_MATCH}, {@link #RULE_PREFIX_MATCH},
123      * {@link #RULE_PATTERN_MATCH}, {@link #RULE_CASE_SENSITIVE},
124      * {@link #RULE_CAMELCASE_MATCH} combined with one of following
125      * values: {@link #RULE_EXACT_MATCH}, {@link #RULE_PREFIX_MATCH},
126      * {@link #RULE_PATTERN_MATCH} or {@link #RULE_CAMELCASE_MATCH}.
127      * e.g. {@link #RULE_EXACT_MATCH} | {@link #RULE_CASE_SENSITIVE}
128      * if an exact and case sensitive match is requested,
129      * {@link #RULE_PREFIX_MATCH} if a prefix non case sensitive
130      * match is requested or {@link #RULE_EXACT_MATCH} if a non case
131      * sensitive and erasure match is requested.<br>
132      * Note also that default behavior for generic types/methods
133      * search is to find exact matches.
134      */

135     public SearchPattern(int allowedRules) {
136         this.allowedRules = allowedRules;
137     }
138
139     /**
140      * Gets string pattern used by matcher
141      *
142      * @return pattern
143      */

144     public String JavaDoc getPattern() {
145         return this.stringPattern;
146     }
147
148     /**
149      * @param stringPattern
150      * The stringPattern to set.
151      */

152     public void setPattern(String JavaDoc stringPattern) {
153         this.initialPattern = stringPattern;
154         this.stringPattern = stringPattern;
155         initializePatternAndMatchRule(stringPattern);
156         matchRule = matchRule & this.allowedRules;
157         if (matchRule == RULE_PATTERN_MATCH) {
158             stringMatcher = new StringMatcher(this.stringPattern, true, false);
159         }
160     }
161
162     /**
163      * Matches text with pattern. matching is determine by matchKind.
164      *
165      * @param text
166      * @return true if search pattern was matched with text false in other way
167      */

168     public boolean matches(String JavaDoc text) {
169         switch (matchRule) {
170         case RULE_BLANK_MATCH:
171             return true;
172         case RULE_PATTERN_MATCH:
173             return stringMatcher.match(text);
174         case RULE_EXACT_MATCH:
175             return stringPattern.equalsIgnoreCase(text);
176         case RULE_CAMELCASE_MATCH:
177             if (camelCaseMatch(stringPattern, text)) {
178                 return true;
179             }
180         default:
181             return startsWithIgnoreCase(text, stringPattern);
182         }
183     }
184
185     private void initializePatternAndMatchRule(String JavaDoc pattern) {
186         int length = pattern.length();
187         if (length == 0) {
188             matchRule = RULE_BLANK_MATCH;
189             stringPattern = pattern;
190             return;
191         }
192         char last = pattern.charAt(length - 1);
193
194         if (pattern.indexOf('*') != -1 || pattern.indexOf('?') != -1) {
195             matchRule = RULE_PATTERN_MATCH;
196             switch (last) {
197             case END_SYMBOL:
198             case BLANK:
199                 stringPattern = pattern.substring(0, length - 1);
200                 break;
201             case ANY_STRING:
202                 stringPattern = pattern;
203                 break;
204             default:
205                 stringPattern = pattern + ANY_STRING;
206             }
207             return;
208         }
209
210         if (validateMatchRule(pattern, RULE_CAMELCASE_MATCH) == RULE_CAMELCASE_MATCH) {
211             matchRule = RULE_CAMELCASE_MATCH;
212             stringPattern = pattern;
213             return;
214         }
215         
216         if (last == END_SYMBOL || last == BLANK) {
217             matchRule = RULE_EXACT_MATCH;
218             stringPattern = pattern.substring(0, length - 1);
219             return;
220         }
221
222         matchRule = RULE_PREFIX_MATCH;
223         stringPattern = pattern;
224
225     }
226
227     /**
228      * @param text
229      * @param prefix
230      * @return true if text starts with given prefix, ignoring case false in
231      * other way
232      */

233     private boolean startsWithIgnoreCase(String JavaDoc text, String JavaDoc prefix) {
234         int textLength = text.length();
235         int prefixLength = prefix.length();
236         if (textLength < prefixLength)
237             return false;
238         for (int i = prefixLength - 1; i >= 0; i--) {
239             if (Character.toLowerCase(prefix.charAt(i)) != Character
240                     .toLowerCase(text.charAt(i)))
241                 return false;
242         }
243         return true;
244     }
245
246     /**
247      * Answers true if the pattern matches the given name using CamelCase rules,
248      * or false otherwise. CamelCase matching does NOT accept explicit
249      * wild-cards '*' and '?' and is inherently case sensitive. <br>
250      * CamelCase denotes the convention of writing compound names without
251      * spaces, and capitalizing every term. This function recognizes both upper
252      * and lower CamelCase, depending whether the leading character is
253      * capitalized or not. The leading part of an upper CamelCase pattern is
254      * assumed to contain a sequence of capitals which are appearing in the
255      * matching name; e.g. 'NPE' will match 'NullPointerException', but not
256      * 'NewPerfData'. A lower CamelCase pattern uses a lowercase first
257      * character. In Java, type names follow the upper CamelCase convention,
258      * whereas method or field names follow the lower CamelCase convention. <br>
259      * The pattern may contain lowercase characters, which will be match in a
260      * case sensitive way. These characters must appear in sequence in the name.
261      * For instance, 'NPExcep' will match 'NullPointerException', but not
262      * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but
263      * not 'NoPointerException'. <br>
264      * <br>
265      * Examples:
266      * <ol>
267      * <li>
268      *
269      * <pre>
270      * pattern = &quot;NPE&quot;
271      * name = NullPointerException / NoPermissionException
272      * result =&gt; true
273      * </pre>
274      *
275      * </li>
276      * <li>
277      *
278      * <pre>
279      * pattern = &quot;NuPoEx&quot;
280      * name = NullPointerException
281      * result =&gt; true
282      * </pre>
283      *
284      * </li>
285      * <li>
286      *
287      * <pre>
288      * pattern = &quot;npe&quot;
289      * name = NullPointerException
290      * result =&gt; false
291      * </pre>
292      *
293      * </li>
294      * </ol>
295      *
296      * @param pattern
297      * the given pattern
298      * @param name
299      * the given name
300      * @return true if the pattern matches the given name, false otherwise
301      *
302      */

303     private boolean camelCaseMatch(String JavaDoc pattern, String JavaDoc name) {
304         if (pattern == null)
305             return true; // null pattern is equivalent to '*'
306
if (name == null)
307             return false; // null name cannot match
308

309         return camelCaseMatch(pattern, 0, pattern.length(), name, 0, name
310                 .length());
311     }
312
313     /**
314      * Answers true if a sub-pattern matches the subpart of the given name using
315      * CamelCase rules, or false otherwise. CamelCase matching does NOT accept
316      * explicit wild-cards '*' and '?' and is inherently case sensitive. Can
317      * match only subset of name/pattern, considering end positions as
318      * non-inclusive. The subpattern is defined by the patternStart and
319      * patternEnd positions. <br>
320      * CamelCase denotes the convention of writing compound names without
321      * spaces, and capitalizing every term. This function recognizes both upper
322      * and lower CamelCase, depending whether the leading character is
323      * capitalized or not. The leading part of an upper CamelCase pattern is
324      * assumed to contain a sequence of capitals which are appearing in the
325      * matching name; e.g. 'NPE' will match 'NullPointerException', but not
326      * 'NewPerfData'. A lower CamelCase pattern uses a lowercase first
327      * character. In Java, type names follow the upper CamelCase convention,
328      * whereas method or field names follow the lower CamelCase convention. <br>
329      * The pattern may contain lowercase characters, which will be match in a
330      * case sensitive way. These characters must appear in sequence in the name.
331      * For instance, 'NPExcep' will match 'NullPointerException', but not
332      * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but
333      * not 'NoPointerException'. <br>
334      * <br>
335      * Examples:
336      * <ol>
337      * <li>
338      *
339      * <pre>
340      * pattern = &quot;NPE&quot;
341      * patternStart = 0
342      * patternEnd = 3
343      * name = NullPointerException
344      * nameStart = 0
345      * nameEnd = 20
346      * result =&gt; true
347      * </pre>
348      *
349      * </li>
350      * <li>
351      *
352      * <pre>
353      * pattern = &quot;NPE&quot;
354      * patternStart = 0
355      * patternEnd = 3
356      * name = NoPermissionException
357      * nameStart = 0
358      * nameEnd = 21
359      * result =&gt; true
360      * </pre>
361      *
362      * </li>
363      * <li>
364      *
365      * <pre>
366      * pattern = &quot;NuPoEx&quot;
367      * patternStart = 0
368      * patternEnd = 6
369      * name = NullPointerException
370      * nameStart = 0
371      * nameEnd = 20
372      * result =&gt; true
373      * </pre>
374      *
375      * </li>
376      * <li>
377      *
378      * <pre>
379      * pattern = &quot;NuPoEx&quot;
380      * patternStart = 0
381      * patternEnd = 6
382      * name = NoPermissionException
383      * nameStart = 0
384      * nameEnd = 21
385      * result =&gt; false
386      * </pre>
387      *
388      * </li>
389      * <li>
390      *
391      * <pre>
392      * pattern = &quot;npe&quot;
393      * patternStart = 0
394      * patternEnd = 3
395      * name = NullPointerException
396      * nameStart = 0
397      * nameEnd = 20
398      * result =&gt; false
399      * </pre>
400      *
401      * </li>
402      * </ol>
403      *
404      * @param pattern
405      * the given pattern
406      * @param patternStart
407      * the start index of the pattern, inclusive
408      * @param patternEnd
409      * the end index of the pattern, exclusive
410      * @param name
411      * the given name
412      * @param nameStart
413      * the start index of the name, inclusive
414      * @param nameEnd
415      * the end index of the name, exclusive
416      * @return true if a sub-pattern matches the subpart of the given name,
417      * false otherwise
418      */

419     private boolean camelCaseMatch(String JavaDoc pattern, int patternStart,
420             int patternEnd, String JavaDoc name, int nameStart, int nameEnd) {
421         if (name == null)
422             return false; // null name cannot match
423
if (pattern == null)
424             return true; // null pattern is equivalent to '*'
425
if (patternEnd < 0)
426             patternEnd = pattern.length();
427         if (nameEnd < 0)
428             nameEnd = name.length();
429
430         if (patternEnd <= patternStart)
431             return nameEnd <= nameStart;
432         if (nameEnd <= nameStart)
433             return false;
434         // check first pattern char
435
if (name.charAt(nameStart) != pattern.charAt(patternStart)) {
436             // first char must strictly match (upper/lower)
437
return false;
438         }
439
440         int patternLength = patternEnd;
441         
442         if (pattern.charAt(patternEnd - 1) == END_SYMBOL || pattern.charAt(patternEnd - 1) == BLANK )
443             patternLength = patternEnd - 1;
444
445
446         char patternChar, nameChar;
447         int iPattern = patternStart;
448         int iName = nameStart;
449
450         // Main loop is on pattern characters
451
while (true) {
452
453             iPattern++;
454             iName++;
455
456             if (iPattern == patternEnd) {
457                 // We have exhausted pattern, so it's a match
458
return true;
459             }
460
461             if (iName == nameEnd) {
462                 if (iPattern == patternLength)
463                     return true;
464                 // We have exhausted name (and not pattern), so it's not a match
465
return false;
466             }
467
468             // For as long as we're exactly matching, bring it on (even if it's
469
// a lower case character)
470
if ((patternChar = pattern.charAt(iPattern)) == name.charAt(iName)) {
471                 continue;
472             }
473
474             // If characters are not equals, then it's not a match if
475
// patternChar is lowercase
476
if (!isPatternCharAllowed(patternChar))
477                 return false;
478
479             // patternChar is uppercase, so let's find the next uppercase in
480
// name
481
while (true) {
482                 if (iName == nameEnd) {
483                     if ((iPattern == patternLength) && (patternChar == END_SYMBOL || patternChar == BLANK))
484                         return true;
485                     return false;
486                 }
487
488                 nameChar = name.charAt(iName);
489
490                 if ((iPattern == patternLength) && (patternChar == END_SYMBOL || patternChar == BLANK)) {
491                     if (isNameCharAllowed(nameChar)) {
492                         return false;
493                     }
494                     iName++;
495                     continue;
496                 }
497
498                 if (!isNameCharAllowed(nameChar)) {
499                     // nameChar is lowercase
500
iName++;
501                     // nameChar is uppercase...
502
} else if (patternChar != nameChar) {
503                     // .. and it does not match patternChar, so it's not a match
504
return false;
505                 } else {
506                     // .. and it matched patternChar. Back to the big loop
507
break;
508                 }
509             }
510             // At this point, either name has been exhausted, or it is at an
511
// uppercase letter.
512
// Since pattern is also at an uppercase letter
513
}
514     }
515
516     /**
517      * Checks pattern's character is allowed for specified set. It could be
518      * override if you want change logic of camelCaseMatch methods.
519      *
520      * @param patternChar
521      * @return true if patternChar is in set of allowed characters for pattern
522      */

523     protected boolean isPatternCharAllowed(char patternChar) {
524         return Character.isUpperCase(patternChar) || patternChar == END_SYMBOL
525                 || patternChar == BLANK;
526     }
527
528     /**
529      * Checks character of element's name is allowed for specified set. It could
530      * be override if you want change logic of camelCaseMatch methods.
531      *
532      * @param nameChar -
533      * name of searched element
534      * @return if nameChar is in set of allowed characters for name of element
535      */

536     protected boolean isNameCharAllowed(char nameChar) {
537         return Character.isUpperCase(nameChar);
538     }
539
540     /**
541      * Returns the rule to apply for matching keys. Can be exact match, prefix
542      * match, pattern match or camelcase match. Rule can also be combined with a
543      * case sensitivity flag.
544      *
545      * @return one of RULE_EXACT_MATCH, RULE_PREFIX_MATCH, RULE_PATTERN_MATCH,
546      * RULE_CAMELCASE_MATCH, combined with RULE_CASE_SENSITIVE, e.g.
547      * RULE_EXACT_MATCH | RULE_CASE_SENSITIVE if an exact and case
548      * sensitive match is requested, or RULE_PREFIX_MATCH if a prefix
549      * non case sensitive match is requested.
550      */

551     public final int getMatchRule() {
552         return this.matchRule;
553     }
554
555     /**
556      * Validate compatibility between given string pattern and match rule. <br>
557      * Optimized (ie. returned match rule is modified) combinations are:
558      * <ul>
559      * <li>{@link #RULE_PATTERN_MATCH} without any '*' or '?' in string
560      * pattern: pattern match bit is unset, </li>
561      * <li>{@link #RULE_PATTERN_MATCH} and {@link #RULE_PREFIX_MATCH} bits
562      * simultaneously set: prefix match bit is unset, </li>
563      * <li>{@link #RULE_PATTERN_MATCH} and {@link #RULE_CAMELCASE_MATCH} bits
564      * simultaneously set: camel case match bit is unset, </li>
565      * <li>{@link #RULE_CAMELCASE_MATCH} with invalid combination of uppercase
566      * and lowercase characters: camel case match bit is unset and replaced with
567      * prefix match pattern, </li>
568      * <li>{@link #RULE_CAMELCASE_MATCH} combined with
569      * {@link #RULE_PREFIX_MATCH} and {@link #RULE_CASE_SENSITIVE} bits is
570      * reduced to only {@link #RULE_CAMELCASE_MATCH} as Camel Case search is
571      * already prefix and case sensitive, </li>
572      * </ul>
573      * <br>
574      * Rejected (ie. returned match rule -1) combinations are:
575      * <ul>
576      * <li>{@link #RULE_REGEXP_MATCH} with any other match mode bit set, </li>
577      * </ul>
578      *
579      * @param stringPattern
580      * The string pattern
581      * @param matchRule
582      * The match rule
583      * @return Optimized valid match rule or -1 if an incompatibility was
584      * detected.
585      */

586     private int validateMatchRule(String JavaDoc stringPattern, int matchRule) {
587
588         // Verify Pattern match rule
589
int starIndex = stringPattern.indexOf('*');
590         int questionIndex = stringPattern.indexOf('?');
591         if (starIndex < 0 && questionIndex < 0) {
592             // reset pattern match bit if any
593
matchRule &= ~RULE_PATTERN_MATCH;
594         } else {
595             // force Pattern rule
596
matchRule |= RULE_PATTERN_MATCH;
597         }
598         if ((matchRule & RULE_PATTERN_MATCH) != 0) {
599             // remove Camel Case and Prefix match bits if any
600
matchRule &= ~RULE_CAMELCASE_MATCH;
601             matchRule &= ~RULE_PREFIX_MATCH;
602         }
603
604         // Verify Camel Case match rule
605
if ((matchRule & RULE_CAMELCASE_MATCH) != 0) {
606             // Verify sting pattern validity
607
int length = stringPattern.length();
608             boolean validCamelCase = true;
609             for (int i = 0; i < length && validCamelCase; i++) {
610                 char ch = stringPattern.charAt(i);
611                 validCamelCase = isValidCamelCaseChar(ch);
612             }
613             validCamelCase = validCamelCase && Character.isUpperCase(stringPattern.charAt(0));
614             // Verify bits compatibility
615
if (validCamelCase) {
616                 if ((matchRule & RULE_PREFIX_MATCH) != 0) {
617                     if ((matchRule & RULE_CASE_SENSITIVE) != 0) {
618                         // This is equivalent to Camel Case match rule
619
matchRule &= ~RULE_PREFIX_MATCH;
620                         matchRule &= ~RULE_CASE_SENSITIVE;
621                     }
622                 }
623             } else {
624                 matchRule &= ~RULE_CAMELCASE_MATCH;
625                 if ((matchRule & RULE_PREFIX_MATCH) == 0) {
626                     matchRule |= RULE_PREFIX_MATCH;
627                     matchRule |= RULE_CASE_SENSITIVE;
628                 }
629             }
630         }
631         return matchRule;
632     }
633
634     /**
635      * Check if character is valid camelCase character
636      *
637      * @param ch
638      * character to be validated
639      * @return true if character is valid
640      */

641     protected boolean isValidCamelCaseChar(char ch) {
642         return true;
643     }
644
645     /**
646      * Tells whether the given <code>SearchPattern</code> equals this pattern.
647      *
648      * @param pattern
649      * pattern to be checked
650      * @return true if the given pattern equals this search pattern
651      */

652     public boolean equalsPattern(SearchPattern pattern) {
653         return trimWildcardCharacters(pattern.initialPattern).equals(
654                 trimWildcardCharacters(this.initialPattern));
655     }
656
657     /**
658      * Tells whether the given <code>SearchPattern</code> is a subpattern of
659      * this pattern.
660      *
661      * @param pattern
662      * pattern to be checked
663      * @return true if the given pattern is a sub pattern of this search pattern
664      */

665     public boolean isSubPattern(SearchPattern pattern) {
666         return trimWildcardCharacters(pattern.initialPattern).startsWith(
667                 trimWildcardCharacters(this.initialPattern));
668     }
669
670     /**
671      * Trims sequences of '*' characters
672      *
673      * @param pattern
674      * string to be trimmed
675      * @return trimmed pattern
676      */

677     private String JavaDoc trimWildcardCharacters(String JavaDoc pattern) {
678         return pattern.replaceAll("\\*+", "\\*"); //$NON-NLS-1$ //$NON-NLS-2$ }
679
}
680
681 }
682
Popular Tags