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 }