CountingPathVisitor.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.io.file;

  18. import java.io.IOException;
  19. import java.math.BigInteger;
  20. import java.nio.file.FileVisitResult;
  21. import java.nio.file.Files;
  22. import java.nio.file.Path;
  23. import java.nio.file.attribute.BasicFileAttributes;
  24. import java.util.Objects;
  25. import java.util.function.UnaryOperator;

  26. import org.apache.commons.io.file.Counters.PathCounters;
  27. import org.apache.commons.io.filefilter.IOFileFilter;
  28. import org.apache.commons.io.filefilter.SymbolicLinkFileFilter;
  29. import org.apache.commons.io.filefilter.TrueFileFilter;
  30. import org.apache.commons.io.function.IOBiFunction;

  31. /**
  32.  * Counts files, directories, and sizes, as a visit proceeds.
  33.  *
  34.  * @since 2.7
  35.  */
  36. public class CountingPathVisitor extends SimplePathVisitor {

  37.     /**
  38.      * Builds instances of {@link CountingPathVisitor}.
  39.      *
  40.      * @param <T> The CountingPathVisitor type.
  41.      * @param <B> The AbstractBuilder type.
  42.      * @since 2.19.0
  43.      */
  44.     public abstract static class AbstractBuilder<T, B extends AbstractBuilder<T, B>> extends SimplePathVisitor.AbstractBuilder<T, B> {

  45.         private PathCounters pathCounters = defaultPathCounters();
  46.         private PathFilter fileFilter = defaultFileFilter();
  47.         private PathFilter directoryFilter = defaultDirectoryFilter();
  48.         private UnaryOperator<Path> directoryPostTransformer = defaultDirectoryTransformer();

  49.         /**
  50.          * Constructs a new builder for subclasses.
  51.          */
  52.         public AbstractBuilder() {
  53.             // empty.
  54.         }

  55.         PathFilter getDirectoryFilter() {
  56.             return directoryFilter;
  57.         }

  58.         UnaryOperator<Path> getDirectoryPostTransformer() {
  59.             return directoryPostTransformer;
  60.         }

  61.         PathFilter getFileFilter() {
  62.             return fileFilter;
  63.         }

  64.         PathCounters getPathCounters() {
  65.             return pathCounters;
  66.         }

  67.         /**
  68.          * Sets how to filter directories.
  69.          *
  70.          * @param directoryFilter how to filter files.
  71.          * @return this instance.
  72.          */
  73.         public B setDirectoryFilter(final PathFilter directoryFilter) {
  74.             this.directoryFilter = directoryFilter != null ? directoryFilter : defaultDirectoryFilter();
  75.             return asThis();
  76.         }

  77.         /**
  78.          * Sets how to transform directories, defaults to {@link UnaryOperator#identity()}.
  79.          *
  80.          * @param directoryTransformer how to filter files.
  81.          * @return this instance.
  82.          */
  83.         public B setDirectoryPostTransformer(final UnaryOperator<Path> directoryTransformer) {
  84.             this.directoryPostTransformer = directoryTransformer != null ? directoryTransformer : defaultDirectoryTransformer();
  85.             return asThis();
  86.         }

  87.         /**
  88.          * Sets how to filter files.
  89.          *
  90.          * @param fileFilter how to filter files.
  91.          * @return this instance.
  92.          */
  93.         public B setFileFilter(final PathFilter fileFilter) {
  94.             this.fileFilter = fileFilter != null ? fileFilter : defaultFileFilter();
  95.             return asThis();
  96.         }

  97.         /**
  98.          * Sets how to count path visits.
  99.          *
  100.          * @param pathCounters How to count path visits.
  101.          * @return this instance.
  102.          */
  103.         public B setPathCounters(final PathCounters pathCounters) {
  104.             this.pathCounters = pathCounters != null ? pathCounters : defaultPathCounters();
  105.             return asThis();
  106.         }
  107.     }

  108.     /**
  109.      * Builds instances of {@link CountingPathVisitor}.
  110.      *
  111.      * @since 2.18.0
  112.      */
  113.     public static class Builder extends AbstractBuilder<CountingPathVisitor, Builder> {

  114.         /**
  115.          * Constructs a new builder.
  116.          */
  117.         public Builder() {
  118.             // empty.
  119.         }

  120.         @Override
  121.         public CountingPathVisitor get() {
  122.             return new CountingPathVisitor(this);
  123.         }
  124.     }

  125.     static final String[] EMPTY_STRING_ARRAY = {};

  126.     static IOFileFilter defaultDirectoryFilter() {
  127.         return TrueFileFilter.INSTANCE;
  128.     }

  129.     static UnaryOperator<Path> defaultDirectoryTransformer() {
  130.         return UnaryOperator.identity();
  131.     }

  132.     static IOFileFilter defaultFileFilter() {
  133.         return new SymbolicLinkFileFilter(FileVisitResult.TERMINATE, FileVisitResult.CONTINUE);
  134.     }

  135.     static PathCounters defaultPathCounters() {
  136.         return Counters.longPathCounters();
  137.     }

  138.     /**
  139.      * Constructs a new instance configured with a {@link BigInteger} {@link PathCounters}.
  140.      *
  141.      * @return a new instance configured with a {@link BigInteger} {@link PathCounters}.
  142.      */
  143.     public static CountingPathVisitor withBigIntegerCounters() {
  144.         return new Builder().setPathCounters(Counters.bigIntegerPathCounters()).get();
  145.     }

  146.     /**
  147.      * Constructs a new instance configured with a {@code long} {@link PathCounters}.
  148.      *
  149.      * @return a new instance configured with a {@code long} {@link PathCounters}.
  150.      */
  151.     public static CountingPathVisitor withLongCounters() {
  152.         return new Builder().setPathCounters(Counters.longPathCounters()).get();
  153.     }

  154.     private final PathCounters pathCounters;
  155.     private final PathFilter fileFilter;
  156.     private final PathFilter directoryFilter;
  157.     private final UnaryOperator<Path> directoryPostTransformer;

  158.     CountingPathVisitor(final AbstractBuilder<?, ?> builder) {
  159.         super(builder);
  160.         this.pathCounters = builder.getPathCounters();
  161.         this.fileFilter = builder.getFileFilter();
  162.         this.directoryFilter = builder.getDirectoryFilter();
  163.         this.directoryPostTransformer = builder.getDirectoryPostTransformer();
  164.     }

  165.     /**
  166.      * Constructs a new instance.
  167.      *
  168.      * @param pathCounters How to count path visits.
  169.      * @see Builder
  170.      */
  171.     public CountingPathVisitor(final PathCounters pathCounters) {
  172.         this(new Builder().setPathCounters(pathCounters));
  173.     }

  174.     /**
  175.      * Constructs a new instance.
  176.      *
  177.      * @param pathCounters    How to count path visits.
  178.      * @param fileFilter      Filters which files to count.
  179.      * @param directoryFilter Filters which directories to count.
  180.      * @see Builder
  181.      * @since 2.9.0
  182.      */
  183.     public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter) {
  184.         this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters");
  185.         this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter");
  186.         this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter");
  187.         this.directoryPostTransformer = UnaryOperator.identity();
  188.     }

  189.     /**
  190.      * Constructs a new instance.
  191.      *
  192.      * @param pathCounters    How to count path visits.
  193.      * @param fileFilter      Filters which files to count.
  194.      * @param directoryFilter Filters which directories to count.
  195.      * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}.
  196.      * @since 2.12.0
  197.      * @deprecated Use {@link Builder}.
  198.      */
  199.     @Deprecated
  200.     public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter,
  201.             final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) {
  202.         super(visitFileFailed);
  203.         this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters");
  204.         this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter");
  205.         this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter");
  206.         this.directoryPostTransformer = UnaryOperator.identity();
  207.     }

  208.     @Override
  209.     public boolean equals(final Object obj) {
  210.         if (this == obj) {
  211.             return true;
  212.         }
  213.         if (!(obj instanceof CountingPathVisitor)) {
  214.             return false;
  215.         }
  216.         final CountingPathVisitor other = (CountingPathVisitor) obj;
  217.         return Objects.equals(pathCounters, other.pathCounters);
  218.     }

  219.     /**
  220.      * Gets the visitation counts.
  221.      *
  222.      * @return the visitation counts.
  223.      */
  224.     public PathCounters getPathCounters() {
  225.         return pathCounters;
  226.     }

  227.     @Override
  228.     public int hashCode() {
  229.         return Objects.hash(pathCounters);
  230.     }

  231.     @Override
  232.     public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
  233.         updateDirCounter(directoryPostTransformer.apply(dir), exc);
  234.         return FileVisitResult.CONTINUE;
  235.     }

  236.     @Override
  237.     public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException {
  238.         final FileVisitResult accept = directoryFilter.accept(dir, attributes);
  239.         return accept != FileVisitResult.CONTINUE ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE;
  240.     }

  241.     @Override
  242.     public String toString() {
  243.         return pathCounters.toString();
  244.     }

  245.     /**
  246.      * Updates the counter for visiting the given directory.
  247.      *
  248.      * @param dir the visited directory.
  249.      * @param exc Encountered exception.
  250.      * @since 2.9.0
  251.      */
  252.     protected void updateDirCounter(final Path dir, final IOException exc) {
  253.         pathCounters.getDirectoryCounter().increment();
  254.     }

  255.     /**
  256.      * Updates the counters for visiting the given file.
  257.      *
  258.      * @param file       the visited file.
  259.      * @param attributes the visited file attributes.
  260.      */
  261.     protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) {
  262.         pathCounters.getFileCounter().increment();
  263.         pathCounters.getByteCounter().add(attributes.size());
  264.     }

  265.     @Override
  266.     public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException {
  267.         // Note: A file can be a symbolic link to a directory.
  268.         if (Files.exists(file) && fileFilter.accept(file, attributes) == FileVisitResult.CONTINUE) {
  269.             updateFileCounters(file, attributes);
  270.         }
  271.         return FileVisitResult.CONTINUE;
  272.     }
  273. }