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 but not directories as a visit proceeds. 033 * 034 * @since 2.7 035 */ 036public class CleaningPathVisitor extends CountingPathVisitor { 037 038 /** 039 * Creates a new instance configured with a BigInteger {@link PathCounters}. 040 * 041 * @return a new instance configured with a BigInteger {@link PathCounters}. 042 */ 043 public static CountingPathVisitor withBigIntegerCounters() { 044 return new CleaningPathVisitor(Counters.bigIntegerPathCounters()); 045 } 046 047 /** 048 * Creates a new instance configured with a long {@link PathCounters}. 049 * 050 * @return a new instance configured with a long {@link PathCounters}. 051 */ 052 public static CountingPathVisitor withLongCounters() { 053 return new CleaningPathVisitor(Counters.longPathCounters()); 054 } 055 056 private final String[] skip; 057 private final boolean overrideReadOnly; 058 059 /** 060 * Constructs a new visitor that deletes files except for the files and directories explicitly given. 061 * 062 * @param pathCounter How to count visits. 063 * @param deleteOption How deletion is handled. 064 * @param skip The files to skip deleting. 065 * @since 2.8.0 066 */ 067 public CleaningPathVisitor(final PathCounters pathCounter, final DeleteOption[] deleteOption, 068 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 * @param skip The files to skip deleting. 081 */ 082 public CleaningPathVisitor(final PathCounters pathCounter, final String... skip) { 083 this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip); 084 } 085 086 /** 087 * Returns true to process the given path, false if not. 088 * 089 * @param path the path to test. 090 * @return true to process the given path, false if not. 091 */ 092 private boolean accept(final Path path) { 093 return Arrays.binarySearch(skip, Objects.toString(path.getFileName(), null)) < 0; 094 } 095 096 @Override 097 public boolean equals(final Object obj) { 098 if (this == obj) { 099 return true; 100 } 101 if (!super.equals(obj)) { 102 return false; 103 } 104 if (getClass() != obj.getClass()) { 105 return false; 106 } 107 final CleaningPathVisitor other = (CleaningPathVisitor) obj; 108 return overrideReadOnly == other.overrideReadOnly && Arrays.equals(skip, other.skip); 109 } 110 111 @Override 112 public int hashCode() { 113 final int prime = 31; 114 int result = super.hashCode(); 115 result = prime * result + Arrays.hashCode(skip); 116 result = prime * result + Objects.hash(overrideReadOnly); 117 return result; 118 } 119 120 @Override 121 public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException { 122 super.preVisitDirectory(dir, attributes); 123 return accept(dir) ? FileVisitResult.CONTINUE : FileVisitResult.SKIP_SUBTREE; 124 } 125 126 @Override 127 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attributes) throws IOException { 128 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 129 if (accept(file) && Files.exists(file, LinkOption.NOFOLLOW_LINKS)) { 130 if (overrideReadOnly) { 131 PathUtils.setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS); 132 } 133 Files.deleteIfExists(file); 134 } 135 updateFileCounters(file, attributes); 136 return FileVisitResult.CONTINUE; 137 } 138}