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 2022/07/12 17:29:59 UTC

[commons-vfs] 03/05: Convert for to while loop to avoid the empty control statement.

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit c835519dcad65201839320024e17a2ce61a800dd
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Tue Jul 12 13:26:17 2022 -0400

    Convert for to while loop to avoid the empty control statement.
---
 .../vfs2/provider/LayeredFileNameParser.java       |  233 ++---
 .../apache/commons/vfs2/provider/UriParser.java    | 1100 ++++++++++----------
 .../vfs2/provider/local/WindowsFileNameParser.java |  272 ++---
 3 files changed, 804 insertions(+), 801 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/LayeredFileNameParser.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/LayeredFileNameParser.java
index 7c878f76..4254337d 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/LayeredFileNameParser.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/LayeredFileNameParser.java
@@ -1,116 +1,117 @@
-/*
- * 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.vfs2.provider;
-
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-
-/**
- * Implementation for layered file systems.
- * <p>
- * Additionally encodes the '!' character.
- * </p>
- */
-public class LayeredFileNameParser extends AbstractFileNameParser {
-
-    private static final LayeredFileNameParser INSTANCE = new LayeredFileNameParser();
-
-    /**
-     * Gets the singleton instance.
-     *
-     * @return the singleton instance.
-     */
-    public static LayeredFileNameParser getInstance() {
-        return INSTANCE;
-    }
-
-    /**
-     * Determines if a character should be encoded.
-     *
-     * @param ch The character to check.
-     * @return true if the character should be encoded.
-     */
-    @Override
-    public boolean encodeCharacter(final char ch) {
-        return super.encodeCharacter(ch) || ch == LayeredFileName.LAYER_SEPARATOR;
-    }
-
-    /**
-     * Pops the root prefix off a URI, which has had the scheme removed.
-     *
-     * @param uri string builder which gets modified.
-     * @return the extracted root name.
-     */
-    protected String extractRootName(final StringBuilder uri) {
-        // Looking for <name>!<abspath> (staring at the end)
-        final int maxlen = uri.length();
-        int pos = maxlen - 1;
-        for (; pos > 0 && uri.charAt(pos) != LayeredFileName.LAYER_SEPARATOR; pos--) {
-        }
-
-        if (pos == 0 && uri.charAt(pos) != LayeredFileName.LAYER_SEPARATOR) {
-            // not ! found, so take the whole path a root
-            // e.g. zip:/my/zip/file.zip
-            pos = maxlen;
-        }
-
-        // Extract the name
-        final String prefix = uri.substring(0, pos);
-        if (pos < maxlen) {
-            uri.delete(0, pos + 1);
-        } else {
-            uri.setLength(0);
-        }
-
-        return prefix;
-    }
-
-    /**
-     * Parses the base and name into a FileName.
-     *
-     * @param context The component context.
-     * @param baseFileName The base FileName.
-     * @param fileName name The target file name.
-     * @return The constructed FileName.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileName parseUri(final VfsComponentContext context, final FileName baseFileName, final String fileName)
-            throws FileSystemException {
-        final StringBuilder name = new StringBuilder();
-
-        // Extract the scheme
-        final String scheme = UriParser.extractScheme(context.getFileSystemManager().getSchemes(), fileName, name);
-
-        // Extract the Layered file URI
-        final String rootUriName = extractRootName(name);
-        FileName rootUri = null;
-        if (rootUriName != null) {
-            rootUri = context.parseURI(rootUriName);
-        }
-
-        // Decode and normalise the path
-        UriParser.canonicalizePath(name, 0, name.length(), this);
-        UriParser.fixSeparators(name);
-        final FileType fileType = UriParser.normalisePath(name);
-        final String path = name.toString();
-
-        return new LayeredFileName(scheme, rootUri, path, fileType);
-    }
-
-}
+/*
+ * 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.vfs2.provider;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+
+/**
+ * Implementation for layered file systems.
+ * <p>
+ * Additionally encodes the '!' character.
+ * </p>
+ */
+public class LayeredFileNameParser extends AbstractFileNameParser {
+
+    private static final LayeredFileNameParser INSTANCE = new LayeredFileNameParser();
+
+    /**
+     * Gets the singleton instance.
+     *
+     * @return the singleton instance.
+     */
+    public static LayeredFileNameParser getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Determines if a character should be encoded.
+     *
+     * @param ch The character to check.
+     * @return true if the character should be encoded.
+     */
+    @Override
+    public boolean encodeCharacter(final char ch) {
+        return super.encodeCharacter(ch) || ch == LayeredFileName.LAYER_SEPARATOR;
+    }
+
+    /**
+     * Pops the root prefix off a URI, which has had the scheme removed.
+     *
+     * @param uri string builder which gets modified.
+     * @return the extracted root name.
+     */
+    protected String extractRootName(final StringBuilder uri) {
+        // Looking for <name>!<abspath> (staring at the end)
+        final int maxlen = uri.length();
+        int pos = maxlen - 1;
+        while (pos > 0 && uri.charAt(pos) != LayeredFileName.LAYER_SEPARATOR) {
+            pos--;
+        }
+
+        if (pos == 0 && uri.charAt(pos) != LayeredFileName.LAYER_SEPARATOR) {
+            // not ! found, so take the whole path a root
+            // e.g. zip:/my/zip/file.zip
+            pos = maxlen;
+        }
+
+        // Extract the name
+        final String prefix = uri.substring(0, pos);
+        if (pos < maxlen) {
+            uri.delete(0, pos + 1);
+        } else {
+            uri.setLength(0);
+        }
+
+        return prefix;
+    }
+
+    /**
+     * Parses the base and name into a FileName.
+     *
+     * @param context The component context.
+     * @param baseFileName The base FileName.
+     * @param fileName name The target file name.
+     * @return The constructed FileName.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileName parseUri(final VfsComponentContext context, final FileName baseFileName, final String fileName)
+            throws FileSystemException {
+        final StringBuilder name = new StringBuilder();
+
+        // Extract the scheme
+        final String scheme = UriParser.extractScheme(context.getFileSystemManager().getSchemes(), fileName, name);
+
+        // Extract the Layered file URI
+        final String rootUriName = extractRootName(name);
+        FileName rootUri = null;
+        if (rootUriName != null) {
+            rootUri = context.parseURI(rootUriName);
+        }
+
+        // Decode and normalise the path
+        UriParser.canonicalizePath(name, 0, name.length(), this);
+        UriParser.fixSeparators(name);
+        final FileType fileType = UriParser.normalisePath(name);
+        final String path = name.toString();
+
+        return new LayeredFileName(scheme, rootUri, path, fileType);
+    }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
index eb8e1e6c..5eb024b5 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/UriParser.java
@@ -1,549 +1,551 @@
-/*
- * 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.vfs2.provider;
-
-import java.util.Arrays;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.VFS;
-
-/**
- * Utilities for dealing with URIs. See RFC 2396 for details.
- */
-public final class UriParser {
-
-    /**
-     * The set of valid separators. These are all converted to the normalized one. Does <i>not</i> contain the
-     * normalized separator
-     */
-    // public static final char[] separators = {'\\'};
-    public static final char TRANS_SEPARATOR = '\\';
-
-    /**
-     * The normalised separator to use.
-     */
-    private static final char SEPARATOR_CHAR = FileName.SEPARATOR_CHAR;
-
-    private static final int HEX_BASE = 16;
-
-    private static final int BITS_IN_HALF_BYTE = 4;
-
-    private static final char LOW_MASK = 0x0F;
-
-    private UriParser() {
-    }
-
-    /**
-     * Encodes and appends a string to a StringBuilder.
-     *
-     * @param buffer The StringBuilder to append to.
-     * @param unencodedValue The String to encode and append.
-     * @param reserved characters to encode.
-     */
-    public static void appendEncoded(final StringBuilder buffer, final String unencodedValue, final char[] reserved) {
-        final int offset = buffer.length();
-        buffer.append(unencodedValue);
-        encode(buffer, offset, unencodedValue.length(), reserved);
-    }
-
-    static void appendEncodedRfc2396(final StringBuilder buffer, final String unencodedValue, final char[] allowed) {
-        final int offset = buffer.length();
-        buffer.append(unencodedValue);
-        encodeRfc2396(buffer, offset, unencodedValue.length(), allowed);
-    }
-
-    /**
-     * Canonicalizes a path.
-     *
-     * @param buffer Source data.
-     * @param offset Where to start reading.
-     * @param length How much to read.
-     * @param fileNameParser Now to encode and decode.
-     * @throws FileSystemException If an I/O error occurs.
-     */
-    public static void canonicalizePath(final StringBuilder buffer, final int offset, final int length,
-            final FileNameParser fileNameParser) throws FileSystemException {
-        int index = offset;
-        int count = length;
-        for (; count > 0; count--, index++) {
-            final char ch = buffer.charAt(index);
-            if (ch == '%') {
-                if (count < 3) {
-                    throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
-                            buffer.substring(index, index + count));
-                }
-
-                // Decode
-                final int dig1 = Character.digit(buffer.charAt(index + 1), HEX_BASE);
-                final int dig2 = Character.digit(buffer.charAt(index + 2), HEX_BASE);
-                if (dig1 == -1 || dig2 == -1) {
-                    throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
-                            buffer.substring(index, index + 3));
-                }
-                final char value = (char) (dig1 << BITS_IN_HALF_BYTE | dig2);
-
-                final boolean match = value == '%' || fileNameParser.encodeCharacter(value);
-
-                if (match) {
-                    // this is a reserved character, not allowed to decode
-                    index += 2;
-                    count -= 2;
-                    continue;
-                }
-
-                // Replace
-                buffer.setCharAt(index, value);
-                buffer.delete(index + 1, index + 3);
-                count -= 2;
-            } else if (fileNameParser.encodeCharacter(ch)) {
-                // Encode
-                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
-                buffer.setCharAt(index, '%');
-                buffer.insert(index + 1, digits);
-                index += 2;
-            }
-        }
-    }
-
-    /**
-     * Decodes the String.
-     *
-     * @param uri The String to decode.
-     * @throws FileSystemException if an error occurs.
-     */
-    public static void checkUriEncoding(final String uri) throws FileSystemException {
-        decode(uri);
-    }
-
-    /**
-     * Removes %nn encodings from a string.
-     *
-     * @param encodedStr The encoded String.
-     * @return The decoded String.
-     * @throws FileSystemException if an error occurs.
-     */
-    public static String decode(final String encodedStr) throws FileSystemException {
-        if (encodedStr == null) {
-            return null;
-        }
-        if (encodedStr.indexOf('%') < 0) {
-            return encodedStr;
-        }
-        final StringBuilder buffer = new StringBuilder(encodedStr);
-        decode(buffer, 0, buffer.length());
-        return buffer.toString();
-    }
-
-    /**
-     * Removes %nn encodings from a string.
-     *
-     * @param buffer StringBuilder containing the string to decode.
-     * @param offset The position in the string to start decoding.
-     * @param length The number of characters to decode.
-     * @throws FileSystemException if an error occurs.
-     */
-    public static void decode(final StringBuilder buffer, final int offset, final int length)
-            throws FileSystemException {
-        int index = offset;
-        int count = length;
-        for (; count > 0; count--, index++) {
-            final char ch = buffer.charAt(index);
-            if (ch != '%') {
-                continue;
-            }
-            if (count < 3) {
-                throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
-                        buffer.substring(index, index + count));
-            }
-
-            // Decode
-            final int dig1 = Character.digit(buffer.charAt(index + 1), HEX_BASE);
-            final int dig2 = Character.digit(buffer.charAt(index + 2), HEX_BASE);
-            if (dig1 == -1 || dig2 == -1) {
-                throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
-                        buffer.substring(index, index + 3));
-            }
-            final char value = (char) (dig1 << BITS_IN_HALF_BYTE | dig2);
-
-            // Replace
-            buffer.setCharAt(index, value);
-            buffer.delete(index + 1, index + 3);
-            count -= 2;
-        }
-    }
-
-    /**
-     * Converts "special" characters to their %nn value.
-     *
-     * @param decodedStr The decoded String.
-     * @return The encoded String.
-     */
-    public static String encode(final String decodedStr) {
-        return encode(decodedStr, null);
-    }
-
-    /**
-     * Converts "special" characters to their %nn value.
-     *
-     * @param decodedStr The decoded String.
-     * @param reserved Characters to encode.
-     * @return The encoded String
-     */
-    public static String encode(final String decodedStr, final char[] reserved) {
-        if (decodedStr == null) {
-            return null;
-        }
-        final StringBuilder buffer = new StringBuilder(decodedStr);
-        encode(buffer, 0, buffer.length(), reserved);
-        return buffer.toString();
-    }
-
-    /**
-     * Encode an array of Strings.
-     *
-     * @param strings The array of Strings to encode.
-     * @return An array of encoded Strings.
-     */
-    public static String[] encode(final String[] strings) {
-        if (strings == null) {
-            return null;
-        }
-        for (int i = 0; i < strings.length; i++) {
-            strings[i] = encode(strings[i]);
-        }
-        return strings;
-    }
-
-    /**
-     * Encodes a set of reserved characters in a StringBuilder, using the URI %nn encoding. Always encodes % characters.
-     *
-     * @param buffer The StringBuilder to append to.
-     * @param offset The position in the buffer to start encoding at.
-     * @param length The number of characters to encode.
-     * @param reserved characters to encode.
-     */
-    public static void encode(final StringBuilder buffer, final int offset, final int length, final char[] reserved) {
-        int index = offset;
-        int count = length;
-        for (; count > 0; index++, count--) {
-            final char ch = buffer.charAt(index);
-            boolean match = ch == '%';
-            if (reserved != null) {
-                for (int i = 0; !match && i < reserved.length; i++) {
-                    if (ch == reserved[i]) {
-                        match = true;
-                        break;
-                    }
-                }
-            }
-            if (match) {
-                // Encode
-                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
-                buffer.setCharAt(index, '%');
-                buffer.insert(index + 1, digits);
-                index += 2;
-            }
-        }
-    }
-
-    static void encodeRfc2396(final StringBuilder buffer, final int offset, final int length, final char[] allowed) {
-        int index = offset;
-        int count = length;
-        for (; count > 0; index++, count--) {
-            final char ch = buffer.charAt(index);
-            if (Arrays.binarySearch(allowed, ch) < 0) {
-                // Encode
-                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
-                buffer.setCharAt(index, '%');
-                buffer.insert(index + 1, digits);
-                index += 2;
-            }
-        }
-    }
-
-    /**
-     * Extracts the first element of a path.
-     *
-     * @param name StringBuilder containing the path.
-     * @return The first element of the path.
-     */
-    public static String extractFirstElement(final StringBuilder name) {
-        final int len = name.length();
-        if (len < 1) {
-            return null;
-        }
-        int startPos = 0;
-        if (name.charAt(0) == SEPARATOR_CHAR) {
-            startPos = 1;
-        }
-        for (int pos = startPos; pos < len; pos++) {
-            if (name.charAt(pos) == SEPARATOR_CHAR) {
-                // Found a separator
-                final String elem = name.substring(startPos, pos);
-                name.delete(startPos, pos + 1);
-                return elem;
-            }
-        }
-
-        // No separator
-        final String elem = name.substring(startPos);
-        name.setLength(0);
-        return elem;
-    }
-
-    /**
-     * Extract the query String from the URI.
-     *
-     * @param name StringBuilder containing the URI.
-     * @return The query string, if any. null otherwise.
-     */
-    public static String extractQueryString(final StringBuilder name) {
-        for (int pos = 0; pos < name.length(); pos++) {
-            if (name.charAt(pos) == '?') {
-                final String queryString = name.substring(pos + 1);
-                name.delete(pos, name.length());
-                return queryString;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Extracts the scheme from a URI.
-     *
-     * @param uri The URI.
-     * @return The scheme name. Returns null if there is no scheme.
-     * @deprecated Use instead {@link #extractScheme}.  Will be removed in 3.0.
-     */
-    @Deprecated
-    public static String extractScheme(final String uri) {
-        return extractScheme(uri, null);
-    }
-
-    /**
-     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
-     *
-     * @param uri The URI.
-     * @param buffer Returns the remainder of the URI.
-     * @return The scheme name. Returns null if there is no scheme.
-     * @deprecated Use instead {@link #extractScheme}.  Will be removed in 3.0.
-     */
-    @Deprecated
-    public static String extractScheme(final String uri, final StringBuilder buffer) {
-        if (buffer != null) {
-            buffer.setLength(0);
-            buffer.append(uri);
-        }
-
-        final int maxPos = uri.length();
-        for (int pos = 0; pos < maxPos; pos++) {
-            final char ch = uri.charAt(pos);
-
-            if (ch == ':') {
-                // Found the end of the scheme
-                final String scheme = uri.substring(0, pos);
-                if (scheme.length() <= 1 && SystemUtils.IS_OS_WINDOWS) {
-                    // This is not a scheme, but a Windows drive letter
-                    return null;
-                }
-                if (buffer != null) {
-                    buffer.delete(0, pos + 1);
-                }
-                return scheme.intern();
-            }
-
-            if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') {
-                // A scheme character
-                continue;
-            }
-            if (!(pos > 0 && (ch >= '0' && ch <= '9' || ch == '+' || ch == '-' || ch == '.'))) {
-                // Not a scheme character
-                break;
-            }
-            // A scheme character (these are not allowed as the first
-            // character of the scheme, but can be used as subsequent
-            // characters.
-        }
-
-        // No scheme in URI
-        return null;
-    }
-
-    /**
-     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
-     * <p>
-     * The scheme is extracted based on the currently supported schemes in the system.  That is to say the schemes
-     * supported by the registered providers.
-     * </p>
-     * <p>
-     * This allows us to handle varying scheme's without making assumptions based on the ':' character.  Specifically
-     * handle scheme extraction calls for URI parameters that are not actually uri's, but may be names with ':' in them.
-     * </p>
-     * @param schemes The schemes to check.
-     * @param uri The potential URI. May also be a name.
-     * @return The scheme name. Returns null if there is no scheme.
-     * @since 2.3
-     */
-    public static String extractScheme(final String[] schemes, final String uri) {
-        return extractScheme(schemes, uri, null);
-    }
-
-    /**
-     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
-     * <p>
-     * The scheme is extracted based on the given set of schemes. Normally, that is to say the schemes
-     * supported by the registered providers.
-     * </p>
-     * <p>
-     * This allows us to handle varying scheme's without making assumptions based on the ':' character. Specifically
-     * handle scheme extraction calls for URI parameters that are not actually URI's, but may be names with ':' in them.
-     * </p>
-     * @param schemes The schemes to check.
-     * @param uri The potential URI. May also just be a name.
-     * @param buffer Returns the remainder of the URI.
-     * @return The scheme name. Returns null if there is no scheme.
-     * @since 2.3
-     */
-    public static String extractScheme(final String[] schemes, final String uri, final StringBuilder buffer) {
-        if (buffer != null) {
-            buffer.setLength(0);
-            buffer.append(uri);
-        }
-        for (final String scheme : schemes) {
-            if (uri.startsWith(scheme + ":")) {
-                if (buffer != null) {
-                    buffer.delete(0, uri.indexOf(':') + 1);
-                }
-                return scheme;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Normalises the separators in a name.
-     *
-     * @param name The StringBuilder containing the name
-     * @return true if the StringBuilder was modified.
-     */
-    public static boolean fixSeparators(final StringBuilder name) {
-        boolean changed = false;
-        final int maxlen = name.length();
-        for (int i = 0; i < maxlen; i++) {
-            final char ch = name.charAt(i);
-            if (ch == TRANS_SEPARATOR) {
-                name.setCharAt(i, SEPARATOR_CHAR);
-                changed = true;
-            }
-        }
-        return changed;
-    }
-
-    /**
-     * Normalises a path. Does the following:
-     * <ul>
-     * <li>Removes empty path elements.
-     * <li>Handles '.' and '..' elements.
-     * <li>Removes trailing separator.
-     * </ul>
-     *
-     * Its assumed that the separators are already fixed.
-     *
-     * @param path The path to normalize.
-     * @return The FileType.
-     * @throws FileSystemException if an error occurs.
-     *
-     * @see #fixSeparators
-     */
-    public static FileType normalisePath(final StringBuilder path) throws FileSystemException {
-        FileType fileType = FileType.FOLDER;
-        if (path.length() == 0) {
-            return fileType;
-        }
-
-        if (path.charAt(path.length() - 1) != '/') {
-            fileType = FileType.FILE;
-        }
-
-        // Adjust separators
-        // fixSeparators(path);
-
-        // Determine the start of the first element
-        int startFirstElem = 0;
-        if (path.charAt(0) == SEPARATOR_CHAR) {
-            if (path.length() == 1) {
-                return fileType;
-            }
-            startFirstElem = 1;
-        }
-
-        // Iterate over each element
-        int startElem = startFirstElem;
-        int maxlen = path.length();
-        while (startElem < maxlen) {
-            // Find the end of the element
-            int endElem = startElem;
-            for (; endElem < maxlen && path.charAt(endElem) != SEPARATOR_CHAR; endElem++) {
-            }
-
-            final int elemLen = endElem - startElem;
-            if (elemLen == 0) {
-                // An empty element - axe it
-                path.delete(endElem, endElem + 1);
-                maxlen = path.length();
-                continue;
-            }
-            if (elemLen == 1 && path.charAt(startElem) == '.') {
-                // A '.' element - axe it
-                path.delete(startElem, endElem + 1);
-                maxlen = path.length();
-                continue;
-            }
-            if (elemLen == 2 && path.charAt(startElem) == '.' && path.charAt(startElem + 1) == '.') {
-                // A '..' element - remove the previous element
-                if (startElem == startFirstElem) {
-                    // Previous element is missing
-                    throw new FileSystemException("vfs.provider/invalid-relative-path.error");
-                }
-
-                // Find start of previous element
-                int pos = startElem - 2;
-                for (; pos >= 0 && path.charAt(pos) != SEPARATOR_CHAR; pos--) {
-                }
-                startElem = pos + 1;
-
-                path.delete(startElem, endElem + 1);
-                maxlen = path.length();
-                continue;
-            }
-
-            // A regular element
-            startElem = endElem + 1;
-        }
-
-        // Remove trailing separator
-        if (!VFS.isUriStyle() && maxlen > 1 && path.charAt(maxlen - 1) == SEPARATOR_CHAR) {
-            path.delete(maxlen - 1, maxlen);
-        }
-
-        return fileType;
-    }
-}
+/*
+ * 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.vfs2.provider;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.VFS;
+
+/**
+ * Utilities for dealing with URIs. See RFC 2396 for details.
+ */
+public final class UriParser {
+
+    /**
+     * The set of valid separators. These are all converted to the normalized one. Does <i>not</i> contain the
+     * normalized separator
+     */
+    // public static final char[] separators = {'\\'};
+    public static final char TRANS_SEPARATOR = '\\';
+
+    /**
+     * The normalised separator to use.
+     */
+    private static final char SEPARATOR_CHAR = FileName.SEPARATOR_CHAR;
+
+    private static final int HEX_BASE = 16;
+
+    private static final int BITS_IN_HALF_BYTE = 4;
+
+    private static final char LOW_MASK = 0x0F;
+
+    private UriParser() {
+    }
+
+    /**
+     * Encodes and appends a string to a StringBuilder.
+     *
+     * @param buffer The StringBuilder to append to.
+     * @param unencodedValue The String to encode and append.
+     * @param reserved characters to encode.
+     */
+    public static void appendEncoded(final StringBuilder buffer, final String unencodedValue, final char[] reserved) {
+        final int offset = buffer.length();
+        buffer.append(unencodedValue);
+        encode(buffer, offset, unencodedValue.length(), reserved);
+    }
+
+    static void appendEncodedRfc2396(final StringBuilder buffer, final String unencodedValue, final char[] allowed) {
+        final int offset = buffer.length();
+        buffer.append(unencodedValue);
+        encodeRfc2396(buffer, offset, unencodedValue.length(), allowed);
+    }
+
+    /**
+     * Canonicalizes a path.
+     *
+     * @param buffer Source data.
+     * @param offset Where to start reading.
+     * @param length How much to read.
+     * @param fileNameParser Now to encode and decode.
+     * @throws FileSystemException If an I/O error occurs.
+     */
+    public static void canonicalizePath(final StringBuilder buffer, final int offset, final int length,
+            final FileNameParser fileNameParser) throws FileSystemException {
+        int index = offset;
+        int count = length;
+        for (; count > 0; count--, index++) {
+            final char ch = buffer.charAt(index);
+            if (ch == '%') {
+                if (count < 3) {
+                    throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
+                            buffer.substring(index, index + count));
+                }
+
+                // Decode
+                final int dig1 = Character.digit(buffer.charAt(index + 1), HEX_BASE);
+                final int dig2 = Character.digit(buffer.charAt(index + 2), HEX_BASE);
+                if (dig1 == -1 || dig2 == -1) {
+                    throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
+                            buffer.substring(index, index + 3));
+                }
+                final char value = (char) (dig1 << BITS_IN_HALF_BYTE | dig2);
+
+                final boolean match = value == '%' || fileNameParser.encodeCharacter(value);
+
+                if (match) {
+                    // this is a reserved character, not allowed to decode
+                    index += 2;
+                    count -= 2;
+                    continue;
+                }
+
+                // Replace
+                buffer.setCharAt(index, value);
+                buffer.delete(index + 1, index + 3);
+                count -= 2;
+            } else if (fileNameParser.encodeCharacter(ch)) {
+                // Encode
+                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
+                buffer.setCharAt(index, '%');
+                buffer.insert(index + 1, digits);
+                index += 2;
+            }
+        }
+    }
+
+    /**
+     * Decodes the String.
+     *
+     * @param uri The String to decode.
+     * @throws FileSystemException if an error occurs.
+     */
+    public static void checkUriEncoding(final String uri) throws FileSystemException {
+        decode(uri);
+    }
+
+    /**
+     * Removes %nn encodings from a string.
+     *
+     * @param encodedStr The encoded String.
+     * @return The decoded String.
+     * @throws FileSystemException if an error occurs.
+     */
+    public static String decode(final String encodedStr) throws FileSystemException {
+        if (encodedStr == null) {
+            return null;
+        }
+        if (encodedStr.indexOf('%') < 0) {
+            return encodedStr;
+        }
+        final StringBuilder buffer = new StringBuilder(encodedStr);
+        decode(buffer, 0, buffer.length());
+        return buffer.toString();
+    }
+
+    /**
+     * Removes %nn encodings from a string.
+     *
+     * @param buffer StringBuilder containing the string to decode.
+     * @param offset The position in the string to start decoding.
+     * @param length The number of characters to decode.
+     * @throws FileSystemException if an error occurs.
+     */
+    public static void decode(final StringBuilder buffer, final int offset, final int length)
+            throws FileSystemException {
+        int index = offset;
+        int count = length;
+        for (; count > 0; count--, index++) {
+            final char ch = buffer.charAt(index);
+            if (ch != '%') {
+                continue;
+            }
+            if (count < 3) {
+                throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
+                        buffer.substring(index, index + count));
+            }
+
+            // Decode
+            final int dig1 = Character.digit(buffer.charAt(index + 1), HEX_BASE);
+            final int dig2 = Character.digit(buffer.charAt(index + 2), HEX_BASE);
+            if (dig1 == -1 || dig2 == -1) {
+                throw new FileSystemException("vfs.provider/invalid-escape-sequence.error",
+                        buffer.substring(index, index + 3));
+            }
+            final char value = (char) (dig1 << BITS_IN_HALF_BYTE | dig2);
+
+            // Replace
+            buffer.setCharAt(index, value);
+            buffer.delete(index + 1, index + 3);
+            count -= 2;
+        }
+    }
+
+    /**
+     * Converts "special" characters to their %nn value.
+     *
+     * @param decodedStr The decoded String.
+     * @return The encoded String.
+     */
+    public static String encode(final String decodedStr) {
+        return encode(decodedStr, null);
+    }
+
+    /**
+     * Converts "special" characters to their %nn value.
+     *
+     * @param decodedStr The decoded String.
+     * @param reserved Characters to encode.
+     * @return The encoded String
+     */
+    public static String encode(final String decodedStr, final char[] reserved) {
+        if (decodedStr == null) {
+            return null;
+        }
+        final StringBuilder buffer = new StringBuilder(decodedStr);
+        encode(buffer, 0, buffer.length(), reserved);
+        return buffer.toString();
+    }
+
+    /**
+     * Encode an array of Strings.
+     *
+     * @param strings The array of Strings to encode.
+     * @return An array of encoded Strings.
+     */
+    public static String[] encode(final String[] strings) {
+        if (strings == null) {
+            return null;
+        }
+        for (int i = 0; i < strings.length; i++) {
+            strings[i] = encode(strings[i]);
+        }
+        return strings;
+    }
+
+    /**
+     * Encodes a set of reserved characters in a StringBuilder, using the URI %nn encoding. Always encodes % characters.
+     *
+     * @param buffer The StringBuilder to append to.
+     * @param offset The position in the buffer to start encoding at.
+     * @param length The number of characters to encode.
+     * @param reserved characters to encode.
+     */
+    public static void encode(final StringBuilder buffer, final int offset, final int length, final char[] reserved) {
+        int index = offset;
+        int count = length;
+        for (; count > 0; index++, count--) {
+            final char ch = buffer.charAt(index);
+            boolean match = ch == '%';
+            if (reserved != null) {
+                for (int i = 0; !match && i < reserved.length; i++) {
+                    if (ch == reserved[i]) {
+                        match = true;
+                        break;
+                    }
+                }
+            }
+            if (match) {
+                // Encode
+                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
+                buffer.setCharAt(index, '%');
+                buffer.insert(index + 1, digits);
+                index += 2;
+            }
+        }
+    }
+
+    static void encodeRfc2396(final StringBuilder buffer, final int offset, final int length, final char[] allowed) {
+        int index = offset;
+        int count = length;
+        for (; count > 0; index++, count--) {
+            final char ch = buffer.charAt(index);
+            if (Arrays.binarySearch(allowed, ch) < 0) {
+                // Encode
+                final char[] digits = {Character.forDigit(ch >> BITS_IN_HALF_BYTE & LOW_MASK, HEX_BASE), Character.forDigit(ch & LOW_MASK, HEX_BASE)};
+                buffer.setCharAt(index, '%');
+                buffer.insert(index + 1, digits);
+                index += 2;
+            }
+        }
+    }
+
+    /**
+     * Extracts the first element of a path.
+     *
+     * @param name StringBuilder containing the path.
+     * @return The first element of the path.
+     */
+    public static String extractFirstElement(final StringBuilder name) {
+        final int len = name.length();
+        if (len < 1) {
+            return null;
+        }
+        int startPos = 0;
+        if (name.charAt(0) == SEPARATOR_CHAR) {
+            startPos = 1;
+        }
+        for (int pos = startPos; pos < len; pos++) {
+            if (name.charAt(pos) == SEPARATOR_CHAR) {
+                // Found a separator
+                final String elem = name.substring(startPos, pos);
+                name.delete(startPos, pos + 1);
+                return elem;
+            }
+        }
+
+        // No separator
+        final String elem = name.substring(startPos);
+        name.setLength(0);
+        return elem;
+    }
+
+    /**
+     * Extract the query String from the URI.
+     *
+     * @param name StringBuilder containing the URI.
+     * @return The query string, if any. null otherwise.
+     */
+    public static String extractQueryString(final StringBuilder name) {
+        for (int pos = 0; pos < name.length(); pos++) {
+            if (name.charAt(pos) == '?') {
+                final String queryString = name.substring(pos + 1);
+                name.delete(pos, name.length());
+                return queryString;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Extracts the scheme from a URI.
+     *
+     * @param uri The URI.
+     * @return The scheme name. Returns null if there is no scheme.
+     * @deprecated Use instead {@link #extractScheme}.  Will be removed in 3.0.
+     */
+    @Deprecated
+    public static String extractScheme(final String uri) {
+        return extractScheme(uri, null);
+    }
+
+    /**
+     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
+     *
+     * @param uri The URI.
+     * @param buffer Returns the remainder of the URI.
+     * @return The scheme name. Returns null if there is no scheme.
+     * @deprecated Use instead {@link #extractScheme}.  Will be removed in 3.0.
+     */
+    @Deprecated
+    public static String extractScheme(final String uri, final StringBuilder buffer) {
+        if (buffer != null) {
+            buffer.setLength(0);
+            buffer.append(uri);
+        }
+
+        final int maxPos = uri.length();
+        for (int pos = 0; pos < maxPos; pos++) {
+            final char ch = uri.charAt(pos);
+
+            if (ch == ':') {
+                // Found the end of the scheme
+                final String scheme = uri.substring(0, pos);
+                if (scheme.length() <= 1 && SystemUtils.IS_OS_WINDOWS) {
+                    // This is not a scheme, but a Windows drive letter
+                    return null;
+                }
+                if (buffer != null) {
+                    buffer.delete(0, pos + 1);
+                }
+                return scheme.intern();
+            }
+
+            if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') {
+                // A scheme character
+                continue;
+            }
+            if (!(pos > 0 && (ch >= '0' && ch <= '9' || ch == '+' || ch == '-' || ch == '.'))) {
+                // Not a scheme character
+                break;
+            }
+            // A scheme character (these are not allowed as the first
+            // character of the scheme, but can be used as subsequent
+            // characters.
+        }
+
+        // No scheme in URI
+        return null;
+    }
+
+    /**
+     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
+     * <p>
+     * The scheme is extracted based on the currently supported schemes in the system.  That is to say the schemes
+     * supported by the registered providers.
+     * </p>
+     * <p>
+     * This allows us to handle varying scheme's without making assumptions based on the ':' character.  Specifically
+     * handle scheme extraction calls for URI parameters that are not actually uri's, but may be names with ':' in them.
+     * </p>
+     * @param schemes The schemes to check.
+     * @param uri The potential URI. May also be a name.
+     * @return The scheme name. Returns null if there is no scheme.
+     * @since 2.3
+     */
+    public static String extractScheme(final String[] schemes, final String uri) {
+        return extractScheme(schemes, uri, null);
+    }
+
+    /**
+     * Extracts the scheme from a URI. Removes the scheme and ':' delimiter from the front of the URI.
+     * <p>
+     * The scheme is extracted based on the given set of schemes. Normally, that is to say the schemes
+     * supported by the registered providers.
+     * </p>
+     * <p>
+     * This allows us to handle varying scheme's without making assumptions based on the ':' character. Specifically
+     * handle scheme extraction calls for URI parameters that are not actually URI's, but may be names with ':' in them.
+     * </p>
+     * @param schemes The schemes to check.
+     * @param uri The potential URI. May also just be a name.
+     * @param buffer Returns the remainder of the URI.
+     * @return The scheme name. Returns null if there is no scheme.
+     * @since 2.3
+     */
+    public static String extractScheme(final String[] schemes, final String uri, final StringBuilder buffer) {
+        if (buffer != null) {
+            buffer.setLength(0);
+            buffer.append(uri);
+        }
+        for (final String scheme : schemes) {
+            if (uri.startsWith(scheme + ":")) {
+                if (buffer != null) {
+                    buffer.delete(0, uri.indexOf(':') + 1);
+                }
+                return scheme;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Normalises the separators in a name.
+     *
+     * @param name The StringBuilder containing the name
+     * @return true if the StringBuilder was modified.
+     */
+    public static boolean fixSeparators(final StringBuilder name) {
+        boolean changed = false;
+        final int maxlen = name.length();
+        for (int i = 0; i < maxlen; i++) {
+            final char ch = name.charAt(i);
+            if (ch == TRANS_SEPARATOR) {
+                name.setCharAt(i, SEPARATOR_CHAR);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Normalises a path. Does the following:
+     * <ul>
+     * <li>Removes empty path elements.
+     * <li>Handles '.' and '..' elements.
+     * <li>Removes trailing separator.
+     * </ul>
+     *
+     * Its assumed that the separators are already fixed.
+     *
+     * @param path The path to normalize.
+     * @return The FileType.
+     * @throws FileSystemException if an error occurs.
+     *
+     * @see #fixSeparators
+     */
+    public static FileType normalisePath(final StringBuilder path) throws FileSystemException {
+        FileType fileType = FileType.FOLDER;
+        if (path.length() == 0) {
+            return fileType;
+        }
+
+        if (path.charAt(path.length() - 1) != '/') {
+            fileType = FileType.FILE;
+        }
+
+        // Adjust separators
+        // fixSeparators(path);
+
+        // Determine the start of the first element
+        int startFirstElem = 0;
+        if (path.charAt(0) == SEPARATOR_CHAR) {
+            if (path.length() == 1) {
+                return fileType;
+            }
+            startFirstElem = 1;
+        }
+
+        // Iterate over each element
+        int startElem = startFirstElem;
+        int maxlen = path.length();
+        while (startElem < maxlen) {
+            // Find the end of the element
+            int endElem = startElem;
+            while (endElem < maxlen && path.charAt(endElem) != SEPARATOR_CHAR) {
+                endElem++;
+            }
+
+            final int elemLen = endElem - startElem;
+            if (elemLen == 0) {
+                // An empty element - axe it
+                path.delete(endElem, endElem + 1);
+                maxlen = path.length();
+                continue;
+            }
+            if (elemLen == 1 && path.charAt(startElem) == '.') {
+                // A '.' element - axe it
+                path.delete(startElem, endElem + 1);
+                maxlen = path.length();
+                continue;
+            }
+            if (elemLen == 2 && path.charAt(startElem) == '.' && path.charAt(startElem + 1) == '.') {
+                // A '..' element - remove the previous element
+                if (startElem == startFirstElem) {
+                    // Previous element is missing
+                    throw new FileSystemException("vfs.provider/invalid-relative-path.error");
+                }
+
+                // Find start of previous element
+                int pos = startElem - 2;
+                while (pos >= 0 && path.charAt(pos) != SEPARATOR_CHAR) {
+                    pos--;
+                }
+                startElem = pos + 1;
+
+                path.delete(startElem, endElem + 1);
+                maxlen = path.length();
+                continue;
+            }
+
+            // A regular element
+            startElem = endElem + 1;
+        }
+
+        // Remove trailing separator
+        if (!VFS.isUriStyle() && maxlen > 1 && path.charAt(maxlen - 1) == SEPARATOR_CHAR) {
+            path.delete(maxlen - 1, maxlen);
+        }
+
+        return fileType;
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/local/WindowsFileNameParser.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/local/WindowsFileNameParser.java
index 64b8642f..186fb222 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/local/WindowsFileNameParser.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/local/WindowsFileNameParser.java
@@ -1,136 +1,136 @@
-/*
- * 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.vfs2.provider.local;
-
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-
-/**
- * A parser for Windows file names.
- */
-public class WindowsFileNameParser extends LocalFileNameParser {
-
-    @Override
-    protected FileName createFileName(final String scheme, final String rootFile, final String path,
-            final FileType type) {
-        return new WindowsFileName(scheme, rootFile, path, type);
-    }
-
-    /**
-     * Extracts a drive prefix from a path. Leading '/' chars have been removed.
-     */
-    private String extractDrivePrefix(final StringBuilder name) {
-        // Looking for <letter> ':' '/'
-        if (name.length() < 3) {
-            // Too short
-            return null;
-        }
-        final char ch = name.charAt(0);
-        if (ch == '/' || ch == ':') {
-            // Missing drive letter
-            return null;
-        }
-        if (name.charAt(1) != ':') {
-            // Missing ':'
-            return null;
-        }
-        if (name.charAt(2) != '/') {
-            // Missing separator
-            return null;
-        }
-
-        final String prefix = name.substring(0, 2);
-        name.delete(0, 2);
-
-        return prefix.intern();
-    }
-
-    /**
-     * Pops the root prefix off a URI, which has had the scheme removed.
-     */
-    @Override
-    protected String extractRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
-        return extractWindowsRootPrefix(uri, name);
-    }
-
-    /**
-     * Extracts a UNC name from a path. Leading '/' chars have been removed.
-     */
-    private String extractUNCPrefix(final String uri, final StringBuilder name) throws FileSystemException {
-        // Looking for <name> '/' <name> ( '/' | <end> )
-
-        // Look for first separator
-        final int maxpos = name.length();
-        int pos = 0;
-        for (; pos < maxpos && name.charAt(pos) != '/'; pos++) {
-            // empty
-        }
-        pos++;
-        if (pos >= maxpos) {
-            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
-        }
-
-        // Now have <name> '/'
-        final int startShareName = pos;
-        for (; pos < maxpos && name.charAt(pos) != '/'; pos++) {
-            // empty
-        }
-        if (pos == startShareName) {
-            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
-        }
-
-        // Now have <name> '/' <name> ( '/' | <end> )
-        final String prefix = name.substring(0, pos);
-        name.delete(0, pos);
-        return prefix;
-    }
-
-    /**
-     * Extracts a Windows root prefix from a name.
-     */
-    private String extractWindowsRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
-        // Looking for:
-        // ('/'){0, 3} <letter> ':' '/'
-        // ['/'] '//' <name> '/' <name> ( '/' | <end> )
-
-        // Skip over first 4 (unc) leading '/' chars
-        int startPos = 0;
-        final int maxlen = Math.min(4, name.length());
-        for (; startPos < maxlen && name.charAt(startPos) == '/'; startPos++) {
-            // empty
-        }
-        if (startPos == maxlen && name.length() > (startPos + 1) && name.charAt(startPos + 1) == '/') {
-            // Too many '/'
-            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
-        }
-        name.delete(0, startPos);
-
-        // Look for drive name
-        final String driveName = extractDrivePrefix(name);
-        if (driveName != null) {
-            return driveName;
-        }
-
-        // Look for UNC name
-        if (startPos < 2) {
-            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
-        }
-
-        return "//" + extractUNCPrefix(uri, name);
-    }
-}
+/*
+ * 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.vfs2.provider.local;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+
+/**
+ * A parser for Windows file names.
+ */
+public class WindowsFileNameParser extends LocalFileNameParser {
+
+    @Override
+    protected FileName createFileName(final String scheme, final String rootFile, final String path,
+            final FileType type) {
+        return new WindowsFileName(scheme, rootFile, path, type);
+    }
+
+    /**
+     * Extracts a drive prefix from a path. Leading '/' chars have been removed.
+     */
+    private String extractDrivePrefix(final StringBuilder name) {
+        // Looking for <letter> ':' '/'
+        if (name.length() < 3) {
+            // Too short
+            return null;
+        }
+        final char ch = name.charAt(0);
+        if (ch == '/' || ch == ':') {
+            // Missing drive letter
+            return null;
+        }
+        if (name.charAt(1) != ':') {
+            // Missing ':'
+            return null;
+        }
+        if (name.charAt(2) != '/') {
+            // Missing separator
+            return null;
+        }
+
+        final String prefix = name.substring(0, 2);
+        name.delete(0, 2);
+
+        return prefix.intern();
+    }
+
+    /**
+     * Pops the root prefix off a URI, which has had the scheme removed.
+     */
+    @Override
+    protected String extractRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
+        return extractWindowsRootPrefix(uri, name);
+    }
+
+    /**
+     * Extracts a UNC name from a path. Leading '/' chars have been removed.
+     */
+    private String extractUNCPrefix(final String uri, final StringBuilder name) throws FileSystemException {
+        // Looking for <name> '/' <name> ( '/' | <end> )
+
+        // Look for first separator
+        final int maxpos = name.length();
+        int pos = 0;
+        while (pos < maxpos && name.charAt(pos) != '/') {
+            pos++;
+        }
+        pos++;
+        if (pos >= maxpos) {
+            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
+        }
+
+        // Now have <name> '/'
+        final int startShareName = pos;
+        while (pos < maxpos && name.charAt(pos) != '/') {
+            pos++;
+        }
+        if (pos == startShareName) {
+            throw new FileSystemException("vfs.provider.local/missing-share-name.error", uri);
+        }
+
+        // Now have <name> '/' <name> ( '/' | <end> )
+        final String prefix = name.substring(0, pos);
+        name.delete(0, pos);
+        return prefix;
+    }
+
+    /**
+     * Extracts a Windows root prefix from a name.
+     */
+    private String extractWindowsRootPrefix(final String uri, final StringBuilder name) throws FileSystemException {
+        // Looking for:
+        // ('/'){0, 3} <letter> ':' '/'
+        // ['/'] '//' <name> '/' <name> ( '/' | <end> )
+
+        // Skip over first 4 (unc) leading '/' chars
+        int startPos = 0;
+        final int maxlen = Math.min(4, name.length());
+        while (startPos < maxlen && name.charAt(startPos) == '/') {
+            startPos++;
+        }
+        if (startPos == maxlen && name.length() > (startPos + 1) && name.charAt(startPos + 1) == '/') {
+            // Too many '/'
+            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
+        }
+        name.delete(0, startPos);
+
+        // Look for drive name
+        final String driveName = extractDrivePrefix(name);
+        if (driveName != null) {
+            return driveName;
+        }
+
+        // Look for UNC name
+        if (startPos < 2) {
+            throw new FileSystemException("vfs.provider.local/not-absolute-file-name.error", uri);
+        }
+
+        return "//" + extractUNCPrefix(uri, name);
+    }
+}