You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by jb...@apache.org on 2015/03/06 16:13:33 UTC

svn commit: r1664650 - in /tomcat/sandbox/niofs: ./ src/ src/niofs/ tst/ tst/data/ tst/niofs/

Author: jboynes
Date: Fri Mar  6 15:13:33 2015
New Revision: 1664650

URL: http://svn.apache.org/r1664650
Log:
Sample code for a NIO2 Filesystem that supports nested archives.
Access using the Files API is supported (albeit crudely).
Still need to figure out URIs, URLs, and a Path-based ClassLoader

Added:
    tomcat/sandbox/niofs/   (with props)
    tomcat/sandbox/niofs/src/
    tomcat/sandbox/niofs/src/niofs/
    tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java   (with props)
    tomcat/sandbox/niofs/tst/
    tomcat/sandbox/niofs/tst/data/
    tomcat/sandbox/niofs/tst/data/archive1.jar   (with props)
    tomcat/sandbox/niofs/tst/data/file1.txt
    tomcat/sandbox/niofs/tst/data/file2.txt
    tomcat/sandbox/niofs/tst/data/nested.jar   (with props)
    tomcat/sandbox/niofs/tst/niofs/
    tomcat/sandbox/niofs/tst/niofs/SmokeTest.java   (with props)
    tomcat/sandbox/niofs/tst/niofs/URITest.java   (with props)

Propchange: tomcat/sandbox/niofs/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Fri Mar  6 15:13:33 2015
@@ -0,0 +1,3 @@
+.idea
+*.iml
+out

Added: tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java?rev=1664650&view=auto
==============================================================================
--- tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java (added)
+++ tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java Fri Mar  6 15:13:33 2015
@@ -0,0 +1,641 @@
+/*
+ * 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 niofs;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.ProviderMismatchException;
+import java.nio.file.ReadOnlyFileSystemException;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * A provider for read-only filesystems based on an underlying archive file in ZIP format.
+ */
+public class ArchiveFileSystemProvider extends FileSystemProvider {
+
+    @Override
+    public String getScheme() {
+        return "archive";
+    }
+
+    // TODO: We will need to support URIs in order to support converting paths to URLs.
+    @Override
+    public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FileSystem getFileSystem(URI uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Path getPath(URI uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
+        return new ArchiveFileSystem(path, env);
+    }
+
+    @Override
+    public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+        return checkPath(path).newByteChannel(options, attrs);
+    }
+
+    @Override
+    public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
+        return checkPath(dir).newDirectoryStream(filter);
+    }
+
+    @Override
+    public boolean isSameFile(Path path, Path path2) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isHidden(Path path) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FileStore getFileStore(Path path) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void checkAccess(Path path, AccessMode... modes) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
+        return checkPath(path).readAttributes(type, options);
+    }
+
+    @Override
+    public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+        throw new ReadOnlyFileSystemException();
+    }
+
+    @Override
+    public void delete(Path path) throws IOException {
+        throw new ReadOnlyFileSystemException();
+    }
+
+    @Override
+    public void copy(Path source, Path target, CopyOption... options) throws IOException {
+        throw new ReadOnlyFileSystemException();
+    }
+
+    @Override
+    public void move(Path source, Path target, CopyOption... options) throws IOException {
+        throw new ReadOnlyFileSystemException();
+    }
+
+    @Override
+    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+        throw new ReadOnlyFileSystemException();
+    }
+
+    static ArchiveFileSystem.ArchivePath checkPath(Path path) {
+        if (path instanceof ArchiveFileSystem.ArchivePath) {
+            return (ArchiveFileSystem.ArchivePath) path;
+        } else if (path == null) {
+            throw new NullPointerException();
+        } else {
+            throw new ProviderMismatchException();
+        }
+    }
+
+    private static class DirectoryNode implements BasicFileAttributes {
+        private final ZipEntry entry;
+        private final byte[] data;
+        private final Set<Path> children = new LinkedHashSet<>();
+
+        public DirectoryNode(ZipEntry entry, byte[] data) {
+            this.entry = entry;
+            this.data = data;
+        }
+
+        private void addChild(Path child) {
+            children.add(child);
+        }
+
+        @Override
+        public FileTime lastModifiedTime() {
+            return entry.getLastModifiedTime();
+        }
+
+        @Override
+        public FileTime lastAccessTime() {
+            return entry.getLastAccessTime();
+        }
+
+        @Override
+        public FileTime creationTime() {
+            return entry.getLastModifiedTime();
+        }
+
+        @Override
+        public boolean isRegularFile() {
+            return !entry.isDirectory();
+        }
+
+        @Override
+        public boolean isDirectory() {
+            return entry.isDirectory();
+        }
+
+        @Override
+        public boolean isSymbolicLink() {
+            return false;
+        }
+
+        @Override
+        public boolean isOther() {
+            return false;
+        }
+
+        @Override
+        public long size() {
+            return entry.getSize();
+        }
+
+        @Override
+        public Object fileKey() {
+            return this;
+        }
+    }
+
+    /**
+     *
+     */
+    class ArchiveFileSystem extends FileSystem {
+        private volatile boolean open = true;
+        private final Map<Path, DirectoryNode> directory;
+
+        public ArchiveFileSystem(Path path, Map<String, ?> env) throws IOException {
+            directory = createIndex(path);
+        }
+
+        /**
+         * Hacky way to build an index of what's in the archive. This should be replaced by
+         * code that reads the locations of each entry's records from the central directory and
+         * then lazily loads the data. Commons VFS may have code to help with that.
+         */
+        private Map<Path, DirectoryNode> createIndex(Path archive) throws IOException {
+            Map<Path, DirectoryNode> directory = new HashMap<>();
+            directory.put(new ArchivePath("/"), new DirectoryNode(new ZipEntry("/"), null));
+            try (InputStream is = Files.newInputStream(archive, StandardOpenOption.READ)) {
+                try (ZipInputStream jarStream = new ZipInputStream(is)) {
+                    ZipEntry entry;
+                    while ((entry = jarStream.getNextEntry()) != null) {
+                        String name = entry.getName();
+                        if (entry.isDirectory()) {
+                            name = "/" + name.substring(0, name.length() - 1);
+                            Path path = new ArchivePath(name);
+                            directory.put(path, new DirectoryNode(entry, null));
+                        } else {
+                            name = "/" + name;
+                            Path path = new ArchivePath(name);
+
+                            // Read the entry's data. entry.size() is -1 for streamed input which
+                            // seems odd. We should be able to read that from the local file header
+                            // and/or data descriptor.
+                            ByteArrayOutputStream os = new ByteArrayOutputStream();
+                            byte[] buffer = new byte[8192];
+                            int read;
+                            while ((read = jarStream.read(buffer, 0, buffer.length)) > 0) {
+                                os.write(buffer, 0, read);
+                            }
+                            directory.put(path, new DirectoryNode(entry, os.toByteArray()));
+                        }
+                    }
+                }
+            }
+            for (Path path : directory.keySet()) {
+                Path parent = path.getParent();
+                if (parent == null) {
+                    continue;
+                }
+                DirectoryNode parentNode = directory.get(parent);
+                if (parentNode == null) {
+                    throw new IllegalStateException("Missing directory entry" + parent);
+                }
+                parentNode.addChild(path);
+            }
+            return directory;
+        }
+
+        @Override
+        public FileSystemProvider provider() {
+            return ArchiveFileSystemProvider.this;
+        }
+
+        @Override
+        public void close() throws IOException {
+            open = false;
+        }
+
+        @Override
+        public boolean isOpen() {
+            return open;
+        }
+
+        @Override
+        public boolean isReadOnly() {
+            return true;
+        }
+
+        @Override
+        public String getSeparator() {
+            return "/";
+        }
+
+        @Override
+        public Iterable<Path> getRootDirectories() {
+            return null;
+        }
+
+        @Override
+        public Iterable<FileStore> getFileStores() {
+            return null;
+        }
+
+        @Override
+        public Set<String> supportedFileAttributeViews() {
+            return null;
+        }
+
+        @Override
+        public Path getPath(String first, String... more) {
+            return new ArchivePath(first, more);
+        }
+
+        @Override
+        public PathMatcher getPathMatcher(String syntaxAndPattern) {
+            return null;
+        }
+
+        @Override
+        public UserPrincipalLookupService getUserPrincipalLookupService() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public WatchService newWatchService() throws IOException {
+            throw new UnsupportedOperationException();
+        }
+
+        private SeekableByteChannel newByteChannel(ArchivePath path, Set<? extends OpenOption> options, FileAttribute<?>[] attrs) throws IOException {
+            DirectoryNode node = directory.get(path);
+            if (node == null) {
+                throw new NoSuchFileException(path.toString());
+            }
+            // This relies on the data being in memory. This could be done by deflating the entry
+            // when the channel is opened (perhaps with a cache to avoid repeated deflation).
+            // Alternatively position could determined by calculating each block size.
+            //
+            // j.u.z.Inflater
+            return new SeekableByteChannel() {
+                private boolean open = true;
+                private int position = 0;
+
+                @Override
+                public int read(ByteBuffer dst) throws IOException {
+                    int size = Math.min(dst.remaining(), node.data.length - position);
+                    dst.put(node.data, position, size);
+                    position += size;
+                    return size;
+                }
+
+                @Override
+                public int write(ByteBuffer src) throws IOException {
+                    throw new NonWritableChannelException();
+                }
+
+                @Override
+                public long position() throws IOException {
+                    return position;
+                }
+
+                @Override
+                public SeekableByteChannel position(long newPosition) throws IOException {
+                    position = (int) newPosition;
+                    return this;
+                }
+
+                @Override
+                public long size() throws IOException {
+                    return node.data.length;
+                }
+
+                @Override
+                public SeekableByteChannel truncate(long size) throws IOException {
+                    throw new NonWritableChannelException();
+                }
+
+                @Override
+                public boolean isOpen() {
+                    return open;
+                }
+
+                @Override
+                public void close() throws IOException {
+                    open = false;
+                }
+            };
+        }
+
+        private DirectoryStream<Path> newDirectoryStream(ArchivePath path, DirectoryStream.Filter<? super Path> filter) throws IOException {
+            DirectoryNode node = directory.get(path);
+            if (node == null) {
+                throw new NoSuchFileException(path.toString());
+            }
+            if (!node.isDirectory()) {
+                throw new NotDirectoryException(path.toString());
+            }
+            List<Path> filtered = new ArrayList<>(node.children.size());
+            for (Path child : node.children) {
+                if (filter.accept(child)) {
+                    filtered.add(child);
+                }
+            }
+            return new DirectoryStream<Path>() {
+                @Override
+                public Iterator<Path> iterator() {
+                    return filtered.iterator();
+                }
+
+                @Override
+                public void close() throws IOException {
+                }
+            };
+        }
+
+        private <A extends BasicFileAttributes> A readAttributes(ArchivePath path, Class<A> type, LinkOption[] options) throws NoSuchFileException {
+            if (!type.isAssignableFrom(DirectoryNode.class)) {
+                return null;
+            }
+            DirectoryNode directoryNode = directory.get(path);
+            if (directoryNode == null) {
+                throw new NoSuchFileException(path.toString());
+            }
+            return type.cast(directoryNode);
+        }
+
+        class ArchivePath implements Path {
+            private final String path;
+
+            public ArchivePath(String first, String... more) {
+                if (more == null) {
+                    path = first;
+                } else {
+                    StringBuilder builder = new StringBuilder(first);
+                    for (String s : more) {
+                        builder.append(s);
+                    }
+                    path = builder.toString();
+                }
+            }
+
+            @Override
+            public FileSystem getFileSystem() {
+                return ArchiveFileSystem.this;
+            }
+
+            @Override
+            public boolean isAbsolute() {
+                return path.length() > 0 && path.charAt(0) == '/';
+            }
+
+            @Override
+            public Path getRoot() {
+                return null;
+            }
+
+            @Override
+            public Path getFileName() {
+                int offset = path.lastIndexOf('/');
+                if (offset == -1) {
+                    return this;
+                }
+                return new ArchivePath(path.substring(offset + 1));
+            }
+
+            @Override
+            public Path getParent() {
+                if ("/".equals(path)) {
+                    return null;
+                }
+                int offset = path.lastIndexOf('/');
+                if (offset == 0) {
+                    return new ArchivePath("/");
+                } else {
+                    return new ArchivePath(path.substring(0, offset));
+                }
+            }
+
+            @Override
+            public int getNameCount() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path getName(int index) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path subpath(int beginIndex, int endIndex) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean startsWith(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean startsWith(String other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean endsWith(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean endsWith(String other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path normalize() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path resolve(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path resolve(String other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path resolveSibling(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path resolveSibling(String other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path relativize(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public URI toUri() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path toAbsolutePath() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Path toRealPath(LinkOption... options) throws IOException {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public File toFile() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public Iterator<Path> iterator() {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public int compareTo(Path other) {
+                throw new UnsupportedOperationException();
+            }
+
+            public <A extends BasicFileAttributes> A readAttributes(Class<A> type, LinkOption[] options) throws NoSuchFileException {
+                return ArchiveFileSystem.this.readAttributes(this, type, options);
+            }
+
+            public SeekableByteChannel newByteChannel(Set<? extends OpenOption> options, FileAttribute<?>[] attrs) throws IOException {
+                return ArchiveFileSystem.this.newByteChannel(this, options, attrs);
+            }
+
+            public DirectoryStream<Path> newDirectoryStream(DirectoryStream.Filter<? super Path> filter) throws IOException {
+                return ArchiveFileSystem.this.newDirectoryStream(this, filter);
+            }
+
+            @Override
+            public String toString() {
+                return path;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) {
+                    return true;
+                }
+                if (o == null || getClass() != o.getClass()) {
+                    return false;
+                }
+                return path.equals(((ArchivePath) o).path);
+
+            }
+
+            @Override
+            public int hashCode() {
+                return path.hashCode();
+            }
+        }
+    }
+}

Propchange: tomcat/sandbox/niofs/src/niofs/ArchiveFileSystemProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/sandbox/niofs/tst/data/archive1.jar
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/data/archive1.jar?rev=1664650&view=auto
==============================================================================
Binary file - no diff available.

Propchange: tomcat/sandbox/niofs/tst/data/archive1.jar
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: tomcat/sandbox/niofs/tst/data/file1.txt
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/data/file1.txt?rev=1664650&view=auto
==============================================================================
--- tomcat/sandbox/niofs/tst/data/file1.txt (added)
+++ tomcat/sandbox/niofs/tst/data/file1.txt Fri Mar  6 15:13:33 2015
@@ -0,0 +1 @@
+Test File 1

Added: tomcat/sandbox/niofs/tst/data/file2.txt
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/data/file2.txt?rev=1664650&view=auto
==============================================================================
--- tomcat/sandbox/niofs/tst/data/file2.txt (added)
+++ tomcat/sandbox/niofs/tst/data/file2.txt Fri Mar  6 15:13:33 2015
@@ -0,0 +1 @@
+Test File 1

Added: tomcat/sandbox/niofs/tst/data/nested.jar
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/data/nested.jar?rev=1664650&view=auto
==============================================================================
Binary file - no diff available.

Propchange: tomcat/sandbox/niofs/tst/data/nested.jar
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: tomcat/sandbox/niofs/tst/niofs/SmokeTest.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/niofs/SmokeTest.java?rev=1664650&view=auto
==============================================================================
--- tomcat/sandbox/niofs/tst/niofs/SmokeTest.java (added)
+++ tomcat/sandbox/niofs/tst/niofs/SmokeTest.java Fri Mar  6 15:13:33 2015
@@ -0,0 +1,76 @@
+/*
+ * 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 niofs;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+
+import org.junit.Test;
+
+/**
+ *
+ */
+public class SmokeTest {
+    private final ArchiveFileSystemProvider provider = new ArchiveFileSystemProvider();
+
+    @Test
+    public void listFiles() throws IOException {
+        Path archive1 = FileSystems.getDefault().getPath("tst", "data", "archive1.jar");
+        FileSystem fileSystem = provider.newFileSystem(archive1, Collections.emptyMap());
+        Path root = fileSystem.getPath("/");
+        Files.list(root).forEach(System.out::println);
+
+        Files.walkFileTree(root, new SimpleFileVisitor<Path>(){
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                System.out.println(file);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+
+    @Test
+    public void nesting() throws IOException {
+        Path nested = FileSystems.getDefault().getPath("tst", "data", "nested.jar");
+        FileSystem fileSystem = provider.newFileSystem(nested, Collections.emptyMap());
+
+        Files.walkFileTree(fileSystem.getPath("/"), new SimpleFileVisitor<Path>(){
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                System.out.println(file);
+                if (file.getFileName().toString().endsWith(".jar")) {
+                    FileSystem nested = provider.newFileSystem(file, Collections.emptyMap());
+                    Files.walkFileTree(nested.getPath("/"), new SimpleFileVisitor<Path>() {
+                        @Override
+                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                            System.out.println(".." + file);
+                            return FileVisitResult.CONTINUE;
+                        }
+                    });
+                }
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+}

Propchange: tomcat/sandbox/niofs/tst/niofs/SmokeTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/sandbox/niofs/tst/niofs/URITest.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/niofs/tst/niofs/URITest.java?rev=1664650&view=auto
==============================================================================
--- tomcat/sandbox/niofs/tst/niofs/URITest.java (added)
+++ tomcat/sandbox/niofs/tst/niofs/URITest.java Fri Mar  6 15:13:33 2015
@@ -0,0 +1,105 @@
+/*
+ * 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 niofs;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.net.URLEncoder.encode;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests that hierarchical URIs with encoded authority work as expected. This is exploring whether
+ * URL-encoding the authority component as described in RFC3986 allows URIs to be nested and still
+ * preserve the relatve resolution behaviour.
+ *
+ * This is exploring an alternative to the non-hierarchical URIs for jar: and jar:war: schemes
+ * that do not support resolving relative URIs.
+ */
+public class URITest {
+
+    private static final String UTF8 = UTF_8.toString();
+
+    @Before
+    public void initURLHandler() {
+        // Need this to even construct URLs.
+        URLStreamHandlerFactory factory = protocol -> {
+            switch (protocol) {
+                case "archive":
+                    return new URLStreamHandler() {
+                        @Override
+                        protected URLConnection openConnection(URL u) throws IOException {
+                            return null;
+                        }
+                    };
+                default:
+                    return null;
+            }
+        };
+        URL.setURLStreamHandlerFactory(factory);
+    }
+
+    @Test
+    public void jarCannotResolve() {
+        URI uri = URI.create("jar:" + "file:///tmp/foo.jar" + "!/WEB-INF/foo.jar");
+        assertNotEquals(URI.create("jar:file:///tmp/foo.jar!/WEB-INF/bar.jar"), uri.resolve("bar.jar"));
+        assertEquals(URI.create("bar.jar"), uri.resolve("bar.jar"));
+    }
+
+    @Test
+    public void encodedAuthority() throws UnsupportedEncodingException, MalformedURLException {
+        String path = "file:///tmp/foo.jar";
+        URI uri = URI.create("archive://" + encode(path, UTF8) + "/WEB-INF/foo.jar");
+        assertEquals("archive", uri.getScheme());
+        assertEquals(path, uri.getAuthority());
+        assertNull(uri.getHost());
+        assertEquals("/WEB-INF/foo.jar", uri.getPath());
+        assertEquals("archive://file%3A%2F%2F%2Ftmp%2Ffoo.jar/WEB-INF/foo.jar", uri.toString());
+        assertEquals("/WEB-INF/bar.jar", uri.resolve("bar.jar").getPath());
+
+        URL url = uri.toURL();
+        assertEquals("archive://file%3A%2F%2F%2Ftmp%2Ffoo.jar/WEB-INF/foo.jar", url.toString());
+        assertEquals("file%3A%2F%2F%2Ftmp%2Ffoo.jar", url.getAuthority());
+        assertEquals("file%3A%2F%2F%2Ftmp%2Ffoo.jar", url.getHost());
+
+        URL relativeUrl = new URL(url, "bar.jar");
+        assertEquals("archive://file%3A%2F%2F%2Ftmp%2Ffoo.jar/WEB-INF/bar.jar", relativeUrl.toString());
+        assertEquals("/WEB-INF/bar.jar", relativeUrl.getPath());
+    }
+
+
+    @Test
+    public void doubleEncoding() throws UnsupportedEncodingException, MalformedURLException {
+        String path = "file:///tmp/foo.jar";
+        URI uri = URI.create("archive://" + encode(path, UTF8) + "/WEB-INF/foo.jar");
+        URI nested = URI.create("archive://" + encode(uri.toString(), UTF8) + "/WEB-INF/nested.jar");
+        assertEquals(uri.toString(), nested.getAuthority());
+    }
+
+}

Propchange: tomcat/sandbox/niofs/tst/niofs/URITest.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org