aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java
diff options
context:
space:
mode:
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java')
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java208
1 files changed, 208 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java b/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java
new file mode 100644
index 0000000..7cdf0c4
--- /dev/null
+++ b/jimfs/src/main/java/com/google/common/jimfs/WindowsPathType.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed 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 com.google.common.jimfs;
+
+import java.nio.file.InvalidPathException;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+/**
+ * Windows-style path type.
+ *
+ * @author Colin Decker
+ */
+final class WindowsPathType extends PathType {
+
+ /** Windows path type. */
+ static final WindowsPathType INSTANCE = new WindowsPathType();
+
+ /**
+ * Matches the C:foo\bar path format, which has a root (C:) and names (foo\bar) and matches a path
+ * relative to the working directory on that drive. Currently can't support that format as it
+ * requires behavior that differs completely from Unix.
+ */
+ // TODO(cgdecker): Can probably support this at some point
+ // It would require:
+ // - A method like PathType.isAbsolute(Path) or something to that effect; this would allow
+ // WindowsPathType to distinguish between an absolute root path (C:\) and a relative root
+ // path (C:)
+ // - Special handling for relative paths that have a root. This handling would determine the
+ // root directory and then determine the working directory from there. The file system would
+ // still have one working directory; for the root that working directory is under, it is the
+ // working directory. For every other root, the root itself is the working directory.
+ private static final Pattern WORKING_DIR_WITH_DRIVE = Pattern.compile("^[a-zA-Z]:([^\\\\].*)?$");
+
+ /** Pattern for matching trailing spaces in file names. */
+ private static final Pattern TRAILING_SPACES = Pattern.compile("[ ]+(\\\\|$)");
+
+ private WindowsPathType() {
+ super(true, '\\', '/');
+ }
+
+ @Override
+ public ParseResult parsePath(String path) {
+ String original = path;
+ path = path.replace('/', '\\');
+
+ if (WORKING_DIR_WITH_DRIVE.matcher(path).matches()) {
+ throw new InvalidPathException(
+ original,
+ "Jimfs does not currently support the Windows syntax for a relative path "
+ + "on a specific drive (e.g. \"C:foo\\bar\")");
+ }
+
+ String root;
+ if (path.startsWith("\\\\")) {
+ root = parseUncRoot(path, original);
+ } else if (path.startsWith("\\")) {
+ throw new InvalidPathException(
+ original,
+ "Jimfs does not currently support the Windows syntax for an absolute path "
+ + "on the current drive (e.g. \"\\foo\\bar\")");
+ } else {
+ root = parseDriveRoot(path);
+ }
+
+ // check for root.length() > 3 because only "C:\" type roots are allowed to have :
+ int startIndex = root == null || root.length() > 3 ? 0 : root.length();
+ for (int i = startIndex; i < path.length(); i++) {
+ char c = path.charAt(i);
+ if (isReserved(c)) {
+ throw new InvalidPathException(original, "Illegal char <" + c + ">", i);
+ }
+ }
+
+ Matcher trailingSpaceMatcher = TRAILING_SPACES.matcher(path);
+ if (trailingSpaceMatcher.find()) {
+ throw new InvalidPathException(original, "Trailing char < >", trailingSpaceMatcher.start());
+ }
+
+ if (root != null) {
+ path = path.substring(root.length());
+
+ if (!root.endsWith("\\")) {
+ root = root + "\\";
+ }
+ }
+
+ return new ParseResult(root, splitter().split(path));
+ }
+
+ /** Pattern for matching UNC \\host\share root syntax. */
+ private static final Pattern UNC_ROOT = Pattern.compile("^(\\\\\\\\)([^\\\\]+)?(\\\\[^\\\\]+)?");
+
+ /**
+ * Parse the root of a UNC-style path, throwing an exception if the path does not start with a
+ * valid UNC root.
+ */
+ private String parseUncRoot(String path, String original) {
+ Matcher uncMatcher = UNC_ROOT.matcher(path);
+ if (uncMatcher.find()) {
+ String host = uncMatcher.group(2);
+ if (host == null) {
+ throw new InvalidPathException(original, "UNC path is missing hostname");
+ }
+ String share = uncMatcher.group(3);
+ if (share == null) {
+ throw new InvalidPathException(original, "UNC path is missing sharename");
+ }
+
+ return path.substring(uncMatcher.start(), uncMatcher.end());
+ } else {
+ // probably shouldn't ever reach this
+ throw new InvalidPathException(original, "Invalid UNC path");
+ }
+ }
+
+ /** Pattern for matching normal C:\ drive letter root syntax. */
+ private static final Pattern DRIVE_LETTER_ROOT = Pattern.compile("^[a-zA-Z]:\\\\");
+
+ /** Parses a normal drive-letter root, e.g. "C:\". */
+ @NullableDecl
+ private String parseDriveRoot(String path) {
+ Matcher drivePathMatcher = DRIVE_LETTER_ROOT.matcher(path);
+ if (drivePathMatcher.find()) {
+ return path.substring(drivePathMatcher.start(), drivePathMatcher.end());
+ }
+ return null;
+ }
+
+ /** Checks if c is one of the reserved characters that aren't allowed in Windows file names. */
+ private static boolean isReserved(char c) {
+ switch (c) {
+ case '<':
+ case '>':
+ case ':':
+ case '"':
+ case '|':
+ case '?':
+ case '*':
+ return true;
+ default:
+ return c <= 31;
+ }
+ }
+
+ @Override
+ public String toString(@NullableDecl String root, Iterable<String> names) {
+ StringBuilder builder = new StringBuilder();
+ if (root != null) {
+ builder.append(root);
+ }
+ joiner().appendTo(builder, names);
+ return builder.toString();
+ }
+
+ @Override
+ public String toUriPath(String root, Iterable<String> names, boolean directory) {
+ if (root.startsWith("\\\\")) {
+ root = root.replace('\\', '/');
+ } else {
+ root = "/" + root.replace('\\', '/');
+ }
+
+ StringBuilder builder = new StringBuilder();
+ builder.append(root);
+
+ Iterator<String> iter = names.iterator();
+ if (iter.hasNext()) {
+ builder.append(iter.next());
+ while (iter.hasNext()) {
+ builder.append('/').append(iter.next());
+ }
+ }
+
+ if (directory && builder.charAt(builder.length() - 1) != '/') {
+ builder.append('/');
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public ParseResult parseUriPath(String uriPath) {
+ uriPath = uriPath.replace('/', '\\');
+ if (uriPath.charAt(0) == '\\' && uriPath.charAt(1) != '\\') {
+ // non-UNC path, so the leading / was just there for the URI path format and isn't part
+ // of what should be parsed
+ uriPath = uriPath.substring(1);
+ }
+ return parsePath(uriPath);
+ }
+}