View Javadoc
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    *      https://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  
18  package org.apache.commons.io.file;
19  
20  import java.io.IOException;
21  import java.math.BigInteger;
22  import java.nio.file.FileVisitResult;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.attribute.BasicFileAttributes;
26  import java.util.Objects;
27  import java.util.function.UnaryOperator;
28  
29  import org.apache.commons.io.file.Counters.PathCounters;
30  import org.apache.commons.io.filefilter.IOFileFilter;
31  import org.apache.commons.io.filefilter.TrueFileFilter;
32  import org.apache.commons.io.function.IOBiFunction;
33  
34  /**
35   * Counts files, directories, and sizes, as a visit proceeds.
36   *
37   * @since 2.7
38   */
39  public class CountingPathVisitor extends SimplePathVisitor {
40  
41      /**
42       * Builds instances of {@link CountingPathVisitor}.
43       *
44       * @param <T> The CountingPathVisitor type.
45       * @param <B> The AbstractBuilder type.
46       * @since 2.19.0
47       */
48      public abstract static class AbstractBuilder<T, B extends AbstractBuilder<T, B>> extends SimplePathVisitor.AbstractBuilder<T, B> {
49  
50          private PathCounters pathCounters = defaultPathCounters();
51          private PathFilter fileFilter = defaultFileFilter();
52          private PathFilter directoryFilter = defaultDirectoryFilter();
53          private UnaryOperator<Path> directoryPostTransformer = defaultDirectoryTransformer();
54  
55          /**
56           * Constructs a new builder for subclasses.
57           */
58          public AbstractBuilder() {
59              // empty.
60          }
61  
62          PathFilter getDirectoryFilter() {
63              return directoryFilter;
64          }
65  
66          UnaryOperator<Path> getDirectoryPostTransformer() {
67              return directoryPostTransformer;
68          }
69  
70          PathFilter getFileFilter() {
71              return fileFilter;
72          }
73  
74          PathCounters getPathCounters() {
75              return pathCounters;
76          }
77  
78          /**
79           * Sets how to filter directories.
80           *
81           * @param directoryFilter how to filter files.
82           * @return {@code this} instance.
83           */
84          public B setDirectoryFilter(final PathFilter directoryFilter) {
85              this.directoryFilter = directoryFilter != null ? directoryFilter : defaultDirectoryFilter();
86              return asThis();
87          }
88  
89          /**
90           * Sets how to transform directories, defaults to {@link UnaryOperator#identity()}.
91           *
92           * @param directoryTransformer how to filter files.
93           * @return {@code this} instance.
94           */
95          public B setDirectoryPostTransformer(final UnaryOperator<Path> directoryTransformer) {
96              this.directoryPostTransformer = directoryTransformer != null ? directoryTransformer : defaultDirectoryTransformer();
97              return asThis();
98          }
99  
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 }