Author: krosenvold
Date: Fri Jun 19 18:00:44 2015
New Revision: 1686472

URL: http://svn.apache.org/r1686472
Log:
IO-452 broken symlink support

Patch by David Standish.

Also added reflection-based java7 symlink support from maven code (original 
author is me)

Added:
    
commons/proper/io/trunk/src/main/java/org/apache/commons/io/Java7Support.java
Modified:
    commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java
    
commons/proper/io/trunk/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java

Modified: 
commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java?rev=1686472&r1=1686471&r2=1686472&view=diff
==============================================================================
--- commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java 
(original)
+++ commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java 
Fri Jun 19 18:00:44 2015
@@ -3009,8 +3009,10 @@ public class FileUtils {
      * Will not return true if there is a Symbolic Link anywhere in the path,
      * only if the specific file is.
      * <p/>
-     * <b>Note:</b> the current implementation always returns {@code false} if 
the system
-     * is detected as Windows using {@link FilenameUtils#isSystemWindows()}
+     * When using jdk1.7, this method delegates to {@code boolean 
java.nio.file.Files.isSymbolicLink(Path path)}
+     *
+     * <b>Note:</b> the current implementation always returns {@code false} if 
running on
+     * jkd1.6 and the system is detected as Windows using {@link 
FilenameUtils#isSystemWindows()}
      * <p/>
      * For code that runs on Java 1.7 or later, use the following method 
instead:
      * <br>
@@ -3021,6 +3023,11 @@ public class FileUtils {
      * @since 2.0
      */
     public static boolean isSymlink(final File file) throws IOException {
+        if ( Java7Support.isAtLeastJava7() )
+        {
+            return Java7Support.isSymLink( file );
+        }
+
         if (file == null) {
             throw new NullPointerException("File must not be null");
         }
@@ -3036,10 +3043,41 @@ public class FileUtils {
         }
 
         if 
(fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile()))
 {
-            return false;
+            return isBrokenSymlink(file);
         } else {
             return true;
         }
     }
 
+    /**
+     * Determines if the specified file is possibly a broken symbolic link.
+     *
+     * @param file the file to check
+
+     * @return true if the file is a Symbolic Link
+     * @throws IOException if an IO error occurs while checking the file
+     */
+    private static boolean isBrokenSymlink(final File file) throws IOException 
{
+        // if file exists then if it is a symlink it's not broken
+        if (file.exists()) {
+            return false;
+        }
+        // a broken symlink will show up in the list of files of its parent 
directory
+        final File canon = file.getCanonicalFile();
+        File parentDir = canon.getParentFile();
+        if (parentDir == null || !parentDir.exists()) {
+            return false;
+        }
+
+        // is it worthwhile to create a FileFilterUtil method for this?
+        // is it worthwhile to create an "identity"  IOFileFilter for this?
+        File[] fileInDir = parentDir.listFiles(
+                new FileFilter() {
+                    public boolean accept(File aFile) {
+                        return aFile.equals(canon);
+                    }
+                }
+        );
+        return fileInDir.length > 0;
+    }
 }

Added: 
commons/proper/io/trunk/src/main/java/org/apache/commons/io/Java7Support.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/main/java/org/apache/commons/io/Java7Support.java?rev=1686472&view=auto
==============================================================================
--- 
commons/proper/io/trunk/src/main/java/org/apache/commons/io/Java7Support.java 
(added)
+++ 
commons/proper/io/trunk/src/main/java/org/apache/commons/io/Java7Support.java 
Fri Jun 19 18:00:44 2015
@@ -0,0 +1,199 @@
+package org.apache.commons.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 java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Java7 feature detection and reflection based feature access.
+ *
+ * Taken from maven-shared-utils, only for private usage until we go full java7
+ */
+class Java7Support
+{
+
+    private static final boolean IS_JAVA7;
+
+    private static Method isSymbolicLink;
+
+    private static Method delete;
+
+    private static Method toPath;
+
+    private static Method exists;
+
+    private static Method toFile;
+
+    private static Method readSymlink;
+
+    private static Method createSymlink;
+
+    private static Object emptyLinkOpts;
+
+    private static Object emptyFileAttributes;
+
+    static
+    {
+        boolean isJava7x = true;
+        try
+        {
+            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+            Class<?> files = cl.loadClass( "java.nio.file.Files" );
+            Class<?> path = cl.loadClass( "java.nio.file.Path" );
+            Class<?> fa = cl.loadClass( 
"java.nio.file.attribute.FileAttribute" );
+            Class<?> linkOption = cl.loadClass( "java.nio.file.LinkOption" );
+            isSymbolicLink = files.getMethod( "isSymbolicLink", path );
+            delete = files.getMethod( "delete", path );
+            readSymlink = files.getMethod( "readSymbolicLink", path );
+
+            emptyFileAttributes = Array.newInstance( fa, 0 );
+            final Object o = emptyFileAttributes;
+            createSymlink = files.getMethod( "createSymbolicLink", path, path, 
o.getClass() );
+            emptyLinkOpts = Array.newInstance( linkOption, 0 );
+            exists = files.getMethod( "exists", path, emptyLinkOpts.getClass() 
);
+            toPath = File.class.getMethod( "toPath" );
+            toFile = path.getMethod( "toFile" );
+        }
+        catch ( ClassNotFoundException e )
+        {
+            isJava7x = false;
+        }
+        catch ( NoSuchMethodException e )
+        {
+            isJava7x = false;
+        }
+        IS_JAVA7 = isJava7x;
+    }
+
+    public static boolean isSymLink( File file )
+    {
+        try
+        {
+            Object path = toPath.invoke( file );
+            return (Boolean) isSymbolicLink.invoke( null, path );
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new RuntimeException( e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+
+    public static File readSymbolicLink( File symlink )
+        throws IOException
+    {
+        try
+        {
+            Object path = toPath.invoke( symlink );
+            Object resultPath =  readSymlink.invoke( null, path );
+            return (File) toFile.invoke( resultPath );
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new RuntimeException( e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            throw new RuntimeException( e );
+        }
+    }
+
+
+    public static boolean exists( File file )
+        throws IOException
+    {
+        try
+        {
+            Object path = toPath.invoke( file );
+            final Object invoke = exists.invoke( null, path, emptyLinkOpts );
+            return (Boolean) invoke;
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new RuntimeException( e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            throw (RuntimeException) e.getTargetException();
+        }
+
+    }
+
+    public static File createSymbolicLink( File symlink, File target )
+        throws IOException
+    {
+        try
+        {
+            if ( !exists( symlink ) )
+            {
+                Object link = toPath.invoke( symlink );
+                Object path = createSymlink.invoke( null, link, toPath.invoke( 
target ), emptyFileAttributes );
+                return (File) toFile.invoke( path );
+            }
+            return symlink;
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new RuntimeException( e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            final Throwable targetException = e.getTargetException();
+            throw (IOException) targetException;
+        }
+
+    }
+    /**
+     * Performs a nio delete
+     * @param file the file to delete
+     * @throws IOException
+     */
+    public static void delete( File file )
+        throws IOException
+    {
+        try
+        {
+            Object path = toPath.invoke( file );
+            delete.invoke( null, path );
+        }
+        catch ( IllegalAccessException e )
+        {
+            throw new RuntimeException( e );
+        }
+        catch ( InvocationTargetException e )
+        {
+            throw (IOException) e.getTargetException();
+        }
+    }
+
+    public static boolean isAtLeastJava7()
+    {
+        return IS_JAVA7;
+    }
+
+}

Modified: 
commons/proper/io/trunk/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java
URL: 
http://svn.apache.org/viewvc/commons/proper/io/trunk/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java?rev=1686472&r1=1686471&r2=1686472&view=diff
==============================================================================
--- 
commons/proper/io/trunk/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java
 (original)
+++ 
commons/proper/io/trunk/src/test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java
 Fri Jun 19 18:00:44 2015
@@ -203,6 +203,25 @@ public class FileUtilsCleanSymlinksTestC
         assertFalse(FileUtils.isSymlink(randomFile));
     }
 
+    public void testIdentifiesBrokenSymlinkFile() throws Exception {
+        if (System.getProperty("os.name").startsWith("Win")) {
+            // cant create symlinks in windows.
+            return;
+        }
+
+        final File noexistFile = new File(top, "noexist");
+        final File symlinkFile = new File(top, "fakeinner");
+        final File badSymlinkInPathFile = new File(symlinkFile, "fakeinner");
+        final File noexistParentFile = new File("noexist", "file");
+
+        setupSymlink(noexistFile, symlinkFile);
+
+        assertTrue(FileUtils.isSymlink(symlinkFile));
+        assertFalse(FileUtils.isSymlink(noexistFile));
+        assertFalse(FileUtils.isSymlink(noexistParentFile));
+        assertFalse(FileUtils.isSymlink(badSymlinkInPathFile));
+    }
+
     public void testCorrectlyIdentifySymlinkWithParentSymLink() throws 
Exception {
         if (System.getProperty("os.name").startsWith("Win")) {
             // cant create symlinks in windows.


Reply via email to