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