View Javadoc
1   package org.codehaus.mojo.wagon.shared;
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.Collections;
24  import java.util.List;
25  
26  import org.apache.maven.plugin.logging.Log;
27  import org.apache.maven.wagon.Wagon;
28  import org.apache.maven.wagon.WagonException;
29  import org.codehaus.plexus.util.StringUtils;
30  
31  public class WagonDirectoryScanner
32  {
33      /**
34       * Patterns which should be excluded by default.
35       *
36       * @see #addDefaultExcludes()
37       */
38      public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES;
39  
40      /**
41       * The wagon
42       */
43      private Wagon wagon;
44  
45      /**
46       * Relative to wagon url
47       */
48      private String directory;
49  
50      /** The patterns for the wagon files to be included. */
51      private String[] includes;
52  
53      /** The patterns for the wagon files to be excluded. */
54      private String[] excludes;
55  
56      /**
57       * Whether or not the file system should be treated as a case sensitive one.
58       */
59      private boolean isCaseSensitive = true;
60  
61      /**
62       * The files which matched at least one include and at least one exclude and relative to directory
63       */
64      private List filesIncluded = new ArrayList();
65  
66      private Log logger;
67  
68      /**
69       * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
70       * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
71       * <p>
72       * When a pattern ends with a '/' or '\', "**" is appended.
73       *
74       * @param includes A list of include patterns. May be <code>null</code>, indicating that all files should be
75       *            included. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
76       */
77      public void setIncludes( String[] includes )
78      {
79          if ( includes == null )
80          {
81              this.includes = null;
82          }
83          else
84          {
85              this.includes = new String[includes.length];
86              for ( int i = 0; i < includes.length; i++ )
87              {
88                  String pattern = includes[i].trim();
89  
90                  if ( pattern.endsWith( "/" ) )
91                  {
92                      pattern += "**";
93                  }
94                  this.includes[i] = pattern;
95              }
96          }
97      }
98  
99      /**
100      * Sets the list of exclude patterns to use. All '\' characters are replaced by '/'
101      * <p>
102      * When a pattern ends with a '/' or '\', "**" is appended.
103      *
104      * @param excludes A list of exclude patterns. May be <code>null</code>, indicating that no files should be
105      *            excluded. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
106      */
107     public void setExcludes( String[] excludes )
108     {
109         if ( excludes == null )
110         {
111             this.excludes = null;
112         }
113         else
114         {
115             this.excludes = new String[excludes.length];
116             for ( int i = 0; i < excludes.length; i++ )
117             {
118                 String pattern = excludes[i].trim();
119 
120                 if ( pattern.endsWith( "/" ) )
121                 {
122                     pattern += "**";
123                 }
124                 this.excludes[i] = pattern;
125             }
126         }
127     }
128 
129     /**
130      * Tests whether or not a name matches against at least one include pattern.
131      *
132      * @param name The name to match. Must not be <code>null</code>.
133      * @return <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
134      *         otherwise.
135      */
136     private boolean isIncluded( String name )
137     {
138         for ( String include : includes )
139         {
140             if ( matchPath( include, name, isCaseSensitive ) )
141             {
142                 return true;
143             }
144         }
145         return false;
146     }
147 
148     /**
149      * Tests whether or not a name matches against at least one exclude pattern.
150      *
151      * @param name The name to match. Must not be <code>null</code>.
152      * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code>
153      *         otherwise.
154      */
155     protected boolean isExcluded( String name )
156     {
157         for ( String exclude : excludes )
158         {
159             if ( matchPath( exclude, name, isCaseSensitive ) )
160             {
161                 return true;
162             }
163         }
164         return false;
165     }
166 
167     /**
168      * Tests whether or not a name matches the start of at least one include pattern.
169      *
170      * @param name The name to match. Must not be <code>null</code>.
171      * @return <code>true</code> when the name matches against the start of at least one include pattern, or
172      *         <code>false</code> otherwise.
173      */
174     protected boolean couldHoldIncluded( String name )
175     {
176         for ( String include : includes )
177         {
178             if ( matchPatternStart( include, name, isCaseSensitive ) )
179             {
180                 return true;
181             }
182         }
183         return false;
184     }
185 
186     /**
187      * Tests whether or not a given path matches the start of a given pattern up to the first "**".
188      * <p>
189      * This is not a general purpose test and should only be used if you can live with false positives. For example,
190      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
191      *
192      * @param pattern The pattern to match against. Must not be <code>null</code>.
193      * @param str The path to match, as a String. Must not be <code>null</code>.
194      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
195      * @return whether or not a given path matches the start of a given pattern up to the first "**".
196      */
197     protected static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
198     {
199         return SelectorUtils.matchPatternStart( pattern, str, isCaseSensitive );
200     }
201 
202     /**
203      * Tests whether or not a given path matches a given pattern.
204      *
205      * @param pattern The pattern to match against. Must not be <code>null</code>.
206      * @param str The path to match, as a String. Must not be <code>null</code>.
207      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
208      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
209      */
210     private static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
211     {
212         return SelectorUtils.matchPath( pattern, str, isCaseSensitive );
213     }
214 
215     public void scan()
216         throws WagonException
217     {
218         if ( wagon == null )
219         {
220             throw new IllegalStateException( "No wagon set" );
221         }
222 
223         if ( StringUtils.isBlank( directory ) )
224         {
225             directory = "";
226         }
227 
228         if ( includes == null )
229         {
230             // No includes supplied, so set it to 'matches all'
231             includes = new String[1];
232             includes[0] = "**";
233         }
234 
235         if ( excludes == null )
236         {
237             excludes = new String[0];
238         }
239 
240         filesIncluded = new ArrayList();
241 
242         scandir( directory, "" );
243 
244         Collections.sort( filesIncluded );
245 
246     }
247 
248     /**
249      * Adds default exclusions to the current exclusions set.
250      */
251     public void addDefaultExcludes()
252     {
253         int excludesLength = excludes == null ? 0 : excludes.length;
254         String[] newExcludes;
255         newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
256         if ( excludesLength > 0 )
257         {
258             System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
259         }
260         System.arraycopy( DEFAULTEXCLUDES, 0, newExcludes, excludesLength, DEFAULTEXCLUDES.length );
261         excludes = newExcludes;
262     }
263 
264     /**
265      * Jenkins, if nothing else, will return pathnames with * characters in them that lead to infinite recursion down
266      * here. Given the impoverished API to the wagons, some ad-hoc filtration is called for. The filters in here are
267      * just culled from strange stuff we see from Jenkins.
268      *
269      * @param fileName supposed file name
270      * @return true if it seems like a bad idea.
271      */
272     private boolean isRidiculousFile( String fileName )
273     {
274         return fileName.endsWith( "." ) || fileName.contains( "*" ) || fileName.startsWith( "?" )
275             || fileName.startsWith( "#" );
276     }
277 
278     // //////////////////////////////////////////////////////////////////////////////////
279     /**
280      * Scans the given directory for files and directories. Found files are placed in a collection, based on the
281      * matching of includes, excludes, and the selectors. When a directory is found, it is scanned recursively.
282      *
283      * @throws WagonException if any wagon error
284      * @see #filesIncluded
285      */
286     private void scandir( String dir, String vpath )
287         throws WagonException
288     {
289         logger.debug( "scandir: dir: " + dir + " vpath: " + vpath );
290         List files = wagon.getFileList( dir );
291 
292         for ( Object file1 : files )
293         {
294             String fileName = (String) file1;
295 
296             if ( isRidiculousFile( fileName ) ) // including ".."
297             {
298                 continue;
299             }
300 
301             String file = fileName;
302 
303             if ( !StringUtils.isBlank( dir ) )
304             {
305                 if ( dir.endsWith( "/" ) )
306                 {
307                     file = dir + fileName;
308                 }
309                 else
310                 {
311                     file = dir + "/" + fileName;
312                 }
313             }
314 
315             String name = vpath + fileName;
316 
317             if ( this.isDirectory( file ) )
318             {
319 
320                 if ( !name.endsWith( "/" ) )
321                 {
322                     name += "/";
323                 }
324 
325                 if ( isIncluded( name ) )
326                 {
327                     if ( !isExcluded( name ) )
328                     {
329                         scandir( file, name );
330                     }
331                     else
332                     {
333                         if ( couldHoldIncluded( name ) )
334                         {
335                             scandir( file, name );
336                         }
337                     }
338                 }
339                 else
340                 {
341                     if ( couldHoldIncluded( name ) )
342                     {
343                         scandir( file, name );
344                     }
345                 }
346 
347             }
348             else
349             {
350 
351                 if ( isIncluded( name ) )
352                 {
353                     if ( !isExcluded( name ) )
354                     {
355                         filesIncluded.add( name );
356                     }
357                 }
358             }
359         }
360     }
361 
362     private boolean isDirectory( String existedRemotePath ) {
363         if ( existedRemotePath.endsWith( "/" ) )
364         {
365             return true;
366         }
367 
368         return canListPath( existedRemotePath + "/" );
369     }
370 
371     private boolean canListPath( String remotePath )
372     {
373         try
374         {
375             List resources = wagon.getFileList( remotePath );
376             return resources != null && !resources.isEmpty();
377         }
378         catch ( WagonException e )
379         {
380             return false;
381         }
382     }
383 
384     // ///////////////////////////////////////////////////////////////////////////////
385     public List getFilesIncluded()
386     {
387         return filesIncluded;
388     }
389 
390     public void setWagon( Wagon wagon )
391     {
392         this.wagon = wagon;
393     }
394 
395     public void setCaseSensitive( boolean isCaseSensitive )
396     {
397         this.isCaseSensitive = isCaseSensitive;
398     }
399 
400     public void setDirectory( String basePath )
401     {
402         this.directory = basePath;
403     }
404 
405     public Log getLogger()
406     {
407         return logger;
408     }
409 
410     public void setLogger( Log logger )
411     {
412         this.logger = logger;
413     }
414 
415 }