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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org