Convert for to while loop to avoid the empty control statement.
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 7c878f7..4254337 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 eb8e1e6..5eb024b 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 64b8642..186fb22 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);
+ }
+}