• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *      http://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.nio.file.FileVisitResult;
22 import java.nio.file.Files;
23 import java.nio.file.LinkOption;
24 import java.nio.file.NoSuchFileException;
25 import java.nio.file.Path;
26 import java.nio.file.attribute.BasicFileAttributes;
27 import java.util.Arrays;
28 import java.util.Objects;
29 
30 import org.apache.commons.io.file.Counters.PathCounters;
31 
32 /**
33  * Deletes files and directories as a visit proceeds.
34  *
35  * @since 2.7
36  */
37 public class DeletingPathVisitor extends CountingPathVisitor {
38 
39     /**
40      * Constructs a new instance configured with a BigInteger {@link PathCounters}.
41      *
42      * @return a new instance configured with a BigInteger {@link PathCounters}.
43      */
withBigIntegerCounters()44     public static DeletingPathVisitor withBigIntegerCounters() {
45         return new DeletingPathVisitor(Counters.bigIntegerPathCounters());
46     }
47 
48     /**
49      * Constructs a new instance configured with a long {@link PathCounters}.
50      *
51      * @return a new instance configured with a long {@link PathCounters}.
52      */
withLongCounters()53     public static DeletingPathVisitor withLongCounters() {
54         return new DeletingPathVisitor(Counters.longPathCounters());
55     }
56 
57     private final String[] skip;
58     private final boolean overrideReadOnly;
59     private final LinkOption[] linkOptions;
60 
61     /**
62      * Constructs a new visitor that deletes files except for the files and directories explicitly given.
63      *
64      * @param pathCounter How to count visits.
65      * @param deleteOption How deletion is handled.
66      * @param skip The files to skip deleting.
67      * @since 2.8.0
68      */
DeletingPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, final String... skip)69     public DeletingPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, final String... skip) {
70         this(pathCounter, PathUtils.noFollowLinkOptionArray(), deleteOption, skip);
71     }
72 
73     /**
74      * Constructs a new visitor that deletes files except for the files and directories explicitly given.
75      *
76      * @param pathCounter How to count visits.
77      * @param linkOptions How symbolic links are handled.
78      * @param deleteOption How deletion is handled.
79      * @param skip The files to skip deleting.
80      * @since 2.9.0
81      */
DeletingPathVisitor(final PathCounters pathCounter, final LinkOption[] linkOptions, final DeleteOption[] deleteOption, final String... skip)82     public DeletingPathVisitor(final PathCounters pathCounter, final LinkOption[] linkOptions, final DeleteOption[] deleteOption, final String... skip) {
83         super(pathCounter);
84         final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY;
85         Arrays.sort(temp);
86         this.skip = temp;
87         this.overrideReadOnly = StandardDeleteOption.overrideReadOnly(deleteOption);
88         // TODO Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
89         this.linkOptions = linkOptions == null ? PathUtils.noFollowLinkOptionArray() : linkOptions.clone();
90     }
91 
92     /**
93      * Constructs a new visitor that deletes files except for the files and directories explicitly given.
94      *
95      * @param pathCounter How to count visits.
96      *
97      * @param skip The files to skip deleting.
98      */
DeletingPathVisitor(final PathCounters pathCounter, final String... skip)99     public DeletingPathVisitor(final PathCounters pathCounter, final String... skip) {
100         this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip);
101     }
102 
103     /**
104      * Returns true to process the given path, false if not.
105      *
106      * @param path the path to test.
107      * @return true to process the given path, false if not.
108      */
accept(final Path path)109     private boolean accept(final Path path) {
110         return Arrays.binarySearch(skip, Objects.toString(path.getFileName(), null)) < 0;
111     }
112 
113     @Override
equals(final Object obj)114     public boolean equals(final Object obj) {
115         if (this == obj) {
116             return true;
117         }
118         if (!super.equals(obj)) {
119             return false;
120         }
121         if (getClass() != obj.getClass()) {
122             return false;
123         }
124         final DeletingPathVisitor other = (DeletingPathVisitor) obj;
125         return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip);
126     }
127 
128     @Override
hashCode()129     public int hashCode() {
130         final int prime = 31;
131         int result = super.hashCode();
132         result = prime * result + Arrays.hashCode(skip);
133         result = prime * result + Objects.hash(overrideReadOnly);
134         return result;
135     }
136 
137     @Override
postVisitDirectory(final Path dir, final IOException exc)138     public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
139         if (PathUtils.isEmptyDirectory(dir)) {
140             Files.deleteIfExists(dir);
141         }
142         return super.postVisitDirectory(dir, exc);
143     }
144 
145     @Override
preVisitDirectory(final Path dir, final BasicFileAttributes attrs)146     public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
147         super.preVisitDirectory(dir, attrs);
148         return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE;
149     }
150 
151     @Override
visitFile(final Path file, final BasicFileAttributes attrs)152     public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
153         if (accept(file)) {
154             // delete files and valid links, respecting linkOptions
155             if (Files.exists(file, linkOptions)) {
156                 if (overrideReadOnly) {
157                     PathUtils.setReadOnly(file, false, linkOptions);
158                 }
159                 Files.deleteIfExists(file);
160             }
161             // invalid links will survive previous delete, different approach needed:
162             if (Files.isSymbolicLink(file)) {
163                 try {
164                     // deleteIfExists does not work for this case
165                     Files.delete(file);
166                 } catch (final NoSuchFileException ignored) {
167                     // ignore
168                 }
169             }
170         }
171         updateFileCounters(file, attrs);
172         return FileVisitResult.CONTINUE;
173     }
174 }