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