View Javadoc

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