/*
* RHQ Management Platform
* Copyright (C) 2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.core.pc.drift;
import static org.rhq.core.util.file.FileUtil.generateRegex;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
import org.rhq.core.domain.drift.Filter;
import org.rhq.core.util.file.FileUtil;
import org.rhq.core.util.file.FileVisitor;
import org.rhq.core.util.file.PathFilter;
/**
* A file visitor that peforms filtering using specified filters. When a file matches a
* filter this visitor delegates to another {@link FileVisitor} object. The filtering rules
* are as follows:
* <p/>
* <ul>
* <li>When no filters are specified, are files are considered matches.</li>
* <li>
* When one or more includes filters is specified and no excludes filters are
* specified, only files matching at least one of the includes filter(s) are
* considered matches.
* </li>
* <li>
* When one or more excludes filters is specified and no includes filters are
* specified, only files that do not match any of the excludes filters are
* considered matches.
* </li>
* <li>
* When both includes and excludes filters are specified, only files that match at
* at least one of the includes and do not match any of the excludes are considered
* matches.
* </li>
* </ul>
* <br/><br/>
* Note that the filtering is done on files and not on directories. This is a subtle yet
* important distinction. Suppose we have the following filter pattern, *.war, that we apply
* to the deploy directory of our JBoss application server. That filter will match things
* like foo.war, foo-123.war, or more precisely, any file that ends with .war. The pattern
* will not however match things like foo.war/ where foo.war is a directory (i.e., exploded
* webapp) since matching is not done on directories. To match the contents of foo.war/
* you could use something like *.war/**.
* <br/><br/>
* If a filter path denotes a directory and if no pattern is specified, then it is assumed
* everything in the directory (including subdirectories) should be considered a match.
*/
public class FilterFileVisitor implements FileVisitor {
private List<PathFilter> includes;
private List<PathFilter> excludes;
private FileVisitor visitor;
private Pattern includesPattern;
private Pattern excludesPattern;
public FilterFileVisitor(File basedir, List<Filter> includes, List<Filter> excludes, FileVisitor visitor) {
this.includes = convert(basedir, includes);
this.excludes = convert(basedir, excludes);
this.visitor = visitor;
includesPattern = generateRegex(this.includes);
excludesPattern = generateRegex(this.excludes);
}
private List<PathFilter> convert(File basedir, List<Filter> filters) {
List<PathFilter> pathFilters = new ArrayList<PathFilter>(filters.size());
for (Filter filter : filters) {
pathFilters.add(normalize(basedir, filter));
}
return pathFilters;
}
/**
* Besides converting the {@link Filter} into a {@link PathFilter}, this method does a
* couple additional things. If the path is relative, it is expanded into an absolute
* path. If the path denotes a directory and if no pattern is specified, it is assumed
* that everything under that directory including sub directories should be considered
* matches.
*
* @param basedir The base directory from which drift detection is being done
* @param filter The filter to convert and normalize
* @return The converted and normalized filter
*/
private PathFilter normalize(File basedir, Filter filter) {
File path = new File(filter.getPath());
File filterPath;
String filterPattern;
if (path.isAbsolute()) {
filterPath = path;
} else {
filterPath = new File(basedir, filter.getPath());
}
if (filterPath.isDirectory() && isEmpty(filter.getPattern())) {
filterPattern = "**/*";
} else {
filterPattern = filter.getPattern();
}
// Calling getAbsolutePath will ensure the filterPath has the native file separator characters. But
// it is also important that the pattern use native separators because the ultimate matching will be
// against native file paths.
filterPattern = FileUtil.useNativeSlash(filterPattern);
return new PathFilter(FilenameUtils.normalize(filterPath.getAbsolutePath()), filterPattern);
}
private boolean isEmpty(String s) {
return s == null || s.length() == 0;
}
@Override
public void visit(File file) {
if (includes.isEmpty() && excludes.isEmpty()) {
visitor.visit(file);
} else if (!includes.isEmpty() && excludes.isEmpty()) {
if (includesPattern.matcher(file.getAbsolutePath()).matches()) {
visitor.visit(file);
}
} else if (includes.isEmpty() && !excludes.isEmpty()) {
if (!excludesPattern.matcher(file.getAbsolutePath()).matches()) {
visitor.visit(file);
}
} else {
// else neither includes nor excludes is empty
if (includesPattern.matcher(file.getAbsolutePath()).matches()
&& !excludesPattern.matcher(file.getAbsolutePath()).matches()) {
visitor.visit(file);
}
}
}
}