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.nio.file.FileVisitResult; 022import java.nio.file.Files; 023import java.nio.file.LinkOption; 024import java.nio.file.NoSuchFileException; 025import java.nio.file.Path; 026import java.nio.file.attribute.BasicFileAttributes; 027import java.util.Arrays; 028import java.util.Objects; 029 030import org.apache.commons.io.file.Counters.PathCounters; 031 032/** 033 * Deletes files and directories as a visit proceeds. 034 * 035 * @since 2.7 036 */ 037public class DeletingPathVisitor extends CountingPathVisitor { 038 039 /** 040 * Creates a new instance configured with a BigInteger {@link PathCounters}. 041 * 042 * @return a new instance configured with a BigInteger {@link PathCounters}. 043 */ 044 public static DeletingPathVisitor withBigIntegerCounters() { 045 return new DeletingPathVisitor(Counters.bigIntegerPathCounters()); 046 } 047 048 /** 049 * Creates a new instance configured with a long {@link PathCounters}. 050 * 051 * @return a new instance configured with a long {@link PathCounters}. 052 */ 053 public static DeletingPathVisitor withLongCounters() { 054 return new DeletingPathVisitor(Counters.longPathCounters()); 055 } 056 057 private final String[] skip; 058 private final boolean overrideReadOnly; 059 private final LinkOption[] linkOptions; 060 061 /** 062 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 063 * 064 * @param pathCounter How to count visits. 065 * @param deleteOption How deletion is handled. 066 * @param skip The files to skip deleting. 067 * @since 2.8.0 068 */ 069 public DeletingPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, 070 final String... skip) { 071 this(pathCounter, PathUtils.NOFOLLOW_LINK_OPTION_ARRAY, deleteOption, skip); 072 } 073 074 /** 075 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 076 * 077 * @param pathCounter How to count visits. 078 * @param linkOptions How symbolic links are handled. 079 * @param deleteOption How deletion is handled. 080 * @param skip The files to skip deleting. 081 * @since 2.9.0 082 */ 083 public DeletingPathVisitor(final PathCounters pathCounter, final LinkOption[] linkOptions, 084 final DeleteOption[] deleteOption, final String... skip) { 085 super(pathCounter); 086 final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY; 087 Arrays.sort(temp); 088 this.skip = temp; 089 this.overrideReadOnly = StandardDeleteOption.overrideReadOnly(deleteOption); 090 // TODO Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 091 this.linkOptions = linkOptions == null ? PathUtils.NOFOLLOW_LINK_OPTION_ARRAY : linkOptions.clone(); 092 } 093 094 /** 095 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 096 * 097 * @param pathCounter How to count visits. 098 * 099 * @param skip The files to skip deleting. 100 */ 101 public DeletingPathVisitor(final PathCounters pathCounter, final String... skip) { 102 this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip); 103 } 104 105 /** 106 * Returns true to process the given path, false if not. 107 * 108 * @param path the path to test. 109 * @return true to process the given path, false if not. 110 */ 111 private boolean accept(final Path path) { 112 return Arrays.binarySearch(skip, Objects.toString(path.getFileName(), null)) < 0; 113 } 114 115 @Override 116 public boolean equals(final Object obj) { 117 if (this == obj) { 118 return true; 119 } 120 if (!super.equals(obj)) { 121 return false; 122 } 123 if (getClass() != obj.getClass()) { 124 return false; 125 } 126 final DeletingPathVisitor other = (DeletingPathVisitor) obj; 127 return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip); 128 } 129 130 @Override 131 public int hashCode() { 132 final int prime = 31; 133 int result = super.hashCode(); 134 result = prime * result + Arrays.hashCode(skip); 135 result = prime * result + Objects.hash(overrideReadOnly); 136 return result; 137 } 138 139 @Override 140 public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 141 if (PathUtils.isEmptyDirectory(dir)) { 142 Files.deleteIfExists(dir); 143 } 144 return super.postVisitDirectory(dir, exc); 145 } 146 147 @Override 148 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { 149 super.preVisitDirectory(dir, attrs); 150 return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; 151 } 152 153 @Override 154 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 155 if (accept(file)) { 156 // delete files and valid links, respecting linkOptions 157 if (Files.exists(file, linkOptions)) { 158 if (overrideReadOnly) { 159 PathUtils.setReadOnly(file, false, linkOptions); 160 } 161 Files.deleteIfExists(file); 162 } 163 // invalid links will survive previous delete, different approach needed: 164 if (Files.isSymbolicLink(file)) { 165 try { 166 // deleteIfExists does not work for this case 167 Files.delete(file); 168 } catch (final NoSuchFileException e) { 169 // ignore 170 } 171 } 172 } 173 updateFileCounters(file, attrs); 174 return FileVisitResult.CONTINUE; 175 } 176}