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.nio.file.FileVisitResult;
022import java.nio.file.Path;
023import java.nio.file.attribute.BasicFileAttributes;
024import java.util.ArrayList;
025import java.util.Comparator;
026import java.util.List;
027import java.util.Objects;
028
029import org.apache.commons.io.file.Counters.PathCounters;
030import org.apache.commons.io.function.IOBiFunction;
031
032/**
033 * Accumulates normalized paths during visitation.
034 * <p>
035 * Use with care on large file trees as each visited Path element is remembered.
036 * </p>
037 * <h2>Example</h2>
038 *
039 * <pre>
040 * Path dir = PathUtils.current();
041 * // We are interested in files older than one day
042 * Instant cutoff = Instant.now().minus(Duration.ofDays(1));
043 * AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(new AgeFileFilter(cutoff));
044 * //
045 * // Walk one directory
046 * Files.walkFileTree(dir, Collections.emptySet(), 1, visitor);
047 * System.out.println(visitor.getPathCounters());
048 * System.out.println(visitor.getFileList());
049 * //
050 * visitor.getPathCounters().reset();
051 * //
052 * // Walk directory tree
053 * Files.walkFileTree(dir, visitor);
054 * System.out.println(visitor.getPathCounters());
055 * System.out.println(visitor.getDirList());
056 * System.out.println(visitor.getFileList());
057 * </pre>
058 *
059 * @since 2.7
060 */
061public class AccumulatorPathVisitor extends CountingPathVisitor {
062
063    /**
064     * Builds instances of {@link AccumulatorPathVisitor}.
065     *
066     * @since 2.19.0
067     */
068    public static class Builder extends AbstractBuilder<AccumulatorPathVisitor, Builder> {
069
070        /**
071         * Constructs a new builder.
072         */
073        public Builder() {
074            // empty.
075        }
076
077        @Override
078        public AccumulatorPathVisitor get() {
079            return new AccumulatorPathVisitor(this);
080        }
081
082    }
083
084    /**
085     * Builds instances of {@link AccumulatorPathVisitor}.
086     *
087     * @return a new builder.
088     * @since 2.19.0
089     */
090    public static Builder builder() {
091        return new Builder();
092    }
093
094    /**
095     * Constructs a new instance configured with a BigInteger {@link PathCounters}.
096     *
097     * @return a new instance configured with a BigInteger {@link PathCounters}.
098     * @see #builder()
099     * @see Builder
100     */
101    public static AccumulatorPathVisitor withBigIntegerCounters() {
102        return builder().setPathCounters(Counters.bigIntegerPathCounters()).get();
103    }
104
105    /**
106     * Constructs a new instance configured with a BigInteger {@link PathCounters}.
107     *
108     * @param fileFilter Filters files to accumulate and count.
109     * @param dirFilter Filters directories to accumulate and count.
110     * @return a new instance configured with a long {@link PathCounters}.
111     * @see #builder()
112     * @see Builder
113     * @since 2.9.0
114     */
115    public static AccumulatorPathVisitor withBigIntegerCounters(final PathFilter fileFilter, final PathFilter dirFilter) {
116        return builder().setPathCounters(Counters.bigIntegerPathCounters()).setFileFilter(fileFilter).setDirectoryFilter(dirFilter).get();
117    }
118
119    /**
120     * Constructs a new instance configured with a long {@link PathCounters}.
121     *
122     * @return a new instance configured with a long {@link PathCounters}.
123     * @see #builder()
124     * @see Builder
125     */
126    public static AccumulatorPathVisitor withLongCounters() {
127        return builder().setPathCounters(Counters.longPathCounters()).get();
128    }
129
130    /**
131     * Constructs a new instance configured with a long {@link PathCounters}.
132     *
133     * @param fileFilter Filters files to accumulate and count.
134     * @param dirFilter Filters directories to accumulate and count.
135     * @return a new instance configured with a long {@link PathCounters}.
136     * @see #builder()
137     * @see Builder
138     * @since 2.9.0
139     */
140    public static AccumulatorPathVisitor withLongCounters(final PathFilter fileFilter, final PathFilter dirFilter) {
141        return builder().setPathCounters(Counters.longPathCounters()).setFileFilter(fileFilter).setDirectoryFilter(dirFilter).get();
142    }
143
144    private final List<Path> dirList = new ArrayList<>();
145
146    private final List<Path> fileList = new ArrayList<>();
147
148    /**
149     * Constructs a new instance with a noop path counter.
150     *
151     * @since 2.9.0
152     * @deprecated Use {@link #builder()}.
153     */
154    @Deprecated
155    public AccumulatorPathVisitor() {
156        super(Counters.noopPathCounters());
157    }
158
159    private AccumulatorPathVisitor(final Builder builder) {
160        super(builder);
161    }
162
163    /**
164     * Constructs a new instance that counts file system elements.
165     *
166     * @param pathCounter How to count path visits.
167     * @deprecated Use {@link #builder()}.
168     */
169    @Deprecated
170    public AccumulatorPathVisitor(final PathCounters pathCounter) {
171        super(pathCounter);
172    }
173
174    /**
175     * Constructs a new instance.
176     *
177     * @param pathCounter How to count path visits.
178     * @param fileFilter Filters which files to count.
179     * @param dirFilter Filters which directories to count.
180     * @since 2.9.0
181     * @deprecated Use {@link #builder()}.
182     */
183    @Deprecated
184    public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter) {
185        super(pathCounter, fileFilter, dirFilter);
186    }
187
188    /**
189     * Constructs a new instance.
190     *
191     * @param pathCounter How to count path visits.
192     * @param fileFilter Filters which files to count.
193     * @param dirFilter Filters which directories to count.
194     * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}.
195     * @since 2.12.0
196     * @deprecated Use {@link #builder()}.
197     */
198    @SuppressWarnings("deprecation")
199    @Deprecated
200    public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter,
201        final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) {
202        super(pathCounter, fileFilter, dirFilter, visitFileFailed);
203    }
204
205    private void add(final List<Path> list, final Path dir) {
206        list.add(dir.normalize());
207    }
208
209    @Override
210    public boolean equals(final Object obj) {
211        if (this == obj) {
212            return true;
213        }
214        if (!super.equals(obj)) {
215            return false;
216        }
217        if (!(obj instanceof AccumulatorPathVisitor)) {
218            return false;
219        }
220        final AccumulatorPathVisitor other = (AccumulatorPathVisitor) obj;
221        return Objects.equals(dirList, other.dirList) && Objects.equals(fileList, other.fileList);
222    }
223
224    /**
225     * Gets a copy of the list of visited directories.
226     *
227     * @return a copy of the list of visited directories.
228     */
229    public List<Path> getDirList() {
230        return new ArrayList<>(dirList);
231    }
232
233    /**
234     * Gets a copy of the list of visited files.
235     *
236     * @return a copy of the list of visited files.
237     */
238    public List<Path> getFileList() {
239        return new ArrayList<>(fileList);
240    }
241
242    @Override
243    public int hashCode() {
244        final int prime = 31;
245        int result = super.hashCode();
246        result = prime * result + Objects.hash(dirList, fileList);
247        return result;
248    }
249
250    /**
251     * Relativizes each directory path with {@link Path#relativize(Path)} against the given {@code parent}, optionally
252     * sorting the result.
253     *
254     * @param parent A parent path
255     * @param sort Whether to sort
256     * @param comparator How to sort, null uses default sorting.
257     * @return A new list
258     */
259    public List<Path> relativizeDirectories(final Path parent, final boolean sort,
260        final Comparator<? super Path> comparator) {
261        return PathUtils.relativize(getDirList(), parent, sort, comparator);
262    }
263
264    /**
265     * Relativizes each file path with {@link Path#relativize(Path)} against the given {@code parent}, optionally
266     * sorting the result.
267     *
268     * @param parent A parent path
269     * @param sort Whether to sort
270     * @param comparator How to sort, null uses default sorting.
271     * @return A new list
272     */
273    public List<Path> relativizeFiles(final Path parent, final boolean sort,
274        final Comparator<? super Path> comparator) {
275        return PathUtils.relativize(getFileList(), parent, sort, comparator);
276    }
277
278    @Override
279    protected void updateDirCounter(final Path dir, final IOException exc) {
280        super.updateDirCounter(dir, exc);
281        add(dirList, dir);
282    }
283
284    @Override
285    protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) {
286        super.updateFileCounters(file, attributes);
287        add(fileList, file);
288    }
289
290}