1 /*
2 * SSHTools - Java SSH2 API
3 *
4 * Copyright (C) 2002-2003 Lee David Painter and Contributors.
5 *
6 * Contributions made by:
7 *
8 * Brett Smith
9 * Richard Pernavas
10 * Erwin Bolwidt
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26 // ===========================================================================
27 // CONTENT : CLASS StringPattern
28 // AUTHOR : Manfred Duchrow
29 // VERSION : 1.7 - 13/02/2003
30 // HISTORY :
31 // 24/01/2000 duma CREATED
32 // 08/01/2002 duma bugfix -> Handle *xxx (equal characters after star) correctly
33 // 16/01/2002 duma changed -> Implements Serializable
34 // 06/07/2002 duma bugfix -> Couldn't match "London" on "L*n"
35 // 19/09/2002 duma bugfix -> Couldn't match "MA_DR_HRBLUB" on "*_HR*"
36 // 19/09/2002 duma changed -> Using now StringExaminer instead of CharacterIterator
37 // 29/09/2002 duma changed -> Refactored: Using StringExaminer instead of StringScanner
38 // 26/12/2002 duma changed -> Comment of matches() was wrong / new hasWildcard()
39 // 13/02/2003 duma added -> setDigitWildcardChar()
40 //
41 // Copyright (c) 2000-2003, by Manfred Duchrow. All rights reserved.
42 // ===========================================================================
43 package com.sshtools.daemon.util;
44
45
46 // ===========================================================================
47 // IMPORTS
48 // ===========================================================================
49 import java.io;
50
51
52 /**
53 * This class provides services for checking strings against string-patterns.
54 * Currently it supports the wildcards<br>
55 * '' for any number of any character and <br>
56 * '?' for any one character. The API is very simple:<br>
57 * <br>
58 * There are only the two class methods <i>match()</i> and
59 * <i>matchIgnoreCase()</i>. <br>
60 * Example: <br>
61 * StringPattern.match( 'Hello World", "H W" ) ; --> evaluates to true <br>
62 * StringPattern.matchIgnoreCase( 'StringPattern", "str???pat" ) ; -->
63 * evaluates to true <br>
64 *
65 * @author Manfred Duchrow
66 * @version 1.7
67 */
68 public class StringPattern implements Serializable {
69 // =========================================================================
70 // CONSTANTS
71 // =========================================================================
72
73 /** */
74 protected final static String MULTI_WILDCARD = "*";
75
76 /** */
77 protected final static char MULTICHAR_WILDCARD = '*';
78
79 /** */
80 protected final static char SINGLECHAR_WILDCARD = '?';
81
82 // =========================================================================
83 // INSTANCE VARIABLES
84 // =========================================================================
85 private boolean ignoreCase = false;
86 private String pattern = null;
87
88 // -------------------------------------------------------------------------
89 private Character digitWildcard = null;
90
91 // -------------------------------------------------------------------------
92 // =========================================================================
93 // CONSTRUCTORS
94 // =========================================================================
95
96 /**
97 * Initializes the new instance with the string pattern and the selecteion,
98 * if case should be ignored when comparing characters.
99 *
100 * @param pattern The pattern to check against ( May contain '' and '?'
101 * wildcards )
102 * @param ignoreCase Definition, if case sensitive character comparison or
103 * not.
104 */
105 public StringPattern(String pattern, boolean ignoreCase) {
106 this.setPattern(pattern);
107 this.setIgnoreCase(ignoreCase);
108 }
109
110 // StringPattern()
111 // -------------------------------------------------------------------------
112
113 /**
114 * Initializes the new instance with the string pattern. The default is
115 * case sensitive checking.
116 *
117 * @param pattern The pattern to check against ( May contain '' and '?'
118 * wildcards )
119 */
120 public StringPattern(String pattern) {
121 this(pattern, false);
122 }
123
124 // StringPattern()
125 // -------------------------------------------------------------------------
126
127 /**
128 * Initializes the new instance with the string pattern and a digit
129 * wildcard character. The default is case sensitive checking.
130 *
131 * @param pattern The pattern to check against ( May contain '', '?'
132 * wildcards and the digit wildcard )
133 * @param digitWildcard A wildcard character that stands as placeholder for
134 * digits
135 */
136 public StringPattern(String pattern, char digitWildcard) {
137 this(pattern, false, digitWildcard);
138 }
139
140 // StringPattern()
141 // -------------------------------------------------------------------------
142
143 /**
144 * Initializes the new instance with the string pattern and the selecteion,
145 * if case should be ignored when comparing characters plus a wildcard
146 * character for digits.
147 *
148 * @param pattern The pattern to check against ( May contain '' and '?'
149 * wildcards )
150 * @param ignoreCase Definition, if case sensitive character comparison or
151 * not.
152 * @param digitWildcard A wildcard character that stands as placeholder for
153 * digits
154 */
155 public StringPattern(String pattern, boolean ignoreCase, char digitWildcard) {
156 this.setPattern(pattern);
157 this.setIgnoreCase(ignoreCase);
158 this.setDigitWildcardChar(digitWildcard);
159 }
160
161 // StringPattern()
162
163 /**
164 * Returns whether or not the pattern matching ignores upper and lower case
165 *
166 * @return
167 */
168 public boolean getIgnoreCase() {
169 return ignoreCase;
170 }
171
172 /**
173 * Sets whether the pattern matching should ignore case or not
174 *
175 * @param newValue
176 */
177 public void setIgnoreCase(boolean newValue) {
178 ignoreCase = newValue;
179 }
180
181 /**
182 * Returns the pattern as string.
183 *
184 * @return
185 */
186 public String getPattern() {
187 return pattern;
188 }
189
190 /**
191 * Sets the pattern to a new value
192 *
193 * @param newValue
194 */
195 public void setPattern(String newValue) {
196 pattern = newValue;
197 }
198
199 /**
200 *
201 *
202 * @return
203 */
204 protected Character digitWildcard() {
205 return digitWildcard;
206 }
207
208 /**
209 *
210 *
211 * @param newValue
212 */
213 protected void digitWildcard(Character newValue) {
214 digitWildcard = newValue;
215 }
216
217 // =========================================================================
218 // CLASS METHODS
219 // =========================================================================
220
221 /**
222 * Returns true, if the given probe string matches the given pattern. <br>
223 * The character comparison is done case sensitive.
224 *
225 * @param probe The string to check against the pattern.
226 * @param pattern The patter, that probably contains wildcards ( '' or '?'
227 * )
228 *
229 * @return
230 */
231 public static boolean match(String probe, String pattern) {
232 StringPattern stringPattern = new StringPattern(pattern, false);
233
234 return (stringPattern.matches(probe));
235 }
236
237 // match()
238 // -------------------------------------------------------------------------
239
240 /**
241 * Returns true, if the given probe string matches the given pattern. <br>
242 * The character comparison is done ignoring upper/lower-case.
243 *
244 * @param probe The string to check against the pattern.
245 * @param pattern The patter, that probably contains wildcards ( '' or '?'
246 * )
247 *
248 * @return
249 */
250 public static boolean matchIgnoreCase(String probe, String pattern) {
251 StringPattern stringPattern = new StringPattern(pattern, true);
252
253 return (stringPattern.matches(probe));
254 }
255
256 // matchIgnoreCase()
257 // -------------------------------------------------------------------------
258 // =========================================================================
259 // PUBLIC INSTANCE METHODS
260 // =========================================================================
261
262 /**
263 * Tests if a specified string matches the pattern.
264 *
265 * @param probe The string to compare to the pattern
266 *
267 * @return true if and only if the probe matches the pattern, false
268 * otherwise.
269 */
270 public boolean matches(String probe) {
271 StringExaminer patternIterator = null;
272 StringExaminer probeIterator = null;
273 char patternCh = '-';
274 char probeCh = '-';
275 String newPattern = null;
276 String subPattern = null;
277 int charIndex = 0;
278
279 if (probe == null) {
280 return false;
281 }
282
283 if (probe.length() == 0) {
284 return false;
285 }
286
287 patternIterator = this.newExaminer(this.getPattern());
288 probeIterator = this.newExaminer(probe);
289 probeCh = probeIterator.nextChar();
290 patternCh = this.getPatternChar(patternIterator, probeCh);
291
292 while ((this.endNotReached(patternCh)) &&
293 (this.endNotReached(probeCh))) {
294 if (patternCh == MULTICHAR_WILDCARD) {
295 patternCh = this.skipWildcards(patternIterator);
296
297 if (this.endReached(patternCh)) {
298 return true; // No more characters after multi wildcard - So everything matches
299 } else {
300 patternIterator.skip(-1);
301 newPattern = this.upToEnd(patternIterator);
302 charIndex = newPattern.indexOf(MULTICHAR_WILDCARD);
303
304 if (charIndex >= 0) {
305 subPattern = newPattern.substring(0, charIndex);
306
307 if (this.skipAfter(probeIterator, subPattern)) {
308 patternIterator = this.newExaminer(newPattern.substring(
309 charIndex));
310 patternCh = probeCh;
311 } else {
312 return false;
313 }
314 } else {
315 probeIterator.skip(-1);
316
317 return this.matchReverse(newPattern, probeIterator);
318 }
319 }
320 }
321
322 if (this.charsAreEqual(probeCh, patternCh)) {
323 if (this.endNotReached(patternCh)) {
324 probeCh = probeIterator.nextChar();
325 patternCh = this.getPatternChar(patternIterator, probeCh);
326 }
327 } else {
328 if (patternCh != MULTICHAR_WILDCARD) {
329 return false; // character is not matching - return immediately
330 }
331 }
332 }
333
334 // while()
335 return ((this.endReached(patternCh)) && (this.endReached(probeCh)));
336 }
337
338 // matches()
339 // -------------------------------------------------------------------------
340
341 /**
342 * Returns the pattern string.
343 *
344 * @see java.lang.Object#toString()
345 */
346 public String toString() {
347 if (this.getPattern() == null) {
348 return super.toString();
349 } else {
350 return this.getPattern();
351 }
352 }
353
354 // toString()
355 // -------------------------------------------------------------------------
356
357 /**
358 * Returns true if the pattern contains any '' or '?' wildcard character.
359 *
360 * @return
361 */
362 public boolean hasWildcard() {
363 if (this.getPattern() == null) {
364 return false;
365 }
366
367 if (this.hasDigitWildcard()) {
368 if (this.getPattern().indexOf(this.digitWildcardChar()) >= 0) {
369 return true;
370 }
371 }
372
373 return (this.getPattern().indexOf(MULTI_WILDCARD) >= 0) ||
374 (this.getPattern().indexOf(SINGLECHAR_WILDCARD) >= 0);
375 }
376
377 // hasWildcard()
378 // -------------------------------------------------------------------------
379
380 /**
381 * Sets the given character as a wildcard character in this pattern to
382 * match only digits ('0'-'9'). <br>
383 *
384 * @param digitWildcard The placeholder character for digits
385 */
386 public void setDigitWildcardChar(char digitWildcard) {
387 if (digitWildcard <= 0) {
388 this.digitWildcard(null);
389 } else {
390 this.digitWildcard(new Character(digitWildcard));
391 }
392 }
393
394 // setDigitWildcardChar()
395
396 /**
397 *
398 *
399 * @return
400 */
401 protected boolean hasDigitWildcard() {
402 return this.digitWildcard() != null;
403 }
404
405 // hasDigitWildcard()
406 // -------------------------------------------------------------------------
407 protected char digitWildcardChar() {
408 if (this.hasDigitWildcard()) {
409 return this.digitWildcard().charValue();
410 } else {
411 return '\0';
412 }
413 }
414
415 // digitWildcardChar()
416 // -------------------------------------------------------------------------
417
418 /**
419 * Moves the iterator position to the next character that is no wildcard.
420 * Doesn't skip digit wildcards !
421 *
422 * @param iterator
423 *
424 * @return
425 */
426 protected char skipWildcards(StringExaminer iterator) {
427 char result = '-';
428
429 do {
430 result = iterator.nextChar();
431 } while ((result == MULTICHAR_WILDCARD) ||
432 (result == SINGLECHAR_WILDCARD));
433
434 return result;
435 }
436
437 // skipWildcards()
438 // -------------------------------------------------------------------------
439
440 /**
441 * Increments the given iterator up to the last character that matched the
442 * character sequence in the given matchString. Returns true, if the
443 * matchString was found, otherwise false.
444 *
445 * @param examiner
446 * @param matchString The string to be found (must not contain )
447 *
448 * @return
449 */
450 protected boolean skipAfter(StringExaminer examiner, String matchString) {
451 // Do not use the method of StringExaminer anymore, because digit wildcard
452 // support is in the charsAreEqual() method which is unknown to the examiner.
453 // return examiner.skipAfter( matchString ) ;
454 char ch = '-';
455 char matchChar = ' ';
456 boolean found = false;
457 int index = 0;
458
459 if ((matchString == null) || (matchString.length() == 0)) {
460 return false;
461 }
462
463 ch = examiner.nextChar();
464
465 while ((examiner.endNotReached(ch)) && (!found)) {
466 matchChar = matchString.charAt(index);
467
468 if (this.charsAreEqual(ch, matchChar)) {
469 index++;
470
471 if (index >= matchString.length()) { // whole matchString checked ?
472 found = true;
473 } else {
474 ch = examiner.nextChar();
475 }
476 } else {
477 if (index == 0) {
478 ch = examiner.nextChar();
479 } else {
480 index = 0;
481 }
482 }
483 }
484
485 return found;
486 }
487
488 // skipAfter()
489 // -------------------------------------------------------------------------
490 protected String upToEnd(StringExaminer iterator) {
491 return iterator.upToEnd();
492 }
493
494 // upToEnd()
495 // -------------------------------------------------------------------------
496 protected boolean matchReverse(String pattern, StringExaminer probeIterator) {
497 String newPattern;
498 String newProbe;
499 StringPattern newMatcher;
500 newPattern = MULTI_WILDCARD + pattern;
501 newProbe = this.upToEnd(probeIterator);
502 newPattern = this.strUtil().reverse(newPattern);
503 newProbe = this.strUtil().reverse(newProbe);
504 newMatcher = new StringPattern(newPattern, this.getIgnoreCase());
505
506 if (this.hasDigitWildcard()) {
507 newMatcher.setDigitWildcardChar(this.digitWildcardChar());
508 }
509
510 return newMatcher.matches(newProbe);
511 }
512
513 // matchReverse()
514 // -------------------------------------------------------------------------
515 protected boolean charsAreEqual(char probeChar, char patternChar) {
516 if (this.hasDigitWildcard()) {
517 if (patternChar == this.digitWildcardChar()) {
518 return Character.isDigit(probeChar);
519 }
520 }
521
522 if (this.getIgnoreCase()) {
523 return (Character.toUpperCase(probeChar) == Character.toUpperCase(patternChar));
524 } else {
525 return (probeChar == patternChar);
526 }
527 }
528
529 // charsAreEqual()
530 // -------------------------------------------------------------------------
531 protected boolean endReached(char character) {
532 return (character == StringExaminer.END_REACHED);
533 }
534
535 // endReached()
536 // -------------------------------------------------------------------------
537 protected boolean endNotReached(char character) {
538 return (!endReached(character));
539 }
540
541 // endNotReached()
542 // -------------------------------------------------------------------------
543 protected char getPatternChar(StringExaminer patternIterator, char probeCh) {
544 char patternCh;
545 patternCh = patternIterator.nextChar();
546
547 return ((patternCh == SINGLECHAR_WILDCARD) ? probeCh : patternCh);
548 }
549
550 // getPatternChar()
551 // -------------------------------------------------------------------------
552 protected StringExaminer newExaminer(String str) {
553 return new StringExaminer(str, this.getIgnoreCase());
554 }
555
556 // newExaminer()
557 // -------------------------------------------------------------------------
558 protected StringUtil strUtil() {
559 return StringUtil.current();
560 }
561
562 // strUtil()
563 // -------------------------------------------------------------------------
564 }
565
566
567 // class StringPattern