diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/FilesTest.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/FilesTest.java new file mode 100644 index 000000000..ff5c53aca --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/FilesTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2019 Universite de Technologie de Belfort-Montbeliard (http://www.utbm.fr) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.util; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import org.junit.Test; + +/** + * @author Stephane Galland - Initial contribution and API + */ +public class FilesTest { + + private static File makeFile(File root, String... elements) { + File res = root; + for (final String component : elements) { + if (res == null) { + res = new File(component); + } else { + res = new File(res, component); + } + } + return res; + } + + private static void touch(File folder, String filename) throws IOException { + folder.mkdirs(); + final File file = new File(folder, filename); + file.createNewFile(); + } + + private File createTestFolder() throws IOException { + File rootPath = makeFile(null, "build", "tmp", "FilesTest" + (int) (Math.random() * 100000000.)); + while (rootPath.exists()) { + rootPath = makeFile(null, "build", "tmp", "FilesTest" + (int) (Math.random() * 100000000.)); + } + final File folder1 = makeFile(rootPath, "src", "main", "java"); + final File folder2 = makeFile(rootPath, "src", "main", "java", "mypack"); + final File folder3 = makeFile(rootPath, "src", "generated-sources", "xtend", "mypack"); + final File folder4 = makeFile(rootPath, "target", "classes", "mypack"); + touch(folder1, "MyFile.java"); + touch(folder2, "MyType.xtend"); + touch(folder3, "MyType.java"); + touch(folder4, "MyType.class"); + touch(rootPath, ".hiddenfile"); + return rootPath; + } + + @Test + public void cleanFolder_deleteParentTrue() throws Exception { + final File root = createTestFolder(); + Files.cleanFolder(root, null, true, true); + assertFalse(root.exists()); + } + + @Test + public void cleanFolder_deleteParentFalse() throws Exception { + final File root = createTestFolder(); + Files.cleanFolder(root, null, true, false); + assertTrue(root.exists()); + assertEquals(0, root.listFiles().length); + } + +} diff --git a/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Files.java b/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Files.java index 16f3e4b4c..fbbaa329a 100644 --- a/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Files.java +++ b/org.eclipse.xtext.util/src/org/eclipse/xtext/util/Files.java @@ -15,6 +15,9 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; +import java.util.Deque; +import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; @@ -25,6 +28,7 @@ import com.google.common.io.ByteStreams; /** * @author Jan Köhnlein - Initial contribution and API + * @author Stephane Galland - fixing cleanFolder behavior */ public class Files { private static Logger log = Logger.getLogger(Files.class); @@ -74,39 +78,54 @@ public class Files { } } + /** Clean the content of the the given folder. + * + * @param parentFolder the folder to be cleaned. It must not be {@code null}. + * @param filter a filter for selecting the files to be removed. If it is {@code null}, all the files are removed. + * @param continueOnError indicates if the cleaning should continue after an error occurs. + * @param deleteParentFolder indicates if {@code parentFolder} should be also deleted if it becomes empty. + * @return {@code true} if the cleaning process goes through all the folders and files. {@code false} if the process + * has been stopped before its termination. The value {@code false} could be replied only if the value of + * {@code continueOnError} is {@code false}. + * @throws FileNotFoundException if the given {@code parentFolder} does not exists. + */ public static boolean cleanFolder(final File parentFolder, final FileFilter filter, boolean continueOnError, boolean deleteParentFolder) throws FileNotFoundException { if (!parentFolder.exists()) { throw new FileNotFoundException(parentFolder.getAbsolutePath()); } - FileFilter myFilter = filter; - if (myFilter == null) - myFilter = new FileFilter() { - @Override - public boolean accept(File pathname) { - return true; - } - }; + final FileFilter myFilter = filter == null ? it -> true : filter; log.debug("Cleaning folder " + parentFolder.toString()); final File[] contents = parentFolder.listFiles(myFilter); - if (contents == null) { - return true; - } - for (int j = 0; j < contents.length; j++) { - final File file = contents[j]; - if (file.isDirectory()) { - if (!cleanFolder(file, myFilter, continueOnError, true) && !continueOnError) - return false; - } else { - if (!file.delete()) { + if (contents != null) { + final Deque filesToRemove = new LinkedList<>(Arrays.asList(contents)); + while (!filesToRemove.isEmpty()) { + final File file = filesToRemove.pop(); + if (file.isDirectory()) { + final File[] children = file.listFiles(myFilter); + if (children != null && children.length > 0) { + // Push back the folder in order to be removed after all its children. + filesToRemove.push(file); + // Push the children in order to be removed before the parent folder. + for (int i = 0; i < children.length; ++i) { + filesToRemove.push(children[i]); + } + } else if (!file.delete()) { + log.error("Couldn't delete " + file.getAbsolutePath()); + if (!continueOnError) { + return false; + } + } + } else if (!file.delete()) { log.error("Couldn't delete " + file.getAbsolutePath()); - if (!continueOnError) + if (!continueOnError) { return false; + } } } } if (deleteParentFolder) { - String[] children = parentFolder.list(); + final String[] children = parentFolder.list(); if (children != null && children.length == 0 && !parentFolder.delete()) { log.error("Couldn't delete " + parentFolder.getAbsolutePath()); return false; @@ -124,12 +143,7 @@ public class Files { * @throws FileNotFoundException if folder does not exists */ public static boolean sweepFolder(File folder) throws FileNotFoundException { - return Files.cleanFolder(folder, new FileFilter() { - @Override - public boolean accept(File pathname) { - return true; - } - }, true, false); + return cleanFolder(folder, null, true, false); } /**