    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.io.file;
    019
    020import java.io.IOException;
    021import java.math.BigInteger;
    022import java.nio.file.FileVisitResult;
    023import java.nio.file.Files;
    024import java.nio.file.Path;
    025import java.nio.file.attribute.BasicFileAttributes;
    026import java.util.Objects;
    027import java.util.function.UnaryOperator;
    028
    029import org.apache.commons.io.file.Counters.PathCounters;
    030import org.apache.commons.io.filefilter.IOFileFilter;
    031import org.apache.commons.io.filefilter.TrueFileFilter;
    032import org.apache.commons.io.function.IOBiFunction;
    033
    034/**
    035 * Counts files, directories, and sizes, as a visit proceeds.
    036 *
    037 * @since 2.7
    038 */
    039public class CountingPathVisitor extends SimplePathVisitor {
    040
    041    /**
    042     * Builds instances of {@link CountingPathVisitor}.
    043     *
    044     * @param <T> The CountingPathVisitor type.
    045     * @param <B> The AbstractBuilder type.
    046     * @since 2.19.0
    047     */
    048    public abstract static class AbstractBuilder<T, B extends AbstractBuilder<T, B>> extends SimplePathVisitor.AbstractBuilder<T, B> {
    049
    050        private PathCounters pathCounters = defaultPathCounters();
    051        private PathFilter fileFilter = defaultFileFilter();
    052        private PathFilter directoryFilter = defaultDirectoryFilter();
    053        private UnaryOperator<Path> directoryPostTransformer = defaultDirectoryTransformer();
    054
    055        /**
    056         * Constructs a new builder for subclasses.
    057         */
    058        public AbstractBuilder() {
    059            // empty.
    060        }
    061
    062        PathFilter getDirectoryFilter() {
    063            return directoryFilter;
    064        }
    065
    066        UnaryOperator<Path> getDirectoryPostTransformer() {
    067            return directoryPostTransformer;
    068        }
    069
    070        PathFilter getFileFilter() {
    071            return fileFilter;
    072        }
    073
    074        PathCounters getPathCounters() {
    075            return pathCounters;
    076        }
    077
    078        /**
    079         * Sets how to filter directories.
    080         *
    081         * @param directoryFilter how to filter files.
    082         * @return {@code this} instance.
    083         */
    084        public B setDirectoryFilter(final PathFilter directoryFilter) {
    085            this.directoryFilter = directoryFilter != null ? directoryFilter : defaultDirectoryFilter();
    086            return asThis();
    087        }
    088
    089        /**
    090         * Sets how to transform directories, defaults to {@link UnaryOperator#identity()}.
    091         *
    092         * @param directoryTransformer how to filter files.
    093         * @return {@code this} instance.
    094         */
    095        public B setDirectoryPostTransformer(final UnaryOperator<Path> directoryTransformer) {
    096            this.directoryPostTransformer = directoryTransformer != null ? directoryTransformer : defaultDirectoryTransformer();
    097            return asThis();
    098        }
    099
    100        /**
    101         * Sets how to filter files.
    102         *
    103         * @param fileFilter how to filter files.
    104         * @return {@code this} instance.
    105         */
    106        public B setFileFilter(final PathFilter fileFilter) {
    107            this.fileFilter = fileFilter != null ? fileFilter : defaultFileFilter();
    108            return asThis();
    109        }
    110
    111        /**
    112         * Sets how to count path visits.
    113         *
    114         * @param pathCounters How to count path visits.
    115         * @return {@code this} instance.
    116         */
    117        public B setPathCounters(final PathCounters pathCounters) {
    118            this.pathCounters = pathCounters != null ? pathCounters : defaultPathCounters();
    119            return asThis();
    120        }
    121    }
    122
    123    /**
    124     * Builds instances of {@link CountingPathVisitor}.
    125     *
    126     * @since 2.18.0
    127     */
    128    public static class Builder extends AbstractBuilder<CountingPathVisitor, Builder> {
    129
    130        /**
    131         * Constructs a new builder.
    132         */
    133        public Builder() {
    134            // empty.
    135        }
    136
    137        @Override
    138        public CountingPathVisitor get() {
    139            return new CountingPathVisitor(this);
    140        }
    141    }
    142
    143    static final String[] EMPTY_STRING_ARRAY = {};
    144
    145    static IOFileFilter defaultDirectoryFilter() {
    146        return TrueFileFilter.INSTANCE;
    147    }
    148
    149    static UnaryOperator<Path> defaultDirectoryTransformer() {
    150        return UnaryOperator.identity();
    151    }
    152
    153    static IOFileFilter defaultFileFilter() {
    154        return TrueFileFilter.INSTANCE;
    155    }
    156
    157    static PathCounters defaultPathCounters() {
    158        return Counters.longPathCounters();
    159    }
    160
    161    /**
    162     * Constructs a new instance configured with a {@link BigInteger} {@link PathCounters}.
    163     *
    164     * @return a new instance configured with a {@link BigInteger} {@link PathCounters}.
    165     */
    166    public static CountingPathVisitor withBigIntegerCounters() {
    167        return new Builder().setPathCounters(Counters.bigIntegerPathCounters()).get();
    168    }
    169
    170    /**
    171     * Constructs a new instance configured with a {@code long} {@link PathCounters}.
    172     *
    173     * @return a new instance configured with a {@code long} {@link PathCounters}.
    174     */
    175    public static CountingPathVisitor withLongCounters() {
    176        return new Builder().setPathCounters(Counters.longPathCounters()).get();
    177    }
    178
    179    private final PathCounters pathCounters;
    180    private final PathFilter fileFilter;
    181    private final PathFilter directoryFilter;
    182    private final UnaryOperator<Path> directoryPostTransformer;
    183
    184    CountingPathVisitor(final AbstractBuilder<?, ?> builder) {
    185        super(builder);
    186        this.pathCounters = builder.getPathCounters();
    187        this.fileFilter = builder.getFileFilter();
    188        this.directoryFilter = builder.getDirectoryFilter();
    189        this.directoryPostTransformer = builder.getDirectoryPostTransformer();
    190    }
    191
    192    /**
    193     * Constructs a new instance.
    194     *
    195     * @param pathCounters How to count path visits.
    196     * @see Builder
    197     */
    198    public CountingPathVisitor(final PathCounters pathCounters) {
    199        this(new Builder().setPathCounters(pathCounters));
    200    }
    201
    202    /**
    203     * Constructs a new instance.
    204     *
    205     * @param pathCounters    How to count path visits.
    206     * @param fileFilter      Filters which files to count.
    207     * @param directoryFilter Filters which directories to count.
    208     * @see Builder
    209     * @since 2.9.0
    210     */
    211    public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter) {
    212        this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters");
    213        this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter");
    214        this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter");
    215        this.directoryPostTransformer = UnaryOperator.identity();
    216    }
    217
    218    /**
    219     * Constructs a new instance.
    220     *
    221     * @param pathCounters    How to count path visits.
    222     * @param fileFilter      Filters which files to count.
    223     * @param directoryFilter Filters which directories to count.
    224     * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}.
    225     * @since 2.12.0
    226     * @deprecated Use {@link Builder}.
    227     */
    228    @Deprecated
    229    public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter,
    230            final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) {
    231        super(visitFileFailed);
    232        this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters");
    233        this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter");
    234        this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter");
    235        this.directoryPostTransformer = UnaryOperator.identity();
    236    }
    237
    238    /**
    239     * Tests whether the given file is accepted by the file filter.
    240     *
    241     * @param file       the visited file.
    242     * @param attributes the visited file attributes.
    243     * @return true to copy the given file, false if not.
    244     * @since 2.20.0
    245     */
    246    protected boolean accept(final Path file, final BasicFileAttributes attributes) {
    247        // Note: A file can be a symbolic link to a directory.
    248        return Files.exists(file) && fileFilter.accept(file, attributes) == FileVisitResult.CONTINUE;
    249    }
    250
    251    @Override
    252    public boolean equals(final Object obj) {
    253        if (this == obj) {
    254            return true;
    255        }
    256        if (!(obj instanceof CountingPathVisitor)) {
    257            return false;
    258        }
    259        final CountingPathVisitor other = (CountingPathVisitor) obj;
    260        return Objects.equals(pathCounters, other.pathCounters);
    261    }
    262
    263    /**
    264     * Gets the visitation counts.
    265     *
    266     * @return the visitation counts.
    267     */
    268    public PathCounters getPathCounters() {
    269        return pathCounters;
    270    }
    271
    272    @Override
    273    public int hashCode() {
    274        return Objects.hash(pathCounters);
    275    }
    276
    277    @Override
    278    public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
    279        updateDirCounter(directoryPostTransformer.apply(dir), exc);
    280        return FileVisitResult.CONTINUE;
    281    }
    282
    283    @Override
    284    public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException {
    285        final FileVisitResult accept = directoryFilter.accept(dir, attributes);
    286        return accept != FileVisitResult.CONTINUE ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE;
    287    }
    288
    289    @Override
    290    public String toString() {
    291        return pathCounters.toString();
    292    }
    293
    294    /**
    295     * Updates the counter for visiting the given directory.
    296     *
    297     * @param dir the visited directory.
    298     * @param exc Encountered exception.
    299     * @since 2.9.0
    300     */
    301    protected void updateDirCounter(final Path dir, final IOException exc) {
    302        pathCounters.getDirectoryCounter().increment();
    303    }
    304
    305    /**
    306     * Updates the counters for visiting the given file.
    307     *
    308     * @param file       the visited file.
    309     * @param attributes the visited file attributes.
    310     */
    311    protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) {
    312        pathCounters.getFileCounter().increment();
    313        pathCounters.getByteCounter().add(attributes.size());
    314    }
    315
    316    @Override
    317    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException {
    318        if (accept(file, attributes)) {
    319            updateFileCounters(file, attributes);
    320        }
    321        return FileVisitResult.CONTINUE;
    322    }
    323
    324}