| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.io.File; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.Vector; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.condition.Os; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.PatternSet; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.FileProvider; |
| import org.apache.tools.ant.types.resources.FileResourceIterator; |
| import org.apache.tools.ant.types.resources.Resources; |
| import org.apache.tools.ant.types.resources.Restrict; |
| import org.apache.tools.ant.types.resources.Sort; |
| import org.apache.tools.ant.types.resources.comparators.FileSystem; |
| import org.apache.tools.ant.types.resources.comparators.ResourceComparator; |
| import org.apache.tools.ant.types.resources.comparators.Reverse; |
| import org.apache.tools.ant.types.resources.selectors.Exists; |
| import org.apache.tools.ant.types.resources.selectors.ResourceSelector; |
| import org.apache.tools.ant.types.selectors.AndSelector; |
| import org.apache.tools.ant.types.selectors.ContainsRegexpSelector; |
| import org.apache.tools.ant.types.selectors.ContainsSelector; |
| import org.apache.tools.ant.types.selectors.DateSelector; |
| import org.apache.tools.ant.types.selectors.DependSelector; |
| import org.apache.tools.ant.types.selectors.DepthSelector; |
| import org.apache.tools.ant.types.selectors.ExtendSelector; |
| import org.apache.tools.ant.types.selectors.FileSelector; |
| import org.apache.tools.ant.types.selectors.FilenameSelector; |
| import org.apache.tools.ant.types.selectors.MajoritySelector; |
| import org.apache.tools.ant.types.selectors.NoneSelector; |
| import org.apache.tools.ant.types.selectors.NotSelector; |
| import org.apache.tools.ant.types.selectors.OrSelector; |
| import org.apache.tools.ant.types.selectors.PresentSelector; |
| import org.apache.tools.ant.types.selectors.SelectSelector; |
| import org.apache.tools.ant.types.selectors.SizeSelector; |
| import org.apache.tools.ant.types.selectors.modifiedselector.ModifiedSelector; |
| import org.apache.tools.ant.util.FileUtils; |
| |
| /** |
| * Deletes a file or directory, or set of files defined by a fileset. |
| * The original delete task would delete a file, or a set of files |
| * using the include/exclude syntax. The deltree task would delete a |
| * directory tree. This task combines the functionality of these two |
| * originally distinct tasks. |
| * <p>Currently Delete extends MatchingTask. This is intended <i>only</i> |
| * to provide backwards compatibility for a release. The future position |
| * is to use nested filesets exclusively.</p> |
| * |
| * @since Ant 1.2 |
| * |
| * @ant.task category="filesystem" |
| */ |
| public class Delete extends MatchingTask { |
| private static final ResourceComparator REVERSE_FILESYSTEM = new Reverse(new FileSystem()); |
| private static final ResourceSelector EXISTS = new Exists(); |
| private static FileUtils FILE_UTILS = FileUtils.getFileUtils(); |
| |
| private static class ReverseDirs implements ResourceCollection { |
| |
| private Project project; |
| private File basedir; |
| private String[] dirs; |
| |
| ReverseDirs(Project project, File basedir, String[] dirs) { |
| this.project = project; |
| this.basedir = basedir; |
| this.dirs = dirs; |
| Arrays.sort(this.dirs, Comparator.reverseOrder()); |
| } |
| |
| @Override |
| public Iterator<Resource> iterator() { |
| return new FileResourceIterator(project, basedir, dirs); |
| } |
| |
| @Override |
| public boolean isFilesystemOnly() { |
| return true; |
| } |
| |
| @Override |
| public int size() { |
| return dirs.length; |
| } |
| } |
| |
| // CheckStyle:VisibilityModifier OFF - bc |
| protected File file = null; |
| protected File dir = null; |
| protected Vector<FileSet> filesets = new Vector<>(); |
| protected boolean usedMatchingTask = false; |
| // by default, remove matching empty dirs |
| protected boolean includeEmpty = false; |
| // CheckStyle:VisibilityModifier ON |
| |
| private int verbosity = Project.MSG_VERBOSE; |
| private boolean quiet = false; |
| private boolean failonerror = true; |
| private boolean deleteOnExit = false; |
| private boolean removeNotFollowedSymlinks = false; |
| private Resources rcs = null; |
| private boolean performGc = Os.isFamily("windows"); |
| |
| /** |
| * Set the name of a single file to be removed. |
| * |
| * @param file the file to be deleted |
| */ |
| public void setFile(File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Set the directory from which files are to be deleted |
| * |
| * @param dir the directory path. |
| */ |
| public void setDir(File dir) { |
| this.dir = dir; |
| getImplicitFileSet().setDir(dir); |
| } |
| |
| /** |
| * If true, list all names of deleted files. |
| * |
| * @param verbose "true" or "on" |
| */ |
| public void setVerbose(boolean verbose) { |
| if (verbose) { |
| this.verbosity = Project.MSG_INFO; |
| } else { |
| this.verbosity = Project.MSG_VERBOSE; |
| } |
| } |
| |
| /** |
| * If true and the file does not exist, do not display a diagnostic |
| * message or modify the exit status to reflect an error. |
| * This means that if a file or directory cannot be deleted, |
| * then no error is reported. This setting emulates the |
| * -f option to the Unix "rm" command. |
| * Default is false meaning things are "noisy" |
| * @param quiet "true" or "on" |
| */ |
| public void setQuiet(boolean quiet) { |
| this.quiet = quiet; |
| if (quiet) { |
| this.failonerror = false; |
| } |
| } |
| |
| /** |
| * If false, note errors but continue. |
| * |
| * @param failonerror true or false |
| */ |
| public void setFailOnError(boolean failonerror) { |
| this.failonerror = failonerror; |
| } |
| |
| /** |
| * If true, on failure to delete, note the error and set |
| * the deleteonexit flag, and continue |
| * |
| * @param deleteOnExit true or false |
| */ |
| public void setDeleteOnExit(boolean deleteOnExit) { |
| this.deleteOnExit = deleteOnExit; |
| } |
| |
| /** |
| * If true, delete empty directories. |
| * @param includeEmpty if true delete empty directories (only |
| * for filesets). Default is false. |
| */ |
| public void setIncludeEmptyDirs(boolean includeEmpty) { |
| this.includeEmpty = includeEmpty; |
| } |
| |
| /** |
| * Whether to perform a garbage collection before retrying a failed delete. |
| * |
| * <p>This may be required on Windows (where it is set to true by |
| * default) but also on other operating systems, for example when |
| * deleting directories from an NFS share.</p> |
| * |
| * @param b boolean |
| * @since Ant 1.8.3 |
| */ |
| public void setPerformGcOnFailedDelete(boolean b) { |
| performGc = b; |
| } |
| |
| /** |
| * Adds a set of files to be deleted. |
| * @param set the set of files to be deleted |
| */ |
| public void addFileset(FileSet set) { |
| filesets.addElement(set); |
| } |
| |
| /** |
| * Add an arbitrary ResourceCollection to be deleted. |
| * @param rc the filesystem-only ResourceCollection. |
| */ |
| public void add(ResourceCollection rc) { |
| if (rc == null) { |
| return; |
| } |
| if (rcs == null) { |
| rcs = new Resources(); |
| rcs.setCache(true); |
| } |
| rcs.add(rc); |
| } |
| |
| /** |
| * add a name entry on the include list |
| * @return a NameEntry object to be configured |
| */ |
| @Override |
| public PatternSet.NameEntry createInclude() { |
| usedMatchingTask = true; |
| return super.createInclude(); |
| } |
| |
| /** |
| * add a name entry on the include files list |
| * @return a PatternFileNameEntry object to be configured |
| */ |
| @Override |
| public PatternSet.NameEntry createIncludesFile() { |
| usedMatchingTask = true; |
| return super.createIncludesFile(); |
| } |
| |
| /** |
| * add a name entry on the exclude list |
| * @return a NameEntry object to be configured |
| */ |
| @Override |
| public PatternSet.NameEntry createExclude() { |
| usedMatchingTask = true; |
| return super.createExclude(); |
| } |
| |
| /** |
| * add a name entry on the include files list |
| * @return a PatternFileNameEntry object to be configured |
| */ |
| @Override |
| public PatternSet.NameEntry createExcludesFile() { |
| usedMatchingTask = true; |
| return super.createExcludesFile(); |
| } |
| |
| /** |
| * add a set of patterns |
| * @return PatternSet object to be configured |
| */ |
| @Override |
| public PatternSet createPatternSet() { |
| usedMatchingTask = true; |
| return super.createPatternSet(); |
| } |
| |
| /** |
| * Sets the set of include patterns. Patterns may be separated by a comma |
| * or a space. |
| * |
| * @param includes the string containing the include patterns |
| */ |
| @Override |
| public void setIncludes(String includes) { |
| usedMatchingTask = true; |
| super.setIncludes(includes); |
| } |
| |
| /** |
| * Sets the set of exclude patterns. Patterns may be separated by a comma |
| * or a space. |
| * |
| * @param excludes the string containing the exclude patterns |
| */ |
| @Override |
| public void setExcludes(String excludes) { |
| usedMatchingTask = true; |
| super.setExcludes(excludes); |
| } |
| |
| /** |
| * Sets whether default exclusions should be used or not. |
| * |
| * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions |
| * should be used, "false"|"off"|"no" when they |
| * shouldn't be used. |
| */ |
| @Override |
| public void setDefaultexcludes(boolean useDefaultExcludes) { |
| usedMatchingTask = true; |
| super.setDefaultexcludes(useDefaultExcludes); |
| } |
| |
| /** |
| * Sets the name of the file containing the includes patterns. |
| * |
| * @param includesfile A string containing the filename to fetch |
| * the include patterns from. |
| */ |
| @Override |
| public void setIncludesfile(File includesfile) { |
| usedMatchingTask = true; |
| super.setIncludesfile(includesfile); |
| } |
| |
| /** |
| * Sets the name of the file containing the includes patterns. |
| * |
| * @param excludesfile A string containing the filename to fetch |
| * the include patterns from. |
| */ |
| @Override |
| public void setExcludesfile(File excludesfile) { |
| usedMatchingTask = true; |
| super.setExcludesfile(excludesfile); |
| } |
| |
| /** |
| * Sets case sensitivity of the file system |
| * |
| * @param isCaseSensitive "true"|"on"|"yes" if file system is case |
| * sensitive, "false"|"off"|"no" when not. |
| */ |
| @Override |
| public void setCaseSensitive(boolean isCaseSensitive) { |
| usedMatchingTask = true; |
| super.setCaseSensitive(isCaseSensitive); |
| } |
| |
| /** |
| * Sets whether or not symbolic links should be followed. |
| * |
| * @param followSymlinks whether or not symbolic links should be followed |
| */ |
| @Override |
| public void setFollowSymlinks(boolean followSymlinks) { |
| usedMatchingTask = true; |
| super.setFollowSymlinks(followSymlinks); |
| } |
| |
| /** |
| * Sets whether the symbolic links that have not been followed |
| * shall be removed (the links, not the locations they point at). |
| * |
| * @param b boolean |
| * @since Ant 1.8.0 |
| */ |
| public void setRemoveNotFollowedSymlinks(boolean b) { |
| removeNotFollowedSymlinks = b; |
| } |
| |
| /** |
| * add a "Select" selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addSelector(SelectSelector selector) { |
| usedMatchingTask = true; |
| super.addSelector(selector); |
| } |
| |
| /** |
| * add an "And" selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addAnd(AndSelector selector) { |
| usedMatchingTask = true; |
| super.addAnd(selector); |
| } |
| |
| /** |
| * add an "Or" selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addOr(OrSelector selector) { |
| usedMatchingTask = true; |
| super.addOr(selector); |
| } |
| |
| /** |
| * add a "Not" selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addNot(NotSelector selector) { |
| usedMatchingTask = true; |
| super.addNot(selector); |
| } |
| |
| /** |
| * add a "None" selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addNone(NoneSelector selector) { |
| usedMatchingTask = true; |
| super.addNone(selector); |
| } |
| |
| /** |
| * add a majority selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addMajority(MajoritySelector selector) { |
| usedMatchingTask = true; |
| super.addMajority(selector); |
| } |
| |
| /** |
| * add a selector date entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addDate(DateSelector selector) { |
| usedMatchingTask = true; |
| super.addDate(selector); |
| } |
| |
| /** |
| * add a selector size entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addSize(SizeSelector selector) { |
| usedMatchingTask = true; |
| super.addSize(selector); |
| } |
| |
| /** |
| * add a selector filename entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addFilename(FilenameSelector selector) { |
| usedMatchingTask = true; |
| super.addFilename(selector); |
| } |
| |
| /** |
| * add an extended selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addCustom(ExtendSelector selector) { |
| usedMatchingTask = true; |
| super.addCustom(selector); |
| } |
| |
| /** |
| * add a contains selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addContains(ContainsSelector selector) { |
| usedMatchingTask = true; |
| super.addContains(selector); |
| } |
| |
| /** |
| * add a present selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addPresent(PresentSelector selector) { |
| usedMatchingTask = true; |
| super.addPresent(selector); |
| } |
| |
| /** |
| * add a depth selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addDepth(DepthSelector selector) { |
| usedMatchingTask = true; |
| super.addDepth(selector); |
| } |
| |
| /** |
| * add a depends selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addDepend(DependSelector selector) { |
| usedMatchingTask = true; |
| super.addDepend(selector); |
| } |
| |
| /** |
| * add a regular expression selector entry on the selector list |
| * |
| * @param selector the selector to be added |
| */ |
| @Override |
| public void addContainsRegexp(ContainsRegexpSelector selector) { |
| usedMatchingTask = true; |
| super.addContainsRegexp(selector); |
| } |
| |
| /** |
| * add the modified selector |
| * |
| * @param selector the selector to add |
| * @since ant 1.6 |
| */ |
| @Override |
| public void addModified(ModifiedSelector selector) { |
| usedMatchingTask = true; |
| super.addModified(selector); |
| } |
| |
| /** |
| * add an arbitrary selector |
| * |
| * @param selector the selector to be added |
| * @since Ant 1.6 |
| */ |
| @Override |
| public void add(FileSelector selector) { |
| usedMatchingTask = true; |
| super.add(selector); |
| } |
| |
| /** |
| * Delete the file(s). |
| * |
| * @exception BuildException if an error occurs |
| */ |
| @Override |
| public void execute() throws BuildException { |
| if (usedMatchingTask) { |
| log("DEPRECATED - Use of the implicit FileSet is deprecated. Use a nested fileset element instead.", |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| } |
| |
| if (file == null && dir == null && filesets.isEmpty() && rcs == null) { |
| throw new BuildException( |
| "At least one of the file or dir attributes, or a nested resource collection, must be set."); |
| } |
| |
| if (quiet && failonerror) { |
| throw new BuildException( |
| "quiet and failonerror cannot both be set to true", getLocation()); |
| } |
| |
| // delete the single file |
| if (file != null) { |
| if (file.exists()) { |
| if (file.isDirectory()) { |
| log("Directory " + file.getAbsolutePath() |
| + " cannot be removed using the file attribute. Use dir instead.", |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| } else { |
| log("Deleting: " + file.getAbsolutePath()); |
| |
| if (!delete(file)) { |
| handle("Unable to delete file " + file.getAbsolutePath()); |
| } |
| } |
| } else if (isDanglingSymlink(file)) { |
| log("Trying to delete file " + file.getAbsolutePath() |
| + " which looks like a broken symlink.", |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| if (!delete(file)) { |
| handle("Unable to delete file " + file.getAbsolutePath()); |
| } |
| } else { |
| log("Could not find file " + file.getAbsolutePath() |
| + " to delete.", quiet ? Project.MSG_VERBOSE : verbosity); |
| } |
| } |
| |
| // delete the directory |
| if (dir != null && !usedMatchingTask) { |
| if (dir.exists() && dir.isDirectory()) { |
| /* |
| If verbosity is MSG_VERBOSE, that mean we are doing |
| regular logging (backwards as that sounds). In that |
| case, we want to print one message about deleting the |
| top of the directory tree. Otherwise, the removeDir |
| method will handle messages for _all_ directories. |
| */ |
| if (verbosity == Project.MSG_VERBOSE) { |
| log("Deleting directory " + dir.getAbsolutePath()); |
| } |
| removeDir(dir); |
| } else if (isDanglingSymlink(dir)) { |
| log("Trying to delete directory " + dir.getAbsolutePath() |
| + " which looks like a broken symlink.", |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| if (!delete(dir)) { |
| handle("Unable to delete directory " + dir.getAbsolutePath()); |
| } |
| } |
| } |
| Resources resourcesToDelete = new Resources(); |
| resourcesToDelete.setProject(getProject()); |
| resourcesToDelete.setCache(true); |
| Resources filesetDirs = new Resources(); |
| filesetDirs.setProject(getProject()); |
| filesetDirs.setCache(true); |
| FileSet implicit = null; |
| if (usedMatchingTask && dir != null && dir.isDirectory()) { |
| //add the files from the default fileset: |
| implicit = getImplicitFileSet(); |
| implicit.setProject(getProject()); |
| filesets.add(implicit); |
| } |
| |
| for (FileSet fs : filesets) { |
| if (fs.getProject() == null) { |
| log("Deleting fileset with no project specified; assuming executing project", |
| Project.MSG_VERBOSE); |
| fs = (FileSet) fs.clone(); |
| fs.setProject(getProject()); |
| } |
| final File fsDir = fs.getDir(); |
| if (!fs.getErrorOnMissingDir() && (fsDir == null || !fsDir.exists())) { |
| continue; |
| } |
| if (fsDir == null) { |
| throw new BuildException("File or Resource without directory or file specified"); |
| } else if (!fsDir.isDirectory()) { |
| handle("Directory does not exist: " + fsDir); |
| } else { |
| DirectoryScanner ds = fs.getDirectoryScanner(); |
| // the previous line has already scanned the |
| // filesystem, in order to avoid a rescan when later |
| // iterating, capture the results now and store them |
| final String[] files = ds.getIncludedFiles(); |
| resourcesToDelete.add(new ResourceCollection() { |
| @Override |
| public boolean isFilesystemOnly() { |
| return true; |
| } |
| |
| @Override |
| public int size() { |
| return files.length; |
| } |
| |
| @Override |
| public Iterator<Resource> iterator() { |
| return new FileResourceIterator(getProject(), |
| fsDir, files); |
| } |
| }); |
| if (includeEmpty) { |
| filesetDirs.add(new ReverseDirs(getProject(), fsDir, |
| ds.getIncludedDirectories())); |
| } |
| |
| if (removeNotFollowedSymlinks) { |
| String[] n = ds.getNotFollowedSymlinks(); |
| if (n.length > 0) { |
| String[] links = new String[n.length]; |
| System.arraycopy(n, 0, links, 0, n.length); |
| Arrays.sort(links, Comparator.reverseOrder()); |
| for (String link : links) { |
| final Path filePath = Paths.get(link); |
| if (!Files.isSymbolicLink(filePath)) { |
| // it's not a symbolic link, so move on |
| continue; |
| } |
| // it's a symbolic link, so delete it |
| final boolean deleted = filePath.toFile().delete(); |
| if (!deleted) { |
| handle("Could not delete symbolic link at " + filePath); |
| } |
| } |
| } |
| } |
| } |
| } |
| resourcesToDelete.add(filesetDirs); |
| if (rcs != null) { |
| // sort first to files, then dirs |
| Restrict exists = new Restrict(); |
| exists.add(EXISTS); |
| exists.add(rcs); |
| Sort s = new Sort(); |
| s.add(REVERSE_FILESYSTEM); |
| s.add(exists); |
| resourcesToDelete.add(s); |
| } |
| try { |
| if (resourcesToDelete.isFilesystemOnly()) { |
| for (Resource r : resourcesToDelete) { |
| // nonexistent resources could only occur if we already |
| // deleted something from a fileset: |
| File f = r.as(FileProvider.class).getFile(); |
| if (!f.exists()) { |
| continue; |
| } |
| if (!f.isDirectory() || f.list() == null || f.list().length == 0) { |
| log("Deleting " + f, verbosity); |
| if (!delete(f) && failonerror) { |
| handle("Unable to delete " |
| + (f.isDirectory() ? "directory " : "file ") + f); |
| } |
| } |
| } |
| } else { |
| handle(getTaskName() + " handles only filesystem resources"); |
| } |
| } catch (Exception e) { |
| handle(e); |
| } finally { |
| if (implicit != null) { |
| filesets.remove(implicit); |
| } |
| } |
| } |
| |
| //************************************************************************ |
| // protected and private methods |
| //************************************************************************ |
| |
| private void handle(String msg) { |
| handle(new BuildException(msg)); |
| } |
| |
| private void handle(Exception e) { |
| if (failonerror) { |
| throw (e instanceof BuildException) ? (BuildException) e : new BuildException(e); |
| } |
| log(e, quiet ? Project.MSG_VERBOSE : verbosity); |
| } |
| |
| /** |
| * Accommodate Windows bug encountered in both Sun and IBM JDKs. |
| * Others possible. If the delete does not work, call System.gc(), |
| * wait a little and try again. |
| */ |
| private boolean delete(File f) { |
| if (!FILE_UTILS.tryHardToDelete(f, performGc)) { |
| if (deleteOnExit) { |
| int level = quiet ? Project.MSG_VERBOSE : Project.MSG_INFO; |
| log("Failed to delete " + f + ", calling deleteOnExit." |
| + " This attempts to delete the file when the Ant jvm" |
| + " has exited and might not succeed.", level); |
| f.deleteOnExit(); |
| return true; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Delete a directory |
| * |
| * @param d the directory to delete |
| */ |
| protected void removeDir(File d) { |
| String[] list = d.list(); |
| if (list == null) { |
| list = new String[0]; |
| } |
| for (String s : list) { |
| File f = new File(d, s); |
| if (f.isDirectory()) { |
| removeDir(f); |
| } else { |
| log("Deleting " + f.getAbsolutePath(), quiet ? Project.MSG_VERBOSE : verbosity); |
| if (!delete(f)) { |
| handle("Unable to delete file " + f.getAbsolutePath()); |
| } |
| } |
| } |
| log("Deleting directory " + d.getAbsolutePath(), verbosity); |
| if (!delete(d)) { |
| handle("Unable to delete directory " + d.getAbsolutePath()); |
| } |
| } |
| |
| /** |
| * remove an array of files in a directory, and a list of subdirectories |
| * which will only be deleted if 'includeEmpty' is true |
| * @param d directory to work from |
| * @param files array of files to delete; can be of zero length |
| * @param dirs array of directories to delete; can of zero length |
| */ |
| protected void removeFiles(File d, String[] files, String[] dirs) { |
| if (files.length > 0) { |
| log("Deleting " + files.length + " files from " |
| + d.getAbsolutePath(), quiet ? Project.MSG_VERBOSE : verbosity); |
| for (String filename : files) { |
| File f = new File(d, filename); |
| log("Deleting " + f.getAbsolutePath(), |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| if (!delete(f)) { |
| handle("Unable to delete file " + f.getAbsolutePath()); |
| } |
| } |
| } |
| |
| if (dirs.length > 0 && includeEmpty) { |
| int dirCount = 0; |
| for (int j = dirs.length - 1; j >= 0; j--) { |
| File currDir = new File(d, dirs[j]); |
| String[] dirFiles = currDir.list(); |
| if (dirFiles == null || dirFiles.length == 0) { |
| log("Deleting " + currDir.getAbsolutePath(), |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| if (!delete(currDir)) { |
| handle("Unable to delete directory " + currDir.getAbsolutePath()); |
| } else { |
| dirCount++; |
| } |
| } |
| } |
| |
| if (dirCount > 0) { |
| log("Deleted " + dirCount |
| + " director" + (dirCount == 1 ? "y" : "ies") |
| + " form " + d.getAbsolutePath(), |
| quiet ? Project.MSG_VERBOSE : verbosity); |
| } |
| } |
| } |
| |
| private boolean isDanglingSymlink(final File f) { |
| if (!Files.isSymbolicLink(f.toPath())) { |
| // it's not a symlink, so clearly it's not a dangling one |
| return false; |
| } |
| // it's a symbolic link, now check the existence of the (target) file (by "following links") |
| final boolean targetFileExists = Files.exists(f.toPath()); |
| return !targetFileExists; |
| } |
| } |