View Javadoc

1   package org.codehaus.mojo.wagon.shared;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
5    * agreements. See the NOTICE file distributed with this work for additional information regarding
6    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance with the License. You may obtain a
8    * copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software distributed under the License
13   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14   * or implied. See the License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.Iterator;
21  import java.util.List;
22  
23  import org.apache.maven.plugin.logging.Log;
24  import org.apache.maven.wagon.Wagon;
25  import org.apache.maven.wagon.WagonException;
26  import org.codehaus.plexus.util.StringUtils;
27  
28  public class WagonDirectoryScanner {
29      private final static String[] NOT_DIRECTORIES = new String[] { ".jar", ".zip", ".md5", ".sha1", ".pom", ".xml",
30              ".war" };
31      /**
32       * Patterns which should be excluded by default.
33       *
34       * @see #addDefaultExcludes()
35       */
36      public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES;
37  
38      /**
39       * The wagon
40       */
41      private Wagon wagon;
42  
43      /**
44       * Relative to wagon url
45       */
46      private String directory;
47  
48      /** The patterns for the wagon files to be included. */
49      private String[] includes;
50  
51      /** The patterns for the wagon files to be excluded. */
52      private String[] excludes;
53  
54      /**
55       * Whether or not the file system should be treated as a case sensitive one.
56       */
57      private boolean isCaseSensitive = true;
58  
59      /**
60       * The files which matched at least one include and at least one exclude and relative to directory
61       */
62      private List<String> filesIncluded = new ArrayList<String>();
63  
64      private Log logger;
65  
66      /**
67       * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
68       * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
69       * <p>
70       * When a pattern ends with a '/' or '\', "**" is appended.
71       *
72       * @param includes
73       *            A list of include patterns. May be <code>null</code>, indicating that all files should be included. If
74       *            a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
75       */
76      public void setIncludes(String[] includes) {
77          if (includes == null) {
78              this.includes = null;
79          } else {
80              this.includes = new String[includes.length];
81              for (int i = 0; i < includes.length; i++) {
82                  String pattern = includes[i].trim();
83  
84                  if (pattern.endsWith("/")) {
85                      pattern += "**";
86                  }
87                  this.includes[i] = pattern;
88              }
89          }
90      }
91  
92      /**
93       * Sets the list of exclude patterns to use. All '\' characters are replaced by '/'
94       * <p>
95       * When a pattern ends with a '/' or '\', "**" is appended.
96       *
97       * @param excludes
98       *            A list of exclude patterns. May be <code>null</code>, indicating that no files should be excluded. If
99       *            a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
100      */
101     public void setExcludes(String[] excludes) {
102         if (excludes == null) {
103             this.excludes = null;
104         } else {
105             this.excludes = new String[excludes.length];
106             for (int i = 0; i < excludes.length; i++) {
107                 String pattern = excludes[i].trim();
108 
109                 if (pattern.endsWith("/")) {
110                     pattern += "**";
111                 }
112                 this.excludes[i] = pattern;
113             }
114         }
115     }
116 
117     /**
118      * Tests whether or not a name matches against at least one include pattern.
119      *
120      * @param name
121      *            The name to match. Must not be <code>null</code>.
122      * @return <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
123      *         otherwise.
124      */
125     private boolean isIncluded(String name) {
126         for (int i = 0; i < includes.length; i++) {
127             if (matchPath(includes[i], name, isCaseSensitive)) {
128                 return true;
129             }
130         }
131         return false;
132     }
133 
134     /**
135      * Tests whether or not a name matches against at least one exclude pattern.
136      *
137      * @param name
138      *            The name to match. Must not be <code>null</code>.
139      * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code>
140      *         otherwise.
141      */
142     protected boolean isExcluded(String name) {
143         for (int i = 0; i < excludes.length; i++) {
144             if (matchPath(excludes[i], name, isCaseSensitive)) {
145                 return true;
146             }
147         }
148         return false;
149     }
150 
151     /**
152      * Tests whether or not a name matches the start of at least one include pattern.
153      *
154      * @param name
155      *            The name to match. Must not be <code>null</code>.
156      * @return <code>true</code> when the name matches against the start of at least one include pattern, or
157      *         <code>false</code> otherwise.
158      */
159     protected boolean couldHoldIncluded(String name) {
160         for (int i = 0; i < includes.length; i++) {
161             if (matchPatternStart(includes[i], name, isCaseSensitive)) {
162                 return true;
163             }
164         }
165         return false;
166     }
167 
168     /**
169      * Tests whether or not a given path matches the start of a given pattern up to the first "**".
170      * <p>
171      * This is not a general purpose test and should only be used if you can live with false positives. For example,
172      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
173      *
174      * @param pattern
175      *            The pattern to match against. Must not be <code>null</code>.
176      * @param str
177      *            The path to match, as a String. Must not be <code>null</code>.
178      * @param isCaseSensitive
179      *            Whether or not matching should be performed case sensitively.
180      *
181      * @return whether or not a given path matches the start of a given pattern up to the first "**".
182      */
183     protected static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
184         return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive);
185     }
186 
187     /**
188      * Tests whether or not a given path matches a given pattern.
189      *
190      * @param pattern
191      *            The pattern to match against. Must not be <code>null</code>.
192      * @param str
193      *            The path to match, as a String. Must not be <code>null</code>.
194      * @param isCaseSensitive
195      *            Whether or not matching should be performed case sensitively.
196      *
197      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
198      */
199     private static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
200         return SelectorUtils.matchPath(pattern, str, isCaseSensitive);
201     }
202 
203     public void scan() throws WagonException {
204         if (wagon == null) {
205             throw new IllegalStateException("No wagon set");
206         }
207 
208         if (StringUtils.isBlank(directory)) {
209             directory = "";
210         }
211 
212         if (includes == null) {
213             // No includes supplied, so set it to 'matches all'
214             includes = new String[1];
215             includes[0] = "**";
216         }
217 
218         if (excludes == null) {
219             excludes = new String[0];
220         }
221 
222         filesIncluded = new ArrayList<String>();
223 
224         scandir(directory, "");
225 
226         Collections.sort(filesIncluded);
227 
228     }
229 
230     /**
231      * Adds default exclusions to the current exclusions set.
232      */
233     public void addDefaultExcludes() {
234         int excludesLength = excludes == null ? 0 : excludes.length;
235         String[] newExcludes;
236         newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
237         if (excludesLength > 0) {
238             System.arraycopy(excludes, 0, newExcludes, 0, excludesLength);
239         }
240         for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
241             newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i];
242         }
243         excludes = newExcludes;
244     }
245 
246     /**
247      * Jenkins, if nothing else, will return pathnames with * characters in them that lead to infinite recursion down
248      * here. Given the impoverished API to the wagons, some ad-hoc filtration is called for. The filters in here are
249      * just culled from strange stuff we see from Jenkins.
250      *
251      * @param fileName
252      *            supposed file name
253      * @return true if it seems like a bad idea.
254      */
255     private boolean isRidiculousFile(String fileName) {
256         return fileName.endsWith(".") || fileName.contains("*") || fileName.startsWith("?") || fileName.startsWith("#");
257     }
258 
259     // //////////////////////////////////////////////////////////////////////////////////
260     /**
261      * Scans the given directory for files and directories. Found files are placed in a collection, based on the
262      * matching of includes, excludes, and the selectors. When a directory is found, it is scanned recursively.
263      *
264      * @throws WagonException
265      *
266      * @see #filesIncluded
267      */
268     private void scandir(String dir, String vpath) throws WagonException {
269         logger.debug("scandir: dir: " + dir + " vpath: " + vpath);
270         List<?> files = wagon.getFileList(dir);
271 
272         for (Iterator<?> iterator = files.iterator(); iterator.hasNext();) {
273             String fileName = (String) iterator.next();
274 
275             if (isRidiculousFile(fileName)) {
276                 continue;
277             }
278 
279             String file = fileName;
280 
281             if (!StringUtils.isBlank(dir)) {
282                 if (dir.endsWith("/")) {
283                     file = dir + fileName;
284                 } else {
285                     file = dir + "/" + fileName;
286                 }
287             }
288 
289             String name = vpath + fileName;
290 
291             if (this.isDirectory(file)) {
292 
293                 if (!name.endsWith("/")) {
294                     name += "/";
295                 }
296 
297                 if (isIncluded(name)) {
298                     if (!isExcluded(name)) {
299                         scandir(file, name);
300                     } else {
301                         if (couldHoldIncluded(name)) {
302                             scandir(file, name);
303                         }
304                     }
305                 } else {
306                     if (couldHoldIncluded(name)) {
307                         scandir(file, name);
308                     }
309                 }
310 
311             } else {
312 
313                 if (isIncluded(name)) {
314                     if (!isExcluded(name)) {
315                         filesIncluded.add(name);
316                     }
317                 }
318             }
319         }
320     }
321 
322     private boolean isDirectory(String existedRemotePath) throws WagonException {
323         for (int x = 0; x < NOT_DIRECTORIES.length; x++) {
324             if (existedRemotePath.endsWith(NOT_DIRECTORIES[x])) {
325                 return false;
326             }
327         }
328         if (existedRemotePath.endsWith("/")) {
329             return true;
330         }
331 
332         return wagon.resourceExists(existedRemotePath + "/");
333     }
334 
335     // ///////////////////////////////////////////////////////////////////////////////
336     public List<String> getFilesIncluded() {
337         return filesIncluded;
338     }
339 
340     public void setWagon(Wagon wagon) {
341         this.wagon = wagon;
342     }
343 
344     public void setCaseSensitive(boolean isCaseSensitive) {
345         this.isCaseSensitive = isCaseSensitive;
346     }
347 
348     public void setDirectory(String basePath) {
349         this.directory = basePath;
350     }
351 
352     public Log getLogger() {
353         return logger;
354     }
355 
356     public void setLogger(Log logger) {
357         this.logger = logger;
358     }
359 
360 }