001 package org.codehaus.mojo.wagon.shared;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
005 * agreements. See the NOTICE file distributed with this work for additional information regarding
006 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance with the License. You may obtain a
008 * copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed under the License
013 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
014 * or implied. See the License for the specific language governing permissions and limitations under
015 * the License.
016 */
017
018 import java.util.ArrayList;
019 import java.util.Collections;
020 import java.util.Iterator;
021 import java.util.List;
022
023 import org.apache.maven.plugin.logging.Log;
024 import org.apache.maven.wagon.Wagon;
025 import org.apache.maven.wagon.WagonException;
026 import org.codehaus.plexus.util.StringUtils;
027
028 public class WagonDirectoryScanner {
029 private final static String[] NOT_DIRECTORIES = new String[] { ".jar", ".zip", ".md5", ".sha1", ".pom", ".xml",
030 ".war" };
031 /**
032 * Patterns which should be excluded by default.
033 *
034 * @see #addDefaultExcludes()
035 */
036 public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES;
037
038 /**
039 * The wagon
040 */
041 private Wagon wagon;
042
043 /**
044 * Relative to wagon url
045 */
046 private String directory;
047
048 /** The patterns for the wagon files to be included. */
049 private String[] includes;
050
051 /** The patterns for the wagon files to be excluded. */
052 private String[] excludes;
053
054 /**
055 * Whether or not the file system should be treated as a case sensitive one.
056 */
057 private boolean isCaseSensitive = true;
058
059 /**
060 * The files which matched at least one include and at least one exclude and relative to directory
061 */
062 private List<String> filesIncluded = new ArrayList<String>();
063
064 private Log logger;
065
066 /**
067 * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
068 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
069 * <p>
070 * When a pattern ends with a '/' or '\', "**" is appended.
071 *
072 * @param includes
073 * A list of include patterns. May be <code>null</code>, indicating that all files should be included. If
074 * a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
075 */
076 public void setIncludes(String[] includes) {
077 if (includes == null) {
078 this.includes = null;
079 } else {
080 this.includes = new String[includes.length];
081 for (int i = 0; i < includes.length; i++) {
082 String pattern = includes[i].trim();
083
084 if (pattern.endsWith("/")) {
085 pattern += "**";
086 }
087 this.includes[i] = pattern;
088 }
089 }
090 }
091
092 /**
093 * Sets the list of exclude patterns to use. All '\' characters are replaced by '/'
094 * <p>
095 * When a pattern ends with a '/' or '\', "**" is appended.
096 *
097 * @param excludes
098 * A list of exclude patterns. May be <code>null</code>, indicating that no files should be excluded. If
099 * 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 }