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.Path; 025import java.nio.file.attribute.BasicFileAttributes; 026import java.util.Arrays; 027import java.util.Objects; 028 029import org.apache.commons.io.file.Counters.PathCounters; 030 031/** 032 * Deletes files and directories as a visit proceeds. 033 * 034 * @since 2.7 035 */ 036public class DeletingPathVisitor extends CountingPathVisitor { 037 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 060 /** 061 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 062 * 063 * @param pathCounter How to count visits. 064 * @param deleteOption options indicating how deletion is handled. 065 * @param skip The files to skip deleting. 066 * @since 2.8.0 067 */ 068 public DeletingPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, final String... skip) { 069 super(pathCounter); 070 final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY; 071 Arrays.sort(temp); 072 this.skip = temp; 073 this.overrideReadOnly = StandardDeleteOption.overrideReadOnly(deleteOption); 074 } 075 076 /** 077 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 078 * 079 * @param pathCounter How to count visits. 080 * 081 * @param skip The files to skip deleting. 082 */ 083 public DeletingPathVisitor(final PathCounters pathCounter, final String... skip) { 084 this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip); 085 } 086 087 /** 088 * Returns true to process the given path, false if not. 089 * 090 * @param path the path to test. 091 * @return true to process the given path, false if not. 092 */ 093 private boolean accept(final Path path) { 094 return Arrays.binarySearch(skip, Objects.toString(path.getFileName(), null)) < 0; 095 } 096 097 @Override 098 public boolean equals(final Object obj) { 099 if (this == obj) { 100 return true; 101 } 102 if (!super.equals(obj)) { 103 return false; 104 } 105 if (getClass() != obj.getClass()) { 106 return false; 107 } 108 final DeletingPathVisitor other = (DeletingPathVisitor) obj; 109 return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip); 110 } 111 112 @Override 113 public int hashCode() { 114 final int prime = 31; 115 int result = super.hashCode(); 116 result = prime * result + Arrays.hashCode(skip); 117 result = prime * result + Objects.hash(overrideReadOnly); 118 return result; 119 } 120 121 @Override 122 public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 123 if (PathUtils.isEmptyDirectory(dir)) { 124 Files.deleteIfExists(dir); 125 } 126 return super.postVisitDirectory(dir, exc); 127 } 128 129 @Override 130 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { 131 super.preVisitDirectory(dir, attrs); 132 return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; 133 } 134 135 @Override 136 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 137 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 138 if (accept(file) && Files.exists(file, LinkOption.NOFOLLOW_LINKS)) { 139 if (overrideReadOnly) { 140 PathUtils.setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS); 141 } 142 Files.deleteIfExists(file); 143 } 144 updateFileCounters(file, attrs); 145 return FileVisitResult.CONTINUE; 146 } 147}