package votorola.g.io; // Copyright 2004-2007, 2009, 2011, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import java.io.*; import java.nio.charset.*; import java.util.logging.*; import java.util.regex.*; import votorola.g.lang.*; import votorola.g.logging.*; /** File utilities. */ public @ThreadSafe final class FileX { private FileX() {} /** Appends the entire file to the specified appendable. * * @return the same appendable. */ public static Appendable appendTo( final Appendable a, final File file, final Charset fileCharset ) throws IOException { final BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream(file), fileCharset )); try{ return ReaderX.appendTo( a, in ); } finally{ in.close(); } } /** A pattern to split a filename (or path) into two groups: body and dot-extension. * The body group (1) may include separators '/'. It will be neither empty nor null. * *

The extension group (2) will either include the preceding dot '.', or it will * be empty. It may comprise a single dot. It will not be null.

*/ public static final Pattern BODY_DOTX_PATTERN = Pattern.compile( "^(.+?)((?:\\.[^.]*)?)$" ); /** Copies a file or directory to a new file. No checking is done to prevent * self-copying. * * @param target pathname to create as copy. It will be overwritten if it * already exists. * @param source file or directory to copy. Directory contents are recursively * copied using {@link #copyTo(File,File) copyTo}. */ public static void copyAs( final File target, final File source ) throws IOException { copyAs( target, source, FileFilterX.TRANSPARENT ); } /** Copies a file or directory to a new file. No checking is done to prevent * self-copying. * * @param target pathname to create as copy. It will be overwritten if it * already exists. * @param source file or directory to copy. Directory contents are recursively * copied using {@link #copyTo(File,File,FileFilter) copyTo}. * @param fileFilter the filter to use in case the source is a directory. The * filter is applied to the content of the source directory, and recursively to * the content of any sub-directories that themselves pass the filter. */ public static void copyAs( File target, File source, FileFilter fileFilter ) throws IOException { if( source.isDirectory() ) { // First the directory itself. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( target.isDirectory() ) // clean it out, in case not empty: { if( !deleteRecursiveFrom(target) ) throw new IOException( "unable to delete directory contents of " + target ); } else if( !target.mkdir() ) throw new IOException( "unable to create directory " + target ); // Then its contents, if any. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - File[] subSourceArray = source.listFiles( fileFilter ); for( int i=0; imv command if it fails. * * @throws IOException if the file cannot be renamed. */ public static void renameFromDefaultToMv( final File oldFile, final File newFile ) throws IOException { if( oldFile.renameTo( newFile )) return; final ProcessBuilder pB = new ProcessBuilder( "/bin/mv", oldFile.getPath(), newFile.getPath() ); logger.config( "Java rename failed, defaulting to OS call: " + pB.command() ); int exitValue = ProcessX.waitForWithoutInterrupt( pB.start() ); if( exitValue != 0 ) throw new IOException( "exit value of " + exitValue + " from process: " + pB.command() ); } // /** First tries renameFrom(), failing that it makes a copy. Does not delete the old // * file. // * // * @return true if it had to resort to copying; false if the rename worked. // */ // public static boolean renameFromDefaultsToCopy( final File oldFile, final File newFile ) throws IOException // { // if( oldFile.renameTo( newFile )) return true; // if( renameFrom( oldFile, newFile )) return false; // // ThreadX.trySleep( 500/*ms*/ ); // logger.fine( "Java rename failed, defaulting to copy: " + oldFile ); // copyAs( newFile, oldFile ); // return true; // } // /** @return safe name for a file, based on originalName supplied. This implementation checks // * ntent only (not length). If all characters are recognized, then the originalName // * reference is returned. Otherwise returns a copy of the originalName with any // * unrecognized characters converted to underscores. Recognizes only alphanumeric // * characters and underscores. All others, including the period '.' character, are // * converted to underscores. // */ @Deprecated // in favour of ~.io.FileNameEncoder // public static String safeName( String originalName ) // { // char[] array = originalName.toCharArray(); // boolean arrayChanged = false; // till proven otherwise // for( int i=0; ilinkPath, pointing to * targetPath. This is likely to fail on non-Unix platforms. It is * equivalent to the Unix command: * *
ln --force --no-dereference --symbolic targetPath linkPath
* * @param targetPath the path to which the link file will point. * If the path is relative, then it is anchored at the link file. * @param linkPath the path of the link file to create. * * @throws IOException if the attempt fails. */ public static void symlink( final String targetPath, final String linkPath ) throws IOException { final File link = new File( linkPath ); if( link.exists() && !link.delete() ) throw new IOException( "unable to delete symbolic link, prior to relinking: " + linkPath ); // If it is not deleted here in the VM, then "new // File(linkPath).getCanonicalFile()" will see the old target for about 30 s. // Java (1.6.0.07) is somehow caching it. final ProcessBuilder pB = new ProcessBuilder( // "/bin/sh", "-c", "ln --force --no-dereference --symbolic " + targetPath + " " + linkPath ); "/bin/ln", "--force", "--no-dereference", "--symbolic", targetPath, linkPath ); logger.config( "calling out to OS: " + pB.command() ); int exitValue = ProcessX.waitForWithoutInterrupt( pB.start() ); if( exitValue != 0 ) throw new IOException( "exit value of " + exitValue + " from process: " + pB.command() ); } // /** Returns a new instance of file system's temporary directory, // * as defined by System property 'java.io.tmpdir'. // */ // public static File tempDirectory() // { // return new File( System.getProperty( "java.io.tmpdir" )); // } /** Tests whether or not the file system is case sensitive, and returns true iff it * is. */ public static boolean testsCaseSensitive() { File lower = new File( "a" ); File upper = new File( "A" ); return !lower.equals( upper ); } /** Traverses the file and its ancestors; first from the bottom up, then from the top * down. Upward traversal commences with the bottom-most file, and stops when all * ancestors are exhausted, or when the upFilter returns false. Downward traversal * commences with the topmost file, and stops on returning to the bottom-most file, * or when the downFilter returns false. * * @param bottomFile the bottom-most file. * @param upFilter the upward traversal filter. It returns true * if upward traversal is to continue; false if it is to stop. * @param downFilter the downward traversal filter. It returns true * if downward traversal is to continue; false if it is to stop. * * @return the last result from the downFilter. */ public static boolean traverse( final File bottomFile, final FileFilter upFilter, final FileFilter downFilter ) { boolean toContinue = true; // so far if( upFilter.accept( bottomFile )) { final File dir = bottomFile.getParentFile(); if( dir != null ) toContinue = traverse( dir, upFilter, downFilter ); } if( toContinue ) toContinue = downFilter.accept( bottomFile ); return toContinue; } /** Traverses the file and its descendants from the top down, beginning with the * topmost file itself. Traversal stops when all descendants are exhausted, or when * the filter returns false. * * @param topFile the top-most file. * @param filter the traversal filter. It returns true if traversal is to * continue; false if it is to stop. * * @return the last result from the filter. */ public static boolean traverseDown( final File topFile, final FileFilter filter ) { boolean toContinue = true; // so far if( !filter.accept( topFile )) toContinue = false; else if( topFile.isDirectory() ) { for( final File child: topFile.listFiles() ) { if( traverseDown( child, filter )) continue; toContinue = false; break; } } return toContinue; } /** Traverses the file and its ancestors from the bottom up, beginning with the * bottom-most file itself. Traversal stops when all ancestors are exhausted, or * when the filter returns false. * * @param bottomFile the bottom-most file. * @param filter the traversal filter. It returns true if traversal is to * continue; false if it is to stop. */ public static void traverseUp( final File bottomFile, final FileFilter filter ) { if( filter.accept( bottomFile )) { final File dir = bottomFile.getParentFile(); if( dir != null ) traverseUp( dir, filter ); } } /** Serializes an object to a file. * * @see #readObject(File) */ public static void writeObject( final Object object, final File file ) throws IOException { final ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream( file ))); try{ out.writeObject( object ); } finally{ out.close(); } } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final Logger logger = LoggerX.i( FileX.class ); }