001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.utils; 018 019import static org.apache.juneau.commons.utils.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.IoUtils.*; 021import static org.apache.juneau.commons.utils.StringUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023import static org.apache.juneau.commons.utils.Utils.*; 024 025import java.io.*; 026import java.nio.file.*; 027 028/** 029 * File utilities. 030 * 031 */ 032public class FileUtils { 033 034 /** 035 * Creates a file if it doesn't already exist using {@link File#createNewFile()}. 036 * 037 * <p> 038 * Throws a {@link RuntimeException} if the file could not be created. 039 * 040 * @param f The file to create. 041 */ 042 public static void create(File f) { 043 if (f.exists()) 044 return; 045 safe(() -> { 046 opt(f.createNewFile()).filter(x -> x).orElseThrow(() -> rex("Could not create file ''{0}''", f.getAbsolutePath())); 047 }); 048 } 049 050 /** 051 * Create a temporary file with the specified name. 052 * 053 * <p> 054 * The name is broken into file name and suffix, and the parts are passed to 055 * {@link File#createTempFile(String, String)}. 056 * 057 * <p> 058 * {@link File#deleteOnExit()} is called on the resulting file before being returned by this method. 059 * 060 * @param name The file name 061 * @return A newly-created temporary file. 062 * @throws IOException Thrown by underlying stream. 063 */ 064 public static File createTempFile(String name) throws IOException { 065 var parts = name.split("\\."); 066 var f = File.createTempFile(parts[0], "." + parts[1]); 067 f.deleteOnExit(); 068 return f; 069 } 070 071 /** 072 * Create a temporary file with the specified name and specified contents. 073 * 074 * <p> 075 * The name is broken into file name and suffix, and the parts are passed to 076 * {@link File#createTempFile(String, String)}. 077 * 078 * <p> 079 * {@link File#deleteOnExit()} is called on the resulting file before being returned by this method. 080 * 081 * @param name The file name 082 * @param contents The file contents. 083 * @return A newly-created temporary file. 084 * @throws IOException Thrown by underlying stream. 085 */ 086 public static File createTempFile(String name, String contents) throws IOException { 087 var f = createTempFile(name); 088 if (contents != null) { 089 try (var r = new StringReader(contents); Writer w = new FileWriter(f)) { 090 pipe(r, w); 091 w.flush(); 092 } 093 } 094 // If contents is null, create an empty file 095 return f; 096 } 097 098 /** 099 * Recursively deletes a file or directory. 100 * 101 * @param f The file or directory to delete. 102 * @return <jk>true</jk> if file or directory was successfully deleted. 103 */ 104 public static boolean deleteFile(File f) { 105 if (f == null) 106 return true; 107 if (f.isDirectory()) { 108 var cf = f.listFiles(); 109 if (nn(cf)) 110 for (var c : cf) 111 deleteFile(c); 112 } 113 return f.delete(); 114 } 115 116 /** 117 * Returns <jk>true</jk> if the specified file exists in the specified directory. 118 * 119 * @param dir The directory. 120 * @param fileName The file name. 121 * @return <jk>true</jk> if the specified file exists in the specified directory. 122 */ 123 public static boolean fileExists(File dir, String fileName) { 124 if (dir == null || fileName == null) 125 return false; 126 return Files.exists(dir.toPath().resolve(fileName)); 127 } 128 129 /** 130 * Strips the extension from a file name. 131 * 132 * @param name The file name. 133 * @return The file name without the extension, or <jk>null</jk> if name was <jk>null</jk>. 134 */ 135 public static String getBaseName(String name) { 136 if (name == null) 137 return null; 138 var i = name.lastIndexOf('.'); 139 if (i == -1) 140 return name; 141 return name.substring(0, i); 142 } 143 144 /** 145 * Returns the extension from a file name. 146 * 147 * @param name The file name. 148 * @return The the extension, or <jk>null</jk> if name was <jk>null</jk>. 149 */ 150 public static String getFileExtension(String name) { 151 if (name == null) 152 return null; 153 var i = name.lastIndexOf('.'); 154 if (i == -1) 155 return ""; 156 return name.substring(i + 1); 157 } 158 159 /** 160 * Given an arbitrary path, returns the file name portion of that path. 161 * 162 * @param path The path to check. 163 * @return The file name. 164 */ 165 public static String getFileName(String path) { 166 if (isEmpty(path)) 167 return null; 168 path = trimTrailingSlashes(path); 169 if (isEmpty(path)) 170 return null; // Path contained only slashes 171 // Handle both forward slashes (Unix) and backslashes (Windows) 172 var i = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); 173 return i == -1 ? path : path.substring(i + 1); 174 } 175 176 /** 177 * Returns <jk>true</jk> if the specified file name contains the specified extension. 178 * 179 * @param name The file name. 180 * @param ext The extension. 181 * @return <jk>true</jk> if the specified file name contains the specified extension. 182 */ 183 public static boolean hasExtension(String name, String ext) { 184 if (name == null || ext == null) 185 return false; 186 return ext.equals(getFileExtension(name)); 187 } 188 189 /** 190 * Same as {@link File#mkdirs()} except throws a RuntimeExeption if directory could not be created. 191 * 192 * @param f The directory to create. Must not be <jk>null</jk>. 193 * @param clean If <jk>true</jk>, deletes the contents of the directory if it already exists. 194 * @return The same file. 195 * @throws RuntimeException if directory could not be created. 196 */ 197 public static File mkdirs(File f, boolean clean) { 198 assertArgNotNull("f", f); 199 if (f.exists()) { 200 if (clean) { 201 opt(deleteFile(f)).filter(x -> x).orElseThrow(() -> rex("Could not clean directory ''{0}''", f.getAbsolutePath())); 202 } else { 203 return f; 204 } 205 } 206 opt(f.mkdirs()).filter(x -> x).orElseThrow(() -> rex("Could not create directory ''{0}''", f.getAbsolutePath())); 207 return f; 208 } 209 210 /** 211 * Same as {@link #mkdirs(String, boolean)} but uses String path. 212 * 213 * @param path The path of the directory to create. Must not be <jk>null</jk> 214 * @param clean If <jk>true</jk>, deletes the contents of the directory if it already exists. 215 * @return The directory. 216 */ 217 public static File mkdirs(String path, boolean clean) { 218 assertArgNotNull("path", path); 219 return mkdirs(new File(path), clean); 220 } 221 222 /** 223 * Updates the modified timestamp on the specified file. 224 * 225 * <p> 226 * Method ensures that the timestamp changes even if it's been modified within the past millisecond. 227 * 228 * @param f The file to modify the modified timestamp on. 229 */ 230 public static void modifyTimestamp(File f) { 231 var lm = f.lastModified(); 232 var l = System.currentTimeMillis(); 233 if (lm == l) 234 l++; 235 opt(f.setLastModified(l)).filter(x -> x).orElseThrow(() -> rex("Could not modify timestamp on file ''{0}''", f.getAbsolutePath())); 236 237 // Linux only gives 1s precision, so set the date 1s into the future. 238 if (lm == f.lastModified()) 239 opt(f.setLastModified(l + 1000)).filter(x -> x).orElseThrow(() -> rex("Could not modify timestamp on file ''{0}''", f.getAbsolutePath())); 240 } 241}