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 }