View Javadoc
1   package org.eclipse.aether.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.Closeable;
23  import java.io.IOException;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.nio.file.StandardCopyOption;
27  
28  import static java.util.Objects.requireNonNull;
29  
30  /**
31   * A utility class to write files.
32   *
33   * @since 1.9.0
34   */
35  public final class FileUtils
36  {
37      private FileUtils()
38      {
39          // hide constructor
40      }
41  
42      /**
43       * A temporary file, that is removed when closed.
44       */
45      public interface TempFile extends Closeable
46      {
47          /**
48           * Returns the path of the created temp file.
49           */
50          Path getPath();
51      }
52  
53      /**
54       * A collocated temporary file, that resides next to a "target" file, and is removed when closed.
55       */
56      public interface CollocatedTempFile extends TempFile
57      {
58          /**
59           * Atomically moves temp file to target file it is collocated with.
60           */
61          void move() throws IOException;
62      }
63  
64      /**
65       * Creates a {@link TempFile}. It will be in the default temporary-file directory. Returned instance should be
66       * handled in try-with-resource construct and created temp file is removed on close, if exists.
67       */
68      public static TempFile newTempFile() throws IOException
69      {
70          Path tempFile = Files.createTempFile( "resolver", "tmp" );
71          return new TempFile()
72          {
73              @Override
74              public Path getPath()
75              {
76                  return tempFile;
77              }
78  
79              @Override
80              public void close() throws IOException
81              {
82                  Files.deleteIfExists( tempFile );
83              }
84          };
85      }
86  
87      /**
88       * Creates a {@link TempFile} for given file. It will be in same directory where given file is, and will reuse its
89       * name for generated name. Returned instance should be handled in try-with-resource construct and created temp
90       * file once ready can be moved to passed in {@code file} parameter place.
91       * <p>
92       * The {@code file} nor it's parent directories have to exist. The parent directories are created if needed.
93       */
94      public static CollocatedTempFile newTempFile( Path file ) throws IOException
95      {
96          Path parent = requireNonNull( file.getParent(), "file must have parent" );
97          Files.createDirectories( parent );
98          Path tempFile = Files.createTempFile( parent, file.getFileName().toString(), "tmp" );
99          return new CollocatedTempFile()
100         {
101             @Override
102             public Path getPath()
103             {
104                 return tempFile;
105             }
106 
107             @Override
108             public void move() throws IOException
109             {
110                 Files.move( tempFile, file, StandardCopyOption.ATOMIC_MOVE );
111             }
112 
113             @Override
114             public void close() throws IOException
115             {
116                 Files.deleteIfExists( tempFile );
117             }
118         };
119     }
120 
121     /**
122      * A file writer, that accepts a {@link Path} to write some content to.
123      */
124     @FunctionalInterface
125     public interface FileWriter
126     {
127         void write( Path path ) throws IOException;
128     }
129 
130     /**
131      * Writes file without backup.
132      *
133      * @param target that is the target file (must be file, the path must have parent).
134      * @param writer the writer that will accept a {@link Path} to write content to.
135      * @throws IOException if at any step IO problem occurs.
136      */
137     public static void writeFile( Path target, FileWriter writer ) throws IOException
138     {
139         writeFile( target, writer, false );
140     }
141 
142     /**
143      * Writes file with backup copy (appends ".bak" extension).
144      *
145      * @param target that is the target file (must be file, the path must have parent).
146      * @param writer the writer that will accept a {@link Path} to write content to.
147      * @throws IOException if at any step IO problem occurs.
148      */
149     public static void writeFileWithBackup( Path target, FileWriter writer ) throws IOException
150     {
151         writeFile( target, writer, true );
152     }
153 
154     /**
155      * Utility method to write out file to disk in "atomic" manner, with optional backups (".bak") if needed. This
156      * ensures that no other thread or process will be able to read not fully written files. Finally, this methos
157      * may create the needed parent directories, if the passed in target parents does not exist.
158      *
159      * @param target   that is the target file (must be an existing or non-existing file, the path must have parent).
160      * @param writer   the writer that will accept a {@link Path} to write content to.
161      * @param doBackup if {@code true}, and target file is about to be overwritten, a ".bak" file with old contents will
162      *                 be created/overwritten.
163      * @throws IOException if at any step IO problem occurs.
164      */
165     private static void writeFile( Path target, FileWriter writer, boolean doBackup ) throws IOException
166     {
167         requireNonNull( target, "target is null" );
168         requireNonNull( writer, "writer is null" );
169         Path parent = requireNonNull( target.getParent(), "target must have parent" );
170 
171         try ( CollocatedTempFile tempFile = newTempFile( target ) )
172         {
173             writer.write( tempFile.getPath() );
174             if ( doBackup && Files.isRegularFile( target ) )
175             {
176                 Files.copy( target, parent.resolve( target.getFileName() + ".bak" ),
177                         StandardCopyOption.REPLACE_EXISTING );
178             }
179             tempFile.move();
180         }
181     }
182 }