001package org.eclipse.aether.util;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.Closeable;
023import java.io.IOException;
024import java.nio.file.Files;
025import java.nio.file.Path;
026import java.nio.file.StandardCopyOption;
027
028import static java.util.Objects.requireNonNull;
029
030/**
031 * A utility class to write files.
032 *
033 * @since 1.9.0
034 */
035public final class FileUtils
036{
037    private FileUtils()
038    {
039        // hide constructor
040    }
041
042    /**
043     * A temporary file, that is removed when closed.
044     */
045    public interface TempFile extends Closeable
046    {
047        Path getPath();
048    }
049
050    /**
051     * Creates a {@link TempFile}. It will be in the default temporary-file directory. Returned instance should be
052     * handled in try-with-resource construct and created temp file is removed on close, if exists.
053     */
054    public static TempFile newTempFile() throws IOException
055    {
056        Path tempFile = Files.createTempFile( "resolver", "tmp" );
057        return new TempFile()
058        {
059            @Override
060            public Path getPath()
061            {
062                return tempFile;
063            }
064
065            @Override
066            public void close() throws IOException
067            {
068                Files.deleteIfExists( tempFile );
069            }
070        };
071    }
072
073    /**
074     * Creates a {@link TempFile} for given file. It will be in same directory where given file is, and will reuse its
075     * name for generated name. Returned instance should be handled in try-with-resource construct and created temp
076     * file is removed on close, if exists.
077     */
078    public static TempFile newTempFile( Path file ) throws IOException
079    {
080        requireNonNull( file.getParent(), "file must have parent" );
081        Path tempFile = Files.createTempFile( file.getParent(), file.getFileName().toString(), "tmp" );
082        return new TempFile()
083        {
084            @Override
085            public Path getPath()
086            {
087                return tempFile;
088            }
089
090            @Override
091            public void close() throws IOException
092            {
093                Files.deleteIfExists( tempFile );
094            }
095        };
096    }
097
098    /**
099     * A file writer, that accepts a {@link Path} to write some content to.
100     */
101    @FunctionalInterface
102    public interface FileWriter
103    {
104        void write( Path path ) throws IOException;
105    }
106
107    /**
108     * Writes file without backup.
109     *
110     * @param target that is the target file (must be file, the path must have parent).
111     * @param writer the writer that will accept a {@link Path} to write content to.
112     * @throws IOException if at any step IO problem occurs.
113     */
114    public static void writeFile( Path target, FileWriter writer ) throws IOException
115    {
116        writeFile( target, writer, false );
117    }
118
119    /**
120     * Writes file with backup copy (appends ".bak" extension).
121     *
122     * @param target that is the target file (must be file, the path must have parent).
123     * @param writer the writer that will accept a {@link Path} to write content to.
124     * @throws IOException if at any step IO problem occurs.
125     */
126    public static void writeFileWithBackup( Path target, FileWriter writer ) throws IOException
127    {
128        writeFile( target, writer, true );
129    }
130
131    /**
132     * Utility method to write out file to disk in "atomic" manner, with optional backups (".bak") if needed. This
133     * ensures that no other thread or process will be able to read not fully written files. Finally, this methos
134     * may create the needed parent directories, if the passed in target parents does not exist.
135     *
136     * @param target   that is the target file (must be an existing or non-existing file, the path must have parent).
137     * @param writer   the writer that will accept a {@link Path} to write content to.
138     * @param doBackup if {@code true}, and target file is about to be overwritten, a ".bak" file with old contents will
139     *                 be created/overwritten.
140     * @throws IOException if at any step IO problem occurs.
141     */
142    private static void writeFile( Path target, FileWriter writer, boolean doBackup ) throws IOException
143    {
144        requireNonNull( target, "target is null" );
145        requireNonNull( writer, "writer is null" );
146        Path parent = requireNonNull( target.getParent(), "target must have parent" );
147        Path temp = null;
148
149        Files.createDirectories( parent );
150        try
151        {
152            temp = Files.createTempFile( parent, "writer", "tmp" );
153            writer.write( temp );
154            if ( doBackup && Files.isRegularFile( target ) )
155            {
156                Files.copy( target, parent.resolve( target.getFileName() + ".bak" ),
157                        StandardCopyOption.REPLACE_EXISTING );
158            }
159            Files.move( temp, target, StandardCopyOption.ATOMIC_MOVE );
160        }
161        finally
162        {
163            if ( temp != null )
164            {
165                Files.deleteIfExists( temp );
166            }
167        }
168    }
169}