View Javadoc
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       */
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       */
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       */
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       */
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       */
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      */
109     private boolean accept(final Path path) {
110         return Arrays.binarySearch(skip, PathUtils.getFileNameString(path)) < 0;
111     }
112 
113     @Override
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
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
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
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
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 }