View Javadoc
1   package org.apache.maven.shared.artifact.filter;
2   
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  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.EnumSet;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
33  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
34  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
35  import org.apache.maven.artifact.versioning.VersionRange;
36  import org.slf4j.Logger;
37  
38  import static java.util.Objects.requireNonNull;
39  
40  /**
41   * TODO: include in maven-artifact in future
42   *
43   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
44   * @see StrictPatternIncludesArtifactFilter
45   */
46  public class PatternIncludesArtifactFilter implements ArtifactFilter, StatisticsReportingArtifactFilter
47  {
48      private static final String SEP = System.lineSeparator();
49  
50      /**
51       * Holds the set of compiled patterns
52       */
53      private final Set<Pattern> patterns;
54  
55      /**
56       * Whether the dependency trail should be checked
57       */
58      private final boolean actTransitively;
59  
60      /**
61       * Set of patterns that have been triggered
62       */
63      private final Set<Pattern> patternsTriggered = new HashSet<>();
64  
65      /**
66       * Set of artifacts that have been filtered out
67       */
68      private final List<Artifact> filteredArtifact = new ArrayList<>();
69  
70      /**
71       * <p>Constructor for PatternIncludesArtifactFilter.</p>
72       *
73       * @param patterns The pattern to be used.
74       */
75      public PatternIncludesArtifactFilter( final Collection<String> patterns )
76      {
77          this( patterns, false );
78      }
79  
80      /**
81       * <p>Constructor for PatternIncludesArtifactFilter.</p>
82       *
83       * @param patterns        The pattern to be used.
84       * @param actTransitively transitive yes/no.
85       */
86      public PatternIncludesArtifactFilter( final Collection<String> patterns, final boolean actTransitively )
87      {
88          this.actTransitively = actTransitively;
89          final Set<Pattern> pat = new LinkedHashSet<>();
90          if ( patterns != null && !patterns.isEmpty() )
91          {
92              for ( String pattern : patterns )
93              {
94                  Pattern p = compile( pattern );
95                  pat.add( p );
96              }
97          }
98          this.patterns = pat;
99      }
100 
101     @Override
102     public boolean include( final Artifact artifact )
103     {
104         final boolean shouldInclude = patternMatches( artifact );
105 
106         if ( !shouldInclude )
107         {
108             addFilteredArtifact( artifact );
109         }
110 
111         return shouldInclude;
112     }
113 
114     protected boolean patternMatches( final Artifact artifact )
115     {
116         Boolean match = match( adapt( artifact ) );
117         if ( match != null )
118         {
119             return match;
120         }
121 
122         if ( actTransitively )
123         {
124             final List<String> depTrail = artifact.getDependencyTrail();
125 
126             if ( depTrail != null && depTrail.size() > 1 )
127             {
128                 for ( String trailItem : depTrail )
129                 {
130                     Artifactoid artifactoid = adapt( trailItem );
131                     match = match( artifactoid );
132                     if ( match != null )
133                     {
134                         return match;
135                     }
136                 }
137             }
138         }
139 
140         return false;
141     }
142 
143     private Boolean match( Artifactoid artifactoid )
144     {
145         for ( Pattern pattern : patterns )
146         {
147             if ( pattern.matches( artifactoid ) )
148             {
149                 patternsTriggered.add( pattern );
150                 return !( pattern instanceof NegativePattern );
151             }
152         }
153 
154         return null;
155     }
156 
157     /**
158      * <p>addFilteredArtifact.</p>
159      *
160      * @param artifact add artifact to the filtered artifacts list.
161      */
162     protected void addFilteredArtifact( final Artifact artifact )
163     {
164         filteredArtifact.add( artifact );
165     }
166 
167     @Override
168     public void reportMissedCriteria( final Logger logger )
169     {
170         // if there are no patterns, there is nothing to report.
171         if ( !patterns.isEmpty() )
172         {
173             final List<Pattern> missed = new ArrayList<>( patterns );
174             missed.removeAll( patternsTriggered );
175 
176             if ( !missed.isEmpty() && logger.isWarnEnabled() )
177             {
178                 final StringBuilder buffer = new StringBuilder();
179 
180                 buffer.append( "The following patterns were never triggered in this " );
181                 buffer.append( getFilterDescription() );
182                 buffer.append( ':' );
183 
184                 for ( Pattern pattern : missed )
185                 {
186                     buffer.append( SEP ) .append( "o  '" ).append( pattern ).append( "'" );
187                 }
188 
189                 buffer.append( SEP );
190 
191                 logger.warn( buffer.toString() );
192             }
193         }
194     }
195 
196     @Override
197     public String toString()
198     {
199         return "Includes filter:" + getPatternsAsString();
200     }
201 
202     protected String getPatternsAsString()
203     {
204         final StringBuilder buffer = new StringBuilder();
205         for ( Pattern pattern : patterns )
206         {
207             buffer.append( SEP ).append( "o '" ).append( pattern ).append( "'" );
208         }
209 
210         return buffer.toString();
211     }
212 
213     protected String getFilterDescription()
214     {
215         return "artifact inclusion filter";
216     }
217 
218     @Override
219     public void reportFilteredArtifacts( final Logger logger )
220     {
221         if ( !filteredArtifact.isEmpty() && logger.isDebugEnabled() )
222         {
223             final StringBuilder buffer = new StringBuilder(
224                     "The following artifacts were removed by this " + getFilterDescription() + ": " );
225 
226             for ( Artifact artifactId : filteredArtifact )
227             {
228                 buffer.append( SEP ).append( artifactId.getId() );
229             }
230 
231             logger.debug( buffer.toString() );
232         }
233     }
234 
235     @Override
236     public boolean hasMissedCriteria()
237     {
238         // if there are no patterns, there is nothing to report.
239         if ( !patterns.isEmpty() )
240         {
241             final List<Pattern> missed = new ArrayList<>( patterns );
242             missed.removeAll( patternsTriggered );
243             return !missed.isEmpty();
244         }
245 
246         return false;
247     }
248 
249     private enum Coordinate
250     {
251         GROUP_ID, ARTIFACT_ID, TYPE, CLASSIFIER, BASE_VERSION
252     }
253 
254     private interface Artifactoid
255     {
256         String getCoordinate( Coordinate coordinate );
257     }
258 
259     private static Artifactoid adapt( final Artifact artifact )
260     {
261         requireNonNull( artifact );
262         return coordinate ->
263         {
264             requireNonNull( coordinate );
265             switch ( coordinate )
266             {
267                 case GROUP_ID:
268                     return artifact.getGroupId();
269                 case ARTIFACT_ID:
270                     return artifact.getArtifactId();
271                 case BASE_VERSION:
272                     return artifact.getBaseVersion();
273                 case CLASSIFIER:
274                     return artifact.hasClassifier() ? artifact.getClassifier() : null;
275                 case TYPE:
276                     return artifact.getType();
277                 default:
278             }
279             throw new IllegalArgumentException( "unknown coordinate: " + coordinate );
280         };
281     }
282 
283     /**
284      * Parses elements of {@link Artifact#getDependencyTrail()} list, they are either {@code G:A:T:V} or if artifact
285      * has classifier {@code G:A:T:C:V}, so strictly 4 or 5 segments only.
286      */
287     private static Artifactoid adapt( final String depTrailString )
288     {
289         requireNonNull( depTrailString );
290         String[] coordinates = depTrailString.split( ":" );
291         if ( coordinates.length != 4 && coordinates.length != 5 )
292         {
293             throw new IllegalArgumentException( "Bad dep trail string: " + depTrailString );
294         }
295         final HashMap<Coordinate, String> map = new HashMap<>();
296         map.put( Coordinate.GROUP_ID, coordinates[0] );
297         map.put( Coordinate.ARTIFACT_ID, coordinates[1] );
298         map.put( Coordinate.TYPE, coordinates[2] );
299         if ( coordinates.length == 5 )
300         {
301             map.put( Coordinate.CLASSIFIER, coordinates[3] );
302             map.put( Coordinate.BASE_VERSION, coordinates[4] );
303         }
304         else
305         {
306             map.put( Coordinate.BASE_VERSION, coordinates[3] );
307         }
308 
309         return coordinate ->
310         {
311             requireNonNull( coordinate );
312             return map.get( coordinate );
313         };
314     }
315 
316     private static final String ANY = "*";
317 
318     /**
319      * Splits the pattern string into tokens, replacing empty tokens with {@link #ANY} for patterns like {@code ::val}
320      * so it retains the position of token.
321      */
322     private static String[] splitAndTokenize( String pattern )
323     {
324         String[] stokens = pattern.split( ":" );
325         String[] tokens = new String[stokens.length];
326         for ( int i = 0; i < stokens.length; i++ )
327         {
328             String str = stokens[i];
329             tokens[i] = str != null && !str.isEmpty() ? str : ANY;
330         }
331         return tokens;
332     }
333 
334     /**
335      * Compiles pattern string into {@link Pattern}.
336      *
337      * TODO: patterns seems NOT documented anywhere, so best we have is source below.
338      * TODO: patterns in some cases (3, 2 tokens) seems ambiguous, we may need to clean up the specs
339      */
340     private static Pattern compile( String pattern )
341     {
342         if ( pattern.startsWith( "!" ) )
343         {
344             return new NegativePattern( pattern, compile( pattern.substring( 1 ) ) );
345         }
346         else
347         {
348             String[] tokens = splitAndTokenize( pattern );
349             if ( tokens.length < 1 || tokens.length > 5 )
350             {
351                 throw new IllegalArgumentException( "Invalid pattern: " + pattern );
352             }
353 
354             ArrayList<Pattern> patterns = new ArrayList<>( 5 );
355 
356             if ( tokens.length == 5 )
357             {
358                 // trivial, full pattern w/ classifier: G:A:T:C:V
359                 patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
360                 patterns.add( toPattern( tokens[1], Coordinate.ARTIFACT_ID ) );
361                 patterns.add( toPattern( tokens[2], Coordinate.TYPE ) );
362                 patterns.add( toPattern( tokens[3], Coordinate.CLASSIFIER ) );
363                 patterns.add( toPattern( tokens[4], Coordinate.BASE_VERSION ) );
364             }
365             else if ( tokens.length == 4 )
366             {
367                 // trivial, full pattern w/o classifier: G:A:T:V
368                 patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
369                 patterns.add( toPattern( tokens[1], Coordinate.ARTIFACT_ID ) );
370                 patterns.add( toPattern( tokens[2], Coordinate.TYPE ) );
371                 patterns.add( toPattern( tokens[3], Coordinate.BASE_VERSION ) );
372             }
373             else if ( tokens.length == 3 )
374             {
375                 // tricky: may be "*:artifact:*" but also "*:war:*"
376 
377                 // *:*:* -> ALL
378                 // *:*:xxx -> TC(xxx)
379                 // *:xxx:* -> AT(xxx)
380                 // *:xxx:yyy -> GA(xxx) + TC(XXX)
381                 // xxx:*:* -> GA(xxx)
382                 // xxx:*:yyy -> G(xxx) + TC(yyy)
383                 // xxx:yyy:* -> G(xxx)+A(yyy)
384                 // xxx:yyy:zzz -> G(xxx)+A(yyy)+T(zzz)
385                 if ( ANY.equals( tokens[0] ) && ANY.equals( tokens[1] ) && ANY.equals( tokens[2] ) )
386                 {
387                     patterns.add( MATCH_ALL_PATTERN );
388                 }
389                 else if ( ANY.equals( tokens[0] ) && ANY.equals( tokens[1] ) )
390                 {
391                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[2],
392                             EnumSet.of( Coordinate.TYPE, Coordinate.CLASSIFIER ) ) );
393                 }
394                 else if ( ANY.equals( tokens[0] ) && ANY.equals( tokens[2] ) )
395                 {
396                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[1],
397                             EnumSet.of( Coordinate.ARTIFACT_ID, Coordinate.TYPE ) ) );
398                 }
399                 else if ( ANY.equals( tokens[0] ) )
400                 {
401                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[1],
402                             EnumSet.of( Coordinate.GROUP_ID, Coordinate.ARTIFACT_ID ) ) );
403                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[2],
404                             EnumSet.of( Coordinate.TYPE, Coordinate.CLASSIFIER ) ) );
405                 }
406                 else if ( ANY.equals( tokens[1] ) && ANY.equals( tokens[2] ) )
407                 {
408                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[0],
409                             EnumSet.of( Coordinate.GROUP_ID, Coordinate.ARTIFACT_ID ) ) );
410                 }
411                 else if ( ANY.equals( tokens[1] ) )
412                 {
413                     patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
414                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[2],
415                             EnumSet.of( Coordinate.TYPE, Coordinate.CLASSIFIER ) ) );
416                 }
417                 else if ( ANY.equals( tokens[2] ) )
418                 {
419                     patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
420                     patterns.add( toPattern( tokens[1], Coordinate.ARTIFACT_ID ) );
421                 }
422                 else
423                 {
424                     patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
425                     patterns.add( toPattern( tokens[1], Coordinate.ARTIFACT_ID ) );
426                     patterns.add( toPattern( tokens[2], Coordinate.TYPE ) );
427                 }
428 
429             }
430             else if ( tokens.length == 2 )
431             {
432                 // tricky: may be "*:artifact" but also "*:war"
433                 // *:* -> ALL
434                 // *:xxx -> GATV(xxx)
435                 // xxx:* -> G(xxx)
436                 // xxx:yyy -> G(xxx)+A(yyy)
437 
438                 if ( ANY.equals( tokens[0] ) && ANY.equals( tokens[1] ) )
439                 {
440                     patterns.add( MATCH_ALL_PATTERN );
441                 }
442                 else if ( ANY.equals( tokens[0] ) )
443                 {
444                     patterns.add( new CoordinateMatchingPattern( pattern, tokens[1],
445                             EnumSet.of( Coordinate.GROUP_ID, Coordinate.ARTIFACT_ID, Coordinate.TYPE,
446                                     Coordinate.BASE_VERSION ) ) );
447                 }
448                 else if ( ANY.equals( tokens[1] ) )
449                 {
450                     patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
451                 }
452                 else
453                 {
454                     patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
455                     patterns.add( toPattern( tokens[1], Coordinate.ARTIFACT_ID ) );
456                 }
457             }
458             else
459             {
460                 // trivial: G
461                 patterns.add( toPattern( tokens[0], Coordinate.GROUP_ID ) );
462             }
463 
464             // build result if needed and retains pattern string
465             if ( patterns.size() == 1 )
466             {
467                 Pattern pat = patterns.get( 0 );
468                 if ( pat == MATCH_ALL_PATTERN )
469                 {
470                     return new MatchAllPattern( pattern );
471                 }
472                 else
473                 {
474                     return pat;
475                 }
476             }
477             else
478             {
479                 return new AndPattern( pattern, patterns.toArray( new Pattern[0] ) );
480             }
481         }
482     }
483 
484     private static Pattern toPattern( final String token, final Coordinate coordinate )
485     {
486         if ( ANY.equals( token ) )
487         {
488             return MATCH_ALL_PATTERN;
489         }
490         else
491         {
492             return new CoordinateMatchingPattern( token, token, EnumSet.of( coordinate ) );
493         }
494     }
495 
496     private static final Pattern MATCH_ALL_PATTERN = new MatchAllPattern( ANY );
497 
498     private abstract static class Pattern
499     {
500         protected final String pattern;
501 
502         private Pattern( String pattern )
503         {
504             this.pattern = requireNonNull( pattern );
505         }
506 
507         public abstract boolean matches( Artifactoid artifact );
508 
509         @Override
510         public String toString()
511         {
512             return pattern;
513         }
514     }
515 
516     private static class AndPattern extends Pattern
517     {
518         private final Pattern[] patterns;
519 
520         private AndPattern( String pattern, Pattern[] patterns )
521         {
522             super( pattern );
523             this.patterns = patterns;
524         }
525 
526         @Override
527         public boolean matches( Artifactoid artifactoid )
528         {
529             for ( Pattern pattern : patterns )
530             {
531                 if ( !pattern.matches( artifactoid ) )
532                 {
533                     return false;
534                 }
535             }
536             return true;
537         }
538     }
539 
540     private static class CoordinateMatchingPattern extends Pattern
541     {
542         private final String token;
543 
544         private final EnumSet<Coordinate> coordinates;
545 
546         private final boolean containsWildcard;
547 
548         private final boolean containsAsterisk;
549 
550         private final VersionRange optionalVersionRange;
551 
552         private CoordinateMatchingPattern( String pattern, String token, EnumSet<Coordinate> coordinates )
553         {
554             super( pattern );
555             this.token = token;
556             this.coordinates = coordinates;
557             this.containsAsterisk = token.contains( "*" );
558             this.containsWildcard = this.containsAsterisk || token.contains( "?" );
559             if ( !this.containsWildcard && coordinates.equals( EnumSet.of( Coordinate.BASE_VERSION ) ) && (
560                     token.startsWith( "[" ) || token.startsWith( "(" ) ) )
561             {
562                 try
563                 {
564                     this.optionalVersionRange = VersionRange.createFromVersionSpec( token );
565                 }
566                 catch ( InvalidVersionSpecificationException e )
567                 {
568                     throw new IllegalArgumentException( "Wrong version spec: " + token, e );
569                 }
570             }
571             else
572             {
573                 this.optionalVersionRange = null;
574             }
575         }
576 
577         @Override
578         public boolean matches( Artifactoid artifactoid )
579         {
580             for ( Coordinate coordinate : coordinates )
581             {
582                 String value = artifactoid.getCoordinate( coordinate );
583                 if ( Coordinate.BASE_VERSION == coordinate && optionalVersionRange != null )
584                 {
585                     if ( optionalVersionRange.containsVersion( new DefaultArtifactVersion( value ) ) )
586                     {
587                         return true;
588                     }
589                 }
590                 else if ( containsWildcard )
591                 {
592                     if ( match( token, containsAsterisk, value ) )
593                     {
594                         return true;
595                     }
596                 }
597                 else
598                 {
599                     if ( token.equals( value ) )
600                     {
601                         return true;
602                     }
603                 }
604             }
605             return false;
606         }
607     }
608 
609     /**
610      * Matches all input
611      */
612     private static class MatchAllPattern extends Pattern
613     {
614         private MatchAllPattern( String pattern )
615         {
616             super( pattern );
617         }
618 
619         @Override
620         public boolean matches( Artifactoid artifactoid )
621         {
622             return true;
623         }
624     }
625 
626     /**
627      * Negative pattern
628      */
629     private static class NegativePattern extends Pattern
630     {
631         private final Pattern inner;
632 
633         private NegativePattern( String pattern, Pattern inner )
634         {
635             super( pattern );
636             this.inner = inner;
637         }
638 
639         @Override
640         public boolean matches( Artifactoid artifactoid )
641         {
642             return inner.matches( artifactoid );
643         }
644     }
645 
646     // this beauty below must be salvaged
647 
648     @SuppressWarnings( "InnerAssignment" )
649     private static boolean match( final String pattern, final boolean containsAsterisk, final String value )
650     {
651         char[] patArr = pattern.toCharArray();
652         char[] strArr = value.toCharArray();
653         int patIdxStart = 0;
654         int patIdxEnd = patArr.length - 1;
655         int strIdxStart = 0;
656         int strIdxEnd = strArr.length - 1;
657         char ch;
658 
659         if ( !containsAsterisk )
660         {
661             // No '*'s, so we make a shortcut
662             if ( patIdxEnd != strIdxEnd )
663             {
664                 return false; // Pattern and string do not have the same size
665             }
666             for ( int i = 0; i <= patIdxEnd; i++ )
667             {
668                 ch = patArr[i];
669                 if ( ch != '?' && ch != strArr[i] )
670                 {
671                     return false; // Character mismatch
672                 }
673             }
674             return true; // String matches against pattern
675         }
676 
677         if ( patIdxEnd == 0 )
678         {
679             return true; // Pattern contains only '*', which matches anything
680         }
681 
682         // Process characters before first star
683         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
684         {
685             if ( ch != '?' && ch != strArr[strIdxStart] )
686             {
687                 return false; // Character mismatch
688             }
689             patIdxStart++;
690             strIdxStart++;
691         }
692         if ( strIdxStart > strIdxEnd )
693         {
694             // All characters in the string are used. Check if only '*'s are
695             // left in the pattern. If so, we succeeded. Otherwise failure.
696             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
697             {
698                 if ( patArr[i] != '*' )
699                 {
700                     return false;
701                 }
702             }
703             return true;
704         }
705 
706         // Process characters after last star
707         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
708         {
709             if ( ch != '?' && ch != strArr[strIdxEnd] )
710             {
711                 return false; // Character mismatch
712             }
713             patIdxEnd--;
714             strIdxEnd--;
715         }
716         if ( strIdxStart > strIdxEnd )
717         {
718             // All characters in the string are used. Check if only '*'s are
719             // left in the pattern. If so, we succeeded. Otherwise failure.
720             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
721             {
722                 if ( patArr[i] != '*' )
723                 {
724                     return false;
725                 }
726             }
727             return true;
728         }
729 
730         // process pattern between stars. padIdxStart and patIdxEnd point
731         // always to a '*'.
732         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
733         {
734             int patIdxTmp = -1;
735             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
736             {
737                 if ( patArr[i] == '*' )
738                 {
739                     patIdxTmp = i;
740                     break;
741                 }
742             }
743             if ( patIdxTmp == patIdxStart + 1 )
744             {
745                 // Two stars next to each other, skip the first one.
746                 patIdxStart++;
747                 continue;
748             }
749             // Find the pattern between padIdxStart & padIdxTmp in str between
750             // strIdxStart & strIdxEnd
751             int patLength = ( patIdxTmp - patIdxStart - 1 );
752             int strLength = ( strIdxEnd - strIdxStart + 1 );
753             int foundIdx = -1;
754             strLoop:
755             for ( int i = 0; i <= strLength - patLength; i++ )
756             {
757                 for ( int j = 0; j < patLength; j++ )
758                 {
759                     ch = patArr[patIdxStart + j + 1];
760                     if ( ch != '?' && ch != strArr[strIdxStart + i + j] )
761                     {
762                         continue strLoop;
763                     }
764                 }
765 
766                 foundIdx = strIdxStart + i;
767                 break;
768             }
769 
770             if ( foundIdx == -1 )
771             {
772                 return false;
773             }
774 
775             patIdxStart = patIdxTmp;
776             strIdxStart = foundIdx + patLength;
777         }
778 
779         // All characters in the string are used. Check if only '*'s are left
780         // in the pattern. If so, we succeeded. Otherwise failure.
781         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
782         {
783             if ( patArr[i] != '*' )
784             {
785                 return false;
786             }
787         }
788         return true;
789     }
790 }