aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main/java/com/google/common
diff options
context:
space:
mode:
authorcgdecker <cgdecker@google.com>2015-02-18 13:58:51 -0800
committerColin Decker <cgdecker@google.com>2015-02-18 17:03:16 -0500
commitf2503678be1a49023c27a023058f8202b9deea74 (patch)
tree431a7b6ea0204280fc607d2f0c7f033588ecc00e /jimfs/src/main/java/com/google/common
parent8794b21e764fc9defbe028c98bb4547951d4f639 (diff)
downloadjimfs-f2503678be1a49023c27a023058f8202b9deea74.tar.gz
Add support for "jimfs:" protocol URLs.
For URLs to work, Java has to be able to find a URLStreamHandler implementation for the URL protocol/scheme. There isn't any really ideal way to do this programatically, but what this CL does is add "com.google.common" to the "java.protocol.handler.pkgs" system property when JimfsFileSystemProvider is initialized. This means that when Java tries to create a URL with the "jimfs" protocol, it'll look for "jimfs.Handler" under "com.google.common", find it and use it. (Documentation of that behavior found here: http://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.lang.String-java.lang.String-int-java.lang.String-) Fixes Github issue #13. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=86627713
Diffstat (limited to 'jimfs/src/main/java/com/google/common')
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/Handler.java77
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java9
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java152
3 files changed, 238 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/Handler.java b/jimfs/src/main/java/com/google/common/jimfs/Handler.java
new file mode 100644
index 0000000..8772dda
--- /dev/null
+++ b/jimfs/src/main/java/com/google/common/jimfs/Handler.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 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 static com.google.common.base.Preconditions.checkArgument;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * {@link URLStreamHandler} implementation for jimfs. Named {@code Handler} so that the class can
+ * be found by Java as described in the documentation for
+ * {@link URL#URL(String, String, int, String) URL}.
+ *
+ * <p>This class is only public because it is necessary for Java to find it. It is not intended
+ * to be used directly.
+ *
+ * @author Colin Decker
+ */
+public final class Handler extends URLStreamHandler {
+
+ private static final String JAVA_PROTOCOL_HANDLER_PACKAGES = "java.protocol.handler.pkgs";
+
+ /**
+ * Registers this handler by adding the package {@code com.google.common} to the system property
+ * {@code "java.protocol.handler.pkgs"}. Java will then look for this class in the {@code jimfs}
+ * (the name of the protocol) package of {@code com.google.common}.
+ *
+ * @throws SecurityException if the system property that needs to be set to register this handler
+ * can't be read or written.
+ */
+ static void register() {
+ register(Handler.class);
+ }
+
+ /**
+ * Generic method that would allow registration of any properly placed {@code Handler} class.
+ */
+ static void register(Class<? extends URLStreamHandler> handlerClass) {
+ checkArgument("Handler".equals(handlerClass.getSimpleName()));
+
+ String pkg = handlerClass.getPackage().getName();
+ int lastDot = pkg.lastIndexOf('.');
+ checkArgument(lastDot > 0, "package for Handler (%s) must have a parent package", pkg);
+
+ String parentPackage = pkg.substring(0, lastDot);
+
+ String packages = System.getProperty(JAVA_PROTOCOL_HANDLER_PACKAGES);
+ if (packages == null) {
+ packages = parentPackage;
+ } else {
+ packages += "|" + parentPackage;
+ }
+ System.setProperty(JAVA_PROTOCOL_HANDLER_PACKAGES, packages);
+ }
+
+ @Override
+ protected URLConnection openConnection(URL url) throws IOException {
+ return new PathURLConnection(url);
+ }
+}
diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
index 237a9e0..caeb9f6 100644
--- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
+++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
@@ -71,6 +71,15 @@ import javax.annotation.Nullable;
@AutoService(FileSystemProvider.class)
public final class JimfsFileSystemProvider extends FileSystemProvider {
+ static {
+ // Register the URL stream handler implementation.
+ try {
+ Handler.register();
+ } catch (SecurityException e) {
+ // Couldn't set the system property needed to register the handler. Nothing we can do really.
+ }
+ }
+
@Override
public String getScheme() {
return URI_SCHEME;
diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java b/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java
new file mode 100644
index 0000000..e0a27d4
--- /dev/null
+++ b/jimfs/src/main/java/com/google/common/jimfs/PathURLConnection.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015 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 static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Ascii;
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.FileTime;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * {@code URLConnection} implementation.
+ *
+ * @author Colin Decker
+ */
+final class PathURLConnection extends URLConnection {
+
+ /*
+ * This implementation should be able to work for any proper file system implementation... it
+ * might be useful to release it and make it usable by other file systems.
+ */
+
+ private static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss \'GMT\'";
+ private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
+
+ private InputStream stream;
+ private ImmutableListMultimap<String, String> headers = ImmutableListMultimap.of();
+
+ PathURLConnection(URL url) {
+ super(checkNotNull(url));
+ }
+
+ @Override
+ public void connect() throws IOException {
+ if (stream != null) {
+ return;
+ }
+
+ Path path = Paths.get(toUri(url));
+
+ long length;
+ if (Files.isDirectory(path)) {
+ // Match File URL behavior for directories by having the stream contain the filenames in
+ // the directory separated by newlines.
+ StringBuilder builder = new StringBuilder();
+ try (DirectoryStream<Path> files = Files.newDirectoryStream(path)) {
+ for (Path file : files) {
+ builder.append(file.getFileName()).append('\n');
+ }
+ }
+ byte[] bytes = builder.toString().getBytes(UTF_8);
+ stream = new ByteArrayInputStream(bytes);
+ length = bytes.length;
+ } else {
+ stream = Files.newInputStream(path);
+ length = Files.size(path);
+ }
+
+ FileTime lastModified = Files.getLastModifiedTime(path);
+ String contentType = MoreObjects.firstNonNull(
+ Files.probeContentType(path), DEFAULT_CONTENT_TYPE);
+
+ ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
+ builder.put("content-length", "" + length);
+ builder.put("content-type", contentType);
+ if (lastModified != null) {
+ DateFormat format = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ builder.put("last-modified", format.format(new Date(lastModified.toMillis())));
+ }
+
+ headers = builder.build();
+ }
+
+ private static URI toUri(URL url) throws IOException {
+ try {
+ return url.toURI();
+ } catch (URISyntaxException e) {
+ throw new IOException("URL " + url + " cannot be converted to a URI", e);
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ connect();
+ return stream;
+ }
+
+ @SuppressWarnings("unchecked") // safe by specification of ListMultimap.asMap()
+ @Override
+ public Map<String, List<String>> getHeaderFields() {
+ try {
+ connect();
+ } catch (IOException e) {
+ return ImmutableMap.of();
+ }
+ return (ImmutableMap<String, List<String>>) (ImmutableMap<String, ?>) headers.asMap();
+ }
+
+ @Override
+ public String getHeaderField(String name) {
+ try {
+ connect();
+ } catch (IOException e) {
+ return null;
+ }
+
+ // no header should have more than one value
+ return Iterables.getFirst(headers.get(Ascii.toLowerCase(name)), null);
+ }
+}