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 * http://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 @Override 240 public boolean equals(final Object obj) { 241 if (this == obj) { 242 return true; 243 } 244 if (!(obj instanceof CountingPathVisitor)) { 245 return false; 246 } 247 final CountingPathVisitor other = (CountingPathVisitor) obj; 248 return Objects.equals(pathCounters, other.pathCounters); 249 } 250 251 /** 252 * Gets the visitation counts. 253 * 254 * @return the visitation counts. 255 */ 256 public PathCounters getPathCounters() { 257 return pathCounters; 258 } 259 260 @Override 261 public int hashCode() { 262 return Objects.hash(pathCounters); 263 } 264 265 @Override 266 public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 267 updateDirCounter(directoryPostTransformer.apply(dir), exc); 268 return FileVisitResult.CONTINUE; 269 } 270 271 @Override 272 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException { 273 final FileVisitResult accept = directoryFilter.accept(dir, attributes); 274 return accept != FileVisitResult.CONTINUE ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE; 275 } 276 277 @Override 278 public String toString() { 279 return pathCounters.toString(); 280 } 281 282 /** 283 * Updates the counter for visiting the given directory. 284 * 285 * @param dir the visited directory. 286 * @param exc Encountered exception. 287 * @since 2.9.0 288 */ 289 protected void updateDirCounter(final Path dir, final IOException exc) { 290 pathCounters.getDirectoryCounter().increment(); 291 } 292 293 /** 294 * Updates the counters for visiting the given file. 295 * 296 * @param file the visited file. 297 * @param attributes the visited file attributes. 298 */ 299 protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) { 300 pathCounters.getFileCounter().increment(); 301 pathCounters.getByteCounter().add(attributes.size()); 302 } 303 304 @Override 305 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException { 306 // Note: A file can be a symbolic link to a directory. 307 if (Files.exists(file) && fileFilter.accept(file, attributes) == FileVisitResult.CONTINUE) { 308 updateFileCounters(file, attributes); 309 } 310 return FileVisitResult.CONTINUE; 311 } 312}