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.Path; 25 import java.nio.file.attribute.BasicFileAttributes; 26 import java.util.Arrays; 27 import java.util.Objects; 28 29 import org.apache.commons.io.file.Counters.PathCounters; 30 31 /** 32 * Deletes files but not directories as a visit proceeds. 33 * 34 * @since 2.7 35 */ 36 public class CleaningPathVisitor extends CountingPathVisitor { 37 38 /** 39 * Constructs a new instance configured with a BigInteger {@link PathCounters}. 40 * 41 * @return a new instance configured with a BigInteger {@link PathCounters}. 42 */ withBigIntegerCounters()43 public static CountingPathVisitor withBigIntegerCounters() { 44 return new CleaningPathVisitor(Counters.bigIntegerPathCounters()); 45 } 46 47 /** 48 * Constructs a new instance configured with a long {@link PathCounters}. 49 * 50 * @return a new instance configured with a long {@link PathCounters}. 51 */ withLongCounters()52 public static CountingPathVisitor withLongCounters() { 53 return new CleaningPathVisitor(Counters.longPathCounters()); 54 } 55 56 private final String[] skip; 57 private final boolean overrideReadOnly; 58 59 /** 60 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 61 * 62 * @param pathCounter How to count visits. 63 * @param deleteOption How deletion is handled. 64 * @param skip The files to skip deleting. 65 * @since 2.8.0 66 */ CleaningPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, final String... skip)67 public CleaningPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, final String... skip) { 68 super(pathCounter); 69 final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY; 70 Arrays.sort(temp); 71 this.skip = temp; 72 this.overrideReadOnly = StandardDeleteOption.overrideReadOnly(deleteOption); 73 } 74 75 /** 76 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 77 * 78 * @param pathCounter How to count visits. 79 * @param skip The files to skip deleting. 80 */ CleaningPathVisitor(final PathCounters pathCounter, final String... skip)81 public CleaningPathVisitor(final PathCounters pathCounter, final String... skip) { 82 this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip); 83 } 84 85 /** 86 * Returns true to process the given path, false if not. 87 * 88 * @param path the path to test. 89 * @return true to process the given path, false if not. 90 */ accept(final Path path)91 private boolean accept(final Path path) { 92 return Arrays.binarySearch(skip, Objects.toString(path.getFileName(), null)) < 0; 93 } 94 95 @Override equals(final Object obj)96 public boolean equals(final Object obj) { 97 if (this == obj) { 98 return true; 99 } 100 if (!super.equals(obj)) { 101 return false; 102 } 103 if (getClass() != obj.getClass()) { 104 return false; 105 } 106 final CleaningPathVisitor other = (CleaningPathVisitor) obj; 107 return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip); 108 } 109 110 @Override hashCode()111 public int hashCode() { 112 final int prime = 31; 113 int result = super.hashCode(); 114 result = prime * result + Arrays.hashCode(skip); 115 result = prime * result + Objects.hash(overrideReadOnly); 116 return result; 117 } 118 119 @Override preVisitDirectory(final Path dir, final BasicFileAttributes attributes)120 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException { 121 super.preVisitDirectory(dir, attributes); 122 return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; 123 } 124 125 @Override visitFile(final Path file, final BasicFileAttributes attributes)126 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException { 127 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 128 if (accept(file) && Files.exists(file, LinkOption.NOFOLLOW_LINKS)) { 129 if (overrideReadOnly) { 130 PathUtils.setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS); 131 } 132 Files.deleteIfExists(file); 133 } 134 updateFileCounters(file, attributes); 135 return FileVisitResult.CONTINUE; 136 } 137 }