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