package jdk.internal.jrtfs;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.ClosedFileSystemException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.InvalidPathException;
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.ReadOnlyFileSystemException;
import java.nio.file.StandardOpenOption;
import java.nio.file.WatchService;
import java.nio.file.attribute.FileAttribute;
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.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import jdk.internal.jimage.ImageReader.Node;
import static java.util.stream.Collectors.toList;
class JrtFileSystem extends FileSystem {
private final JrtFileSystemProvider provider;
private final JrtPath rootPath = new JrtPath(this, "/");
private volatile boolean isOpen;
private volatile boolean isClosable;
private SystemImage image;
JrtFileSystem(JrtFileSystemProvider provider, Map<String, ?> env)
throws IOException
{
this.provider = provider;
this.image = SystemImage.open();
this.isOpen = true;
this.isClosable = env != null;
}
@Override
public boolean isOpen() {
return isOpen;
}
@Override
public void close() throws IOException {
if (!isClosable)
throw new UnsupportedOperationException();
cleanup();
}
@Override
@SuppressWarnings("deprecation")
protected void finalize() throws Throwable {
try {
cleanup();
} catch (IOException ignored) {}
}
@Override
public FileSystemProvider provider() {
return provider;
}
@Override
public Iterable<Path> getRootDirectories() {
return Collections.singleton(getRootPath());
}
@Override
public JrtPath getPath(String first, String... more) {
if (more.length == 0) {
return new JrtPath(this, first);
}
StringBuilder sb = new StringBuilder();
sb.append(first);
for (String path : more) {
if (!path.isEmpty()) {
if (sb.length() > 0) {
sb.append('/');
}
sb.append(path);
}
}
return new JrtPath(this, sb.toString());
}
@Override
public final boolean isReadOnly() {
return true;
}
@Override
public final UserPrincipalLookupService getUserPrincipalLookupService() {
throw new UnsupportedOperationException();
}
@Override
public final WatchService newWatchService() {
throw new UnsupportedOperationException();
}
@Override
public final Iterable<FileStore> getFileStores() {
return Collections.singleton(getFileStore(getRootPath()));
}
private static final Set<String> supportedFileAttributeViews
= Collections.unmodifiableSet(
new HashSet<String>(Arrays.asList("basic", "jrt")));
@Override
public final Set<String> supportedFileAttributeViews() {
return supportedFileAttributeViews;
}
@Override
public final String toString() {
return "jrt:/";
}
@Override
public final String getSeparator() {
return "/";
}
@Override
public PathMatcher getPathMatcher(String syntaxAndInput) {
int pos = syntaxAndInput.indexOf(':');
if (pos <= 0 || pos == syntaxAndInput.length()) {
throw new IllegalArgumentException("pos is " + pos);
}
String syntax = syntaxAndInput.substring(0, pos);
String input = syntaxAndInput.substring(pos + 1);
String expr;
if (syntax.equalsIgnoreCase("glob")) {
expr = JrtUtils.toRegexPattern(input);
} else if (syntax.equalsIgnoreCase("regex")) {
expr = input;
} else {
throw new UnsupportedOperationException("Syntax '" + syntax
+ "' not recognized");
}
final Pattern pattern = Pattern.compile(expr);
return (Path path) -> pattern.matcher(path.toString()).matches();
}
JrtPath resolveLink(JrtPath path) throws IOException {
Node node = checkNode(path);
if (node.isLink()) {
node = node.resolveLink();
return new JrtPath(this, node.getName());
}
return path;
}
JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options)
throws IOException {
Node node = checkNode(path);
if (node.isLink() && followLinks(options)) {
return new JrtFileAttributes(node.resolveLink(true));
}
return new JrtFileAttributes(node);
}
Iterator<Path> iteratorOf(JrtPath path, DirectoryStream.Filter<? super Path> filter)
throws IOException {
Node node = checkNode(path).resolveLink(true);
if (!node.isDirectory()) {
throw new NotDirectoryException(path.getName());
}
if (filter == null) {
return node.getChildren()
.stream()
.map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
.iterator();
}
return node.getChildren()
.stream()
.map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName())))
.filter(p -> { try { return filter.accept(p);
} catch (IOException x) {}
return false;
})
.iterator();
}
byte[] getFileContent(JrtPath path) throws IOException {
Node node = checkNode(path);
if (node.isDirectory()) {
throw new FileSystemException(path + " is a directory");
}
return image.getResource(node);
}
static ReadOnlyFileSystemException readOnly() {
return new ReadOnlyFileSystemException();
}
static boolean followLinks(LinkOption... options) {
if (options != null) {
for (LinkOption lo : options) {
Objects.requireNonNull(lo);
if (lo == LinkOption.NOFOLLOW_LINKS) {
return false;
} else {
throw new AssertionError("should not reach here");
}
}
}
return true;
}
static void checkOptions(Set<? extends OpenOption> options) {
for (OpenOption option : options) {
Objects.requireNonNull(option);
if (!(option instanceof StandardOpenOption)) {
throw new IllegalArgumentException(
"option class: " + option.getClass());
}
}
if (options.contains(StandardOpenOption.WRITE) ||
options.contains(StandardOpenOption.APPEND)) {
throw readOnly();
}
}
synchronized void cleanup() throws IOException {
if (isOpen) {
isOpen = false;
image.close();
image = null;
}
}
final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime)
throws IOException {
throw readOnly();
}
final void createDirectory(JrtPath jrtPath, FileAttribute<?>... attrs) throws IOException {
throw readOnly();
}
final void deleteFile(JrtPath jrtPath, boolean failIfNotExists)
throws IOException {
throw readOnly();
}
final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options)
throws IOException {
throw readOnly();
}
final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options)
throws IOException {
throw readOnly();
}
final FileChannel newFileChannel(JrtPath path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException {
throw new UnsupportedOperationException("newFileChannel");
}
final InputStream newInputStream(JrtPath path) throws IOException {
return new ByteArrayInputStream(getFileContent(path));
}
final SeekableByteChannel newByteChannel(JrtPath path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException {
checkOptions(options);
byte[] buf = getFileContent(path);
final ReadableByteChannel rbc
= Channels.newChannel(new ByteArrayInputStream(buf));
final long size = buf.length;
return new SeekableByteChannel() {
long read = 0;
@Override
public boolean isOpen() {
return rbc.isOpen();
}
@Override
public long position() throws IOException {
return read;
}
@Override
public SeekableByteChannel position(long pos)
throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(ByteBuffer dst) throws IOException {
int n = rbc.read(dst);
if (n > 0) {
read += n;
}
return n;
}
@Override
public SeekableByteChannel truncate(long size)
throws IOException {
throw new NonWritableChannelException();
}
@Override
public int write(ByteBuffer src) throws IOException {
throw new NonWritableChannelException();
}
@Override
public long size() throws IOException {
return size;
}
@Override
public void close() throws IOException {
rbc.close();
}
};
}
final JrtFileStore getFileStore(JrtPath path) {
return new JrtFileStore(path);
}
final void ensureOpen() throws IOException {
if (!isOpen()) {
throw new ClosedFileSystemException();
}
}
final JrtPath getRootPath() {
return rootPath;
}
boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException {
return checkNode(path1) == checkNode(path2);
}
boolean isLink(JrtPath path) throws IOException {
return checkNode(path).isLink();
}
boolean exists(JrtPath path) throws IOException {
try {
checkNode(path);
} catch (NoSuchFileException exp) {
return false;
}
return true;
}
boolean isDirectory(JrtPath path, boolean resolveLinks)
throws IOException {
Node node = checkNode(path);
return resolveLinks && node.isLink()
? node.resolveLink(true).isDirectory()
: node.isDirectory();
}
JrtPath toRealPath(JrtPath path, LinkOption... options)
throws IOException {
Node node = checkNode(path);
if (followLinks(options) && node.isLink()) {
node = node.resolveLink();
}
return new JrtPath(this, node.getName(), true);
}
private Node lookup(String path) {
try {
return image.findNode(path);
} catch (RuntimeException | IOException ex) {
throw new InvalidPathException(path, ex.toString());
}
}
private Node lookupSymbolic(String path) {
int i = 1;
while (i < path.length()) {
i = path.indexOf('/', i);
if (i == -1) {
break;
}
String prefix = path.substring(0, i);
Node node = lookup(prefix);
if (node == null) {
break;
}
if (node.isLink()) {
Node link = node.resolveLink(true);
String resPath = link.getName() + path.substring(i);
node = lookup(resPath);
return node != null ? node : lookupSymbolic(resPath);
}
i++;
}
return null;
}
Node checkNode(JrtPath path) throws IOException {
ensureOpen();
String p = path.getResolvedPath();
Node node = lookup(p);
if (node == null) {
node = lookupSymbolic(p);
if (node == null) {
throw new NoSuchFileException(p);
}
}
return node;
}
}