001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.test.util;
020
021import java.io.BufferedOutputStream;
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.OutputStream;
027import java.io.RandomAccessFile;
028import java.nio.charset.StandardCharsets;
029import java.nio.file.Files;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Properties;
033import java.util.UUID;
034
035/**
036 * Provides utility methods to read and write (temporary) files.
037 */
038public class TestFileUtils {
039
040    private static final File TMP = new File(
041            System.getProperty("java.io.tmpdir"),
042            "aether-" + UUID.randomUUID().toString().substring(0, 8));
043
044    static {
045        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
046            try {
047                deleteFile(TMP);
048            } catch (IOException e) {
049                e.printStackTrace();
050            }
051        }));
052    }
053
054    private TestFileUtils() {
055        // hide constructor
056    }
057
058    public static void deleteTempFiles() throws IOException {
059        deleteFile(TMP);
060    }
061
062    public static void deleteFile(File file) throws IOException {
063        if (file == null) {
064            return;
065        }
066
067        Collection<File> undeletables = new ArrayList<>();
068
069        delete(file, undeletables);
070
071        if (!undeletables.isEmpty()) {
072            throw new IOException("Failed to delete " + undeletables);
073        }
074    }
075
076    private static void delete(File file, Collection<File> undeletables) {
077        String[] children = file.list();
078        if (children != null) {
079            for (String child : children) {
080                delete(new File(file, child), undeletables);
081            }
082        }
083
084        if (!del(file)) {
085            undeletables.add(file.getAbsoluteFile());
086        }
087    }
088
089    private static boolean del(File file) {
090        for (int i = 0; i < 10; i++) {
091            if (file.delete() || !file.exists()) {
092                return true;
093            }
094        }
095        return false;
096    }
097
098    public static boolean mkdirs(File directory) {
099        if (directory == null) {
100            return false;
101        }
102
103        if (directory.exists()) {
104            return false;
105        }
106        if (directory.mkdir()) {
107            return true;
108        }
109
110        File canonDir = null;
111        try {
112            canonDir = directory.getCanonicalFile();
113        } catch (IOException e) {
114            return false;
115        }
116
117        File parentDir = canonDir.getParentFile();
118        return (parentDir != null && (mkdirs(parentDir) || parentDir.exists()) && canonDir.mkdir());
119    }
120
121    public static File createTempFile(String contents) throws IOException {
122        return createTempFile(contents.getBytes(StandardCharsets.UTF_8), 1);
123    }
124
125    public static File createTempFile(byte[] pattern, int repeat) throws IOException {
126        mkdirs(TMP);
127        File tmpFile = File.createTempFile("tmpfile-", ".data", TMP);
128        writeBytes(tmpFile, pattern, repeat);
129        return tmpFile;
130    }
131
132    /**
133     * Creates a temporary directory.
134     *
135     * @return the temporary directory
136     * @throws IOException if an I/O error occurs
137     * @deprecated use @TempDir (JUnit 5} Or TemporaryFolder (JUnit 4) instead
138     */
139    @Deprecated
140    public static File createTempDir() throws IOException {
141        return createTempDir("");
142    }
143
144    /**
145     * Creates a temporary directory.
146     *
147     * @return the temporary directory
148     * @throws IOException if an I/O error occurs
149     * @deprecated use {@code @TempDir) (JUnit 5} Or {@code TemporaryFolder} (JUnit 4) instead
150     */
151    @Deprecated
152    public static File createTempDir(String suffix) throws IOException {
153        mkdirs(TMP);
154        File tmpFile = File.createTempFile("tmpdir-", suffix, TMP);
155        deleteFile(tmpFile);
156        mkdirs(tmpFile);
157        return tmpFile;
158    }
159
160    public static long copyFile(File source, File target) throws IOException {
161        long total = 0;
162
163        FileInputStream fis = null;
164        OutputStream fos = null;
165        try {
166            fis = new FileInputStream(source);
167
168            mkdirs(target.getParentFile());
169
170            fos = new BufferedOutputStream(new FileOutputStream(target));
171
172            for (byte[] buffer = new byte[1024 * 32]; ; ) {
173                int bytes = fis.read(buffer);
174                if (bytes < 0) {
175                    break;
176                }
177
178                fos.write(buffer, 0, bytes);
179
180                total += bytes;
181            }
182
183            fos.close();
184            fos = null;
185
186            fis.close();
187            fis = null;
188        } finally {
189            try {
190                if (fos != null) {
191                    fos.close();
192                }
193            } catch (final IOException e) {
194                // Suppressed due to an exception already thrown in the try block.
195            } finally {
196                try {
197                    if (fis != null) {
198                        fis.close();
199                    }
200                } catch (final IOException e) {
201                    // Suppressed due to an exception already thrown in the try block.
202                }
203            }
204        }
205
206        return total;
207    }
208
209    /**
210     * Reads the contents of a file into a byte array.
211     *
212     * @param file the file to read
213     * @return the contents of the file as a byte array
214     * @throws IOException if an I/O error occurs
215     * @deprecated use {@code Files.readAllBytes(Path)} instead
216     */
217    @Deprecated
218    public static byte[] readBytes(File file) throws IOException {
219        RandomAccessFile in = null;
220        try {
221            in = new RandomAccessFile(file, "r");
222            byte[] actual = new byte[(int) in.length()];
223            in.readFully(actual);
224            in.close();
225            in = null;
226            return actual;
227        } finally {
228            try {
229                if (in != null) {
230                    in.close();
231                }
232            } catch (final IOException e) {
233                // Suppressed due to an exception already thrown in the try block.
234            }
235        }
236    }
237
238    public static void writeBytes(File file, byte[] pattern, int repeat) throws IOException {
239        file.deleteOnExit();
240        file.getParentFile().mkdirs();
241        OutputStream out = null;
242        try {
243            out = new BufferedOutputStream(new FileOutputStream(file));
244            for (int i = 0; i < repeat; i++) {
245                out.write(pattern);
246            }
247            out.close();
248            out = null;
249        } finally {
250            try {
251                if (out != null) {
252                    out.close();
253                }
254            } catch (final IOException e) {
255                // Suppressed due to an exception already thrown in the try block.
256            }
257        }
258    }
259
260    public static String readString(File file) throws IOException {
261        byte[] content = Files.readAllBytes(file.toPath());
262        return new String(content, StandardCharsets.UTF_8);
263    }
264
265    public static void writeString(File file, String content) throws IOException {
266        writeBytes(file, content.getBytes(StandardCharsets.UTF_8), 1);
267    }
268
269    public static void writeString(File file, String content, long timestamp) throws IOException {
270        writeBytes(file, content.getBytes(StandardCharsets.UTF_8), 1);
271        file.setLastModified(timestamp);
272    }
273
274    public static void readProps(File file, Properties props) throws IOException {
275        FileInputStream fis = null;
276        try {
277            fis = new FileInputStream(file);
278            props.load(fis);
279            fis.close();
280            fis = null;
281        } finally {
282            try {
283                if (fis != null) {
284                    fis.close();
285                }
286            } catch (final IOException e) {
287                // Suppressed due to an exception already thrown in the try block.
288            }
289        }
290    }
291
292    public static void writeProps(File file, Properties props) throws IOException {
293        file.getParentFile().mkdirs();
294
295        FileOutputStream fos = null;
296        try {
297            fos = new FileOutputStream(file);
298            props.store(fos, "aether-test");
299            fos.close();
300            fos = null;
301        } finally {
302            try {
303                if (fos != null) {
304                    fos.close();
305                }
306            } catch (final IOException e) {
307                // Suppressed due to an exception already thrown in the try block.
308            }
309        }
310    }
311}