📄 sync.java
字号:
/* * 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 * * http://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.util.HashSet;import java.util.Iterator;import java.util.Map;import java.util.Set;import org.apache.tools.ant.BuildException;import org.apache.tools.ant.DirectoryScanner;import org.apache.tools.ant.Project;import org.apache.tools.ant.Task;import org.apache.tools.ant.types.AbstractFileSet;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.selectors.FileSelector;import org.apache.tools.ant.types.selectors.NoneSelector;/** * Synchronize a local target directory from the files defined * in one or more filesets. * * <p>Uses a <copy> task internally, but forbidding the use of * mappers and filter chains. Files of the destination directory not * present in any of the source fileset are removed.</p> * * @since Ant 1.6 * * revised by <a href="mailto:daniel.armbrust@mayo.edu">Dan Armbrust</a> * to remove orphaned directories. * * @ant.task category="filesystem" */public class Sync extends Task { // Same as regular <copy> task... see at end-of-file! private MyCopy myCopy; // Similar to a fileset, but doesn't allow dir attribute to be set private SyncTarget syncTarget; // Override Task#init /** * Initialize the sync task. * @throws BuildException if there is a problem. * @see Task#init() */ public void init() throws BuildException { // Instantiate it myCopy = new MyCopy(); configureTask(myCopy); // Default config of <mycopy> for our purposes. myCopy.setFiltering(false); myCopy.setIncludeEmptyDirs(false); myCopy.setPreserveLastModified(true); } private void configureTask(Task helper) { helper.setProject(getProject()); helper.setTaskName(getTaskName()); helper.setOwningTarget(getOwningTarget()); helper.init(); } // Override Task#execute /** * Execute the sync task. * @throws BuildException if there is an error. * @see Task#execute() */ public void execute() throws BuildException { // The destination of the files to copy File toDir = myCopy.getToDir(); // The complete list of files to copy Set allFiles = myCopy.nonOrphans; // If the destination directory didn't already exist, // or was empty, then no previous file removal is necessary! boolean noRemovalNecessary = !toDir.exists() || toDir.list().length < 1; // Copy all the necessary out-of-date files log("PASS#1: Copying files to " + toDir, Project.MSG_DEBUG); myCopy.execute(); // Do we need to perform further processing? if (noRemovalNecessary) { log("NO removing necessary in " + toDir, Project.MSG_DEBUG); return; // nope ;-) } // Get rid of all files not listed in the source filesets. log("PASS#2: Removing orphan files from " + toDir, Project.MSG_DEBUG); int[] removedFileCount = removeOrphanFiles(allFiles, toDir); logRemovedCount(removedFileCount[0], "dangling director", "y", "ies"); logRemovedCount(removedFileCount[1], "dangling file", "", "s"); // Get rid of empty directories on the destination side if (!myCopy.getIncludeEmptyDirs()) { log("PASS#3: Removing empty directories from " + toDir, Project.MSG_DEBUG); int removedDirCount = removeEmptyDirectories(toDir, false); logRemovedCount(removedDirCount, "empty director", "y", "ies"); } } private void logRemovedCount(int count, String prefix, String singularSuffix, String pluralSuffix) { File toDir = myCopy.getToDir(); String what = (prefix == null) ? "" : prefix; what += (count < 2) ? singularSuffix : pluralSuffix; if (count > 0) { log("Removed " + count + " " + what + " from " + toDir, Project.MSG_INFO); } else { log("NO " + what + " to remove from " + toDir, Project.MSG_VERBOSE); } } /** * Removes all files and folders not found as keys of a table * (used as a set!). * * <p>If the provided file is a directory, it is recursively * scanned for orphaned files which will be removed as well.</p> * * <p>If the directory is an orphan, it will also be removed.</p> * * @param nonOrphans the table of all non-orphan <code>File</code>s. * @param file the initial file or directory to scan or test. * @return the number of orphaned files and directories actually removed. * Position 0 of the array is the number of orphaned directories. * Position 1 of the array is the number or orphaned files. */ private int[] removeOrphanFiles(Set nonOrphans, File toDir) { int[] removedCount = new int[] {0, 0}; String[] excls = (String[]) nonOrphans.toArray(new String[nonOrphans.size() + 1]); // want to keep toDir itself excls[nonOrphans.size()] = ""; DirectoryScanner ds = null; if (syncTarget != null) { FileSet fs = new FileSet(); fs.setDir(toDir); fs.setCaseSensitive(syncTarget.isCaseSensitive()); fs.setFollowSymlinks(syncTarget.isFollowSymlinks()); // preserveInTarget would find all files we want to keep, // but we need to find all that we want to delete - so the // meaning of all patterns and selectors must be inverted PatternSet ps = syncTarget.mergePatterns(getProject()); fs.appendExcludes(ps.getIncludePatterns(getProject())); fs.appendIncludes(ps.getExcludePatterns(getProject())); fs.setDefaultexcludes(!syncTarget.getDefaultexcludes()); // selectors are implicitly ANDed in DirectoryScanner. To // revert their logic we wrap them into a <none> selector // instead. FileSelector[] s = syncTarget.getSelectors(getProject()); if (s.length > 0) { NoneSelector ns = new NoneSelector(); for (int i = 0; i < s.length; i++) { ns.appendSelector(s[i]); } fs.appendSelector(ns); } ds = fs.getDirectoryScanner(getProject()); } else { ds = new DirectoryScanner(); ds.setBasedir(toDir); } ds.addExcludes(excls); ds.scan(); String[] files = ds.getIncludedFiles(); for (int i = 0; i < files.length; i++) { File f = new File(toDir, files[i]); log("Removing orphan file: " + f, Project.MSG_DEBUG); f.delete(); ++removedCount[1]; } String[] dirs = ds.getIncludedDirectories(); // ds returns the directories in lexicographic order. // iterating through the array backwards means we are deleting // leaves before their parent nodes - thus making sure (well, // more likely) that the directories are empty when we try to // delete them. for (int i = dirs.length - 1; i >= 0; --i) { File f = new File(toDir, dirs[i]); if (f.list().length < 1) { log("Removing orphan directory: " + f, Project.MSG_DEBUG); f.delete(); ++removedCount[0]; } } return removedCount; } /** * Removes all empty directories from a directory. * * <p><em>Note that a directory that contains only empty * directories, directly or not, will be removed!</em></p> * * <p>Recurses depth-first to find the leaf directories * which are empty and removes them, then unwinds the * recursion stack, removing directories which have * become empty themselves, etc...</p> *
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -