| package org.apache.maven.shared.utils.io; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.shared.utils.Os; |
| import org.apache.maven.shared.utils.StringUtils; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import javax.annotation.WillClose; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.nio.channels.FileChannel; |
| import java.nio.file.Files; |
| import java.security.SecureRandom; |
| import java.text.DecimalFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Random; |
| |
| /** |
| * This class provides basic facilities for manipulating files and file paths. |
| * <p/> |
| * <h3>Path-related methods</h3> |
| * <p/> |
| * <p>Methods exist to retrieve the components of a typical file path. For example |
| * <code>/www/hosted/mysite/index.html</code>, can be broken into: |
| * <ul> |
| * <li><code>/www/hosted/mysite/index</code> -- retrievable through {@link #removeExtension}</li> |
| * <li><code>html</code> -- retrievable through {@link #getExtension}</li> |
| * </ul> |
| * </p> |
| * <p/> |
| * <h3>File-related methods</h3> |
| * <p/> |
| * There are methods to create a {@link #toFile File from a URL}, copy a |
| * copy a {@link #copyFile File to another File}, |
| * copy a {@link #copyURLToFile URL's contents to a File}, |
| * as well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File) |
| * clean} a directory. |
| * </p> |
| * <p/> |
| * Common {@link java.io.File} manipulation routines. |
| * <p/> |
| * Taken from the commons-utils repo. |
| * Also code from Alexandria's FileUtils. |
| * And from Avalon Excalibur's IO. |
| * And from Ant. |
| * |
| * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A> |
| * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> |
| * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> |
| * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a> |
| * @author <a href="mailto:peter@apache.org">Peter Donald</a> |
| * @author <a href="mailto:jefft@apache.org">Jeff Turner</a> |
| */ |
| public class FileUtils |
| { |
| /** |
| * protected constructor. |
| */ |
| protected FileUtils() |
| { |
| // This is a utility class. Normally dont instantiate |
| } |
| |
| /** |
| * The number of bytes in a kilobyte. |
| */ |
| private static final int ONE_KB = 1024; |
| |
| /** |
| * The number of bytes in a megabyte. |
| */ |
| private static final int ONE_MB = ONE_KB * ONE_KB; |
| |
| /** |
| * The file copy buffer size (30 MB) |
| */ |
| private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; |
| |
| /** |
| * Non-valid Characters for naming files, folders under Windows: <code>":", "*", "?", "\"", "<", ">", "|"</code> |
| * |
| * @see <a href="http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13"> |
| * http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13</a> |
| */ |
| private static final String[] INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME = { ":", "*", "?", "\"", "<", ">", "|" }; |
| |
| /** |
| * @return the default excludes pattern |
| * @see DirectoryScanner#DEFAULTEXCLUDES |
| */ |
| @Nonnull public static String[] getDefaultExcludes() |
| { |
| return DirectoryScanner.DEFAULTEXCLUDES; |
| } |
| |
| /** |
| * @return the default excludes pattern as list. |
| * @see #getDefaultExcludes() |
| */ |
| @Nonnull public static List<String> getDefaultExcludesAsList() |
| { |
| return Arrays.asList( getDefaultExcludes() ); |
| } |
| |
| /** |
| * @return the default excludes pattern as comma separated string. |
| * @see DirectoryScanner#DEFAULTEXCLUDES |
| * @see StringUtils#join(Object[], String) |
| */ |
| @Nonnull public static String getDefaultExcludesAsString() |
| { |
| return StringUtils.join( DirectoryScanner.DEFAULTEXCLUDES, "," ); |
| } |
| |
| /** |
| * Returns the directory path portion of a file specification string. |
| * Matches the equally named unix command. |
| * |
| * @param filename the file path |
| * @return The directory portion excluding the ending file separator. |
| */ |
| @Nonnull public static String dirname( @Nonnull String filename ) |
| { |
| int i = filename.lastIndexOf( File.separator ); |
| return ( i >= 0 ? filename.substring( 0, i ) : "" ); |
| } |
| |
| /** |
| * Returns the filename portion of a file specification string. |
| * |
| * @param filename the file path |
| * @return The filename string with extension. |
| */ |
| @Nonnull public static String filename( @Nonnull String filename ) |
| { |
| int i = filename.lastIndexOf( File.separator ); |
| return ( i >= 0 ? filename.substring( i + 1 ) : filename ); |
| } |
| |
| /** |
| * Returns the extension portion of a file specification string. |
| * This everything after the last dot '.' in the filename (NOT including |
| * the dot). |
| * |
| * @param filename the file path |
| * @return the extension of the file |
| */ |
| @Nonnull public static String extension( @Nonnull String filename ) |
| { |
| // Ensure the last dot is after the last file separator |
| int lastSep = filename.lastIndexOf( File.separatorChar ); |
| int lastDot; |
| if ( lastSep < 0 ) |
| { |
| lastDot = filename.lastIndexOf( '.' ); |
| } |
| else |
| { |
| lastDot = filename.substring( lastSep + 1 ).lastIndexOf( '.' ); |
| if ( lastDot >= 0 ) |
| { |
| lastDot += lastSep + 1; |
| } |
| } |
| |
| if ( lastDot >= 0 && lastDot > lastSep ) |
| { |
| return filename.substring( lastDot + 1 ); |
| } |
| |
| return ""; |
| } |
| |
| /** |
| * Check if a file exits. |
| * |
| * @param fileName the file path. |
| * @return true if file exists. |
| */ |
| public static boolean fileExists( @Nonnull String fileName ) |
| { |
| File file = new File( fileName ); |
| return file.exists(); |
| } |
| |
| /** |
| * Note: the file content is read with platform encoding. |
| * |
| * @param file the file path |
| * @return the file content using the platform encoding. |
| * @throws IOException if any |
| */ |
| @Nonnull public static String fileRead( @Nonnull String file ) |
| throws IOException |
| { |
| return fileRead( file, null ); |
| } |
| |
| /** |
| * @param file the file path |
| * @param encoding the wanted encoding |
| * @return the file content using the specified encoding. |
| * @throws IOException if any |
| */ |
| @Nonnull private static String fileRead( @Nonnull String file, @Nullable String encoding ) |
| throws IOException |
| { |
| return fileRead( new File( file ), encoding ); |
| } |
| |
| /** |
| * Note: the file content is read with platform encoding |
| * |
| * @param file the file path |
| * @return the file content using the platform encoding. |
| * @throws IOException if any |
| */ |
| @Nonnull public static String fileRead( @Nonnull File file ) |
| throws IOException |
| { |
| return fileRead( file, null ); |
| } |
| |
| /** |
| * @param file the file path |
| * @param encoding the wanted encoding |
| * @return the file content using the specified encoding. |
| * @throws IOException if any |
| */ |
| @Nonnull public static String fileRead( @Nonnull File file, @Nullable String encoding ) |
| throws IOException |
| { |
| StringBuilder buf = new StringBuilder(); |
| |
| try ( Reader reader = getReader( file, encoding ) ) |
| { |
| int count; |
| char[] b = new char[512]; |
| while ( ( count = reader.read( b ) ) >= 0 ) // blocking read |
| { |
| buf.append( b, 0, count ); |
| } |
| } |
| |
| return buf.toString(); |
| } |
| |
| private static Reader getReader( @Nonnull File file, @Nullable String encoding ) throws IOException |
| { |
| if ( encoding != null ) |
| { |
| return new InputStreamReader( new FileInputStream( file ), encoding ); |
| } |
| else |
| { |
| return new InputStreamReader( new FileInputStream( file ) ); |
| } |
| } |
| |
| /** |
| * @param file the file path |
| * @return the file content lines as String[] using the systems default encoding. |
| * An empty List if the file didn't exist. |
| * @throws IOException in case of failure. |
| */ |
| @Nonnull public static String[] fileReadArray( @Nonnull File file ) |
| throws IOException |
| { |
| List<String> lines = loadFile( file ); |
| |
| return lines.toArray( new String[lines.size()] ); |
| } |
| |
| /** |
| * Appends data to a file. The file will be created if it does not exist. |
| * Note: the data is written with platform encoding |
| * |
| * @param fileName The path of the file to write. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileAppend( @Nonnull String fileName, @Nonnull String data ) |
| throws IOException |
| { |
| fileAppend( fileName, null, data ); |
| } |
| |
| /** |
| * Appends data to a file. The file will be created if it does not exist. |
| * |
| * @param fileName The path of the file to write. |
| * @param encoding The encoding of the file. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileAppend( @Nonnull String fileName, @Nullable String encoding, @Nonnull String data ) |
| throws IOException |
| { |
| try ( FileOutputStream out = new FileOutputStream( fileName, true ) ) |
| { |
| if ( encoding != null ) |
| { |
| out.write( data.getBytes( encoding ) ); |
| } |
| else |
| { |
| out.write( data.getBytes() ); |
| } |
| } |
| } |
| |
| /** |
| * Writes data to a file. The file will be created if it does not exist. |
| * Note: the data is written with platform encoding |
| * |
| * @param fileName The path of the file to write. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileWrite( @Nonnull String fileName, @Nonnull String data ) |
| throws IOException |
| { |
| fileWrite( fileName, null, data ); |
| } |
| |
| /** |
| * Writes data to a file. The file will be created if it does not exist. |
| * |
| * @param fileName The path of the file to write. |
| * @param encoding The encoding of the file. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileWrite( @Nonnull String fileName, @Nullable String encoding, @Nonnull String data ) |
| throws IOException |
| { |
| File file = new File( fileName ); |
| fileWrite( file, encoding, data ); |
| } |
| |
| /** |
| * Writes data to a file. The file will be created if it does not exist. |
| * |
| * @param file The path of the file to write. |
| * @param encoding The encoding of the file. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileWrite( @Nonnull File file, @Nullable String encoding, @Nonnull String data ) |
| throws IOException |
| { |
| try ( Writer writer = getWriter( file, encoding ) ) |
| { |
| writer.write( data ); |
| } |
| } |
| |
| private static Writer getWriter( @Nonnull File file, @Nullable String encoding ) throws IOException |
| { |
| if ( encoding != null ) |
| { |
| return new OutputStreamWriter( new FileOutputStream( file ), encoding ); |
| } |
| else |
| { |
| return new OutputStreamWriter( new FileOutputStream( file ) ); |
| } |
| } |
| |
| /** |
| * Writes String array data to a file in the systems default encoding. |
| * The file will be created if it does not exist. |
| * |
| * @param file The path of the file to write. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileWriteArray( @Nonnull File file, @Nullable String... data ) |
| throws IOException |
| { |
| fileWriteArray( file, null, data ); |
| } |
| |
| /** |
| * Writes String array data to a file. The file will be created if it does not exist. |
| * |
| * @param file The path of the file to write. |
| * @param encoding The encoding of the file. |
| * @param data The content to write to the file. |
| * @throws IOException if any |
| */ |
| public static void fileWriteArray( @Nonnull File file, @Nullable String encoding, @Nullable String... data ) |
| throws IOException |
| { |
| try ( Writer writer = getWriter( file, encoding ) ) |
| { |
| for ( int i = 0; data != null && i < data.length; i++ ) |
| { |
| writer.write( data[i] ); |
| if ( i < data.length ) |
| { |
| writer.write( "\n" ); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Deletes a file. |
| * |
| * @param fileName The path of the file to delete. |
| */ |
| public static void fileDelete( @Nonnull String fileName ) |
| { |
| File file = new File( fileName ); |
| //noinspection ResultOfMethodCallIgnored |
| deleteLegacyStyle( file ); |
| } |
| |
| /** |
| * Given a directory and an array of extensions return an array of compliant files. |
| * <p/> |
| * The given extensions should be like "java" and not like ".java" |
| * |
| * @param directory The path of the directory. |
| * @param extensions an array of expected extensions. |
| * @return An array of files for the wanted extensions. |
| */ |
| public static String[] getFilesFromExtension( @Nonnull String directory, @Nonnull String... extensions ) |
| { |
| List<String> files = new ArrayList<>(); |
| |
| File currentDir = new File( directory ); |
| |
| String[] unknownFiles = currentDir.list(); |
| |
| if ( unknownFiles == null ) |
| { |
| return new String[0]; |
| } |
| |
| for ( String unknownFile : unknownFiles ) |
| { |
| String currentFileName = directory + File.separator + unknownFile; |
| File currentFile = new File( currentFileName ); |
| |
| if ( currentFile.isDirectory() ) |
| { |
| // ignore all CVS directories... |
| if ( currentFile.getName().equals( "CVS" ) ) |
| { |
| continue; |
| } |
| |
| // ok... transverse into this directory and get all the files... then combine |
| // them with the current list. |
| |
| String[] fetchFiles = getFilesFromExtension( currentFileName, extensions ); |
| files = blendFilesToList( files, fetchFiles ); |
| } |
| else |
| { |
| // ok... add the file |
| |
| String add = currentFile.getAbsolutePath(); |
| if ( isValidFile( add, extensions ) ) |
| { |
| files.add( add ); |
| } |
| } |
| } |
| |
| // ok... move the Vector into the files list... |
| String[] foundFiles = new String[files.size()]; |
| files.toArray( foundFiles ); |
| |
| return foundFiles; |
| } |
| |
| /** |
| * Private helper method for getFilesFromExtension() |
| */ |
| @Nonnull private static List<String> blendFilesToList( @Nonnull List<String> v, @Nonnull String... files ) |
| { |
| Collections.addAll( v, files ); |
| |
| return v; |
| } |
| |
| /** |
| * Checks to see if a file is of a particular type(s). |
| * Note that if the file does not have an extension, an empty string |
| * ("") is matched for. |
| */ |
| private static boolean isValidFile( @Nonnull String file, @Nonnull String... extensions ) |
| { |
| String extension = extension( file ); |
| |
| // ok.. now that we have the "extension" go through the current know |
| // excepted extensions and determine if this one is OK. |
| |
| for ( String extension1 : extensions ) |
| { |
| if ( extension1.equals( extension ) ) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| |
| } |
| |
| /** |
| * Simple way to make a directory |
| * |
| * @param dir the directory to create |
| * @throws IllegalArgumentException if the dir contains illegal Windows characters under Windows OS. |
| * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME |
| */ |
| public static void mkdir( @Nonnull String dir ) |
| { |
| File file = new File( dir ); |
| |
| if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) ) |
| { |
| throw new IllegalArgumentException( |
| "The file (" + dir + ") cannot contain any of the following characters: \n" + StringUtils.join( |
| INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) ); |
| } |
| |
| if ( !file.exists() ) |
| { |
| //noinspection ResultOfMethodCallIgnored |
| file.mkdirs(); |
| } |
| } |
| |
| /** |
| * Compare the contents of two files to determine if they are equal or not. |
| * |
| * @param file1 the first file |
| * @param file2 the second file |
| * @return true if the content of the files are equal or they both don't exist, false otherwise |
| * @throws IOException if any |
| */ |
| public static boolean contentEquals( @Nonnull final File file1, @Nonnull final File file2 ) |
| throws IOException |
| { |
| final boolean file1Exists = file1.exists(); |
| if ( file1Exists != file2.exists() ) |
| { |
| return false; |
| } |
| |
| if ( !file1Exists ) |
| { |
| // two not existing files are equal |
| return true; |
| } |
| |
| if ( file1.isDirectory() || file2.isDirectory() ) |
| { |
| // don't want to compare directory contents |
| return false; |
| } |
| |
| try ( InputStream input1 = new FileInputStream( file1 ); |
| InputStream input2 = new FileInputStream( file2 ) ) |
| { |
| return IOUtil.contentEquals( input1, input2 ); |
| } |
| } |
| |
| /** |
| * Convert from a <code>URL</code> to a <code>File</code>. |
| * |
| * @param url File URL. |
| * @return The equivalent <code>File</code> object, or <code>null</code> if the URL's protocol |
| * is not <code>file</code> |
| */ |
| @Nullable public static File toFile( @Nullable final URL url ) |
| { |
| if ( url == null || !url.getProtocol().equalsIgnoreCase( "file" ) ) |
| { |
| return null; |
| } |
| |
| String filename = url.getFile().replace( '/', File.separatorChar ); |
| int pos = -1; |
| while ( ( pos = filename.indexOf( '%', pos + 1 ) ) >= 0 ) |
| { |
| if ( pos + 2 < filename.length() ) |
| { |
| String hexStr = filename.substring( pos + 1, pos + 3 ); |
| char ch = (char) Integer.parseInt( hexStr, 16 ); |
| filename = filename.substring( 0, pos ) + ch + filename.substring( pos + 3 ); |
| } |
| } |
| return new File( filename ); |
| } |
| |
| /** |
| * Convert the array of Files into a list of URLs. |
| * |
| * @param files the array of files |
| * @return the array of URLs |
| * @throws IOException if an error occurs |
| */ |
| @Nonnull public static URL[] toURLs( @Nonnull final File... files ) |
| throws IOException |
| { |
| final URL[] urls = new URL[files.length]; |
| |
| for ( int i = 0; i < urls.length; i++ ) |
| { |
| urls[i] = files[i].toURI().toURL(); |
| } |
| |
| return urls; |
| } |
| |
| /** |
| * Remove extension from filename. |
| * ie |
| * <pre> |
| * foo.txt --> foo |
| * a\b\c.jpg --> a\b\c |
| * a\b\c --> a\b\c |
| * </pre> |
| * |
| * @param filename the path of the file |
| * @return the filename minus extension |
| */ |
| @Nonnull public static String removeExtension( @Nonnull final String filename ) |
| { |
| String ext = extension( filename ); |
| |
| if ( "".equals( ext ) ) |
| { |
| return filename; |
| } |
| |
| final int index = filename.lastIndexOf( ext ) - 1; |
| return filename.substring( 0, index ); |
| } |
| |
| /** |
| * Get extension from filename. |
| * ie |
| * <pre> |
| * foo.txt --> "txt" |
| * a\b\c.jpg --> "jpg" |
| * a\b\c --> "" |
| * </pre> |
| * |
| * @param filename the path of the file |
| * @return the extension of filename or "" if none |
| */ |
| @Nonnull public static String getExtension( @Nonnull final String filename ) |
| { |
| return extension( filename ); |
| } |
| |
| /** |
| * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it |
| * (and any parent directories) will be created. If a file <code>source</code> in |
| * <code>destinationDirectory</code> exists, it will be overwritten. |
| * |
| * @param source An existing <code>File</code> to copy. |
| * @param destinationDirectory A directory to copy <code>source</code> into. |
| * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file. |
| * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory. |
| * @throws IOException if <code>source</code> does not exist, the file in |
| * <code>destinationDirectory</code> cannot be written to, or an IO error |
| * occurs during copying. |
| */ |
| public static void copyFileToDirectory( @Nonnull final File source, @Nonnull final File destinationDirectory ) |
| throws IOException |
| { |
| if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() ) |
| { |
| throw new IllegalArgumentException( "Destination is not a directory" ); |
| } |
| |
| copyFile( source, new File( destinationDirectory, source.getName() ) ); |
| } |
| |
| /** |
| * Copy file from source to destination only if source is newer than the target file. |
| * If <code>destinationDirectory</code> does not exist, it |
| * (and any parent directories) will be created. If a file <code>source</code> in |
| * <code>destinationDirectory</code> exists, it will be overwritten. |
| * |
| * @param source An existing <code>File</code> to copy. |
| * @param destinationDirectory A directory to copy <code>source</code> into. |
| * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file. |
| * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory. |
| * @throws IOException if <code>source</code> does not exist, the file in |
| * <code>destinationDirectory</code> cannot be written to, or an IO error |
| * occurs during copying. |
| */ |
| private static void copyFileToDirectoryIfModified( @Nonnull final File source, |
| @Nonnull final File destinationDirectory ) |
| throws IOException |
| { |
| if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() ) |
| { |
| throw new IllegalArgumentException( "Destination is not a directory" ); |
| } |
| |
| copyFileIfModified( source, new File( destinationDirectory, source.getName() ) ); |
| } |
| |
| |
| /** |
| * Copy file from source to destination. The directories up to <code>destination</code> will be |
| * created if they don't already exist. <code>destination</code> will be overwritten if it |
| * already exists. |
| * |
| * @param source An existing non-directory <code>File</code> to copy bytes from. |
| * @param destination A non-directory <code>File</code> to write bytes to (possibly |
| * overwriting). |
| * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be |
| * written to, or an IO error occurs during copying. |
| * @throws java.io.FileNotFoundException if <code>destination</code> is a directory |
| */ |
| public static void copyFile( @Nonnull final File source, @Nonnull final File destination ) |
| throws IOException |
| { |
| //check source exists |
| if ( !source.exists() ) |
| { |
| final String message = "File " + source + " does not exist"; |
| throw new IOException( message ); |
| } |
| if ( Files.isSymbolicLink( source.toPath() ) ) |
| { |
| File target = Files.readSymbolicLink( source.toPath() ).toFile(); |
| Java7Support.createSymbolicLink( destination, target ); |
| return; |
| } |
| |
| //check source != destination, see PLXUTILS-10 |
| if ( source.getCanonicalPath().equals( destination.getCanonicalPath() ) ) |
| { |
| //if they are equal, we can exit the method without doing any work |
| return; |
| } |
| |
| mkdirsFor( destination ); |
| |
| doCopyFile( source, destination ); |
| |
| if ( source.length() != destination.length() ) |
| { |
| final String message = "Failed to copy full contents from " + source + " to " + destination; |
| throw new IOException( message ); |
| } |
| } |
| |
| private static void mkdirsFor( @Nonnull File destination ) |
| { |
| //does destination directory exist ? |
| if ( destination.getParentFile() != null && !destination.getParentFile().exists() ) |
| { |
| //noinspection ResultOfMethodCallIgnored |
| destination.getParentFile().mkdirs(); |
| } |
| } |
| |
| private static void doCopyFile( @Nonnull File source, @Nonnull File destination ) |
| throws IOException |
| { |
| |
| try ( FileInputStream fis = new FileInputStream( source ); |
| FileOutputStream fos = new FileOutputStream( destination ); |
| FileChannel input = fis.getChannel(); |
| FileChannel output = fos.getChannel(); ) |
| { |
| long size = input.size(); |
| long pos = 0; |
| long count; |
| while ( pos < size ) |
| { |
| count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; |
| pos += output.transferFrom( input, pos, count ); |
| } |
| } |
| } |
| |
| /** |
| * Copy file from source to destination only if source timestamp is later than the destination timestamp. |
| * The directories up to <code>destination</code> will be created if they don't already exist. |
| * <code>destination</code> will be overwritten if it already exists. |
| * |
| * @param source An existing non-directory <code>File</code> to copy bytes from. |
| * @param destination A non-directory <code>File</code> to write bytes to (possibly |
| * overwriting). |
| * @return true if no problem occured |
| * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be |
| * written to, or an IO error occurs during copying. |
| */ |
| private static boolean copyFileIfModified( @Nonnull final File source, @Nonnull final File destination ) |
| throws IOException |
| { |
| if ( destination.lastModified() < source.lastModified() ) |
| { |
| copyFile( source, destination ); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Copies bytes from the URL <code>source</code> to a file <code>destination</code>. |
| * The directories up to <code>destination</code> will be created if they don't already exist. |
| * <code>destination</code> will be overwritten if it already exists. |
| * |
| * @param source A <code>URL</code> to copy bytes from. |
| * @param destination A non-directory <code>File</code> to write bytes to (possibly |
| * overwriting). |
| * @throws IOException if |
| * <ul> |
| * <li><code>source</code> URL cannot be opened</li> |
| * <li><code>destination</code> cannot be written to</li> |
| * <li>an IO error occurs during copying</li> |
| * </ul> |
| */ |
| public static void copyURLToFile( @Nonnull final URL source, @Nonnull final File destination ) |
| throws IOException |
| { |
| copyStreamToFile( source.openStream(), destination ); |
| } |
| |
| /** |
| * Copies bytes from the {@link InputStream} <code>source</code> to a file <code>destination</code>. |
| * The directories up to <code>destination</code> will be created if they don't already exist. |
| * <code>destination</code> will be overwritten if it already exists. |
| * |
| * @param source An {@link InputStream} to copy bytes from. This stream is |
| * guaranteed to be closed. |
| * @param destination A non-directory <code>File</code> to write bytes to (possibly |
| * overwriting). |
| * @throws IOException if |
| * <ul> |
| * <li><code>source</code> URL cannot be opened</li> |
| * <li><code>destination</code> cannot be written to</li> |
| * <li>an IO error occurs during copying</li> |
| * </ul> |
| */ |
| private static void copyStreamToFile( @Nonnull @WillClose final InputStream source, |
| @Nonnull final File destination ) |
| throws IOException |
| { |
| //does destination directory exist ? |
| if ( destination.getParentFile() != null && !destination.getParentFile().exists() ) |
| { |
| //noinspection ResultOfMethodCallIgnored |
| destination.getParentFile().mkdirs(); |
| } |
| |
| //make sure we can write to destination |
| if ( destination.exists() && !destination.canWrite() ) |
| { |
| final String message = "Unable to open file " + destination + " for writing."; |
| throw new IOException( message ); |
| } |
| |
| try ( InputStream in = source; |
| OutputStream out = new FileOutputStream( destination ) ) |
| { |
| IOUtil.copy( in, out ); |
| } |
| } |
| |
| /** |
| * Normalize a path. |
| * Eliminates "/../" and "/./" in a string. Returns <code>null</code> if the ..'s went past the |
| * root. |
| * Eg: |
| * <pre> |
| * /foo// --> /foo/ |
| * /foo/./ --> /foo/ |
| * /foo/../bar --> /bar |
| * /foo/../bar/ --> /bar/ |
| * /foo/../bar/../baz --> /baz |
| * //foo//./bar --> /foo/bar |
| * /../ --> null |
| * </pre> |
| * |
| * @param path the path to normalize |
| * @return the normalized String, or <code>null</code> if too many ..'s. |
| */ |
| @Nonnull public static String normalize( @Nonnull final String path ) |
| { |
| String normalized = path; |
| // Resolve occurrences of "//" in the normalized path |
| while ( true ) |
| { |
| int index = normalized.indexOf( "//" ); |
| if ( index < 0 ) |
| { |
| break; |
| } |
| normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 ); |
| } |
| |
| // Resolve occurrences of "/./" in the normalized path |
| while ( true ) |
| { |
| int index = normalized.indexOf( "/./" ); |
| if ( index < 0 ) |
| { |
| break; |
| } |
| normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 ); |
| } |
| |
| // Resolve occurrences of "/../" in the normalized path |
| while ( true ) |
| { |
| int index = normalized.indexOf( "/../" ); |
| if ( index < 0 ) |
| { |
| break; |
| } |
| if ( index == 0 ) |
| { |
| return null; // Trying to go outside our context |
| } |
| int index2 = normalized.lastIndexOf( '/', index - 1 ); |
| normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 ); |
| } |
| |
| // Return the normalized path that we have completed |
| return normalized; |
| } |
| |
| /** |
| * Resolve a file <code>filename</code> to it's canonical form. If <code>filename</code> is |
| * relative (doesn't start with <code>/</code>), it will be resolved relative to |
| * <code>baseFile</code>, otherwise it is treated as a normal root-relative path. |
| * |
| * @param baseFile Where to resolve <code>filename</code> from, if <code>filename</code> is |
| * relative. |
| * @param filename Absolute or relative file path to resolve. |
| * @return The canonical <code>File</code> of <code>filename</code>. |
| */ |
| @Nonnull public static File resolveFile( final File baseFile, @Nonnull String filename ) |
| { |
| String filenm = filename; |
| if ( '/' != File.separatorChar ) |
| { |
| filenm = filename.replace( '/', File.separatorChar ); |
| } |
| |
| if ( '\\' != File.separatorChar ) |
| { |
| filenm = filename.replace( '\\', File.separatorChar ); |
| } |
| |
| // deal with absolute files |
| if ( filenm.startsWith( File.separator ) || ( Os.isFamily( Os.FAMILY_WINDOWS ) && filenm.indexOf( ":" ) > 0 ) ) |
| { |
| File file = new File( filenm ); |
| |
| try |
| { |
| file = file.getCanonicalFile(); |
| } |
| catch ( final IOException ioe ) |
| { |
| // nop |
| } |
| |
| return file; |
| } |
| // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips |
| // them. However, I'm not sure about this UNC stuff. (JT) |
| final char[] chars = filename.toCharArray(); |
| final StringBuilder sb = new StringBuilder(); |
| |
| //remove duplicate file separators in succession - except |
| //on win32 at start of filename as UNC filenames can |
| //be \\AComputer\AShare\myfile.txt |
| int start = 0; |
| if ( '\\' == File.separatorChar ) |
| { |
| sb.append( filenm.charAt( 0 ) ); |
| start++; |
| } |
| |
| for ( int i = start; i < chars.length; i++ ) |
| { |
| final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1]; |
| |
| if ( !doubleSeparator ) |
| { |
| sb.append( chars[i] ); |
| } |
| } |
| |
| filenm = sb.toString(); |
| |
| //must be relative |
| File file = ( new File( baseFile, filenm ) ).getAbsoluteFile(); |
| |
| try |
| { |
| file = file.getCanonicalFile(); |
| } |
| catch ( final IOException ioe ) |
| { |
| // nop |
| } |
| |
| return file; |
| } |
| |
| /** |
| * Delete a file. If file is directory delete it and all sub-directories. |
| * |
| * @param file the file path |
| * @throws IOException if any |
| */ |
| public static void forceDelete( @Nonnull final String file ) |
| throws IOException |
| { |
| forceDelete( new File( file ) ); |
| } |
| |
| /** |
| * Delete a file. If file is directory delete it and all sub-directories. |
| * |
| * @param file a file |
| * @throws IOException if any |
| */ |
| public static void forceDelete( @Nonnull final File file ) |
| throws IOException |
| { |
| if ( file.isDirectory() ) |
| { |
| deleteDirectory( file ); |
| } |
| else |
| { |
| /* |
| * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a |
| * symlink whose target does not exist is deleted, too. |
| */ |
| boolean filePresent = file.getCanonicalFile().exists(); |
| if ( !deleteFile( file ) && filePresent ) |
| { |
| final String message = "File " + file + " unable to be deleted."; |
| throw new IOException( message ); |
| } |
| } |
| } |
| |
| /** |
| * deletes a file. |
| * |
| * @param file The file to delete |
| * @throws IOException If the file cannot be delted. |
| */ |
| |
| |
| public static void delete( @Nonnull File file ) |
| throws IOException |
| { |
| if ( Java7Support.isAtLeastJava7() ) |
| { |
| Java7Support.delete( file ); |
| } |
| else |
| { |
| if ( !file.delete() ) |
| { |
| throw new IOException( "Could not delete " + file.getName() ); |
| } |
| } |
| } |
| |
| /** |
| * @param file The file. |
| * @return true / false |
| */ |
| public static boolean deleteLegacyStyle( @Nonnull File file ) |
| { |
| if ( Java7Support.isAtLeastJava7() ) |
| { |
| try |
| { |
| Java7Support.delete( file ); |
| return true; |
| } |
| catch ( IOException e ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| return file.delete(); |
| } |
| } |
| |
| /** |
| * Accommodate Windows bug encountered in both Sun and IBM JDKs. |
| * Others possible. If the delete does not work, call System.gc(), |
| * wait a little and try again. |
| * |
| * @param file a file |
| * @throws IOException if any |
| */ |
| private static boolean deleteFile( @Nonnull File file ) |
| throws IOException |
| { |
| if ( file.isDirectory() ) |
| { |
| throw new IOException( "File " + file + " isn't a file." ); |
| } |
| |
| if ( !deleteLegacyStyle( file ) ) |
| { |
| if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) |
| { |
| file = file.getCanonicalFile(); |
| } |
| |
| try |
| { |
| Thread.sleep( 10 ); |
| return deleteLegacyStyle( file ); |
| } |
| catch ( InterruptedException ex ) |
| { |
| return deleteLegacyStyle( file ); |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Make a directory. |
| * |
| * @param file not null |
| * @throws IOException If there already exists a file with specified name or |
| * the directory is unable to be created |
| * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS. |
| * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME |
| */ |
| public static void forceMkdir( @Nonnull final File file ) |
| throws IOException |
| { |
| if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) ) |
| { |
| throw new IllegalArgumentException( |
| "The file (" + file.getAbsolutePath() + ") cannot contain any of the following characters: \n" |
| + StringUtils.join( INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) ); |
| } |
| |
| if ( file.exists() ) |
| { |
| if ( file.isFile() ) |
| { |
| final String message = |
| "File " + file + " exists and is " + "not a directory. Unable to create directory."; |
| throw new IOException( message ); |
| } |
| } |
| else |
| { |
| if ( !file.mkdirs() ) |
| { |
| final String message = "Unable to create directory " + file; |
| throw new IOException( message ); |
| } |
| } |
| } |
| |
| /** |
| * Recursively delete a directory. |
| * |
| * @param directory a directory |
| * @throws IOException if any |
| */ |
| public static void deleteDirectory( @Nonnull final String directory ) |
| throws IOException |
| { |
| deleteDirectory( new File( directory ) ); |
| } |
| |
| /** |
| * Recursively delete a directory. |
| * |
| * @param directory a directory |
| * @throws IOException if any |
| */ |
| public static void deleteDirectory( @Nonnull final File directory ) |
| throws IOException |
| { |
| if ( !directory.exists() ) |
| { |
| return; |
| } |
| |
| /* try delete the directory before its contents, which will take |
| * care of any directories that are really symbolic links. |
| */ |
| if ( deleteLegacyStyle( directory ) ) |
| { |
| return; |
| } |
| |
| cleanDirectory( directory ); |
| if ( !deleteLegacyStyle( directory ) ) |
| { |
| final String message = "Directory " + directory + " unable to be deleted."; |
| throw new IOException( message ); |
| } |
| } |
| |
| /** |
| * Clean a directory without deleting it. |
| * |
| * @param directory a directory |
| * @throws IOException if any |
| */ |
| public static void cleanDirectory( @Nonnull final File directory ) |
| throws IOException |
| { |
| if ( !directory.exists() ) |
| { |
| final String message = directory + " does not exist"; |
| throw new IllegalArgumentException( message ); |
| } |
| |
| if ( !directory.isDirectory() ) |
| { |
| final String message = directory + " is not a directory"; |
| throw new IllegalArgumentException( message ); |
| } |
| |
| IOException exception = null; |
| |
| final File[] files = directory.listFiles(); |
| |
| if ( files == null ) |
| { |
| return; |
| } |
| |
| for ( final File file : files ) |
| { |
| try |
| { |
| forceDelete( file ); |
| } |
| catch ( final IOException ioe ) |
| { |
| exception = ioe; |
| } |
| } |
| |
| if ( null != exception ) |
| { |
| throw exception; |
| } |
| } |
| |
| /** |
| * Recursively count size of a directory. |
| * |
| * @param directory a directory |
| * @return size of directory in bytes. |
| */ |
| public static long sizeOfDirectory( @Nonnull final String directory ) |
| { |
| return sizeOfDirectory( new File( directory ) ); |
| } |
| |
| /** |
| * Recursively count size of a directory. |
| * |
| * @param directory a directory |
| * @return size of directory in bytes. |
| */ |
| public static long sizeOfDirectory( @Nonnull final File directory ) |
| { |
| if ( !directory.exists() ) |
| { |
| final String message = directory + " does not exist"; |
| throw new IllegalArgumentException( message ); |
| } |
| |
| if ( !directory.isDirectory() ) |
| { |
| final String message = directory + " is not a directory"; |
| throw new IllegalArgumentException( message ); |
| } |
| |
| long size = 0; |
| |
| final File[] files = directory.listFiles(); |
| if ( files == null ) |
| { |
| throw new IllegalArgumentException( "Problems reading directory" ); |
| } |
| |
| for ( final File file : files ) |
| { |
| if ( file.isDirectory() ) |
| { |
| size += sizeOfDirectory( file ); |
| } |
| else |
| { |
| size += file.length(); |
| } |
| } |
| |
| return size; |
| } |
| |
| /** |
| * Return the files contained in the directory, using inclusion and exclusion Ant patterns, |
| * including the directory name in each of the files |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @return a list of File objects |
| * @throws IOException in case of failure. |
| * @see #getFileNames(File, String, String, boolean) |
| */ |
| @Nonnull |
| public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes ) |
| throws IOException |
| { |
| return getFiles( directory, includes, excludes, true ); |
| } |
| |
| /** |
| * Return the files contained in the directory, using inclusion and exclusion Ant patterns |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each file |
| * @return a list of File objects |
| * @throws IOException in case of failure. |
| * @see #getFileNames(File, String, String, boolean) |
| */ |
| @Nonnull |
| public static List<File> getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes, |
| boolean includeBasedir ) |
| throws IOException |
| { |
| List<String> fileNames = getFileNames( directory, includes, excludes, includeBasedir ); |
| |
| List<File> files = new ArrayList<>(); |
| |
| for ( String filename : fileNames ) |
| { |
| files.add( new File( filename ) ); |
| } |
| |
| return files; |
| } |
| |
| /** |
| * Return a list of files as String depending options. |
| * This method use case sensitive file name. |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each String of file |
| * @return a list of files as String |
| * @throws IOException in case of failure. |
| */ |
| @Nonnull public static List<String> getFileNames( @Nonnull File directory, @Nullable String includes, |
| @Nullable String excludes, boolean includeBasedir ) |
| throws IOException |
| { |
| return getFileNames( directory, includes, excludes, includeBasedir, true ); |
| } |
| |
| /** |
| * Return a list of files as String depending options. |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each String of file |
| * @param isCaseSensitive true if case sensitive |
| * @return a list of files as String |
| * @throws IOException |
| */ |
| @Nonnull private static List<String> getFileNames( @Nonnull File directory, @Nullable String includes, |
| @Nullable String excludes, boolean includeBasedir, |
| boolean isCaseSensitive ) |
| throws IOException |
| { |
| return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, true, false ); |
| } |
| |
| /** |
| * Return a list of directories as String depending options. |
| * This method use case sensitive file name. |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each String of file |
| * @return a list of directories as String |
| * @throws IOException in case of failure. |
| */ |
| @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes, |
| @Nullable String excludes, boolean includeBasedir ) |
| throws IOException |
| { |
| return getDirectoryNames( directory, includes, excludes, includeBasedir, true ); |
| } |
| |
| /** |
| * Return a list of directories as String depending options. |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each String of file |
| * @param isCaseSensitive true if case sensitive |
| * @return a list of directories as String |
| * @throws IOException in case of failure. |
| */ |
| @Nonnull public static List<String> getDirectoryNames( @Nonnull File directory, @Nullable String includes, |
| @Nullable String excludes, boolean includeBasedir, |
| boolean isCaseSensitive ) |
| throws IOException |
| { |
| return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, false, true ); |
| } |
| |
| /** |
| * Return a list of files as String depending options. |
| * |
| * @param directory the directory to scan |
| * @param includes the includes pattern, comma separated |
| * @param excludes the excludes pattern, comma separated |
| * @param includeBasedir true to include the base dir in each String of file |
| * @param isCaseSensitive true if case sensitive |
| * @param getFiles true if get files |
| * @param getDirectories true if get directories |
| * @return a list of files as String |
| */ |
| @Nonnull public static List<String> getFileAndDirectoryNames( File directory, @Nullable String includes, |
| @Nullable String excludes, boolean includeBasedir, |
| boolean isCaseSensitive, boolean getFiles, |
| boolean getDirectories ) |
| { |
| DirectoryScanner scanner = new DirectoryScanner(); |
| |
| scanner.setBasedir( directory ); |
| |
| if ( includes != null ) |
| { |
| scanner.setIncludes( StringUtils.split( includes, "," ) ); |
| } |
| |
| if ( excludes != null ) |
| { |
| scanner.setExcludes( StringUtils.split( excludes, "," ) ); |
| } |
| |
| scanner.setCaseSensitive( isCaseSensitive ); |
| |
| scanner.scan(); |
| |
| List<String> list = new ArrayList<>(); |
| |
| if ( getFiles ) |
| { |
| String[] files = scanner.getIncludedFiles(); |
| |
| for ( String file : files ) |
| { |
| if ( includeBasedir ) |
| { |
| list.add( directory + File.separator + file ); |
| } |
| else |
| { |
| list.add( file ); |
| } |
| } |
| } |
| |
| if ( getDirectories ) |
| { |
| String[] directories = scanner.getIncludedDirectories(); |
| |
| for ( String directory1 : directories ) |
| { |
| if ( includeBasedir ) |
| { |
| list.add( directory + File.separator + directory1 ); |
| } |
| else |
| { |
| list.add( directory1 ); |
| } |
| } |
| } |
| |
| return list; |
| } |
| |
| /** |
| * Copy a directory to an other one. |
| * |
| * @param sourceDirectory the source dir |
| * @param destinationDirectory the target dir |
| * @throws IOException if any |
| */ |
| public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory ) |
| throws IOException |
| { |
| copyDirectory( sourceDirectory, destinationDirectory, "**", null ); |
| } |
| |
| /** |
| * Copy a directory to an other one. |
| * |
| * @param sourceDirectory the source dir |
| * @param destinationDirectory the target dir |
| * @param includes include pattern |
| * @param excludes exlucde pattern |
| * @throws IOException if any |
| * @see #getFiles(File, String, String) |
| */ |
| public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory, |
| @Nullable String includes, @Nullable String excludes ) |
| throws IOException |
| { |
| if ( !sourceDirectory.exists() ) |
| { |
| return; |
| } |
| |
| List<File> files = getFiles( sourceDirectory, includes, excludes ); |
| |
| for ( File file : files ) |
| { |
| copyFileToDirectory( file, destinationDirectory ); |
| } |
| } |
| |
| /** |
| * Copies a entire directory structure. |
| * <p/> |
| * Note: |
| * <ul> |
| * <li>It will include empty directories. |
| * <li>The <code>sourceDirectory</code> must exists. |
| * </ul> |
| * |
| * @param sourceDirectory the source dir |
| * @param destinationDirectory the target dir |
| * @throws IOException if any |
| */ |
| public static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory ) |
| throws IOException |
| { |
| copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, false ); |
| } |
| |
| private static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory, |
| File rootDestinationDirectory, boolean onlyModifiedFiles ) |
| throws IOException |
| { |
| //noinspection ConstantConditions |
| if ( sourceDirectory == null ) |
| { |
| throw new IOException( "source directory can't be null." ); |
| } |
| |
| //noinspection ConstantConditions |
| if ( destinationDirectory == null ) |
| { |
| throw new IOException( "destination directory can't be null." ); |
| } |
| |
| if ( sourceDirectory.equals( destinationDirectory ) ) |
| { |
| throw new IOException( "source and destination are the same directory." ); |
| } |
| |
| if ( !sourceDirectory.exists() ) |
| { |
| throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." ); |
| } |
| |
| File[] files = sourceDirectory.listFiles(); |
| |
| if ( files == null ) |
| { |
| return; |
| } |
| |
| String sourcePath = sourceDirectory.getAbsolutePath(); |
| |
| for ( File file : files ) |
| { |
| if ( file.equals( rootDestinationDirectory ) ) |
| { |
| // We don't copy the destination directory in itself |
| continue; |
| } |
| |
| String dest = file.getAbsolutePath(); |
| |
| dest = dest.substring( sourcePath.length() + 1 ); |
| |
| File destination = new File( destinationDirectory, dest ); |
| |
| if ( file.isFile() ) |
| { |
| destination = destination.getParentFile(); |
| |
| if ( onlyModifiedFiles ) |
| { |
| copyFileToDirectoryIfModified( file, destination ); |
| } |
| else |
| { |
| copyFileToDirectory( file, destination ); |
| } |
| } |
| else if ( file.isDirectory() ) |
| { |
| if ( !destination.exists() && !destination.mkdirs() ) |
| { |
| throw new IOException( |
| "Could not create destination directory '" + destination.getAbsolutePath() + "'." ); |
| } |
| |
| copyDirectoryStructure( file, destination, rootDestinationDirectory, onlyModifiedFiles ); |
| } |
| else |
| { |
| throw new IOException( "Unknown file type: " + file.getAbsolutePath() ); |
| } |
| } |
| } |
| |
| /** |
| * Renames a file, even if that involves crossing file system boundaries. |
| * <p/> |
| * <p>This will remove <code>to</code> (if it exists), ensure that |
| * <code>to</code>'s parent directory exists and move |
| * <code>from</code>, which involves deleting <code>from</code> as |
| * well.</p> |
| * |
| * @param from the file to move |
| * @param to the new file name |
| * @throws IOException if anything bad happens during this process. |
| * Note that <code>to</code> may have been deleted already when this happens. |
| */ |
| public static void rename( @Nonnull File from, @Nonnull File to ) |
| throws IOException |
| { |
| if ( to.exists() && !deleteLegacyStyle( to ) ) |
| { |
| throw new IOException( "Failed to delete " + to + " while trying to rename " + from ); |
| } |
| |
| File parent = to.getParentFile(); |
| if ( parent != null && !parent.exists() && !parent.mkdirs() ) |
| { |
| throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from ); |
| } |
| |
| if ( !from.renameTo( to ) ) |
| { |
| copyFile( from, to ); |
| if ( !deleteLegacyStyle( from ) ) |
| { |
| throw new IOException( "Failed to delete " + from + " while trying to rename it." ); |
| } |
| } |
| } |
| |
| /** |
| * Create a temporary file in a given directory. |
| * <p/> |
| * <p>The file denoted by the returned abstract pathname did not |
| * exist before this method was invoked, any subsequent invocation |
| * of this method will yield a different file name.</p> |
| * <p/> |
| * The filename is prefixNNNNNsuffix where NNNN is a random number |
| * </p> |
| * <p>This method is different to {@link File#createTempFile(String, String, File)} of JDK 1.2 |
| * as it doesn't create the file itself. |
| * It uses the location pointed to by java.io.tmpdir |
| * when the parentDir attribute is |
| * null.</p> |
| * <p>To delete automatically the file created by this method, use the |
| * {@link File#deleteOnExit()} method.</p> |
| * |
| * @param prefix prefix before the random number |
| * @param suffix file extension; include the '.' |
| * @param parentDir Directory to create the temporary file in <code>-java.io.tmpdir</code> |
| * used if not specificed |
| * @return a File reference to the new temporary file. |
| */ |
| public static File createTempFile( @Nonnull String prefix, @Nonnull String suffix, @Nullable File parentDir ) |
| { |
| File result; |
| String parent = System.getProperty( "java.io.tmpdir" ); |
| if ( parentDir != null ) |
| { |
| parent = parentDir.getPath(); |
| } |
| DecimalFormat fmt = new DecimalFormat( "#####" ); |
| SecureRandom secureRandom = new SecureRandom(); |
| long secureInitializer = secureRandom.nextLong(); |
| Random rand = new Random( secureInitializer + Runtime.getRuntime().freeMemory() ); |
| do |
| { |
| result = new File( parent, prefix + fmt.format( positiveRandom( rand ) ) + suffix ); |
| } |
| while ( result.exists() ); |
| |
| return result; |
| } |
| |
| private static int positiveRandom( Random rand ) |
| { |
| int a = rand.nextInt(); |
| while ( a == Integer.MIN_VALUE ) |
| { |
| a = rand.nextInt(); |
| } |
| return Math.abs( a ); |
| } |
| |
| /** |
| * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified()</b> |
| * |
| * @param from the file to copy |
| * @param to the destination file |
| * @param encoding the file output encoding (only if wrappers is not empty) |
| * @param wrappers array of {@link FilterWrapper} |
| * @throws IOException if an IO error occurs during copying or filtering |
| */ |
| public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding, |
| @Nullable FilterWrapper... wrappers ) |
| throws IOException |
| { |
| copyFile( from, to, encoding, wrappers, false ); |
| } |
| |
| /** |
| * Wrapper class for Filter. |
| * |
| */ |
| public abstract static class FilterWrapper |
| { |
| /** |
| * @param fileReader {@link Reader} |
| * @return The Reader instance. |
| */ |
| public abstract Reader getReader( Reader fileReader ); |
| } |
| |
| /** |
| * <b>If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified() or if |
| * overwrite is true</b> |
| * |
| * @param from the file to copy |
| * @param to the destination file |
| * @param encoding the file output encoding (only if wrappers is not empty) |
| * @param wrappers array of {@link FilterWrapper} |
| * @param overwrite if true and f wrappers is null or empty, the file will be copy enven if to.lastModified() < |
| * from.lastModified() |
| * @throws IOException if an IO error occurs during copying or filtering |
| */ |
| public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding, |
| @Nullable FilterWrapper[] wrappers, boolean overwrite ) |
| throws IOException |
| { |
| if ( wrappers != null && wrappers.length > 0 ) |
| { |
| // buffer so it isn't reading a byte at a time! |
| Reader fileReader = null; |
| Writer fileWriter = null; |
| try |
| { |
| if ( encoding == null || encoding.length() < 1 ) |
| { |
| fileReader = new BufferedReader( new FileReader( from ) ); |
| fileWriter = new FileWriter( to ); |
| } |
| else |
| { |
| fileReader = new BufferedReader( new InputStreamReader( new FileInputStream( from ), encoding ) ); |
| fileWriter = new OutputStreamWriter( new FileOutputStream( to ), encoding ); |
| } |
| |
| for ( FilterWrapper wrapper : wrappers ) |
| { |
| fileReader = wrapper.getReader( fileReader ); |
| } |
| |
| IOUtil.copy( fileReader, fileWriter ); |
| fileWriter.close(); |
| fileWriter = null; |
| fileReader.close(); |
| fileReader = null; |
| } |
| finally |
| { |
| IOUtil.close( fileReader ); |
| IOUtil.close( fileWriter ); |
| } |
| } |
| else |
| { |
| if ( to.lastModified() < from.lastModified() || overwrite ) |
| { |
| copyFile( from, to ); |
| } |
| } |
| } |
| |
| /** |
| * Note: the file content is read with platform encoding |
| * |
| * @param file the file |
| * @return a List containing every every line not starting with # and not empty |
| * @throws IOException if any |
| */ |
| @Nonnull public static List<String> loadFile( @Nonnull File file ) |
| throws IOException |
| { |
| List<String> lines = new ArrayList<>(); |
| |
| if ( file.exists() ) |
| { |
| try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) ) |
| { |
| for ( String line = reader.readLine(); line != null; line = reader.readLine() ) |
| { |
| line = line.trim(); |
| |
| if ( !line.startsWith( "#" ) && line.length() != 0 ) |
| { |
| lines.add( line ); |
| } |
| } |
| } |
| } |
| |
| return lines; |
| } |
| |
| /** |
| * For Windows OS, check if the file name contains any of the following characters: |
| * <code>":", "*", "?", "\"", "<", ">", "|"</code> |
| * |
| * @param f not null file |
| * @return <code>false</code> if the file path contains any of forbidden Windows characters, |
| * <code>true</code> if the Os is not Windows or if the file path respect the Windows constraints. |
| * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME |
| */ |
| private static boolean isValidWindowsFileName( @Nonnull File f ) |
| { |
| if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) |
| { |
| if ( StringUtils.indexOfAny( f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME ) != -1 ) |
| { |
| return false; |
| } |
| |
| if ( f.getParentFile() != null ) |
| { |
| return isValidWindowsFileName( f.getParentFile() ); |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Checks whether a given file is a symbolic link. |
| * |
| * @param file the file to check |
| * @throws IOException in case of failure. |
| * @return true if symbolic link false otherwise. |
| * @deprecated Use {@link Files#isSymbolicLink(java.nio.file.Path)} instead |
| */ |
| @Deprecated |
| public static boolean isSymbolicLink( @Nonnull final File file ) |
| throws IOException |
| { |
| return Files.isSymbolicLink( file.toPath() ); |
| } |
| |
| /** |
| * Checks whether a given file is a symbolic link. |
| * |
| * @param file the file to check |
| * @return true if and only if we reliably can say this is a symlink. |
| * |
| * @throws IOException in case of failure. |
| * @deprecated Use {@link Files#isSymbolicLink(java.nio.file.Path)} instead |
| */ |
| @Deprecated |
| public static boolean isSymbolicLinkForSure( @Nonnull final File file ) |
| throws IOException |
| { |
| // Used to return false for Java 6 and older |
| return Files.isSymbolicLink( file.toPath() ); |
| } |
| |
| /** |
| * Checks whether a given file is a symbolic link. |
| * <p> |
| * It doesn't really test for symbolic links but whether the canonical and absolute |
| * paths of the file are identical - this may lead to false positives on some platforms. |
| * |
| * It also returns true for any file that has been reached via a symbolic link, |
| * if you decide to traverse into the symlink. |
| * |
| * As can be seen from the "return" clause of this method, there is really no |
| * guarantee of any sort from this method. Small wonder this ever got used for |
| * anything. |
| * </p> |
| * |
| * @param file the file to check |
| * @return true if the file is a symbolic link or if we're on some crappy os. |
| * false if the file is not a symlink or we're not able to detect it. |
| */ |
| static boolean isSymbolicLinkLegacy( @Nonnull final File file ) |
| throws IOException |
| { |
| final File canonical = new File( file.getCanonicalPath() ); |
| return !file.getAbsolutePath().equals( canonical.getPath() ); |
| } |
| |
| } |