001 /**
002 * Copyright 2008-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.codehaus.mojo.wagon.shared;
017
018 /*
019 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
020 * agreements. See the NOTICE file distributed with this work for additional information regarding
021 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
022 * "License"); you may not use this file except in compliance with the License. You may obtain a
023 * copy of the License at
024 *
025 * http://www.apache.org/licenses/LICENSE-2.0
026 *
027 * Unless required by applicable law or agreed to in writing, software distributed under the License
028 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
029 * or implied. See the License for the specific language governing permissions and limitations under
030 * the License.
031 */
032
033 import java.util.ArrayList;
034 import java.util.Collections;
035 import java.util.Iterator;
036 import java.util.List;
037
038 import org.apache.maven.plugin.logging.Log;
039 import org.apache.maven.wagon.Wagon;
040 import org.apache.maven.wagon.WagonException;
041 import org.codehaus.plexus.util.StringUtils;
042
043 public class WagonDirectoryScanner {
044 private final static String[] NOT_DIRECTORIES = new String[] { ".jar", ".zip", ".md5", ".sha1", ".pom", ".xml",
045 ".war" };
046 /**
047 * Patterns which should be excluded by default.
048 *
049 * @see #addDefaultExcludes()
050 */
051 public static final String[] DEFAULTEXCLUDES = org.codehaus.plexus.util.DirectoryScanner.DEFAULTEXCLUDES;
052
053 /**
054 * The wagon
055 */
056 private Wagon wagon;
057
058 /**
059 * Relative to wagon url
060 */
061 private String directory;
062
063 /** The patterns for the wagon files to be included. */
064 private String[] includes;
065
066 /** The patterns for the wagon files to be excluded. */
067 private String[] excludes;
068
069 /**
070 * Whether or not the file system should be treated as a case sensitive one.
071 */
072 private boolean isCaseSensitive = true;
073
074 /**
075 * The files which matched at least one include and at least one exclude and relative to directory
076 */
077 private List<String> filesIncluded = new ArrayList<String>();
078
079 private Log logger;
080
081 /**
082 * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
083 * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.
084 * <p>
085 * When a pattern ends with a '/' or '\', "**" is appended.
086 *
087 * @param includes
088 * A list of include patterns. May be <code>null</code>, indicating that all files should be included. If
089 * a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
090 */
091 public void setIncludes(String[] includes) {
092 if (includes == null) {
093 this.includes = null;
094 } else {
095 this.includes = new String[includes.length];
096 for (int i = 0; i < includes.length; i++) {
097 String pattern = includes[i].trim();
098
099 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
280 * matching of 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 logger.info("Scanning " + dir);
289 List<?> files = wagon.getFileList(dir);
290 // logger.info("files.size=" + files.size());
291
292 for (Iterator<?> itr = files.iterator(); itr.hasNext();) {
293 String fileName = (String) itr.next();
294 if (isRidiculousFile(fileName)) {
295 continue;
296 }
297
298 boolean directory = isDirectory(fileName);
299 boolean included = isIncluded(fileName);
300 boolean excluded = isExcluded(fileName);
301 boolean chi = directory && couldHoldIncluded(fileName);
302 boolean include = included && !excluded || chi && !excluded;
303
304 if (!include) {
305 logger.debug("Skipping " + fileName);
306 logger.debug("fileName=" + fileName + " included=" + included + " excluded=" + excluded + " chi=" + chi);
307 continue;
308 }
309
310 if (isDirectory(fileName)) {
311 scandir(fileName);
312 } else {
313 filesIncluded.add(fileName);
314 }
315 }
316 }
317
318 private boolean isDirectory(String existingRemotePath) throws WagonException {
319 for (int x = 0; x < NOT_DIRECTORIES.length; x++) {
320 if (existingRemotePath.endsWith(NOT_DIRECTORIES[x])) {
321 return false;
322 }
323 }
324 return existingRemotePath.endsWith("/");
325 }
326
327 public List<String> getFilesIncluded() {
328 return filesIncluded;
329 }
330
331 public void setWagon(Wagon wagon) {
332 this.wagon = wagon;
333 }
334
335 public void setCaseSensitive(boolean isCaseSensitive) {
336 this.isCaseSensitive = isCaseSensitive;
337 }
338
339 public void setDirectory(String basePath) {
340 this.directory = basePath;
341 }
342
343 public Log getLogger() {
344 return logger;
345 }
346
347 public void setLogger(Log logger) {
348 this.logger = logger;
349 }
350
351 }