StoryFinder.java
package org.jbehave.core.io;
import static java.util.Arrays.asList;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.plexus.util.DirectoryScanner;
import org.jbehave.core.configuration.Configuration;
/**
* <p>Finds stories by scanning source paths, which can be either filesystem
* directories or jars. Jars are identified by paths ending in ".jar".</p>
*
* <p>Stories can be either in the form of class names or story paths.</p>
*
* <p>The default class name extension is ".java".</p>
*
* <p>Stories can be sorted by providing a sorting {@link Comparator}.
* Alternatively, stories can be sorted at execution-time using the
* {@link Configuration#useStoryExecutionComparator(Comparator)} instead.</p>
*/
public class StoryFinder {
private static final String JAR = ".jar";
private static final String JAVA = ".java";
private final String classNameExtension;
private final Comparator<? super String> sortingComparator;
/**
* Creates default StoryFinder for ".java" class names and no sorting.
*/
public StoryFinder() {
this(JAVA);
}
/**
* Creates a StoryFinder with a given class name extension and no sorting.
*
* @param classNameExtension the extension
*/
public StoryFinder(String classNameExtension) {
this(classNameExtension, null);
}
/**
* Creates a StoryFinder with a given sorting comparator.
*
* @param sortingComparator comparator to sort stories by path
*/
public StoryFinder(Comparator<? super String> sortingComparator) {
this(JAVA, sortingComparator);
}
/**
* Creates a StoryFinder with given class name extension and sorting comparator.
*
* @param classNameExtension class name extensions to find
* @param sortingComparator comparator to sort stories by path
*/
private StoryFinder(String classNameExtension, Comparator<? super String> sortingComparator) {
this.classNameExtension = classNameExtension;
this.sortingComparator = sortingComparator;
}
/**
* Finds Java classes from a source path, allowing for includes/excludes, and converts them to class names.
*
* @param searchIn the path to search in
* @param includes the List of include patterns, or <code>null</code> if none
* @param excludes the List of exclude patterns, or <code>null</code> if none
* @return A List of class names found
*/
public List<String> findClassNames(String searchIn, List<String> includes, List<String> excludes) {
return classNames(normalise(sort(scan(searchIn, includes, excludes))));
}
/**
* Finds paths from a source URL, allowing for single include/exclude pattern. Paths found are normalised by
* {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source URL to search in
* @param include the include pattern, or <code>""</code> if none
* @param exclude the exclude pattern, or <code>""</code> if none
* @return A List of paths found
*/
public List<String> findPaths(URL searchIn, String include, String exclude) {
return findPaths(CodeLocations.getPathFromURL(searchIn), asCSVList(include), asCSVList(exclude));
}
/**
* Finds paths from a source URL, allowing for includes/excludes patterns. Paths found are normalised by
* {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source URL to search in
* @param includes the Array of include patterns, or <code>null</code> if none
* @param excludes the Array of exclude patterns, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(URL searchIn, String[] includes, String[] excludes) {
return findPaths(CodeLocations.getPathFromURL(searchIn), asList(includes), asList(excludes));
}
/**
* Finds paths from a source path, allowing for include/exclude patterns, which can be comma-separated values of
* multiple patterns. Paths found are normalised by {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source path to search in
* @param include the CSV include pattern, or <code>null</code> if none
* @param exclude the CSV exclude pattern, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(String searchIn, String include, String exclude) {
return findPaths(searchIn, asCSVList(include), asCSVList(exclude));
}
/**
* Finds paths from a source path, allowing for include/exclude patterns. Paths found are normalised by
* {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source path to search in
* @param includes the Array of include patterns, or <code>null</code> if none
* @param excludes the Array of exclude patterns, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(String searchIn, String[] includes, String[] excludes) {
return findPaths(searchIn, asList(includes), asList(excludes));
}
/**
* Finds paths from a source URL, allowing for includes/excludes patterns. Paths found are normalised by
* {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source URL to search in
* @param includes the List of include patterns, or <code>null</code> if none
* @param excludes the List of exclude patterns, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(URL searchIn, List<String> includes, List<String> excludes) {
return findPaths(CodeLocations.getPathFromURL(searchIn), includes, excludes);
}
/**
* Finds paths from a source path, allowing for include/exclude patterns. Paths found are normalised by
* {@link StoryFinder#normalise(List)}.
* .
*
* @param searchIn the source path to search in
* @param includes the List of include patterns, or <code>null</code> if none
* @param excludes the List of exclude patterns, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(String searchIn, List<String> includes, List<String> excludes) {
return normalise(sort(scan(searchIn, includes, excludes)));
}
/**
* Finds paths from a source path, allowing for includes/excludes. Paths found are prefixed with specified path by
* {@link StoryFinder#prefix(String, List)} and normalised by {@link StoryFinder#normalise(List)}.
*
* @param searchIn the source path to search in
* @param includes the List of include patterns, or <code>null</code> if none
* @param excludes the List of exclude patterns, or <code>null</code> if none
* @param prefixWith the root path prefixed to all paths found, or <code>null</code> if none
* @return A List of paths found
*/
public List<String> findPaths(String searchIn, List<String> includes, List<String> excludes, String prefixWith) {
return normalise(prefix(prefixWith, sort(scan(searchIn, includes, excludes))));
}
protected List<String> normalise(List<String> paths) {
return paths.stream().map(path -> path.replace('\\', '/')).collect(Collectors.toList());
}
protected List<String> prefix(final String prefixWith, List<String> paths) {
if (StringUtils.isBlank(prefixWith)) {
return paths;
}
return paths.stream().map(prefixWith::concat).collect(Collectors.toList());
}
protected List<String> classNames(List<String> paths) {
return paths.stream().map(path -> {
if (!StringUtils.endsWithIgnoreCase(path, classNameExtension())) {
return path;
}
return StringUtils.removeEndIgnoreCase(path, classNameExtension()).replace('/', '.');
}).collect(Collectors.toList());
}
protected String classNameExtension() {
return classNameExtension;
}
protected List<String> sort(List<String> input) {
List<String> sorted = new ArrayList<>(input);
Collections.sort(sorted, sortingComparator());
return sorted;
}
/**
* Comparator used for sorting. A <code>null</code> comparator means that {@link Collections#sort(List)} will use
* natural ordering.
*
* @return A Comparator or <code>null</code> for natural ordering.
*/
protected Comparator<? super String> sortingComparator() {
return sortingComparator;
}
protected List<String> scan(String source, List<String> includes, List<String> excludes) {
if (source.endsWith(JAR)) {
return scanJar(source, includes, excludes);
}
return scanDirectory(source, includes, excludes);
}
private List<String> asCSVList(String pattern) {
List<String> list;
if (pattern == null) {
list = asList();
} else {
list = asList(pattern.split(","));
}
return list;
}
private List<String> scanDirectory(String basedir, List<String> includes, List<String> excludes) {
DirectoryScanner scanner = new DirectoryScanner();
if (!new File(basedir).exists()) {
return new ArrayList<>();
}
scanner.setBasedir(basedir);
if (includes != null) {
scanner.setIncludes(includes.toArray(new String[includes.size()]));
}
if (excludes != null) {
scanner.setExcludes(excludes.toArray(new String[excludes.size()]));
}
scanner.scan();
return asList(scanner.getIncludedFiles());
}
protected List<String> scanJar(String jarPath, List<String> includes, List<String> excludes) {
return new JarFileScanner(jarPath, includes, excludes).scan();
}
}