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}