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}