You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2017/11/16 04:26:33 UTC

commons-io git commit: [IO-555] Recasting this issue away from a new method in FilenameUtils to a solution to convert Strings to legal files names with the new enum FileSystem.

Repository: commons-io
Updated Branches:
  refs/heads/master 5e2ace28e -> 6e2ac190c


[IO-555] Recasting this issue away from a new method in FilenameUtils to
a solution to convert Strings to legal files names with the new enum
FileSystem.

Project: http://git-wip-us.apache.org/repos/asf/commons-io/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-io/commit/6e2ac190
Tree: http://git-wip-us.apache.org/repos/asf/commons-io/tree/6e2ac190
Diff: http://git-wip-us.apache.org/repos/asf/commons-io/diff/6e2ac190

Branch: refs/heads/master
Commit: 6e2ac190ce379fb19acb8b5cf06c0a1b25d19059
Parents: 5e2ace2
Author: Gary Gregory <gg...@apache.org>
Authored: Wed Nov 15 21:26:30 2017 -0700
Committer: Gary Gregory <gg...@apache.org>
Committed: Wed Nov 15 21:26:30 2017 -0700

----------------------------------------------------------------------
 .../java/org/apache/commons/io/FileSystem.java  | 228 +++++++++++++++++++
 .../org/apache/commons/io/FilenameUtils.java    |  67 ------
 .../apache/commons/io/FileSystemTestCase.java   |  50 ++++
 .../commons/io/FilenameUtilsTestCase.java       |  22 --
 4 files changed, 278 insertions(+), 89 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-io/blob/6e2ac190/src/main/java/org/apache/commons/io/FileSystem.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/io/FileSystem.java b/src/main/java/org/apache/commons/io/FileSystem.java
new file mode 100644
index 0000000..158c0c9
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/FileSystem.java
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.io;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Abstracts an OS' file system details, currently supporting the single use case of converting a file name String to a
+ * legal file name with {@link #toLegalFileName(String, char)}.
+ * <p>
+ * The starting point of any operation is {@link #getCurrent()} which gets you the enum for the file system that matches
+ * the OS hosting the running JVM.
+ * </p>
+ * 
+ * @since 2.7
+ */
+public enum FileSystem {
+
+    LINUX(255, 4096, new char[] {
+            // KEEP THIS ARRAY SORTED!
+            // @formatter:off
+            // ASCII NULL
+            0,
+             '/'
+            // @formatter:on
+    }),
+
+    MAC_OSX(255, 1024, new char[] {
+            // KEEP THIS ARRAY SORTED!
+            // @formatter:off
+            // ASCII NULL
+            0,
+            '/',
+             ':'
+            // @formatter:on
+    }),
+
+    GENERIC(Integer.MAX_VALUE, Integer.MAX_VALUE, new char[] { 0 }),
+
+    WINDOWS(255, 32000, new char[] {
+            // KEEP THIS ARRAY SORTED!
+            // @formatter:off
+            // ASCII NULL
+            0,
+            // 1-31 may be allowed in file streams
+            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+            29, 30, 31,
+            '"', '*', '/', ':', '<', '>', '?', '\\', '|'
+            // @formatter:on
+    });
+
+    /**
+     * The prefix String for all Windows OS.
+     */
+    private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
+
+    /**
+     * <p>
+     * Is {@code true} if this is Windows.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_WINDOWS = getOsMatchesName(OS_NAME_WINDOWS_PREFIX);
+
+    /**
+     * <p>
+     * Is {@code true} if this is Linux.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_LINUX = getOsMatchesName("Linux") || getOsMatchesName("LINUX");
+
+    /**
+     * <p>
+     * Is {@code true} if this is Mac.
+     * </p>
+     * <p>
+     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
+     * </p>
+     */
+    private static final boolean IS_OS_MAC = getOsMatchesName("Mac");
+
+    private static final String OS_NAME = getSystemProperty("os.name");
+
+    public static FileSystem getCurrent() {
+        if (IS_OS_LINUX) {
+            return LINUX;
+        }
+        if (IS_OS_MAC) {
+            return FileSystem.MAC_OSX;
+        }
+        if (IS_OS_WINDOWS) {
+            return FileSystem.WINDOWS;
+        }
+        return GENERIC;
+    }
+
+    /**
+     * Decides if the operating system matches.
+     *
+     * @param osNamePrefix
+     *            the prefix for the os name
+     * @return true if matches, or false if not or can't determine
+     */
+    private static boolean getOsMatchesName(final String osNamePrefix) {
+        return isOsNameMatch(OS_NAME, osNamePrefix);
+    }
+
+    /**
+     * <p>
+     * Gets a System property, defaulting to {@code null} if the property cannot be read.
+     * </p>
+     * <p>
+     * If a {@code SecurityException} is caught, the return value is {@code null} and a message is written to
+     * {@code System.err}.
+     * </p>
+     *
+     * @param property
+     *            the system property name
+     * @return the system property value or {@code null} if a security problem occurs
+     */
+    private static String getSystemProperty(final String property) {
+        try {
+            return System.getProperty(property);
+        } catch (final SecurityException ex) {
+            // we are not allowed to look at this property
+            System.err.println("Caught a SecurityException reading the system property '" + property
+                    + "'; the SystemUtils property value will default to null.");
+            return null;
+        }
+    }
+
+    /**
+     * Decides if the operating system matches.
+     * <p>
+     * This method is package private instead of private to support unit test invocation.
+     * </p>
+     *
+     * @param osName
+     *            the actual OS name
+     * @param osNamePrefix
+     *            the prefix for the expected OS name
+     * @return true if matches, or false if not or can't determine
+     */
+    private static boolean isOsNameMatch(final String osName, final String osNamePrefix) {
+        if (osName == null) {
+            return false;
+        }
+        return osName.startsWith(osNamePrefix);
+    }
+
+    private final char[] illegalFileNameChars;
+    private final int maxFileNameLength;
+
+    private final int maxPathLength;
+
+    private FileSystem(final int maxFileLength, final int maxPathLength, final char[] illegalFileNameChars) {
+        this.maxFileNameLength = maxFileLength;
+        this.maxPathLength = maxPathLength;
+        this.illegalFileNameChars = Objects.requireNonNull(illegalFileNameChars, "illegalFileNameChars");
+    }
+
+    public char[] getIllegalFileNameChars() {
+        return this.illegalFileNameChars.clone();
+    }
+
+    public int getMaxFileNameLength() {
+        return maxFileNameLength;
+    }
+
+    public int getMaxPathLength() {
+        return maxPathLength;
+    }
+
+    private boolean isIllegalFileNameChar(final char c) {
+        return Arrays.binarySearch(illegalFileNameChars, c) >= 0;
+    }
+
+    /**
+     * Converts a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} to a legal file
+     * name. Illegal characters in the candidate name are replaced by the {@code replacement} character. If the file
+     * name exceeds {@link #getMaxFileNameLength()}, then the name is truncated to {@link #getMaxFileNameLength()}.
+     *
+     * @param candidate
+     *            a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}
+     * @param replacement
+     *            Illegal characters in the candidate name are replaced by this character
+     * @return a String without illegal characters
+     */
+    public String toLegalFileName(final String candidate, final char replacement) {
+        if (isIllegalFileNameChar(replacement)) {
+            throw new IllegalArgumentException(
+                    String.format("The replacement character '%s' cannot be one of the %s illegal characters: %s",
+                            replacement, name(), Arrays.toString(illegalFileNameChars)));
+        }
+        final String truncated = candidate.length() > maxFileNameLength ? candidate.substring(0, maxFileNameLength)
+                : candidate;
+        boolean changed = false;
+        final char[] charArray = truncated.toCharArray();
+        for (int i = 0; i < charArray.length; i++) {
+            if (isIllegalFileNameChar(charArray[i])) {
+                charArray[i] = replacement;
+                changed = true;
+            }
+        }
+        return changed ? String.valueOf(charArray) : truncated;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/commons-io/blob/6e2ac190/src/main/java/org/apache/commons/io/FilenameUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/io/FilenameUtils.java b/src/main/java/org/apache/commons/io/FilenameUtils.java
index ec8dcb0..9cddebb 100644
--- a/src/main/java/org/apache/commons/io/FilenameUtils.java
+++ b/src/main/java/org/apache/commons/io/FilenameUtils.java
@@ -19,7 +19,6 @@ package org.apache.commons.io;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Stack;
 
@@ -102,39 +101,6 @@ public class FilenameUtils {
     private static final char UNIX_SEPARATOR = '/';
 
     /**
-     * The characters that are illegal in Windows file names.
-     * 
-     * <ul>
-     * <li>&lt; (less than</li>
-     * <li>&gt; (greater than</li>
-     * <li>: (colon</li>
-     * <li>" (double quote</li>
-     * <li>/ (forward slash</li>
-     * <li>\ (backslash</li>
-     * <li>| (vertical bar or pipe</li>
-     * <li>? (question mark</li>
-     * <li>* (asterisk</li>
-     * <li>ASCII NUL (0)</li>
-     * <li>Integer characters 1 through 31</li>
-     * </ul>
-     * 
-     * @since 2.7
-     * @see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx">Naming Files,
-     *      Paths, and Namespaces</a>
-     */
-    private static final char[] WINDOWS_ILLEGAL_FILE_NAME_CHARS = {
-            // KEEP THIS ARRAY SORTED!
-            // @formatter:off
-            // ASCII NULL
-            0,  
-            // 1-31 may be allowed in file streams
-            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
-            29, 30, 31, 
-            '"', '*', '/', ':', '<', '>', '?', '\\', '|'
-            // @formatter:on
-    };
-
-    /**
      * The Windows separator character.
      */
     private static final char WINDOWS_SEPARATOR = '\\';
@@ -1292,39 +1258,6 @@ public class FilenameUtils {
         return false;
     }
 
-    /**
-     * Checks whether the given character is illegal in a Windows file name.
-     * <p>
-     * The illegal characters are:
-     * </p>
-     * <ul>
-     * <li>&lt; (less than</li>
-     * <li>&gt; (greater than</li>
-     * <li>: (colon</li>
-     * <li>" (double quote</li>
-     * <li>/ (forward slash</li>
-     * <li>\ (backslash</li>
-     * <li>| (vertical bar or pipe</li>
-     * <li>? (question mark</li>
-     * <li>* (asterisk</li>
-     * <li>ASCII NUL (0)</li>
-     * <li>Integer characters 1 through 31</li>
-     * <li>There may be other characters that the file system does not allow. Please see
-     * <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx">Naming Files, Paths,
-     * and Namespaces</a></li>
-     * </ul>
-     * 
-     * @see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx">Naming Files,
-     *      Paths, and Namespaces</a>
-     * @param c
-     *            the character to check
-     * @return {@code true} if the given character is illegal
-     * @since 2.7
-     */
-    public static boolean isIllegalWindowsFileName(final char c) {
-        return Arrays.binarySearch(WINDOWS_ILLEGAL_FILE_NAME_CHARS, c) >= 0;
-    }
-    
     //-----------------------------------------------------------------------
     /**
      * Checks a filename to see if it matches the specified wildcard matcher,

http://git-wip-us.apache.org/repos/asf/commons-io/blob/6e2ac190/src/test/java/org/apache/commons/io/FileSystemTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/io/FileSystemTestCase.java b/src/test/java/org/apache/commons/io/FileSystemTestCase.java
new file mode 100644
index 0000000..e62eace
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/FileSystemTestCase.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.io;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FileSystemTestCase {
+
+    @Test
+    public void testToLegalFileNameWindows() {
+        FileSystem fs = FileSystem.WINDOWS;
+        char replacement = '-';
+        for (char i = 0; i < 32; i++) {
+            Assert.assertEquals(replacement, fs.toLegalFileName(String.valueOf(i), replacement).charAt(0));
+        }
+        char[] illegal = new char[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*' };
+        Arrays.sort(illegal);
+        System.out.println(Arrays.toString(illegal));
+        for (char i = 0; i < illegal.length; i++) {
+            Assert.assertEquals(replacement, fs.toLegalFileName(String.valueOf(i), replacement).charAt(0));
+        }
+        for (char i = 'a'; i < 'z'; i++) {
+            Assert.assertEquals(i, fs.toLegalFileName(String.valueOf(i), replacement).charAt(0));
+        }
+        for (char i = 'A'; i < 'Z'; i++) {
+            Assert.assertEquals(i, fs.toLegalFileName(String.valueOf(i), replacement).charAt(0));
+        }
+        for (char i = '0'; i < '9'; i++) {
+            Assert.assertEquals(i, fs.toLegalFileName(String.valueOf(i), replacement).charAt(0));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-io/blob/6e2ac190/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java b/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java
index dcc7e2b..234c25e 100644
--- a/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java
+++ b/src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java
@@ -247,28 +247,6 @@ public class FilenameUtilsTestCase {
     }
 
     @Test
-    public void testIsIllegalWindowsFileName() {
-        for (char i = 0; i < 32; i++) {
-            assertTrue(FilenameUtils.isIllegalWindowsFileName(i));
-        }
-        char[] illegal = new char[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*' };
-        Arrays.sort(illegal);
-        System.out.println(Arrays.toString(illegal));
-        for (char i = 0; i < illegal.length; i++) {
-            assertTrue(FilenameUtils.isIllegalWindowsFileName(illegal[i]));
-        }
-        for (char i = 'a'; i < 'z'; i++) {
-            assertFalse("i = " + (int) i, FilenameUtils.isIllegalWindowsFileName(i));
-        }
-        for (char i = 'A'; i < 'Z'; i++) {
-            assertFalse("i = " + (int) i, FilenameUtils.isIllegalWindowsFileName(i));
-        }
-        for (char i = '0'; i < '9'; i++) {
-            assertFalse("i = " + (int) i, FilenameUtils.isIllegalWindowsFileName(i));
-        }
-    }
-    
-    @Test
     public void testNormalize_with_nullbytes() throws Exception {
         try {
             assertEquals("a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("a\\b/c\u0000.txt"));