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          Path getPath();
48      }
49  
50      /**
51       * Creates a {@link TempFile}. It will be in the default temporary-file directory. Returned instance should be
52       * handled in try-with-resource construct and created temp file is removed on close, if exists.
53       */
54      public static TempFile newTempFile() throws IOException
55      {
56          Path tempFile = Files.createTempFile( "resolver", "tmp" );
57          return new TempFile()
58          {
59              @Override
60              public Path getPath()
61              {
62                  return tempFile;
63              }
64  
65              @Override
66              public void close() throws IOException
67              {
68                  Files.deleteIfExists( tempFile );
69              }
70          };
71      }
72  
73      /**
74       * Creates a {@link TempFile} for given file. It will be in same directory where given file is, and will reuse its
75       * name for generated name. Returned instance should be handled in try-with-resource construct and created temp
76       * file is removed on close, if exists.
77       */
78      public static TempFile newTempFile( Path file ) throws IOException
79      {
80          requireNonNull( file.getParent(), "file must have parent" );
81          Path tempFile = Files.createTempFile( file.getParent(), file.getFileName().toString(), "tmp" );
82          return new TempFile()
83          {
84              @Override
85              public Path getPath()
86              {
87                  return tempFile;
88              }
89  
90              @Override
91              public void close() throws IOException
92              {
93                  Files.deleteIfExists( tempFile );
94              }
95          };
96      }
97  
98      /**
99       * 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 }