Return-Path: X-Original-To: apmail-commons-commits-archive@minotaur.apache.org Delivered-To: apmail-commons-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D465718DFF for ; Fri, 19 Jun 2015 18:00:44 +0000 (UTC) Received: (qmail 11249 invoked by uid 500); 19 Jun 2015 18:00:44 -0000 Delivered-To: apmail-commons-commits-archive@commons.apache.org Received: (qmail 11179 invoked by uid 500); 19 Jun 2015 18:00:44 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 11170 invoked by uid 99); 19 Jun 2015 18:00:44 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 19 Jun 2015 18:00:44 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id 86FE3AC048D for ; Fri, 19 Jun 2015 18:00:44 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1686472 - in /commons/proper/io/trunk/src: main/java/org/apache/commons/io/FileUtils.java main/java/org/apache/commons/io/Java7Support.java test/java/org/apache/commons/io/FileUtilsCleanSymlinksTestCase.java Date: Fri, 19 Jun 2015 18:00:44 -0000 To: commits@commons.apache.org From: krosenvold@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20150619180044.86FE3AC048D@hades.apache.org> 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. *

- * Note: 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)} + * + * Note: the current implementation always returns {@code false} if running on + * jkd1.6 and the system is detected as Windows using {@link FilenameUtils#isSystemWindows()} *

* For code that runs on Java 1.7 or later, use the following method instead: *
@@ -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.