/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.util;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.util.datasource.DataSource;
import net.sourceforge.pmd.util.datasource.FileDataSource;
import net.sourceforge.pmd.util.datasource.ZipDataSource;
import net.sourceforge.pmd.util.filter.AndFilter;
import net.sourceforge.pmd.util.filter.Filter;
import net.sourceforge.pmd.util.filter.Filters;
import net.sourceforge.pmd.util.filter.OrFilter;
/**
* This is a utility class for working with Files.
*/
public final class FileUtil {
private FileUtil() {
}
/**
* Helper method to get a filename without its extension
*
* @param fileName
* String
* @return String
*/
public static String getFileNameWithoutExtension(String fileName) {
String name = fileName;
int index = fileName.lastIndexOf('.');
if (index != -1) {
name = fileName.substring(0, index);
}
return name;
}
/**
* Normalizes the filename by taking the casing into account, e.g. on
* Windows, the filename is changed to lowercase only.
*
* @param fileName
* the file name
* @return the normalized file name
*/
public static String normalizeFilename(String fileName) {
if (fileName != null && File.separatorChar == '\\') {
// windows
return fileName.toLowerCase(Locale.ROOT);
}
return fileName;
}
/**
* Collects a list of DataSources using a comma separated list of input file
* locations to process. If a file location is a directory, the directory
* hierarchy will be traversed to look for files. If a file location is a
* ZIP or Jar the archive will be scanned looking for files. If a file
* location is a file, it will be used. For each located file, a
* FilenameFilter is used to decide whether to return a DataSource.
*
* @param fileLocations
* A comma-separated list of file locations.
* @param filenameFilter
* The FilenameFilter to apply to files.
* @return A list of DataSources, one for each file collected.
*/
public static List<DataSource> collectFiles(String fileLocations, FilenameFilter filenameFilter) {
List<DataSource> dataSources = new ArrayList<>();
for (String fileLocation : fileLocations.split(",")) {
collect(dataSources, fileLocation, filenameFilter);
}
return dataSources;
}
private static List<DataSource> collect(List<DataSource> dataSources, String fileLocation,
FilenameFilter filenameFilter) {
File file = new File(fileLocation);
if (!file.exists()) {
throw new RuntimeException("File " + file.getName() + " doesn't exist");
}
if (!file.isDirectory()) {
if (fileLocation.endsWith(".zip") || fileLocation.endsWith(".jar")) {
ZipFile zipFile;
try {
zipFile = new ZipFile(fileLocation);
Enumeration<? extends ZipEntry> e = zipFile.entries();
while (e.hasMoreElements()) {
ZipEntry zipEntry = e.nextElement();
if (filenameFilter.accept(null, zipEntry.getName())) {
dataSources.add(new ZipDataSource(zipFile, zipEntry));
}
}
} catch (IOException ze) {
throw new RuntimeException("Archive file " + file.getName() + " can't be opened");
}
} else {
dataSources.add(new FileDataSource(file));
}
} else {
// Match files, or directories which are not excluded.
// FUTURE Make the excluded directories be some configurable option
Filter<File> filter = new OrFilter<>(Filters.toFileFilter(filenameFilter),
new AndFilter<>(Filters.getDirectoryFilter(), Filters.toNormalizedFileFilter(
Filters.buildRegexFilterExcludeOverInclude(null, Collections.singletonList("SCCS")))));
FileFinder finder = new FileFinder();
List<File> files = finder.findFilesFrom(file, Filters.toFilenameFilter(filter), true);
for (File f : files) {
dataSources.add(new FileDataSource(f));
}
}
return dataSources;
}
/**
* Handy method to find a certain pattern into a file. While this method
* lives in the FileUtils, it was designed with with unit test in mind (to
* check result redirected into a file)
*
* @param file
* @param pattern
* @return
*/
public static boolean findPatternInFile(final File file, final String pattern) {
Pattern regexp = Pattern.compile(pattern);
Matcher matcher = regexp.matcher("");
FileIterable it = new FileIterable(file);
for (String line : it) {
matcher.reset(line); // reset the input
if (matcher.find()) {
return true;
}
}
return false;
}
/**
* Reads the file, which contains the filelist. This is used for the
* command line arguments --filelist/-filelist for both PMD and CPD.
* The separator in the filelist is a command and/or newlines.
*
* @param filelist the file which contains the list of path names
* @return a comma-separated list of file paths
* @throws IOException if the file couldn't be read
*/
public static String readFilelist(File filelist) throws IOException {
String filePaths = FileUtils.readFileToString(filelist);
filePaths = StringUtils.trimToEmpty(filePaths);
filePaths = filePaths.replaceAll("\\r?\\n", ",");
filePaths = filePaths.replaceAll(",+", ",");
return filePaths;
}
}