001package org.junit.rules;
002
003import java.io.File;
004import java.io.IOException;
005import java.lang.reflect.Array;
006import java.lang.reflect.InvocationTargetException;
007import java.lang.reflect.Method;
008
009import org.junit.Rule;
010
011/**
012 * The TemporaryFolder Rule allows creation of files and folders that should
013 * be deleted when the test method finishes (whether it passes or
014 * fails). Whether the deletion is successful or not is not checked by this rule.
015 * No exception will be thrown in case the deletion fails.
016 *
017 * <p>Example of usage:
018 * <pre>
019 * public static class HasTempFolder {
020 *  &#064;Rule
021 *  public TemporaryFolder folder= new TemporaryFolder();
022 *
023 *  &#064;Test
024 *  public void testUsingTempFolder() throws IOException {
025 *      File createdFile= folder.newFile(&quot;myfile.txt&quot;);
026 *      File createdFolder= folder.newFolder(&quot;subfolder&quot;);
027 *      // ...
028 *     }
029 * }
030 * </pre>
031 *
032 * @since 4.7
033 */
034public class TemporaryFolder extends ExternalResource {
035    private final File parentFolder;
036    private File folder;
037
038    public TemporaryFolder() {
039        this(null);
040    }
041
042    public TemporaryFolder(File parentFolder) {
043        this.parentFolder = parentFolder;
044    }
045
046    @Override
047    protected void before() throws Throwable {
048        create();
049    }
050
051    @Override
052    protected void after() {
053        delete();
054    }
055
056    // testing purposes only
057
058    /**
059     * for testing purposes only. Do not use.
060     */
061    public void create() throws IOException {
062        folder = createTemporaryFolderIn(parentFolder);
063    }
064
065    /**
066     * Returns a new fresh file with the given name under the temporary folder.
067     */
068    public File newFile(String fileName) throws IOException {
069        File file = new File(getRoot(), fileName);
070        if (!file.createNewFile()) {
071            throw new IOException(
072                    "a file with the name \'" + fileName + "\' already exists in the test folder");
073        }
074        return file;
075    }
076
077    /**
078     * Returns a new fresh file with a random name under the temporary folder.
079     */
080    public File newFile() throws IOException {
081        return File.createTempFile("junit", null, getRoot());
082    }
083
084    /**
085     * Returns a new fresh folder with the given name under the temporary
086     * folder.
087     */
088    public File newFolder(String folder) throws IOException {
089        return newFolder(new String[]{folder});
090    }
091
092    /**
093     * Returns a new fresh folder with the given name(s) under the temporary
094     * folder.
095     */
096    public File newFolder(String... folderNames) throws IOException {
097        File file = getRoot();
098        for (int i = 0; i < folderNames.length; i++) {
099            String folderName = folderNames[i];
100            validateFolderName(folderName);
101            file = new File(file, folderName);
102            if (!file.mkdir() && isLastElementInArray(i, folderNames)) {
103                throw new IOException(
104                        "a folder with the name \'" + folderName + "\' already exists");
105            }
106        }
107        return file;
108    }
109    
110    /**
111     * Validates if multiple path components were used while creating a folder.
112     * 
113     * @param folderName
114     *            Name of the folder being created
115     */
116    private void validateFolderName(String folderName) throws IOException {
117        File tempFile = new File(folderName);
118        if (tempFile.getParent() != null) {
119            String errorMsg = "Folder name cannot consist of multiple path components separated by a file separator."
120                    + " Please use newFolder('MyParentFolder','MyFolder') to create hierarchies of folders";
121            throw new IOException(errorMsg);
122        }
123    }
124
125    private boolean isLastElementInArray(int index, String[] array) {
126        return index == array.length - 1;
127    }
128
129    /**
130     * Returns a new fresh folder with a random name under the temporary folder.
131     */
132    public File newFolder() throws IOException {
133        return createTemporaryFolderIn(getRoot());
134    }
135
136        private static File createTemporaryFolderIn(File parentFolder) throws IOException {
137        try {
138            return createTemporaryFolderWithNioApi(parentFolder);
139        } catch (ClassNotFoundException ignore) {
140            // Fallback for Java 5 and 6
141            return createTemporaryFolderWithFileApi(parentFolder);
142        } catch (InvocationTargetException e) {
143            Throwable cause = e.getCause();
144            if (cause instanceof IOException) {
145                throw (IOException) cause;
146            }
147            if (cause instanceof RuntimeException) {
148                throw (RuntimeException) cause;
149            }
150            IOException exception = new IOException("Failed to create temporary folder in " + parentFolder);
151            exception.initCause(cause);
152            throw exception;
153        } catch (Exception e) {
154            throw new RuntimeException("Failed to create temporary folder in " + parentFolder, e);
155        }
156    }
157
158    private static File createTemporaryFolderWithNioApi(File parentFolder) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
159        Class<?> filesClass = Class.forName("java.nio.file.Files");
160        Object fileAttributeArray = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0);
161        Class<?> pathClass = Class.forName("java.nio.file.Path");
162        Object tempDir;
163        if (parentFolder != null) {
164            Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", pathClass, String.class, fileAttributeArray.getClass());
165            Object parentPath = File.class.getDeclaredMethod("toPath").invoke(parentFolder);
166            tempDir = createTempDirectoryMethod.invoke(null, parentPath, "junit", fileAttributeArray);
167        } else {
168            Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", String.class, fileAttributeArray.getClass());
169            tempDir = createTempDirectoryMethod.invoke(null, "junit", fileAttributeArray);
170        }
171        return (File) pathClass.getDeclaredMethod("toFile").invoke(tempDir);
172    }
173
174    private static File createTemporaryFolderWithFileApi(File parentFolder) throws IOException {
175        File createdFolder = File.createTempFile("junit", "", parentFolder);
176        createdFolder.delete();
177        createdFolder.mkdir();
178        return createdFolder;
179    }
180
181    /**
182     * @return the location of this temporary folder.
183     */
184    public File getRoot() {
185        if (folder == null) {
186            throw new IllegalStateException(
187                    "the temporary folder has not yet been created");
188        }
189        return folder;
190    }
191
192    /**
193     * Delete all files and folders under the temporary folder. Usually not
194     * called directly, since it is automatically applied by the {@link Rule}
195     */
196    public void delete() {
197        if (folder != null) {
198            recursiveDelete(folder);
199        }
200    }
201
202    private void recursiveDelete(File file) {
203        File[] files = file.listFiles();
204        if (files != null) {
205            for (File each : files) {
206                recursiveDelete(each);
207            }
208        }
209        file.delete();
210    }
211}