StoryFinder.java

  1. package org.jbehave.core.io;

  2. import static java.util.Arrays.asList;

  3. import java.io.File;
  4. import java.net.URL;
  5. import java.util.ArrayList;
  6. import java.util.Collections;
  7. import java.util.Comparator;
  8. import java.util.List;
  9. import java.util.stream.Collectors;

  10. import org.apache.commons.lang3.StringUtils;
  11. import org.codehaus.plexus.util.DirectoryScanner;
  12. import org.jbehave.core.configuration.Configuration;

  13. /**
  14.  * <p>Finds stories by scanning source paths, which can be either filesystem
  15.  * directories or jars. Jars are identified by paths ending in ".jar".</p>
  16.  *
  17.  * <p>Stories can be either in the form of class names or story paths.</p>
  18.  *
  19.  * <p>The default class name extension is ".java".</p>
  20.  *
  21.  * <p>Stories can be sorted by providing a sorting {@link Comparator}.
  22.  * Alternatively, stories can be sorted at execution-time using the
  23.  * {@link Configuration#useStoryExecutionComparator(Comparator)} instead.</p>
  24.  */
  25. public class StoryFinder {

  26.     private static final String JAR = ".jar";
  27.     private static final String JAVA = ".java";
  28.     private final String classNameExtension;
  29.     private final Comparator<? super String> sortingComparator;

  30.     /**
  31.      * Creates default StoryFinder for ".java" class names and no sorting.
  32.      */
  33.     public StoryFinder() {
  34.         this(JAVA);
  35.     }

  36.     /**
  37.      * Creates a StoryFinder with a given class name extension and no sorting.
  38.      *
  39.      * @param classNameExtension the extension
  40.      */
  41.     public StoryFinder(String classNameExtension) {
  42.         this(classNameExtension, null);
  43.     }

  44.     /**
  45.      * Creates a StoryFinder with a given sorting comparator.
  46.      *
  47.      * @param sortingComparator comparator to sort stories by path
  48.      */
  49.     public StoryFinder(Comparator<? super String> sortingComparator) {
  50.         this(JAVA, sortingComparator);
  51.     }

  52.     /**
  53.      * Creates a StoryFinder with given class name extension and sorting comparator.
  54.      *
  55.      * @param classNameExtension class name extensions to find
  56.      * @param sortingComparator  comparator to sort stories by path
  57.      */
  58.     private StoryFinder(String classNameExtension, Comparator<? super String> sortingComparator) {
  59.         this.classNameExtension = classNameExtension;
  60.         this.sortingComparator = sortingComparator;
  61.     }

  62.     /**
  63.      * Finds Java classes from a source path, allowing for includes/excludes, and converts them to class names.
  64.      *
  65.      * @param searchIn the path to search in
  66.      * @param includes the List of include patterns, or <code>null</code> if none
  67.      * @param excludes the List of exclude patterns, or <code>null</code> if none
  68.      * @return A List of class names found
  69.      */
  70.     public List<String> findClassNames(String searchIn, List<String> includes, List<String> excludes) {
  71.         return classNames(normalise(sort(scan(searchIn, includes, excludes))));
  72.     }

  73.     /**
  74.      * Finds paths from a source URL, allowing for single include/exclude pattern. Paths found are normalised by
  75.      * {@link StoryFinder#normalise(List)}.
  76.      *
  77.      * @param searchIn the source URL to search in
  78.      * @param include  the include pattern, or <code>""</code> if none
  79.      * @param exclude  the exclude pattern, or <code>""</code> if none
  80.      * @return A List of paths found
  81.      */
  82.     public List<String> findPaths(URL searchIn, String include, String exclude) {
  83.         return findPaths(CodeLocations.getPathFromURL(searchIn), asCSVList(include), asCSVList(exclude));
  84.     }

  85.     /**
  86.      * Finds paths from a source URL, allowing for includes/excludes patterns. Paths found are normalised by
  87.      * {@link StoryFinder#normalise(List)}.
  88.      *
  89.      * @param searchIn the source URL to search in
  90.      * @param includes the Array of include patterns, or <code>null</code> if none
  91.      * @param excludes the Array of exclude patterns, or <code>null</code> if none
  92.      * @return A List of paths found
  93.      */
  94.     public List<String> findPaths(URL searchIn, String[] includes, String[] excludes) {
  95.         return findPaths(CodeLocations.getPathFromURL(searchIn), asList(includes), asList(excludes));
  96.     }

  97.     /**
  98.      * Finds paths from a source path, allowing for include/exclude patterns, which can be comma-separated values of
  99.      * multiple patterns. Paths found are normalised by {@link StoryFinder#normalise(List)}.
  100.      *
  101.      * @param searchIn the source path to search in
  102.      * @param include the CSV include pattern, or <code>null</code> if none
  103.      * @param exclude the CSV exclude pattern, or <code>null</code> if none
  104.      * @return A List of paths found
  105.      */
  106.     public List<String> findPaths(String searchIn, String include, String exclude) {
  107.         return findPaths(searchIn, asCSVList(include), asCSVList(exclude));
  108.     }

  109.     /**
  110.      * Finds paths from a source path, allowing for include/exclude patterns. Paths found are normalised by
  111.      * {@link StoryFinder#normalise(List)}.
  112.      *
  113.      * @param searchIn the source path to search in
  114.      * @param includes the Array of include patterns, or <code>null</code> if none
  115.      * @param excludes the Array of exclude patterns, or <code>null</code> if none
  116.      * @return A List of paths found
  117.      */
  118.     public List<String> findPaths(String searchIn, String[] includes, String[] excludes) {
  119.         return findPaths(searchIn, asList(includes), asList(excludes));
  120.     }

  121.     /**
  122.      * Finds paths from a source URL, allowing for includes/excludes patterns. Paths found are normalised by
  123.      * {@link StoryFinder#normalise(List)}.
  124.      *
  125.      * @param searchIn the source URL to search in
  126.      * @param includes the List of include patterns, or <code>null</code> if none
  127.      * @param excludes the List of exclude patterns, or <code>null</code> if none
  128.      * @return A List of paths found
  129.      */
  130.     public List<String> findPaths(URL searchIn, List<String> includes, List<String> excludes) {
  131.         return findPaths(CodeLocations.getPathFromURL(searchIn), includes, excludes);
  132.     }

  133.     /**
  134.      * Finds paths from a source path, allowing for include/exclude patterns. Paths found are normalised by
  135.      * {@link StoryFinder#normalise(List)}.
  136.      * .
  137.      *
  138.      * @param searchIn the source path to search in
  139.      * @param includes the List of include patterns, or <code>null</code> if none
  140.      * @param excludes the List of exclude patterns, or <code>null</code> if none
  141.      * @return A List of paths found
  142.      */
  143.     public List<String> findPaths(String searchIn, List<String> includes, List<String> excludes) {
  144.         return normalise(sort(scan(searchIn, includes, excludes)));
  145.     }

  146.     /**
  147.      * Finds paths from a source path, allowing for includes/excludes. Paths found are prefixed with specified path by
  148.      * {@link StoryFinder#prefix(String, List)} and normalised by {@link StoryFinder#normalise(List)}.
  149.      *
  150.      * @param searchIn the source path to search in
  151.      * @param includes the List of include patterns, or <code>null</code> if none
  152.      * @param excludes the List of exclude patterns, or <code>null</code> if none
  153.      * @param prefixWith the root path prefixed to all paths found, or <code>null</code> if none
  154.      * @return A List of paths found
  155.      */
  156.     public List<String> findPaths(String searchIn, List<String> includes, List<String> excludes, String prefixWith) {
  157.         return normalise(prefix(prefixWith, sort(scan(searchIn, includes, excludes))));
  158.     }

  159.     protected List<String> normalise(List<String> paths) {
  160.         return paths.stream().map(path -> path.replace('\\', '/')).collect(Collectors.toList());
  161.     }

  162.     protected List<String> prefix(final String prefixWith, List<String> paths) {
  163.         if (StringUtils.isBlank(prefixWith)) {
  164.             return paths;
  165.         }
  166.         return paths.stream().map(prefixWith::concat).collect(Collectors.toList());
  167.     }

  168.     protected List<String> classNames(List<String> paths) {
  169.         return paths.stream().map(path -> {
  170.             if (!StringUtils.endsWithIgnoreCase(path, classNameExtension())) {
  171.                 return path;
  172.             }
  173.             return StringUtils.removeEndIgnoreCase(path, classNameExtension()).replace('/', '.');
  174.         }).collect(Collectors.toList());
  175.     }

  176.     protected String classNameExtension() {
  177.         return classNameExtension;
  178.     }

  179.     protected List<String> sort(List<String> input) {
  180.         List<String> sorted = new ArrayList<>(input);
  181.         Collections.sort(sorted, sortingComparator());
  182.         return sorted;
  183.     }

  184.     /**
  185.      * Comparator used for sorting. A <code>null</code> comparator means that {@link Collections#sort(List)} will use
  186.      * natural ordering.
  187.      *
  188.      * @return A Comparator or <code>null</code> for natural ordering.
  189.      */
  190.     protected Comparator<? super String> sortingComparator() {
  191.         return sortingComparator;
  192.     }

  193.     protected List<String> scan(String source, List<String> includes, List<String> excludes) {
  194.         if (source.endsWith(JAR)) {
  195.             return scanJar(source, includes, excludes);
  196.         }
  197.         return scanDirectory(source, includes, excludes);
  198.     }

  199.     private List<String> asCSVList(String pattern) {
  200.         List<String> list;
  201.         if (pattern == null) {
  202.             list = asList();
  203.         } else {
  204.             list = asList(pattern.split(","));
  205.         }
  206.         return list;
  207.     }

  208.     private List<String> scanDirectory(String basedir, List<String> includes, List<String> excludes) {
  209.         DirectoryScanner scanner = new DirectoryScanner();
  210.         if (!new File(basedir).exists()) {
  211.             return new ArrayList<>();
  212.         }
  213.         scanner.setBasedir(basedir);
  214.         if (includes != null) {
  215.             scanner.setIncludes(includes.toArray(new String[includes.size()]));
  216.         }
  217.         if (excludes != null) {
  218.             scanner.setExcludes(excludes.toArray(new String[excludes.size()]));
  219.         }
  220.         scanner.scan();
  221.         return asList(scanner.getIncludedFiles());
  222.     }

  223.     protected List<String> scanJar(String jarPath, List<String> includes, List<String> excludes) {
  224.         return new JarFileScanner(jarPath, includes, excludes).scan();
  225.     }

  226. }