diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ByteArrayChannel.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ByteArrayChannel.java index eb37e013f999..76255452c680 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ByteArrayChannel.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ByteArrayChannel.java @@ -71,7 +71,7 @@ public SeekableByteChannel position(long pos) throws IOException { if (pos < 0 || pos >= Integer.MAX_VALUE) { throw new IllegalArgumentException("Illegal position " + pos); } - this.pos = Math.min((int)pos, last); + this.pos = Math.min((int) pos, last); return this; } finally { endWrite(); @@ -141,7 +141,7 @@ public void close() throws IOException { if (closed) { return; } - System.out.println("Close method!"); + beginWrite(); try { closed = true; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceDirectoryStream.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceDirectoryStream.java index 372c67f7fbd3..4191b25d7bbb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceDirectoryStream.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceDirectoryStream.java @@ -2,53 +2,53 @@ import java.io.IOException; import java.nio.file.ClosedDirectoryStreamException; +import java.nio.file.DirectoryIteratorException; import java.nio.file.DirectoryStream; import java.nio.file.NotDirectoryException; import java.nio.file.Path; -import java.util.Arrays; import java.util.Iterator; -import java.util.List; import java.util.NoSuchElementException; public class ResourceDirectoryStream implements DirectoryStream { private final ResourceFileSystem fileSystem; private final DirectoryStream.Filter filter; + private final ResourcePath dir; private volatile boolean isClosed = false; - private final List data; + private Iterator directoryIterator; - public ResourceDirectoryStream(ResourcePath path, Filter filter) throws IOException { - this.fileSystem = path.getFileSystem(); + public ResourceDirectoryStream(ResourcePath dir, Filter filter) throws IOException { + this.fileSystem = dir.getFileSystem(); + this.dir = dir; this.filter = filter; - this.data = formatDirData(path); - if (!fileSystem.isDirectory(path.getResolvedPath())) { - throw new NotDirectoryException(path.toString()); + if (!fileSystem.isDirectory(dir.getResolvedPath())) { + throw new NotDirectoryException(dir.toString()); } } - // TODO: After recomposing resource storage, get with zero index will be replaced with - // appropriate one. - private List formatDirData(ResourcePath path) { - byte[] data = Resources.get(fileSystem.getString(path.getResolvedPath())).get(0); - return Arrays.asList(fileSystem.getString(data).split("\n")); - } - - // TODO: We need to filter data, based on filter parameter. @Override public Iterator iterator() { if (isClosed) { throw new ClosedDirectoryStreamException(); } + if (directoryIterator != null) { + throw new IllegalStateException("Iterator has already been returned"); + } + + try { + directoryIterator = fileSystem.iteratorOf(dir, filter); + } catch (IOException ioException) { + throw new DirectoryIteratorException(ioException); + } return new Iterator() { - final Iterator iterator = data.iterator(); @Override public boolean hasNext() { if (isClosed) { return false; } - return iterator.hasNext(); + return directoryIterator.hasNext(); } @Override @@ -56,7 +56,7 @@ public Path next() { if (isClosed) { throw new NoSuchElementException(); } - return new ResourcePath(fileSystem, fileSystem.getBytes(iterator.next())); + return directoryIterator.next(); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileStore.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileStore.java index 72e6454e7138..72d16c467fb8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileStore.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileStore.java @@ -60,23 +60,26 @@ public boolean supportsFileAttributeView(String name) { @Override public V getFileStoreAttributeView(Class type) { - if (type == null) + if (type == null) { throw new NullPointerException(); + } return null; } @Override public Object getAttribute(String attribute) throws IOException { - if (attribute.equals("totalSpace")) + if (attribute.equals("totalSpace")) { return getTotalSpace(); - if (attribute.equals("usableSpace")) + } + if (attribute.equals("usableSpace")) { return getUsableSpace(); - if (attribute.equals("unallocatedSpace")) + } + if (attribute.equals("unallocatedSpace")) { return getUnallocatedSpace(); + } throw new UnsupportedOperationException("Attribute isn't supported!"); } - // TODO: Need enhancements. private static class ResourceFileStoreAttributes { private final FileStore fileStore; private final long size; @@ -92,11 +95,11 @@ public long totalSpace() { } public long usableSpace() throws IOException { - return 0; + return fileStore.getUsableSpace(); } public long unallocatedSpace() throws IOException { - return 0; + return fileStore.getUnallocatedSpace(); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystem.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystem.java index 9196ffe59084..4d7f00561c79 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystem.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystem.java @@ -19,6 +19,7 @@ import java.nio.file.ClosedFileSystemException; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystem; @@ -26,6 +27,7 @@ import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; 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; @@ -39,6 +41,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -48,6 +51,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; +import static com.oracle.svm.core.jdk.ResourceFileSystemUtil.toRegexPattern; import static java.lang.Boolean.TRUE; import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -58,7 +62,6 @@ import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; -// TODO: Add support for encoding/decoding based on env parameter. public class ResourceFileSystem extends FileSystem { private static final String GLOB_SYNTAX = "glob"; @@ -81,7 +84,7 @@ public class ResourceFileSystem extends FileSystem { private static final byte[] ROOT_PATH = new byte[]{'/'}; private final IndexNode LOOKUP_KEY = new IndexNode(null, true); - LinkedHashMap inodes = new LinkedHashMap<>(10); + private final LinkedHashMap inodes = new LinkedHashMap<>(10); public ResourceFileSystem(ResourceFileSystemProvider provider, Path resourcePath, Map env) { this.provider = provider; @@ -94,7 +97,7 @@ public ResourceFileSystem(ResourceFileSystemProvider provider, Path resourcePath readAllEntries(); } - // returns true if there is a name=true/"true" setting in env + // Returns true if there is a name=true/"true" setting in env. private static boolean isTrue(Map env, String name) { return "true".equals(env.get(name)) || TRUE.equals(env.get(name)); } @@ -217,12 +220,6 @@ public Path getPath(String first, String... more) { return new ResourcePath(this, getBytes(path)); } - // TODO: Implementation. - public static String toRegexPattern(String globPattern) { - return null; - } - - // TODO: Test this method. @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { int pos = syntaxAndPattern.indexOf(':'); @@ -285,11 +282,10 @@ private void checkOptions(Set options) { } } - // TODO: Need enhancement. public SeekableByteChannel newByteChannel(byte[] path, Set options, FileAttribute[] attrs) throws IOException { checkOptions(options); if (options.contains(StandardOpenOption.WRITE) || options.contains(StandardOpenOption.APPEND)) { - beginRead(); // only need a read lock, the "update()" will obtain the write lock when the channel is closed + beginRead(); // Only need a read lock, the "update()" will obtain the write lock when the channel is closed. try { Entry e = getEntry(path); if (e != null) { @@ -672,24 +668,6 @@ private void buildNodeTree() { } } - // Temporary, for debug purpose. - void printTree() { - System.out.println(">>> Tree..."); - IndexNode node = inodes.get(LOOKUP_KEY.as(ROOT_PATH)); - node = node.child; - ArrayList queue = new ArrayList<>(); - queue.add(node); - while (!queue.isEmpty()) { - node = queue.remove(0); - while (node != null) { - System.out.println(getString(node.name) + " " + node.isDir); - queue.add(node.child); - node = node.sibling; - } - System.out.println("------"); - } - } - private InputStream getInputStream(Entry e) throws IOException { InputStream eis = null; if (e.type == Entry.NEW) { @@ -830,7 +808,6 @@ public FileChannel newFileChannel(byte[] path, Set options final Path tmpFile = isFCH ? e.file : getTempPathForEntry(path); final FileChannel fch = tmpFile.getFileSystem().provider().newFileChannel(tmpFile, options, attrs); final Entry target = isFCH ? e : new Entry(path, tmpFile, Entry.FILE_CH); - // TODO: Is there a better way to hook into the FileChannel's close method? return new FileChannel() { @Override @@ -935,6 +912,32 @@ protected void implCloseChannel() throws IOException { } } + Iterator iteratorOf(ResourcePath dir, DirectoryStream.Filter filter) throws IOException { + beginWrite(); + try { + ensureOpen(); + byte[] path = dir.getResolvedPath(); + IndexNode inode = getInode(path); + if (inode == null) { + throw new NotDirectoryException(getString(path)); + } + List list = new ArrayList<>(); + IndexNode child = inode.child; + while (child != null) { + byte[] childName = child.name; + ResourcePath childPath = new ResourcePath(this, childName, true); + Path childFileName = childPath.getFileName(); + Path resourcePath = dir.resolve(childFileName); + if (filter == null || filter.accept(resourcePath)) + list.add(resourcePath); + child = child.sibling; + } + return list.iterator(); + } finally { + endWrite(); + } + } + private static class IndexNode { private static final ThreadLocal cachedKey = new ThreadLocal<>(); @@ -1177,7 +1180,7 @@ private class EntryOutputStream extends FilterOutputStream { private int written; private boolean isClosed; - EntryOutputStream(Entry e, OutputStream os) throws IOException { + EntryOutputStream(Entry e, OutputStream os) { super(os); this.e = Objects.requireNonNull(e, "Entry is null!"); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemProvider.java index 5a24103e4bd2..74d768f4a905 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemProvider.java @@ -65,31 +65,34 @@ public String getScheme() { return "resource"; } - // TODO: Need further testing. @Override - public FileSystem newFileSystem(URI uri, Map env) { + public FileSystem newFileSystem(URI uri, Map env) throws IOException { Path path = uriToPath(uri); synchronized (filesystems) { - if (filesystems.containsKey(path)) { + Path realPath = path.toRealPath(); + if (filesystems.containsKey(realPath)) { throw new FileSystemAlreadyExistsException(); } ResourceFileSystem resourceFileSystem = new ResourceFileSystem(this, path, env); - filesystems.put(path, resourceFileSystem); + filesystems.put(realPath, resourceFileSystem); return resourceFileSystem; } } - // TODO: Implementation. @Override - public FileSystem newFileSystem(Path path, Map env) throws IOException { - return super.newFileSystem(path, env); + public FileSystem newFileSystem(Path path, Map env) { + return new ResourceFileSystem(this, path, env); } - // TODO: Need further testing. @Override public FileSystem getFileSystem(URI uri) { synchronized (filesystems) { - ResourceFileSystem resourceFileSystem = filesystems.get(uriToPath(uri)); + ResourceFileSystem resourceFileSystem; + try { + resourceFileSystem = filesystems.get(uriToPath(uri).toRealPath()); + } catch (IOException e) { + throw new FileSystemNotFoundException(); + } if (resourceFileSystem == null) { throw new FileSystemNotFoundException(); } @@ -102,7 +105,8 @@ public Path getPath(URI uri) { String spec = uri.getSchemeSpecificPart(); int sep = spec.indexOf("!/"); if (sep == -1) { - throw new IllegalArgumentException("URI: " + uri + " does not contain path info ex. resource:foo.jar!/bar.txt"); + throw new IllegalArgumentException("URI: " + uri + + " does not contain path info ex. resource:file:/foo.jar!/bar.txt"); } return getFileSystem(uri).getPath(spec.substring(sep + 1)); } @@ -149,10 +153,9 @@ public boolean isSameFile(Path path, Path path2) { @Override public boolean isHidden(Path path) { - return false; + return toResourcePath(path).isHidden(); } - // TODO: Implement FileStore. @Override public FileStore getFileStore(Path path) throws IOException { return toResourcePath(path).getFileStore(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemUtil.java new file mode 100644 index 000000000000..585ea1263d60 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourceFileSystemUtil.java @@ -0,0 +1,153 @@ +package com.oracle.svm.core.jdk; + +import java.util.regex.PatternSyntaxException; + +public class ResourceFileSystemUtil { + + private static final String regexMetaChars = ".^$+{[]|()"; + private static final String globMetaChars = "\\*?[{"; + private static final char EOL = 0; + + private static boolean isRegexMeta(char c) { + return regexMetaChars.indexOf(c) != -1; + } + + private static boolean isGlobMeta(char c) { + return globMetaChars.indexOf(c) != -1; + } + + private static char next(String glob, int i) { + if (i < glob.length()) { + return glob.charAt(i); + } + return EOL; + } + + public static String toRegexPattern(String globPattern) { + boolean inGroup = false; + StringBuilder regex = new StringBuilder("^"); + + int i = 0; + while (i < globPattern.length()) { + char c = globPattern.charAt(i++); + switch (c) { + case '\\': + // escape special characters + if (i == globPattern.length()) { + throw new PatternSyntaxException("No character to escape", globPattern, i - 1); + } + char next = globPattern.charAt(i++); + if (isGlobMeta(next) || isRegexMeta(next)) { + regex.append('\\'); + } + regex.append(next); + break; + case '/': + regex.append(c); + break; + case '[': + // don't match name separator in class + regex.append("[[^/]&&["); + if (next(globPattern, i) == '^') { + // escape the regex negation char if it appears + regex.append("\\^"); + i++; + } else { + // negation + if (next(globPattern, i) == '!') { + regex.append('^'); + i++; + } + // hyphen allowed at start + if (next(globPattern, i) == '-') { + regex.append('-'); + i++; + } + } + boolean hasRangeStart = false; + char last = 0; + while (i < globPattern.length()) { + c = globPattern.charAt(i++); + if (c == ']') { + break; + } + if (c == '/') { + throw new PatternSyntaxException("Explicit 'name separator' in class", globPattern, i - 1); + } + if (c == '\\' || c == '[' || c == '&' && next(globPattern, i) == '&') { + // escape '\', '[' or "&&" for regex class + regex.append('\\'); + } + regex.append(c); + + if (c == '-') { + if (!hasRangeStart) { + throw new PatternSyntaxException("Invalid range", globPattern, i - 1); + } + if ((c = next(globPattern, i++)) == EOL || c == ']') { + break; + } + if (c < last) { + throw new PatternSyntaxException("Invalid range", globPattern, i - 3); + } + regex.append(c); + hasRangeStart = false; + } else { + hasRangeStart = true; + last = c; + } + } + if (c != ']') { + throw new PatternSyntaxException("Missing ']", globPattern, i - 1); + } + regex.append("]]"); + break; + case '{': + if (inGroup) { + throw new PatternSyntaxException("Cannot nest groups", + globPattern, i - 1); + } + regex.append("(?:(?:"); + inGroup = true; + break; + case '}': + if (inGroup) { + regex.append("))"); + inGroup = false; + } else { + regex.append('}'); + } + break; + case ',': + if (inGroup) { + regex.append(")|(?:"); + } else { + regex.append(','); + } + break; + case '*': + if (next(globPattern, i) == '*') { + // crosses directory boundaries + regex.append(".*"); + i++; + } else { + // within directory boundary + regex.append("[^/]*"); + } + break; + case '?': + regex.append("[^/]"); + break; + default: + if (isRegexMeta(c)) { + regex.append('\\'); + } + regex.append(c); + } + } + if (inGroup) { + throw new PatternSyntaxException("Missing '}", globPattern, i - 1); + } + return regex.append('$').toString(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcePath.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcePath.java index 59e2b9ab7006..39df6c526e3b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcePath.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcePath.java @@ -165,8 +165,9 @@ public Path getName(int index) { int len; if (index == (offsets.length - 1)) { len = path.length - begin; - } else + } else { len = offsets[index + 1] - begin - 1; + } byte[] result = new byte[len]; System.arraycopy(path, begin, result, 0, len); @@ -317,7 +318,7 @@ public Path relativize(Path other) { if (p1.isAbsolute() != p2.isAbsolute()) { throw new IllegalArgumentException(); } - // Check how many segments are common + // Check how many segments are common. int nbNames1 = p1.getNameCount(); int nbNames2 = p2.getNameCount(); int l = Math.min(nbNames1, nbNames2); @@ -326,12 +327,12 @@ public Path relativize(Path other) { nbCommon++; } int nbUp = nbNames1 - nbCommon; - // Compute the resulting length + // Compute the resulting length. int length = nbUp * 3 - 1; if (nbCommon < nbNames2) { length += p2.path.length - p2.offsets[nbCommon] + 1; } - // Compute result + // Compute result. byte[] result = new byte[length]; int idx = 0; while (nbUp-- > 0) { @@ -341,7 +342,7 @@ public Path relativize(Path other) { result[idx++] = '/'; } } - // Copy remaining segments + // Copy remaining segments. if (nbCommon < nbNames2) { System.arraycopy(p2.path, p2.offsets[nbCommon], result, idx, p2.path.length - p2.offsets[nbCommon]); } @@ -805,7 +806,6 @@ private void copyToTarget(ResourcePath target, CopyOption... options) throws IOE } public InputStream newInputStream(OpenOption... options) throws IOException { - // TODO: Is this check sufficient? if (options.length > 0) { for (OpenOption opt : options) { if (opt != READ) @@ -825,4 +825,8 @@ public OutputStream newOutputStream(OpenOption... options) throws IOException { public FileChannel newFileChannel(Set options, FileAttribute... attrs) throws IOException { return fileSystem.newFileChannel(getResolvedPath(), options, attrs); } + + public boolean isHidden() { + return false; + } }