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: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.util.Arrays;
023: import java.util.Vector;
024: import java.util.Iterator;
025: import java.util.Comparator;
026:
027: import org.apache.tools.ant.Project;
028: import org.apache.tools.ant.BuildException;
029: import org.apache.tools.ant.taskdefs.condition.Os;
030: import org.apache.tools.ant.types.FileSet;
031: import org.apache.tools.ant.types.PatternSet;
032: import org.apache.tools.ant.types.ResourceCollection;
033: import org.apache.tools.ant.types.resources.Sort;
034: import org.apache.tools.ant.types.resources.Restrict;
035: import org.apache.tools.ant.types.resources.Resources;
036: import org.apache.tools.ant.types.resources.FileResource;
037: import org.apache.tools.ant.types.resources.FileResourceIterator;
038: import org.apache.tools.ant.types.resources.comparators.Reverse;
039: import org.apache.tools.ant.types.resources.comparators.FileSystem;
040: import org.apache.tools.ant.types.resources.comparators.ResourceComparator;
041: import org.apache.tools.ant.types.resources.selectors.Exists;
042: import org.apache.tools.ant.types.resources.selectors.ResourceSelector;
043: import org.apache.tools.ant.types.selectors.OrSelector;
044: import org.apache.tools.ant.types.selectors.AndSelector;
045: import org.apache.tools.ant.types.selectors.NotSelector;
046: import org.apache.tools.ant.types.selectors.DateSelector;
047: import org.apache.tools.ant.types.selectors.FileSelector;
048: import org.apache.tools.ant.types.selectors.NoneSelector;
049: import org.apache.tools.ant.types.selectors.SizeSelector;
050: import org.apache.tools.ant.types.selectors.DepthSelector;
051: import org.apache.tools.ant.types.selectors.DependSelector;
052: import org.apache.tools.ant.types.selectors.ExtendSelector;
053: import org.apache.tools.ant.types.selectors.SelectSelector;
054: import org.apache.tools.ant.types.selectors.PresentSelector;
055: import org.apache.tools.ant.types.selectors.ContainsSelector;
056: import org.apache.tools.ant.types.selectors.FilenameSelector;
057: import org.apache.tools.ant.types.selectors.MajoritySelector;
058: import org.apache.tools.ant.types.selectors.ContainsRegexpSelector;
059: import org.apache.tools.ant.types.selectors.modifiedselector.ModifiedSelector;
060:
061: /**
062: * Deletes a file or directory, or set of files defined by a fileset.
063: * The original delete task would delete a file, or a set of files
064: * using the include/exclude syntax. The deltree task would delete a
065: * directory tree. This task combines the functionality of these two
066: * originally distinct tasks.
067: * <p>Currently Delete extends MatchingTask. This is intended <i>only</i>
068: * to provide backwards compatibility for a release. The future position
069: * is to use nested filesets exclusively.</p>
070: *
071: * @since Ant 1.2
072: *
073: * @ant.task category="filesystem"
074: */
075: public class Delete extends MatchingTask {
076: private static final int DELETE_RETRY_SLEEP_MILLIS = 10;
077: private static final ResourceComparator REVERSE_FILESYSTEM = new Reverse(
078: new FileSystem());
079: private static final ResourceSelector EXISTS = new Exists();
080:
081: private static class ReverseDirs implements ResourceCollection {
082: static final Comparator REVERSE = new Comparator() {
083: public int compare(Object foo, Object bar) {
084: return ((Comparable) foo).compareTo(bar) * -1;
085: }
086: };
087: private File basedir;
088: private String[] dirs;
089:
090: ReverseDirs(File basedir, String[] dirs) {
091: this .basedir = basedir;
092: this .dirs = dirs;
093: Arrays.sort(this .dirs, REVERSE);
094: }
095:
096: public Iterator iterator() {
097: return new FileResourceIterator(basedir, dirs);
098: }
099:
100: public boolean isFilesystemOnly() {
101: return true;
102: }
103:
104: public int size() {
105: return dirs.length;
106: }
107: }
108:
109: // CheckStyle:VisibilityModifier OFF - bc
110: protected File file = null;
111: protected File dir = null;
112: protected Vector filesets = new Vector();
113: protected boolean usedMatchingTask = false;
114: // by default, remove matching empty dirs
115: protected boolean includeEmpty = false;
116:
117: private int verbosity = Project.MSG_VERBOSE;
118: private boolean quiet = false;
119: private boolean failonerror = true;
120: private boolean deleteOnExit = false;
121: private Resources rcs = null;
122:
123: // CheckStyle:VisibilityModifier ON
124:
125: /**
126: * Set the name of a single file to be removed.
127: *
128: * @param file the file to be deleted
129: */
130: public void setFile(File file) {
131: this .file = file;
132: }
133:
134: /**
135: * Set the directory from which files are to be deleted
136: *
137: * @param dir the directory path.
138: */
139: public void setDir(File dir) {
140: this .dir = dir;
141: getImplicitFileSet().setDir(dir);
142: }
143:
144: /**
145: * If true, list all names of deleted files.
146: *
147: * @param verbose "true" or "on"
148: */
149: public void setVerbose(boolean verbose) {
150: if (verbose) {
151: this .verbosity = Project.MSG_INFO;
152: } else {
153: this .verbosity = Project.MSG_VERBOSE;
154: }
155: }
156:
157: /**
158: * If true and the file does not exist, do not display a diagnostic
159: * message or modify the exit status to reflect an error.
160: * This means that if a file or directory cannot be deleted,
161: * then no error is reported. This setting emulates the
162: * -f option to the Unix "rm" command.
163: * Default is false meaning things are "noisy"
164: * @param quiet "true" or "on"
165: */
166: public void setQuiet(boolean quiet) {
167: this .quiet = quiet;
168: if (quiet) {
169: this .failonerror = false;
170: }
171: }
172:
173: /**
174: * If false, note errors but continue.
175: *
176: * @param failonerror true or false
177: */
178: public void setFailOnError(boolean failonerror) {
179: this .failonerror = failonerror;
180: }
181:
182: /**
183: * If true, on failure to delete, note the error and set
184: * the deleteonexit flag, and continue
185: *
186: * @param deleteOnExit true or false
187: */
188: public void setDeleteOnExit(boolean deleteOnExit) {
189: this .deleteOnExit = deleteOnExit;
190: }
191:
192: /**
193: * If true, delete empty directories.
194: * @param includeEmpty if true delete empty directories (only
195: * for filesets). Default is false.
196: */
197: public void setIncludeEmptyDirs(boolean includeEmpty) {
198: this .includeEmpty = includeEmpty;
199: }
200:
201: /**
202: * Adds a set of files to be deleted.
203: * @param set the set of files to be deleted
204: */
205: public void addFileset(FileSet set) {
206: filesets.addElement(set);
207: }
208:
209: /**
210: * Add an arbitrary ResourceCollection to be deleted.
211: * @param rc the filesystem-only ResourceCollection.
212: */
213: public void add(ResourceCollection rc) {
214: if (rc == null) {
215: return;
216: }
217: rcs = (rcs == null) ? new Resources() : rcs;
218: rcs.add(rc);
219: }
220:
221: /**
222: * add a name entry on the include list
223: * @return a NameEntry object to be configured
224: */
225: public PatternSet.NameEntry createInclude() {
226: usedMatchingTask = true;
227: return super .createInclude();
228: }
229:
230: /**
231: * add a name entry on the include files list
232: * @return an NameEntry object to be configured
233: */
234: public PatternSet.NameEntry createIncludesFile() {
235: usedMatchingTask = true;
236: return super .createIncludesFile();
237: }
238:
239: /**
240: * add a name entry on the exclude list
241: * @return an NameEntry object to be configured
242: */
243: public PatternSet.NameEntry createExclude() {
244: usedMatchingTask = true;
245: return super .createExclude();
246: }
247:
248: /**
249: * add a name entry on the include files list
250: * @return an NameEntry object to be configured
251: */
252: public PatternSet.NameEntry createExcludesFile() {
253: usedMatchingTask = true;
254: return super .createExcludesFile();
255: }
256:
257: /**
258: * add a set of patterns
259: * @return PatternSet object to be configured
260: */
261: public PatternSet createPatternSet() {
262: usedMatchingTask = true;
263: return super .createPatternSet();
264: }
265:
266: /**
267: * Sets the set of include patterns. Patterns may be separated by a comma
268: * or a space.
269: *
270: * @param includes the string containing the include patterns
271: */
272: public void setIncludes(String includes) {
273: usedMatchingTask = true;
274: super .setIncludes(includes);
275: }
276:
277: /**
278: * Sets the set of exclude patterns. Patterns may be separated by a comma
279: * or a space.
280: *
281: * @param excludes the string containing the exclude patterns
282: */
283: public void setExcludes(String excludes) {
284: usedMatchingTask = true;
285: super .setExcludes(excludes);
286: }
287:
288: /**
289: * Sets whether default exclusions should be used or not.
290: *
291: * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions
292: * should be used, "false"|"off"|"no" when they
293: * shouldn't be used.
294: */
295: public void setDefaultexcludes(boolean useDefaultExcludes) {
296: usedMatchingTask = true;
297: super .setDefaultexcludes(useDefaultExcludes);
298: }
299:
300: /**
301: * Sets the name of the file containing the includes patterns.
302: *
303: * @param includesfile A string containing the filename to fetch
304: * the include patterns from.
305: */
306: public void setIncludesfile(File includesfile) {
307: usedMatchingTask = true;
308: super .setIncludesfile(includesfile);
309: }
310:
311: /**
312: * Sets the name of the file containing the includes patterns.
313: *
314: * @param excludesfile A string containing the filename to fetch
315: * the include patterns from.
316: */
317: public void setExcludesfile(File excludesfile) {
318: usedMatchingTask = true;
319: super .setExcludesfile(excludesfile);
320: }
321:
322: /**
323: * Sets case sensitivity of the file system
324: *
325: * @param isCaseSensitive "true"|"on"|"yes" if file system is case
326: * sensitive, "false"|"off"|"no" when not.
327: */
328: public void setCaseSensitive(boolean isCaseSensitive) {
329: usedMatchingTask = true;
330: super .setCaseSensitive(isCaseSensitive);
331: }
332:
333: /**
334: * Sets whether or not symbolic links should be followed.
335: *
336: * @param followSymlinks whether or not symbolic links should be followed
337: */
338: public void setFollowSymlinks(boolean followSymlinks) {
339: usedMatchingTask = true;
340: super .setFollowSymlinks(followSymlinks);
341: }
342:
343: /**
344: * add a "Select" selector entry on the selector list
345: * @param selector the selector to be added
346: */
347: public void addSelector(SelectSelector selector) {
348: usedMatchingTask = true;
349: super .addSelector(selector);
350: }
351:
352: /**
353: * add an "And" selector entry on the selector list
354: * @param selector the selector to be added
355: */
356: public void addAnd(AndSelector selector) {
357: usedMatchingTask = true;
358: super .addAnd(selector);
359: }
360:
361: /**
362: * add an "Or" selector entry on the selector list
363: * @param selector the selector to be added
364: */
365: public void addOr(OrSelector selector) {
366: usedMatchingTask = true;
367: super .addOr(selector);
368: }
369:
370: /**
371: * add a "Not" selector entry on the selector list
372: * @param selector the selector to be added
373: */
374: public void addNot(NotSelector selector) {
375: usedMatchingTask = true;
376: super .addNot(selector);
377: }
378:
379: /**
380: * add a "None" selector entry on the selector list
381: * @param selector the selector to be added
382: */
383: public void addNone(NoneSelector selector) {
384: usedMatchingTask = true;
385: super .addNone(selector);
386: }
387:
388: /**
389: * add a majority selector entry on the selector list
390: * @param selector the selector to be added
391: */
392: public void addMajority(MajoritySelector selector) {
393: usedMatchingTask = true;
394: super .addMajority(selector);
395: }
396:
397: /**
398: * add a selector date entry on the selector list
399: * @param selector the selector to be added
400: */
401: public void addDate(DateSelector selector) {
402: usedMatchingTask = true;
403: super .addDate(selector);
404: }
405:
406: /**
407: * add a selector size entry on the selector list
408: * @param selector the selector to be added
409: */
410: public void addSize(SizeSelector selector) {
411: usedMatchingTask = true;
412: super .addSize(selector);
413: }
414:
415: /**
416: * add a selector filename entry on the selector list
417: * @param selector the selector to be added
418: */
419: public void addFilename(FilenameSelector selector) {
420: usedMatchingTask = true;
421: super .addFilename(selector);
422: }
423:
424: /**
425: * add an extended selector entry on the selector list
426: * @param selector the selector to be added
427: */
428: public void addCustom(ExtendSelector selector) {
429: usedMatchingTask = true;
430: super .addCustom(selector);
431: }
432:
433: /**
434: * add a contains selector entry on the selector list
435: * @param selector the selector to be added
436: */
437: public void addContains(ContainsSelector selector) {
438: usedMatchingTask = true;
439: super .addContains(selector);
440: }
441:
442: /**
443: * add a present selector entry on the selector list
444: * @param selector the selector to be added
445: */
446: public void addPresent(PresentSelector selector) {
447: usedMatchingTask = true;
448: super .addPresent(selector);
449: }
450:
451: /**
452: * add a depth selector entry on the selector list
453: * @param selector the selector to be added
454: */
455: public void addDepth(DepthSelector selector) {
456: usedMatchingTask = true;
457: super .addDepth(selector);
458: }
459:
460: /**
461: * add a depends selector entry on the selector list
462: * @param selector the selector to be added
463: */
464: public void addDepend(DependSelector selector) {
465: usedMatchingTask = true;
466: super .addDepend(selector);
467: }
468:
469: /**
470: * add a regular expression selector entry on the selector list
471: * @param selector the selector to be added
472: */
473: public void addContainsRegexp(ContainsRegexpSelector selector) {
474: usedMatchingTask = true;
475: super .addContainsRegexp(selector);
476: }
477:
478: /**
479: * add the modified selector
480: * @param selector the selector to add
481: * @since ant 1.6
482: */
483: public void addModified(ModifiedSelector selector) {
484: usedMatchingTask = true;
485: super .addModified(selector);
486: }
487:
488: /**
489: * add an arbitrary selector
490: * @param selector the selector to be added
491: * @since Ant 1.6
492: */
493: public void add(FileSelector selector) {
494: usedMatchingTask = true;
495: super .add(selector);
496: }
497:
498: /**
499: * Delete the file(s).
500: * @exception BuildException if an error occurs
501: */
502: public void execute() throws BuildException {
503: if (usedMatchingTask) {
504: log(
505: "DEPRECATED - Use of the implicit FileSet is deprecated. "
506: + "Use a nested fileset element instead.",
507: quiet ? Project.MSG_VERBOSE : verbosity);
508: }
509:
510: if (file == null && dir == null && filesets.size() == 0
511: && rcs == null) {
512: throw new BuildException("At least one of the file or dir "
513: + "attributes, or a nested resource collection, "
514: + "must be set.");
515: }
516:
517: if (quiet && failonerror) {
518: throw new BuildException(
519: "quiet and failonerror cannot both be "
520: + "set to true", getLocation());
521: }
522:
523: // delete the single file
524: if (file != null) {
525: if (file.exists()) {
526: if (file.isDirectory()) {
527: log(
528: "Directory "
529: + file.getAbsolutePath()
530: + " cannot be removed using the file attribute. "
531: + "Use dir instead.",
532: quiet ? Project.MSG_VERBOSE : verbosity);
533: } else {
534: log("Deleting: " + file.getAbsolutePath());
535:
536: if (!delete(file)) {
537: handle("Unable to delete file "
538: + file.getAbsolutePath());
539: }
540: }
541: } else {
542: log("Could not find file " + file.getAbsolutePath()
543: + " to delete.", quiet ? Project.MSG_VERBOSE
544: : verbosity);
545: }
546: }
547:
548: // delete the directory
549: if (dir != null && dir.exists() && dir.isDirectory()
550: && !usedMatchingTask) {
551: /*
552: If verbosity is MSG_VERBOSE, that mean we are doing
553: regular logging (backwards as that sounds). In that
554: case, we want to print one message about deleting the
555: top of the directory tree. Otherwise, the removeDir
556: method will handle messages for _all_ directories.
557: */
558: if (verbosity == Project.MSG_VERBOSE) {
559: log("Deleting directory " + dir.getAbsolutePath());
560: }
561: removeDir(dir);
562: }
563: Resources resourcesToDelete = new Resources();
564: resourcesToDelete.setProject(getProject());
565: Resources filesetDirs = new Resources();
566: filesetDirs.setProject(getProject());
567: FileSet implicit = null;
568: if (usedMatchingTask && dir != null && dir.isDirectory()) {
569: //add the files from the default fileset:
570: implicit = getImplicitFileSet();
571: implicit.setProject(getProject());
572: filesets.add(implicit);
573: }
574:
575: for (int i = 0, size = filesets.size(); i < size; i++) {
576: FileSet fs = (FileSet) filesets.get(i);
577: if (fs.getProject() == null) {
578: log("Deleting fileset with no project specified;"
579: + " assuming executing project",
580: Project.MSG_VERBOSE);
581: fs = (FileSet) fs.clone();
582: fs.setProject(getProject());
583: }
584: if (!fs.getDir().isDirectory()) {
585: handle("Directory does not exist:" + fs.getDir());
586: } else {
587: resourcesToDelete.add(fs);
588: if (includeEmpty) {
589: filesetDirs.add(new ReverseDirs(fs.getDir(), fs
590: .getDirectoryScanner()
591: .getIncludedDirectories()));
592: }
593: }
594: }
595: resourcesToDelete.add(filesetDirs);
596: if (rcs != null) {
597: // sort first to files, then dirs
598: Restrict exists = new Restrict();
599: exists.add(EXISTS);
600: exists.add(rcs);
601: Sort s = new Sort();
602: s.add(REVERSE_FILESYSTEM);
603: s.add(exists);
604: resourcesToDelete.add(s);
605: }
606: try {
607: if (resourcesToDelete.isFilesystemOnly()) {
608: for (Iterator iter = resourcesToDelete.iterator(); iter
609: .hasNext();) {
610: FileResource r = (FileResource) iter.next();
611: // nonexistent resources could only occur if we already
612: // deleted something from a fileset:
613: if (!r.isExists()) {
614: continue;
615: }
616: if (!(r.isDirectory())
617: || r.getFile().list().length == 0) {
618: log("Deleting " + r, verbosity);
619: if (!delete(r.getFile()) && failonerror) {
620: handle("Unable to delete "
621: + (r.isDirectory() ? "directory "
622: : "file ") + r);
623: }
624: }
625: }
626: } else {
627: handle(getTaskName()
628: + " handles only filesystem resources");
629: }
630: } catch (Exception e) {
631: handle(e);
632: } finally {
633: if (implicit != null) {
634: filesets.remove(implicit);
635: }
636: }
637: }
638:
639: //************************************************************************
640: // protected and private methods
641: //************************************************************************
642:
643: private void handle(String msg) {
644: handle(new BuildException(msg));
645: }
646:
647: private void handle(Exception e) {
648: if (failonerror) {
649: throw (e instanceof BuildException) ? (BuildException) e
650: : new BuildException(e);
651: }
652: log(e, quiet ? Project.MSG_VERBOSE : verbosity);
653: }
654:
655: /**
656: * Accommodate Windows bug encountered in both Sun and IBM JDKs.
657: * Others possible. If the delete does not work, call System.gc(),
658: * wait a little and try again.
659: */
660: private boolean delete(File f) {
661: if (!f.delete()) {
662: if (Os.isFamily("windows")) {
663: System.gc();
664: }
665: try {
666: Thread.sleep(DELETE_RETRY_SLEEP_MILLIS);
667: } catch (InterruptedException ex) {
668: // Ignore Exception
669: }
670: if (!f.delete()) {
671: if (deleteOnExit) {
672: int level = quiet ? Project.MSG_VERBOSE
673: : Project.MSG_INFO;
674: log(
675: "Failed to delete "
676: + f
677: + ", calling deleteOnExit."
678: + " This attempts to delete the file when the Ant jvm"
679: + " has exited and might not succeed.",
680: level);
681: f.deleteOnExit();
682: return true;
683: }
684: return false;
685: }
686: }
687: return true;
688: }
689:
690: /**
691: * Delete a directory
692: *
693: * @param d the directory to delete
694: */
695: protected void removeDir(File d) {
696: String[] list = d.list();
697: if (list == null) {
698: list = new String[0];
699: }
700: for (int i = 0; i < list.length; i++) {
701: String s = list[i];
702: File f = new File(d, s);
703: if (f.isDirectory()) {
704: removeDir(f);
705: } else {
706: log("Deleting " + f.getAbsolutePath(),
707: quiet ? Project.MSG_VERBOSE : verbosity);
708: if (!delete(f)) {
709: handle("Unable to delete file "
710: + f.getAbsolutePath());
711: }
712: }
713: }
714: log("Deleting directory " + d.getAbsolutePath(), verbosity);
715: if (!delete(d)) {
716: handle("Unable to delete directory "
717: + dir.getAbsolutePath());
718: }
719: }
720:
721: /**
722: * remove an array of files in a directory, and a list of subdirectories
723: * which will only be deleted if 'includeEmpty' is true
724: * @param d directory to work from
725: * @param files array of files to delete; can be of zero length
726: * @param dirs array of directories to delete; can of zero length
727: */
728: protected void removeFiles(File d, String[] files, String[] dirs) {
729: if (files.length > 0) {
730: log("Deleting " + files.length + " files from "
731: + d.getAbsolutePath(), quiet ? Project.MSG_VERBOSE
732: : verbosity);
733: for (int j = 0; j < files.length; j++) {
734: File f = new File(d, files[j]);
735: log("Deleting " + f.getAbsolutePath(),
736: quiet ? Project.MSG_VERBOSE : verbosity);
737: if (!delete(f)) {
738: handle("Unable to delete file "
739: + f.getAbsolutePath());
740: }
741: }
742: }
743:
744: if (dirs.length > 0 && includeEmpty) {
745: int dirCount = 0;
746: for (int j = dirs.length - 1; j >= 0; j--) {
747: File currDir = new File(d, dirs[j]);
748: String[] dirFiles = currDir.list();
749: if (dirFiles == null || dirFiles.length == 0) {
750: log("Deleting " + currDir.getAbsolutePath(),
751: quiet ? Project.MSG_VERBOSE : verbosity);
752: if (!delete(currDir)) {
753: handle("Unable to delete directory "
754: + currDir.getAbsolutePath());
755: } else {
756: dirCount++;
757: }
758: }
759: }
760:
761: if (dirCount > 0) {
762: log("Deleted " + dirCount + " director"
763: + (dirCount == 1 ? "y" : "ies") + " form "
764: + d.getAbsolutePath(),
765: quiet ? Project.MSG_VERBOSE : verbosity);
766: }
767: }
768: }
769: }
|