From 03728b9324ace5a22fbd8b7a833c065c7eef7df1 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 20 Oct 2014 13:17:16 +0200 Subject: [PATCH] Removed 'FilenameUtils', moved normalize to 'FileUtil', removed debug code for input console --- Dorkbox-Util/LICENSE.TXT | 2 +- Dorkbox-Util/src/dorkbox/util/FileUtil.java | 492 +++++- .../src/dorkbox/util/FilenameUtils.java | 1401 ----------------- Dorkbox-Util/src/dorkbox/util/IOCase.java | 256 --- .../util/process/JavaProcessBuilder.java | 8 +- .../dorkbox/util/process/ProcessProxy.java | 1 - 6 files changed, 490 insertions(+), 1670 deletions(-) delete mode 100644 Dorkbox-Util/src/dorkbox/util/FilenameUtils.java delete mode 100644 Dorkbox-Util/src/dorkbox/util/IOCase.java diff --git a/Dorkbox-Util/LICENSE.TXT b/Dorkbox-Util/LICENSE.TXT index 3e33be0..73bc147 100644 --- a/Dorkbox-Util/LICENSE.TXT +++ b/Dorkbox-Util/LICENSE.TXT @@ -94,7 +94,7 @@ Legal: - - FilenameUtils.java, IOCase.java - Apache 2.0 License + - FilenameUtils.java (normalize + dependencies) - Apache 2.0 License http://commons.apache.org/proper/commons-io/ Copyright 2013 ASF Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck, diff --git a/Dorkbox-Util/src/dorkbox/util/FileUtil.java b/Dorkbox-Util/src/dorkbox/util/FileUtil.java index 0c37d8f..65b7f7d 100644 --- a/Dorkbox-Util/src/dorkbox/util/FileUtil.java +++ b/Dorkbox-Util/src/dorkbox/util/FileUtil.java @@ -23,9 +23,44 @@ import org.slf4j.LoggerFactory; /** * File related utilities. + * + * + * Contains code from FilenameUtils.java (normalize + dependencies) - Apache 2.0 License + * http://commons.apache.org/proper/commons-io/ + * Copyright 2013 ASF + * Authors: Kevin A. Burton, Scott Sanders, Daniel Rall, Christoph.Reck, + * Peter Donald, Jeff Turner, Matthew Hawthorne, Martin Cooper, + * Jeremias Maerki, Stephen Colebourne */ public class FileUtil { private static final Logger logger = LoggerFactory.getLogger(FileUtil.class); + /** + * The Unix separator character. + */ + private static final char UNIX_SEPARATOR = '/'; + + /** + * The Windows separator character. + */ + private static final char WINDOWS_SEPARATOR = '\\'; + + /** + * The system separator character. + */ + private static final char SYSTEM_SEPARATOR = File.separatorChar; + + /** + * The separator character that is the opposite of the system separator. + */ + private static final char OTHER_SEPARATOR; + static { + if (OS.isWindows()) { + OTHER_SEPARATOR = UNIX_SEPARATOR; + } else { + OTHER_SEPARATOR = WINDOWS_SEPARATOR; + } + } + public static byte[] ZIP_HEADER = { 'P', 'K', 0x3, 0x4 }; @@ -118,8 +153,8 @@ public class FileUtil { } - String normalizedIn = FilenameUtils.normalize(in.getAbsolutePath()); - String normalizedout = FilenameUtils.normalize(out.getAbsolutePath()); + String normalizedIn = normalize(in.getAbsolutePath()); + String normalizedout = normalize(out.getAbsolutePath()); // if out doesn't exist, then create it. File parentOut = out.getParentFile(); @@ -162,7 +197,7 @@ public class FileUtil { * Copies the contents of file two onto the END of file one. */ @SuppressWarnings("resource") - public static File concatFiles(File one, File two) throws IOException { + public static File concatFiles(File one, File two) { if (one == null) { throw new IllegalArgumentException("one cannot be null."); } @@ -170,8 +205,8 @@ public class FileUtil { throw new IllegalArgumentException("two cannot be null."); } - String normalizedOne = FilenameUtils.normalize(one.getAbsolutePath()); - String normalizedTwo = FilenameUtils.normalize(two.getAbsolutePath()); + String normalizedOne = normalize(one.getAbsolutePath()); + String normalizedTwo = normalize(two.getAbsolutePath()); Logger logger2 = logger; if (logger2.isTraceEnabled()) { @@ -215,7 +250,7 @@ public class FileUtil { /** * Moves a file, overwriting any existing file at the destination. */ - public static File moveFile(String in, String out) throws IOException { + public static File moveFile(String in, String out) { if (in == null || in.isEmpty()) { throw new IllegalArgumentException("in cannot be null."); } @@ -229,7 +264,7 @@ public class FileUtil { /** * Moves a file, overwriting any existing file at the destination. */ - public static File moveFile(File in, File out) throws IOException { + public static File moveFile(File in, File out) { if (in == null) { throw new IllegalArgumentException("in cannot be null."); } @@ -918,4 +953,447 @@ public class FileUtil { return null; } + + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

+ * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo/
+     * /foo/./              -->   /foo/
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar/
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo/
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar/
+     * ~/../bar             -->   null
+     * 
+ * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalize(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, true); + } + + /** + * Normalizes a path, removing double and single dot path steps. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

+ * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo/
+     * /foo/./              -->   /foo/
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar/
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo/
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar/
+     * ~/../bar             -->   null
+     * 
+ * (Note the file separator returned will be correct for Windows/Unix) + * + * @param file the file to normalize, null returns null + * @return the normalized file, or null if invalid + */ + public static File normalize(File file) { + if (file == null) { + return null; + } + + String asString = doNormalize(file.getAbsolutePath(), SYSTEM_SEPARATOR, true); + if (asString == null) { + return null; + } + + return new File(asString); + } + + + /** + * Normalizes a path, removing double and single dot path steps. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

+ * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo/
+     * /foo/./              -->   /foo/
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar/
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo/
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar/
+     * ~/../bar             -->   null
+     * 
+ * The output will be the same on both Unix and Windows including + * the separator character. + * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separator should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalize(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, true); + } + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

+ * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo
+     * /foo/./              -->   /foo
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar
+     * ~/../bar             -->   null
+     * 
+ * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalizeNoEndSeparator(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, false); + } + + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

+ * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows including + * the separator character. + *

+     * /foo//               -->   /foo
+     * /foo/./              -->   /foo
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar
+     * ~/../bar             -->   null
+     * 
+ * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separtor should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, false); + } + + /** + * Internal method to perform the normalization. + * + * @param filename the filename + * @param separator The separator character to use + * @param keepSeparator true to keep the final separator + * @return the normalized filename + */ + private static String doNormalize(String filename, char separator, boolean keepSeparator) { + if (filename == null) { + return null; + } + int size = filename.length(); + if (size == 0) { + return filename; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + + char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy + filename.getChars(0, filename.length(), array, 0); + + // fix separators throughout + char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; + for (int i = 0; i < array.length; i++) { + if (array[i] == otherSeparator) { + array[i] = separator; + } + } + + // add extra separator on the end to simplify code below + boolean lastIsDirectory = true; + if (array[size - 1] != separator) { + array[size++] = separator; + lastIsDirectory = false; + } + + // adjoining slashes + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == separator) { + System.arraycopy(array, i, array, i - 1, size - i); + size--; + i--; + } + } + + // dot slash + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && + (i == prefix + 1 || array[i - 2] == separator)) { + if (i == size - 1) { + lastIsDirectory = true; + } + System.arraycopy(array, i + 1, array, i - 1, size - i); + size -=2; + i--; + } + } + + // double dot slash + outer: + for (int i = prefix + 2; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && + (i == prefix + 2 || array[i - 3] == separator)) { + if (i == prefix + 2) { + return null; + } + if (i == size - 1) { + lastIsDirectory = true; + } + int j; + for (j = i - 4 ; j >= prefix; j--) { + if (array[j] == separator) { + // remove b/../ from a/b/../c + System.arraycopy(array, i + 1, array, j + 1, size - i); + size -= i - j; + i = j + 1; + continue outer; + } + } + // remove a/../ from a/../c + System.arraycopy(array, i + 1, array, prefix, size - i); + size -= i + 1 - prefix; + i = prefix + 1; + } + } + + if (size <= 0) { // should never be less than 0 + return ""; + } + if (size <= prefix) { // should never be less than prefix + return new String(array, 0, size); + } + if (lastIsDirectory && keepSeparator) { + return new String(array, 0, size); // keep trailing separator + } + return new String(array, 0, size - 1); // lose trailing separator + } + + //----------------------------------------------------------------------- + /** + * Returns the length of the filename prefix, such as C:/ or ~/. + *

+ * This method will handle a file in either Unix or Windows format. + *

+ * The prefix length includes the first slash in the full filename + * if applicable. Thus, it is possible that the length returned is greater + * than the length of the input string. + *

+     * Windows:
+     * a\b\c.txt           --> ""          --> relative
+     * \a\b\c.txt          --> "\"         --> current drive absolute
+     * C:a\b\c.txt         --> "C:"        --> drive relative
+     * C:\a\b\c.txt        --> "C:\"       --> absolute
+     * \\server\a\b\c.txt  --> "\\server\" --> UNC
+     *
+     * Unix:
+     * a/b/c.txt           --> ""          --> relative
+     * /a/b/c.txt          --> "/"         --> absolute
+     * ~/a/b/c.txt         --> "~/"        --> current user
+     * ~                   --> "~/"        --> current user (slash added)
+     * ~user/a/b/c.txt     --> "~user/"    --> named user
+     * ~user               --> "~user/"    --> named user (slash added)
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * ie. both Unix and Windows prefixes are matched regardless. + * + * @param filename the filename to find the prefix in, null returns -1 + * @return the length of the prefix, -1 if invalid or null + */ + public static int getPrefixLength(String filename) { + if (filename == null) { + return -1; + } + int len = filename.length(); + if (len == 0) { + return 0; + } + char ch0 = filename.charAt(0); + if (ch0 == ':') { + return -1; + } + if (len == 1) { + if (ch0 == '~') { + return 2; // return a length greater than the input + } + return isSeparator(ch0) ? 1 : 0; + } else { + if (ch0 == '~') { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); + if (posUnix == -1 && posWin == -1) { + return len + 1; // return a length greater than the input + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } + char ch1 = filename.charAt(1); + if (ch1 == ':') { + ch0 = Character.toUpperCase(ch0); + if (ch0 >= 'A' && ch0 <= 'Z') { + if (len == 2 || isSeparator(filename.charAt(2)) == false) { + return 2; + } + return 3; + } + return -1; + + } else if (isSeparator(ch0) && isSeparator(ch1)) { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); + if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { + return -1; + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } else { + return isSeparator(ch0) ? 1 : 0; + } + } + } + + //----------------------------------------------------------------------- + /** + * Checks if the character is a separator. + * + * @param ch the character to check + * @return true if it is a separator character + */ + private static boolean isSeparator(char ch) { + return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; + } + + } diff --git a/Dorkbox-Util/src/dorkbox/util/FilenameUtils.java b/Dorkbox-Util/src/dorkbox/util/FilenameUtils.java deleted file mode 100644 index ff2a2a7..0000000 --- a/Dorkbox-Util/src/dorkbox/util/FilenameUtils.java +++ /dev/null @@ -1,1401 +0,0 @@ -/* - * 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 dorkbox.util; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Stack; - -/** - * General filename and filepath manipulation utilities. - *

- * When dealing with filenames you can hit problems when moving from a Windows - * based development machine to a Unix based production machine. - * This class aims to help avoid those problems. - *

- * NOTE: You may be able to avoid using this class entirely simply by - * using JDK {@link java.io.File File} objects and the two argument constructor - * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}. - *

- * Most methods on this class are designed to work the same on both Unix and Windows. - * Those that don't include 'System', 'Unix' or 'Windows' in their name. - *

- * Most methods recognise both separators (forward and back), and both - * sets of prefixes. See the javadoc of each method for details. - *

- * This class defines six components within a filename - * (example C:\dev\project\file.txt): - *

- * Note that this class works best if directory filenames end with a separator. - * If you omit the last separator, it is impossible to determine if the filename - * corresponds to a file or a directory. As a result, we have chosen to say - * it corresponds to a file. - *

- * This class only supports Unix and Windows style names. - * Prefixes are matched as follows: - *

- * Windows:
- * a\b\c.txt           --> ""          --> relative
- * \a\b\c.txt          --> "\"         --> current drive absolute
- * C:a\b\c.txt         --> "C:"        --> drive relative
- * C:\a\b\c.txt        --> "C:\"       --> absolute
- * \\server\a\b\c.txt  --> "\\server\" --> UNC
- *
- * Unix:
- * a/b/c.txt           --> ""          --> relative
- * /a/b/c.txt          --> "/"         --> absolute
- * ~/a/b/c.txt         --> "~/"        --> current user
- * ~                   --> "~/"        --> current user (slash added)
- * ~user/a/b/c.txt     --> "~user/"    --> named user
- * ~user               --> "~user/"    --> named user (slash added)
- * 
- * Both prefix styles are matched always, irrespective of the machine that you are - * currently running on. - *

- * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. - * - * @version $Id: FilenameUtils.java 1307462 2012-03-30 15:13:11Z ggregory $ - * @since 1.1 - */ -public class FilenameUtils { - - /** - * The extension separator character. - * @since 1.4 - */ - public static final char EXTENSION_SEPARATOR = '.'; - - /** - * The extension separator String. - * @since 1.4 - */ - public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); - - /** - * The Unix separator character. - */ - private static final char UNIX_SEPARATOR = '/'; - - /** - * The Windows separator character. - */ - private static final char WINDOWS_SEPARATOR = '\\'; - - /** - * The system separator character. - */ - private static final char SYSTEM_SEPARATOR = File.separatorChar; - - /** - * The separator character that is the opposite of the system separator. - */ - private static final char OTHER_SEPARATOR; - static { - if (isSystemWindows()) { - OTHER_SEPARATOR = UNIX_SEPARATOR; - } else { - OTHER_SEPARATOR = WINDOWS_SEPARATOR; - } - } - - /** - * Instances should NOT be constructed in standard programming. - */ - public FilenameUtils() { - super(); - } - - //----------------------------------------------------------------------- - /** - * Determines if Windows file system is in use. - * - * @return true if the system is Windows - */ - static boolean isSystemWindows() { - return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Checks if the character is a separator. - * - * @param ch the character to check - * @return true if it is a separator character - */ - private static boolean isSeparator(char ch) { - return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

- * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo/
-     * /foo/./              -->   /foo/
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar/
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo/
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar/
-     * ~/../bar             -->   null
-     * 
- * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalize(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, true); - } - /** - * Normalizes a path, removing double and single dot path steps. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

- * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo/
-     * /foo/./              -->   /foo/
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar/
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo/
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar/
-     * ~/../bar             -->   null
-     * 
- * The output will be the same on both Unix and Windows including - * the separator character. - * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separator should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalize(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, true); - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

- * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo
-     * /foo/./              -->   /foo
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar
-     * ~/../bar             -->   null
-     * 
- * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalizeNoEndSeparator(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, false); - } - - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

- * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows including - * the separator character. - *

-     * /foo//               -->   /foo
-     * /foo/./              -->   /foo
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar
-     * ~/../bar             -->   null
-     * 
- * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separtor should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, false); - } - - /** - * Internal method to perform the normalization. - * - * @param filename the filename - * @param separator The separator character to use - * @param keepSeparator true to keep the final separator - * @return the normalized filename - */ - private static String doNormalize(String filename, char separator, boolean keepSeparator) { - if (filename == null) { - return null; - } - int size = filename.length(); - if (size == 0) { - return filename; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - - char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy - filename.getChars(0, filename.length(), array, 0); - - // fix separators throughout - char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; - for (int i = 0; i < array.length; i++) { - if (array[i] == otherSeparator) { - array[i] = separator; - } - } - - // add extra separator on the end to simplify code below - boolean lastIsDirectory = true; - if (array[size - 1] != separator) { - array[size++] = separator; - lastIsDirectory = false; - } - - // adjoining slashes - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == separator) { - System.arraycopy(array, i, array, i - 1, size - i); - size--; - i--; - } - } - - // dot slash - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && - (i == prefix + 1 || array[i - 2] == separator)) { - if (i == size - 1) { - lastIsDirectory = true; - } - System.arraycopy(array, i + 1, array, i - 1, size - i); - size -=2; - i--; - } - } - - // double dot slash - outer: - for (int i = prefix + 2; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && - (i == prefix + 2 || array[i - 3] == separator)) { - if (i == prefix + 2) { - return null; - } - if (i == size - 1) { - lastIsDirectory = true; - } - int j; - for (j = i - 4 ; j >= prefix; j--) { - if (array[j] == separator) { - // remove b/../ from a/b/../c - System.arraycopy(array, i + 1, array, j + 1, size - i); - size -= i - j; - i = j + 1; - continue outer; - } - } - // remove a/../ from a/../c - System.arraycopy(array, i + 1, array, prefix, size - i); - size -= i + 1 - prefix; - i = prefix + 1; - } - } - - if (size <= 0) { // should never be less than 0 - return ""; - } - if (size <= prefix) { // should never be less than prefix - return new String(array, 0, size); - } - if (lastIsDirectory && keepSeparator) { - return new String(array, 0, size); // keep trailing separator - } - return new String(array, 0, size - 1); // lose trailing separator - } - - //----------------------------------------------------------------------- - /** - * Concatenates a filename to a base path using normal command line style rules. - *

- * The effect is equivalent to resultant directory after changing - * directory to the first argument, followed by changing directory to - * the second argument. - *

- * The first argument is the base path, the second is the path to concatenate. - * The returned path is always normalized via {@link #normalize(String)}, - * thus .. is handled. - *

- * If pathToAdd is absolute (has an absolute prefix), then - * it will be normalized and returned. - * Otherwise, the paths will be joined, normalized and returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo/ + bar          -->   /foo/bar
-     * /foo + bar           -->   /foo/bar
-     * /foo + /bar          -->   /bar
-     * /foo + C:/bar        -->   C:/bar
-     * /foo + C:bar         -->   C:bar (*)
-     * /foo/a/ + ../bar     -->   foo/bar
-     * /foo/ + ../../bar    -->   null
-     * /foo/ + /bar         -->   /bar
-     * /foo/.. + /bar       -->   /bar
-     * /foo + bar/c.txt     -->   /foo/bar/c.txt
-     * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
-     * 
- * (*) Note that the Windows relative drive prefix is unreliable when - * used with this method. - * (!) Note that the first parameter must be a path. If it ends with a name, then - * the name will be built into the concatenated path. If this might be a problem, - * use {@link #getFullPath(String)} on the base path argument. - * - * @param basePath the base path to attach to, always treated as a path - * @param fullFilenameToAdd the filename (or path) to attach to the base - * @return the concatenated path, or null if invalid - */ - public static String concat(String basePath, String fullFilenameToAdd) { - int prefix = getPrefixLength(fullFilenameToAdd); - if (prefix < 0) { - return null; - } - if (prefix > 0) { - return normalize(fullFilenameToAdd); - } - if (basePath == null) { - return null; - } - int len = basePath.length(); - if (len == 0) { - return normalize(fullFilenameToAdd); - } - char ch = basePath.charAt(len - 1); - if (isSeparator(ch)) { - return normalize(basePath + fullFilenameToAdd); - } else { - return normalize(basePath + '/' + fullFilenameToAdd); - } - } - - /** - * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory). - *

- * The files names are expected to be normalized. - *

- * - * Edge cases: - * - * - * @param canonicalParent - * the file to consider as the parent. - * @param canonicalChild - * the file to consider as the child. - * @return true is the candidate leaf is under by the specified composite. False otherwise. - * @throws IOException - * if an IO error occurs while checking the files. - * @since 2.2 - * @see FileUtils#directoryContains(File, File) - */ - public static boolean directoryContains(final String canonicalParent, final String canonicalChild) - throws IOException { - - // Fail fast against NullPointerException - if (canonicalParent == null) { - throw new IllegalArgumentException("Directory must not be null"); - } - - if (canonicalChild == null) { - return false; - } - - if (IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) { - return false; - } - - return IOCase.SYSTEM.checkStartsWith(canonicalChild, canonicalParent); - } - - //----------------------------------------------------------------------- - /** - * Converts all separators to the Unix separator of forward slash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToUnix(String path) { - if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) { - return path; - } - return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); - } - - /** - * Converts all separators to the Windows separator of backslash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToWindows(String path) { - if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) { - return path; - } - return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); - } - - /** - * Converts all separators to the system separator. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToSystem(String path) { - if (path == null) { - return null; - } - if (isSystemWindows()) { - return separatorsToWindows(path); - } else { - return separatorsToUnix(path); - } - } - - //----------------------------------------------------------------------- - /** - * Returns the length of the filename prefix, such as C:/ or ~/. - *

- * This method will handle a file in either Unix or Windows format. - *

- * The prefix length includes the first slash in the full filename - * if applicable. Thus, it is possible that the length returned is greater - * than the length of the input string. - *

-     * Windows:
-     * a\b\c.txt           --> ""          --> relative
-     * \a\b\c.txt          --> "\"         --> current drive absolute
-     * C:a\b\c.txt         --> "C:"        --> drive relative
-     * C:\a\b\c.txt        --> "C:\"       --> absolute
-     * \\server\a\b\c.txt  --> "\\server\" --> UNC
-     *
-     * Unix:
-     * a/b/c.txt           --> ""          --> relative
-     * /a/b/c.txt          --> "/"         --> absolute
-     * ~/a/b/c.txt         --> "~/"        --> current user
-     * ~                   --> "~/"        --> current user (slash added)
-     * ~user/a/b/c.txt     --> "~user/"    --> named user
-     * ~user               --> "~user/"    --> named user (slash added)
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to find the prefix in, null returns -1 - * @return the length of the prefix, -1 if invalid or null - */ - public static int getPrefixLength(String filename) { - if (filename == null) { - return -1; - } - int len = filename.length(); - if (len == 0) { - return 0; - } - char ch0 = filename.charAt(0); - if (ch0 == ':') { - return -1; - } - if (len == 1) { - if (ch0 == '~') { - return 2; // return a length greater than the input - } - return isSeparator(ch0) ? 1 : 0; - } else { - if (ch0 == '~') { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); - if (posUnix == -1 && posWin == -1) { - return len + 1; // return a length greater than the input - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } - char ch1 = filename.charAt(1); - if (ch1 == ':') { - ch0 = Character.toUpperCase(ch0); - if (ch0 >= 'A' && ch0 <= 'Z') { - if (len == 2 || isSeparator(filename.charAt(2)) == false) { - return 2; - } - return 3; - } - return -1; - - } else if (isSeparator(ch0) && isSeparator(ch1)) { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); - if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { - return -1; - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } else { - return isSeparator(ch0) ? 1 : 0; - } - } - } - - /** - * Returns the index of the last directory separator character. - *

- * This method will handle a file in either Unix or Windows format. - * The position of the last forward or backslash is returned. - *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfLastSeparator(String filename) { - if (filename == null) { - return -1; - } - int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); - int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); - return Math.max(lastUnixPos, lastWindowsPos); - } - - /** - * Returns the index of the last extension separator character, which is a dot. - *

- * This method also checks that there is no directory separator after the last dot. - * To do this it uses {@link #indexOfLastSeparator(String)} which will - * handle a file in either Unix or Windows format. - *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfExtension(String filename) { - if (filename == null) { - return -1; - } - int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); - int lastSeparator = indexOfLastSeparator(filename); - return lastSeparator > extensionPos ? -1 : extensionPos; - } - - //----------------------------------------------------------------------- - /** - * Gets the prefix from a full filename, such as C:/ - * or ~/. - *

- * This method will handle a file in either Unix or Windows format. - * The prefix includes the first slash in the full filename where applicable. - *

-     * Windows:
-     * a\b\c.txt           --> ""          --> relative
-     * \a\b\c.txt          --> "\"         --> current drive absolute
-     * C:a\b\c.txt         --> "C:"        --> drive relative
-     * C:\a\b\c.txt        --> "C:\"       --> absolute
-     * \\server\a\b\c.txt  --> "\\server\" --> UNC
-     *
-     * Unix:
-     * a/b/c.txt           --> ""          --> relative
-     * /a/b/c.txt          --> "/"         --> absolute
-     * ~/a/b/c.txt         --> "~/"        --> current user
-     * ~                   --> "~/"        --> current user (slash added)
-     * ~user/a/b/c.txt     --> "~user/"    --> named user
-     * ~user               --> "~user/"    --> named user (slash added)
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to query, null returns null - * @return the prefix of the file, null if invalid - */ - public static String getPrefix(String filename) { - if (filename == null) { - return null; - } - int len = getPrefixLength(filename); - if (len < 0) { - return null; - } - if (len > filename.length()) { - return filename + UNIX_SEPARATOR; // we know this only happens for unix - } - return filename.substring(0, len); - } - - /** - * Gets the path from a full filename, which excludes the prefix. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

-     * C:\a\b\c.txt --> a\b\
-     * ~/a/b/c.txt  --> a/b/
-     * a.txt        --> ""
-     * a/b/c        --> a/b/
-     * a/b/c/       --> a/b/c/
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - *

- * This method drops the prefix from the result. - * See {@link #getFullPath(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPath(String filename) { - return doGetPath(filename, 1); - } - - /** - * Gets the path from a full filename, which excludes the prefix, and - * also excluding the final directory separator. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

-     * C:\a\b\c.txt --> a\b
-     * ~/a/b/c.txt  --> a/b
-     * a.txt        --> ""
-     * a/b/c        --> a/b
-     * a/b/c/       --> a/b/c
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - *

- * This method drops the prefix from the result. - * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPathNoEndSeparator(String filename) { - return doGetPath(filename, 0); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param separatorAdd 0 to omit the end separator, 1 to return it - * @return the path - */ - private static String doGetPath(String filename, int separatorAdd) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - int index = indexOfLastSeparator(filename); - int endIndex = index+separatorAdd; - if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { - return ""; - } - return filename.substring(prefix, endIndex); - } - - /** - * Gets the full path from a full filename, which is the prefix + path. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

-     * C:\a\b\c.txt --> C:\a\b\
-     * ~/a/b/c.txt  --> ~/a/b/
-     * a.txt        --> ""
-     * a/b/c        --> a/b/
-     * a/b/c/       --> a/b/c/
-     * C:           --> C:
-     * C:\          --> C:\
-     * ~            --> ~/
-     * ~/           --> ~/
-     * ~user        --> ~user/
-     * ~user/       --> ~user/
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPath(String filename) { - return doGetFullPath(filename, true); - } - - /** - * Gets the full path from a full filename, which is the prefix + path, - * and also excluding the final directory separator. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

-     * C:\a\b\c.txt --> C:\a\b
-     * ~/a/b/c.txt  --> ~/a/b
-     * a.txt        --> ""
-     * a/b/c        --> a/b
-     * a/b/c/       --> a/b/c
-     * C:           --> C:
-     * C:\          --> C:\
-     * ~            --> ~
-     * ~/           --> ~
-     * ~user        --> ~user
-     * ~user/       --> ~user
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPathNoEndSeparator(String filename) { - return doGetFullPath(filename, false); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param includeSeparator true to include the end separator - * @return the path - */ - private static String doGetFullPath(String filename, boolean includeSeparator) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - if (prefix >= filename.length()) { - if (includeSeparator) { - return getPrefix(filename); // add end slash if necessary - } else { - return filename; - } - } - int index = indexOfLastSeparator(filename); - if (index < 0) { - return filename.substring(0, prefix); - } - int end = index + (includeSeparator ? 1 : 0); - if (end == 0) { - end++; - } - return filename.substring(0, end); - } - - /** - * Gets the name minus the path from a full filename. - *

- * This method will handle a file in either Unix or Windows format. - * The text after the last forward or backslash is returned. - *

-     * a/b/c.txt --> c.txt
-     * a.txt     --> a.txt
-     * a/b/c     --> c
-     * a/b/c/    --> ""
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the name of the file without the path, or an empty string if none exists - */ - public static String getName(String filename) { - if (filename == null) { - return null; - } - int index = indexOfLastSeparator(filename); - return filename.substring(index + 1); - } - - /** - * Gets the base name, minus the full path and extension, from a full filename. - *

- * This method will handle a file in either Unix or Windows format. - * The text after the last forward or backslash and before the last dot is returned. - *

-     * a/b/c.txt --> c
-     * a.txt     --> a
-     * a/b/c     --> c
-     * a/b/c/    --> ""
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the name of the file without the path, or an empty string if none exists - */ - public static String getBaseName(String filename) { - return removeExtension(getName(filename)); - } - - /** - * Gets the extension of a filename. - *

- * This method returns the textual part of the filename after the last dot. - * There must be no directory separator after the dot. - *

-     * foo.txt      --> "txt"
-     * a/b/c.jpg    --> "jpg"
-     * a/b.txt/c    --> ""
-     * a/b/c        --> ""
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to retrieve the extension of. - * @return the extension of the file or an empty string if none exists or {@code null} - * if the filename is {@code null}. - */ - public static String getExtension(String filename) { - if (filename == null) { - return null; - } - int index = indexOfExtension(filename); - if (index == -1) { - return ""; - } else { - return filename.substring(index + 1); - } - } - - //----------------------------------------------------------------------- - /** - * Removes the extension from a filename. - *

- * This method returns the textual part of the filename before the last dot. - * There must be no directory separator after the dot. - *

-     * foo.txt    --> foo
-     * a\b\c.jpg  --> a\b\c
-     * a\b\c      --> a\b\c
-     * a.b\c      --> a.b\c
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the filename minus the extension - */ - public static String removeExtension(String filename) { - if (filename == null) { - return null; - } - int index = indexOfExtension(filename); - if (index == -1) { - return filename; - } else { - return filename.substring(0, index); - } - } - - //----------------------------------------------------------------------- - /** - * Checks whether two filenames are equal exactly. - *

- * No processing is performed on the filenames other than comparison, - * thus this is merely a null-safe case-sensitive equals. - * - * @param filename1 the first filename to query, may be null - * @param filename2 the second filename to query, may be null - * @return true if the filenames are equal, null equals null - * @see IOCase#SENSITIVE - */ - public static boolean equals(String filename1, String filename2) { - return equals(filename1, filename2, false, IOCase.SENSITIVE); - } - - /** - * Checks whether two filenames are equal using the case rules of the system. - *

- * No processing is performed on the filenames other than comparison. - * The check is case-sensitive on Unix and case-insensitive on Windows. - * - * @param filename1 the first filename to query, may be null - * @param filename2 the second filename to query, may be null - * @return true if the filenames are equal, null equals null - * @see IOCase#SYSTEM - */ - public static boolean equalsOnSystem(String filename1, String filename2) { - return equals(filename1, filename2, false, IOCase.SYSTEM); - } - - //----------------------------------------------------------------------- - /** - * Checks whether two filenames are equal after both have been normalized. - *

- * Both filenames are first passed to {@link #normalize(String)}. - * The check is then performed in a case-sensitive manner. - * - * @param filename1 the first filename to query, may be null - * @param filename2 the second filename to query, may be null - * @return true if the filenames are equal, null equals null - * @see IOCase#SENSITIVE - */ - public static boolean equalsNormalized(String filename1, String filename2) { - return equals(filename1, filename2, true, IOCase.SENSITIVE); - } - - /** - * Checks whether two filenames are equal after both have been normalized - * and using the case rules of the system. - *

- * Both filenames are first passed to {@link #normalize(String)}. - * The check is then performed case-sensitive on Unix and - * case-insensitive on Windows. - * - * @param filename1 the first filename to query, may be null - * @param filename2 the second filename to query, may be null - * @return true if the filenames are equal, null equals null - * @see IOCase#SYSTEM - */ - public static boolean equalsNormalizedOnSystem(String filename1, String filename2) { - return equals(filename1, filename2, true, IOCase.SYSTEM); - } - - /** - * Checks whether two filenames are equal, optionally normalizing and providing - * control over the case-sensitivity. - * - * @param filename1 the first filename to query, may be null - * @param filename2 the second filename to query, may be null - * @param normalized whether to normalize the filenames - * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive - * @return true if the filenames are equal, null equals null - * @since 1.3 - */ - public static boolean equals( - String filename1, String filename2, - boolean normalized, IOCase caseSensitivity) { - - if (filename1 == null || filename2 == null) { - return filename1 == null && filename2 == null; - } - if (normalized) { - filename1 = normalize(filename1); - filename2 = normalize(filename2); - if (filename1 == null || filename2 == null) { - throw new NullPointerException( - "Error normalizing one or both of the file names"); - } - } - if (caseSensitivity == null) { - caseSensitivity = IOCase.SENSITIVE; - } - return caseSensitivity.checkEquals(filename1, filename2); - } - - //----------------------------------------------------------------------- - /** - * Checks whether the extension of the filename is that specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extension the extension to check for, null or empty checks for no extension - * @return true if the filename has the specified extension - */ - public static boolean isExtension(String filename, String extension) { - if (filename == null) { - return false; - } - if (extension == null || extension.length() == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - return fileExt.equals(extension); - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, String[] extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.length == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, Collection extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.isEmpty()) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - //----------------------------------------------------------------------- - /** - * Checks a filename to see if it matches the specified wildcard matcher, - * always testing case-sensitive. - *

- * The wildcard matcher uses the characters '?' and '*' to represent a - * single or multiple (zero or more) wildcard characters. - * This is the same as often found on Dos/Unix command lines. - * The check is case-sensitive always. - *

-     * wildcardMatch("c.txt", "*.txt")      --> true
-     * wildcardMatch("c.txt", "*.jpg")      --> false
-     * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
-     * wildcardMatch("c.txt", "*.???")      --> true
-     * wildcardMatch("c.txt", "*.????")     --> false
-     * 
- * N.B. the sequence "*?" does not work properly at present in match strings. - * - * @param filename the filename to match on - * @param wildcardMatcher the wildcard string to match against - * @return true if the filename matches the wilcard string - * @see IOCase#SENSITIVE - */ - public static boolean wildcardMatch(String filename, String wildcardMatcher) { - return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE); - } - - /** - * Checks a filename to see if it matches the specified wildcard matcher - * using the case rules of the system. - *

- * The wildcard matcher uses the characters '?' and '*' to represent a - * single or multiple (zero or more) wildcard characters. - * This is the same as often found on Dos/Unix command lines. - * The check is case-sensitive on Unix and case-insensitive on Windows. - *

-     * wildcardMatch("c.txt", "*.txt")      --> true
-     * wildcardMatch("c.txt", "*.jpg")      --> false
-     * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
-     * wildcardMatch("c.txt", "*.???")      --> true
-     * wildcardMatch("c.txt", "*.????")     --> false
-     * 
- * N.B. the sequence "*?" does not work properly at present in match strings. - * - * @param filename the filename to match on - * @param wildcardMatcher the wildcard string to match against - * @return true if the filename matches the wilcard string - * @see IOCase#SYSTEM - */ - public static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher) { - return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM); - } - - /** - * Checks a filename to see if it matches the specified wildcard matcher - * allowing control over case-sensitivity. - *

- * The wildcard matcher uses the characters '?' and '*' to represent a - * single or multiple (zero or more) wildcard characters. - * N.B. the sequence "*?" does not work properly at present in match strings. - * - * @param filename the filename to match on - * @param wildcardMatcher the wildcard string to match against - * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive - * @return true if the filename matches the wilcard string - * @since 1.3 - */ - public static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity) { - if (filename == null && wildcardMatcher == null) { - return true; - } - if (filename == null || wildcardMatcher == null) { - return false; - } - if (caseSensitivity == null) { - caseSensitivity = IOCase.SENSITIVE; - } - String[] wcs = splitOnTokens(wildcardMatcher); - boolean anyChars = false; - int textIdx = 0; - int wcsIdx = 0; - Stack backtrack = new Stack(); - - // loop around a backtrack stack, to handle complex * matching - do { - if (backtrack.size() > 0) { - int[] array = backtrack.pop(); - wcsIdx = array[0]; - textIdx = array[1]; - anyChars = true; - } - - // loop whilst tokens and text left to process - while (wcsIdx < wcs.length) { - - if (wcs[wcsIdx].equals("?")) { - // ? so move to next text char - textIdx++; - if (textIdx > filename.length()) { - break; - } - anyChars = false; - - } else if (wcs[wcsIdx].equals("*")) { - // set any chars status - anyChars = true; - if (wcsIdx == wcs.length - 1) { - textIdx = filename.length(); - } - - } else { - // matching text token - if (anyChars) { - // any chars then try to locate text token - textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]); - if (textIdx == -1) { - // token not found - break; - } - int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]); - if (repeat >= 0) { - backtrack.push(new int[] {wcsIdx, repeat}); - } - } else { - // matching from current position - if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx])) { - // couldnt match token - break; - } - } - - // matched text token, move text index to end of matched token - textIdx += wcs[wcsIdx].length(); - anyChars = false; - } - - wcsIdx++; - } - - // full match - if (wcsIdx == wcs.length && textIdx == filename.length()) { - return true; - } - - } while (backtrack.size() > 0); - - return false; - } - - /** - * Splits a string into a number of tokens. - * The text is split by '?' and '*'. - * Where multiple '*' occur consecutively they are collapsed into a single '*'. - * - * @param text the text to split - * @return the array of tokens, never null - */ - static String[] splitOnTokens(String text) { - // used by wildcardMatch - // package level so a unit test may run on this - - if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { - return new String[] { text }; - } - - char[] array = text.toCharArray(); - ArrayList list = new ArrayList(); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < array.length; i++) { - if (array[i] == '?' || array[i] == '*') { - if (buffer.length() != 0) { - list.add(buffer.toString()); - buffer.setLength(0); - } - if (array[i] == '?') { - list.add("?"); - } else if (list.isEmpty() || - i > 0 && list.get(list.size() - 1).equals("*") == false) { - list.add("*"); - } - } else { - buffer.append(array[i]); - } - } - if (buffer.length() != 0) { - list.add(buffer.toString()); - } - - return list.toArray( new String[ list.size() ] ); - } - -} \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/IOCase.java b/Dorkbox-Util/src/dorkbox/util/IOCase.java deleted file mode 100644 index 7bb477a..0000000 --- a/Dorkbox-Util/src/dorkbox/util/IOCase.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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 dorkbox.util; - -import java.io.Serializable; - -/** - * Enumeration of IO case sensitivity. - *

- * Different filing systems have different rules for case-sensitivity. - * Windows is case-insensitive, Unix is case-sensitive. - *

- * This class captures that difference, providing an enumeration to - * control how filename comparisons should be performed. It also provides - * methods that use the enumeration to perform comparisons. - *

- * Wherever possible, you should use the check methods in this - * class to compare filenames. - * - * @version $Id: IOCase.java 1307459 2012-03-30 15:11:44Z ggregory $ - * @since 1.3 - */ -public final class IOCase implements Serializable { - - /** - * The constant for case sensitive regardless of operating system. - */ - public static final IOCase SENSITIVE = new IOCase("Sensitive", true); - - /** - * The constant for case insensitive regardless of operating system. - */ - public static final IOCase INSENSITIVE = new IOCase("Insensitive", false); - - /** - * The constant for case sensitivity determined by the current operating system. - * Windows is case-insensitive when comparing filenames, Unix is case-sensitive. - *

- * Note: This only caters for Windows and Unix. Other operating - * systems (e.g. OSX and OpenVMS) are treated as case sensitive if they use the - * Unix file separator and case-insensitive if they use the Windows file separator - * (see {@link java.io.File#separatorChar}). - *

- * If you derialize this constant of Windows, and deserialize on Unix, or vice - * versa, then the value of the case-sensitivity flag will change. - */ - public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows()); - - /** Serialization version. */ - private static final long serialVersionUID = -6343169151696340687L; - - /** The enumeration name. */ - private final String name; - - /** The sensitivity flag. */ - private final transient boolean sensitive; - - //----------------------------------------------------------------------- - /** - * Factory method to create an IOCase from a name. - * - * @param name the name to find - * @return the IOCase object - * @throws IllegalArgumentException if the name is invalid - */ - public static IOCase forName(String name) { - if (IOCase.SENSITIVE.name.equals(name)){ - return IOCase.SENSITIVE; - } - if (IOCase.INSENSITIVE.name.equals(name)){ - return IOCase.INSENSITIVE; - } - if (IOCase.SYSTEM.name.equals(name)){ - return IOCase.SYSTEM; - } - throw new IllegalArgumentException("Invalid IOCase name: " + name); - } - - //----------------------------------------------------------------------- - /** - * Private constructor. - * - * @param name the name - * @param sensitive the sensitivity - */ - private IOCase(String name, boolean sensitive) { - this.name = name; - this.sensitive = sensitive; - } - - /** - * Replaces the enumeration from the stream with a real one. - * This ensures that the correct flag is set for SYSTEM. - * - * @return the resolved object - */ - private Object readResolve() { - return forName(this.name); - } - - //----------------------------------------------------------------------- - /** - * Gets the name of the constant. - * - * @return the name of the constant - */ - public String getName() { - return this.name; - } - - /** - * Does the object represent case sensitive comparison. - * - * @return true if case sensitive - */ - public boolean isCaseSensitive() { - return this.sensitive; - } - - //----------------------------------------------------------------------- - /** - * Compares two strings using the case-sensitivity rule. - *

- * This method mimics {@link String#compareTo} but takes case-sensitivity - * into account. - * - * @param str1 the first string to compare, not null - * @param str2 the second string to compare, not null - * @return true if equal using the case rules - * @throws NullPointerException if either string is null - */ - public int checkCompareTo(String str1, String str2) { - if (str1 == null || str2 == null) { - throw new NullPointerException("The strings must not be null"); - } - return this.sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2); - } - - /** - * Compares two strings using the case-sensitivity rule. - *

- * This method mimics {@link String#equals} but takes case-sensitivity - * into account. - * - * @param str1 the first string to compare, not null - * @param str2 the second string to compare, not null - * @return true if equal using the case rules - * @throws NullPointerException if either string is null - */ - public boolean checkEquals(String str1, String str2) { - if (str1 == null || str2 == null) { - throw new NullPointerException("The strings must not be null"); - } - return this.sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2); - } - - /** - * Checks if one string starts with another using the case-sensitivity rule. - *

- * This method mimics {@link String#startsWith(String)} but takes case-sensitivity - * into account. - * - * @param str the string to check, not null - * @param start the start to compare against, not null - * @return true if equal using the case rules - * @throws NullPointerException if either string is null - */ - public boolean checkStartsWith(String str, String start) { - return str.regionMatches(!this.sensitive, 0, start, 0, start.length()); - } - - /** - * Checks if one string ends with another using the case-sensitivity rule. - *

- * This method mimics {@link String#endsWith} but takes case-sensitivity - * into account. - * - * @param str the string to check, not null - * @param end the end to compare against, not null - * @return true if equal using the case rules - * @throws NullPointerException if either string is null - */ - public boolean checkEndsWith(String str, String end) { - int endLen = end.length(); - return str.regionMatches(!this.sensitive, str.length() - endLen, end, 0, endLen); - } - - /** - * Checks if one string contains another starting at a specific index using the - * case-sensitivity rule. - *

- * This method mimics parts of {@link String#indexOf(String, int)} - * but takes case-sensitivity into account. - * - * @param str the string to check, not null - * @param strStartIndex the index to start at in str - * @param search the start to search for, not null - * @return the first index of the search String, - * -1 if no match or {@code null} string input - * @throws NullPointerException if either string is null - * @since 2.0 - */ - public int checkIndexOf(String str, int strStartIndex, String search) { - int endIndex = str.length() - search.length(); - if (endIndex >= strStartIndex) { - for (int i = strStartIndex; i <= endIndex; i++) { - if (checkRegionMatches(str, i, search)) { - return i; - } - } - } - return -1; - } - - /** - * Checks if one string contains another at a specific index using the case-sensitivity rule. - *

- * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)} - * but takes case-sensitivity into account. - * - * @param str the string to check, not null - * @param strStartIndex the index to start at in str - * @param search the start to search for, not null - * @return true if equal using the case rules - * @throws NullPointerException if either string is null - */ - public boolean checkRegionMatches(String str, int strStartIndex, String search) { - return str.regionMatches(!this.sensitive, strStartIndex, search, 0, search.length()); - } - - //----------------------------------------------------------------------- - /** - * Gets a string describing the sensitivity. - * - * @return a string describing the sensitivity - */ - @Override - public String toString() { - return this.name; - } - -} \ No newline at end of file diff --git a/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java b/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java index e948e0a..7107108 100644 --- a/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java +++ b/Dorkbox-Util/src/dorkbox/util/process/JavaProcessBuilder.java @@ -6,7 +6,7 @@ import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -import dorkbox.util.FilenameUtils; +import dorkbox.util.FileUtil; import dorkbox.util.OS; /** @@ -79,7 +79,7 @@ public class JavaProcessBuilder extends ShellProcessBuilder { for (String classpathEntry : this.classpathEntries) { try { // make sure the classpath is ABSOLUTE pathname - classpathEntry = FilenameUtils.normalize(new File(classpathEntry).getAbsolutePath()); + classpathEntry = FileUtil.normalize(new File(classpathEntry).getAbsolutePath()); // fix a nasty problem when spaces aren't properly escaped! classpathEntry = classpathEntry.replaceAll(" ", "\\ "); @@ -204,8 +204,8 @@ public class JavaProcessBuilder extends ShellProcessBuilder { // even though the former is a symlink to the latter! To work around this, see if the // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead. if (OS.isMacOsX()) { - String localVM = FilenameUtils.normalize(new File("/usr/bin/java").getAbsolutePath()); - String vmCheck = FilenameUtils.normalize(new File(vmpath).getAbsolutePath()); + String localVM = FileUtil.normalize(new File("/usr/bin/java").getAbsolutePath()); + String vmCheck = FileUtil.normalize(new File(vmpath).getAbsolutePath()); if (localVM.equals(vmCheck)) { vmpath = "/usr/bin/java"; } diff --git a/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java index 8d4dd60..4ced31c 100644 --- a/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java +++ b/Dorkbox-Util/src/dorkbox/util/process/ProcessProxy.java @@ -38,7 +38,6 @@ public class ProcessProxy extends Thread { } } else { while ((readInt = this.is.read()) != -1) { - System.err.println("READ : " + (char)readInt); this.os.write(readInt); // always flush this.os.flush();