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}