All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.truffle.api.TruffleFile Maven / Gradle / Ivy

Go to download

Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.pkl.thirdparty.truffle.api;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.AccessMode;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Random;
import java.util.Set;

import org.pkl.thirdparty.graalvm.polyglot.io.FileSystem;

import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.TruffleLanguage.Env;

/**
 * An abstract representation of a file used by Truffle languages.
 *
 * @since 19.0
 */
public final class TruffleFile {

    /**
     * The file's last modified time. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor LAST_MODIFIED_TIME = new AttributeDescriptor<>(AttributeGroup.BASIC, "lastModifiedTime", FileTime.class);

    /**
     * The file's last access time. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor LAST_ACCESS_TIME = new AttributeDescriptor<>(AttributeGroup.BASIC, "lastAccessTime", FileTime.class);

    /**
     * The file's creation time. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor CREATION_TIME = new AttributeDescriptor<>(AttributeGroup.BASIC, "creationTime", FileTime.class);

    /**
     * Represents the file a regular file. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor IS_REGULAR_FILE = new AttributeDescriptor<>(AttributeGroup.BASIC, "isRegularFile", Boolean.class);

    /**
     * Represents the file a directory. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor IS_DIRECTORY = new AttributeDescriptor<>(AttributeGroup.BASIC, "isDirectory", Boolean.class);

    /**
     * Represents the file a symbolic link. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor IS_SYMBOLIC_LINK = new AttributeDescriptor<>(AttributeGroup.BASIC, "isSymbolicLink", Boolean.class);

    /**
     * Represents the file a special file (device, named pipe). Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor IS_OTHER = new AttributeDescriptor<>(AttributeGroup.BASIC, "isOther", Boolean.class);

    /**
     * The file's size in bytes. Supported by all filesystems.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor SIZE = new AttributeDescriptor<>(AttributeGroup.BASIC, "size", Long.class);

    /**
     * The owner of the file. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_OWNER = new AttributeDescriptor<>(AttributeGroup.POSIX, "owner", UserPrincipal.class);

    /**
     * The group owner of the file. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_GROUP = new AttributeDescriptor<>(AttributeGroup.POSIX, "group", GroupPrincipal.class);

    /**
     * The file's Posix permissions. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor> UNIX_PERMISSIONS = new AttributeDescriptor<>(AttributeGroup.POSIX, Set.class, "permissions");

    /**
     * The file's mode containing the protection and file type bits. Supported only by UNIX native
     * filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_MODE = new AttributeDescriptor<>(AttributeGroup.UNIX, "mode", Integer.class);

    /**
     * The file's inode number. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_INODE = new AttributeDescriptor<>(AttributeGroup.UNIX, "ino", Long.class);

    /**
     * The id of a device containing the file. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_DEV = new AttributeDescriptor<>(AttributeGroup.UNIX, "dev", Long.class);

    /**
     * The id of a device represented by the file. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_RDEV = new AttributeDescriptor<>(AttributeGroup.UNIX, "rdev", Long.class);

    /**
     * The number of hard links. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_NLINK = new AttributeDescriptor<>(AttributeGroup.UNIX, "nlink", Integer.class);

    /**
     * The user id of file owner. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_UID = new AttributeDescriptor<>(AttributeGroup.UNIX, "uid", Integer.class);

    /**
     * The group id of file owner. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_GID = new AttributeDescriptor<>(AttributeGroup.UNIX, "gid", Integer.class);

    /**
     * The file's last status change time. Supported only by UNIX native filesystem.
     *
     * @since 19.0
     */
    public static final AttributeDescriptor UNIX_CTIME = new AttributeDescriptor<>(AttributeGroup.UNIX, "ctime", FileTime.class);

    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
    private static final int BUFFER_SIZE = 8192;

    private final FileSystemContext fileSystemContext;
    private final Path path;
    private final Path normalizedPath;
    private final boolean isEmptyPath;

    TruffleFile(final FileSystemContext fileSystemContext, final Path path) {
        this(fileSystemContext, path, path.normalize(), isEmptyPath(path));
    }

    TruffleFile(final FileSystemContext fileSystemContext, final Path path, final Path normalizedPath, boolean isEmptyPath) {
        Objects.requireNonNull(fileSystemContext, "FileSystemContext must not be null.");
        Objects.requireNonNull(path, "Path must not be null.");
        Objects.requireNonNull(normalizedPath, "NormalizedPath must not be null.");
        this.fileSystemContext = fileSystemContext;
        this.path = path;
        this.normalizedPath = normalizedPath;
        this.isEmptyPath = isEmptyPath;
    }

    Path getSPIPath() {
        return normalizedPath;
    }

    FileSystemContext getFileSystemContext() {
        return fileSystemContext;
    }

    FileSystem getSPIFileSystem() {
        return fileSystemContext.fileSystem;
    }

    /**
     * Tests existence of a file.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return {@code true} if the file exists
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean exists(LinkOption... options) {
        try {
            return checkAccess(EnumSet.noneOf(AccessMode.class), options);
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is readable. Checks if the file exists and this Java virtual machine has
     * enough privileges to read the file.
     *
     * @return {@code true} if the file exists and is readable
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isReadable() {
        try {
            return checkAccess(AccessMode.READ);
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is writable. Checks if the file exists and this Java virtual machine has
     * enough privileges to write to the file.
     *
     * @return {@code true} if the file exists and is writable
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isWritable() {
        try {
            return checkAccess(AccessMode.WRITE);
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is executable. Checks if the file exists and this Java virtual machine has
     * enough privileges to execute the file.
     *
     * @return {@code true} if the file exists and is executable
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isExecutable() {
        try {
            return checkAccess(AccessMode.EXECUTE);
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is a directory. Checks if the file exists and is a directory.
     *
     * @param options the options determining how the symbolic links should be handled, by default
     *            the symbolic links are followed.
     * @return {@code true} if the file exists and is a directory
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isDirectory(LinkOption... options) {
        try {
            return getAttributeImpl("isDirectory", Boolean.class, options);
        } catch (IOException ioe) {
            return false;
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is a regular file. Checks if the file exists and is a regular file.
     *
     * @param options the options determining how the symbolic links should be handled, by default
     *            the symbolic links are followed.
     * @return {@code true} if the file exists and is a regular file
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isRegularFile(LinkOption... options) {
        try {
            return getAttributeImpl("isRegularFile", Boolean.class, options);
        } catch (IOException ioe) {
            return false;
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if a file is a symbolic link. Checks if the file exists and is a symbolic link.
     *
     * @return {@code true} if the file exists and is a symbolic link
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isSymbolicLink() {
        try {
            return getAttributeImpl("isSymbolicLink", Boolean.class, LinkOption.NOFOLLOW_LINKS);
        } catch (IOException ioe) {
            return false;
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Tests if this {@link TruffleFile}'s path is absolute.
     *
     * @return {@code true} if the file path is absolute
     * @since 19.0
     */
    @TruffleBoundary
    public boolean isAbsolute() {
        try {
            return path.isAbsolute();
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the name of this {@link TruffleFile}.
     *
     * @return the name of file or directory denoted by this {@link TruffleFile}, or {@code null} if
     *         the file is a root directory
     * @since 19.0
     */
    @TruffleBoundary
    public String getName() {
        try {
            final Path fileName = path.getFileName();
            return fileName == null ? null : fileName.toString();
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the string representation of this {@link TruffleFile}.
     *
     * @return the path of this {@link TruffleFile}
     * @since 19.0
     */
    @TruffleBoundary
    public String getPath() {
        try {
            return path.toString();
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the absolute {@link URI} representation of this {@link TruffleFile}.
     *
     * @return the absolute {@link URI} representing the {@link TruffleFile}
     * @throws SecurityException if the {@link FileSystem} denied a resolution of an absolute path
     * @since 19.0
     */
    @TruffleBoundary
    public URI toUri() {
        try {
            final Path absolutePath = path.isAbsolute() ? path : toAbsolutePathImpl()[0];
            return absolutePath.toUri();
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns a relative {@link URI} representation of non absolute {@link TruffleFile}. If this
     * {@link TruffleFile} is relative it returns a relative {@link URI}. For an
     * {@link #isAbsolute() absolute} {@link TruffleFile} it returns an absolute {@link URI}.
     *
     * @return the {@link URI} representing the {@link TruffleFile}
     * @since 19.0
     */
    @TruffleBoundary
    public URI toRelativeUri() {
        if (isAbsolute()) {
            return toUri();
        }
        try {
            String strPath = "/".equals(fileSystemContext.fileSystem.getSeparator()) ? path.toString() : path.toString().replace(fileSystemContext.fileSystem.getSeparator(), "/");
            return new URI(null, null, strPath, null);
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Resolves this {@link TruffleFile} to absolute {@link TruffleFile}. If this
     * {@link TruffleFile} is already absolute this method returns this {@link TruffleFile} without
     * any resolution.
     *
     * @return the absolute {@link TruffleFile}
     * @throws SecurityException if the {@link FileSystem} denied a resolution of an absolute path
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile getAbsoluteFile() {
        if (path.isAbsolute()) {
            return this;
        }
        try {
            Path[] absolutePaths = toAbsolutePathImpl();
            return new TruffleFile(fileSystemContext, absolutePaths[0], absolutePaths[1], false);
        } catch (SecurityException se) {
            throw se;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns a {@link TruffleFile} representing the real (canonical) path of an existing file.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return a {@link TruffleFile} representing the absolute canonical path
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile getCanonicalFile(LinkOption... options) throws IOException {
        try {
            Path realPath = fileSystemContext.fileSystem.toRealPath(normalizedPath, options);
            return new TruffleFile(fileSystemContext, realPath, realPath, false);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns a parent {@link TruffleFile} or null when the file does not have a parent.
     *
     * @return the parent {@link TruffleFile}
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile getParent() {
        try {
            final Path parent = path.getParent();
            return parent == null ? null : new TruffleFile(fileSystemContext, parent);
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Resolves given string path representation against this {@link TruffleFile}.
     *
     * @param name the path to resolve
     * @return the resolved {@link TruffleFile}
     * @throws InvalidPathException if the path string contains non valid characters
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile resolve(String name) {
        try {
            return new TruffleFile(fileSystemContext, path.resolve(name));
        } catch (InvalidPathException ip) {
            throw ip;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Resolves given string path representation against the parent of this {@link TruffleFile}.
     *
     * @param name the path to resolve
     * @return the resolved {@link TruffleFile}
     * @throws InvalidPathException if the path string contains non valid characters
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile resolveSibling(String name) {
        try {
            return new TruffleFile(fileSystemContext, path.resolveSibling(name));
        } catch (InvalidPathException ip) {
            throw ip;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the size of a file.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return the file size in bytes
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public long size(LinkOption... options) throws IOException {
        try {
            return getAttributeImpl("size", Long.class, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the last modified time.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return the {@link FileTime} representing the time this {@link TruffleFile} was last modified
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public FileTime getLastModifiedTime(LinkOption... options) throws IOException {
        try {
            return getAttributeImpl("lastModifiedTime", FileTime.class, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Sets the file's last modified time.
     *
     * @param time the new value of the last modified time
     * @param options the options determining how the symbolic links should be handled
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void setLastModifiedTime(FileTime time, LinkOption... options) throws IOException {
        try {
            checkFileOperationPreconditions();
            fileSystemContext.fileSystem.setAttribute(normalizedPath, "lastModifiedTime", time, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the last access time.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return the {@link FileTime} representing the time this {@link TruffleFile} was last accessed
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public FileTime getLastAccessTime(LinkOption... options) throws IOException {
        try {
            return getAttributeImpl("lastAccessTime", FileTime.class, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Sets the file's last access time.
     *
     * @param time the new value of the last access time
     * @param options the options determining how the symbolic links should be handled
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void setLastAccessTime(FileTime time, LinkOption... options) throws IOException {
        try {
            checkFileOperationPreconditions();
            fileSystemContext.fileSystem.setAttribute(normalizedPath, "lastAccessTime", time, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the creation time.
     *
     * @param options the options determining how the symbolic links should be handled
     * @return the {@link FileTime} representing the time this {@link TruffleFile} was created
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public FileTime getCreationTime(LinkOption... options) throws IOException {
        try {
            return getAttributeImpl("creationTime", FileTime.class, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Sets the file's creation time.
     *
     * @param time the new value of the creation time
     * @param options the options determining how the symbolic links should be handled
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void setCreationTime(FileTime time, LinkOption... options) throws IOException {
        try {
            checkFileOperationPreconditions();
            fileSystemContext.fileSystem.setAttribute(normalizedPath, "creationTime", time, options);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns a collection of {@link TruffleFile}s in the directory denoted by this
     * {@link TruffleFile}.
     *
     * @return a collection of {@link TruffleFile}s located in the directory denoted by this
     *         {@link TruffleFile}
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public Collection list() throws IOException {
        try {
            checkFileOperationPreconditions();
            final Collection result = new ArrayList<>();
            final boolean normalized = isNormalized();
            try (DirectoryStream stream = fileSystemContext.fileSystem.newDirectoryStream(normalizedPath, AllFiles.INSTANCE)) {
                for (Path p : stream) {
                    result.add(new TruffleFile(
                                    fileSystemContext,
                                    normalized ? p : path.resolve(p.getFileName()),
                                    normalized ? p : normalizedPath.resolve(p.getFileName()),
                                    false));
                }
            }
            return result;
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Opens or creates a file returning a {@link SeekableByteChannel} to access the file content.
     * In most cases, the returned {@link SeekableByteChannel} should be closed using
     * try-with-resources construct. When the channel must keep being opened for the lifetime of a
     * context it should be {@link Env#registerOnDispose(Closeable) registered} for automatic close
     * on context dispose.
     *
     *
     * @param options the options specifying how the file should be opened
     * @param attributes the optional attributes to set atomically when creating the new file
     * @return the created {@link SeekableByteChannel}
     * @throws FileAlreadyExistsException if {@link StandardOpenOption#CREATE_NEW} option is set and
     *             a file already exists on given path
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException if the attributes contain an attribute which cannot be
     *             set atomically
     * @throws IllegalArgumentException in case of invalid options combination
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public SeekableByteChannel newByteChannel(Set options, FileAttribute... attributes) throws IOException {
        try {
            checkFileOperationPreconditions();
            return ByteChannelDecorator.create(fileSystemContext.fileSystem.newByteChannel(normalizedPath, options, attributes));
        } catch (IOException | UnsupportedOperationException | IllegalArgumentException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Opens a file for reading returning an {@link InputStream} to access the file content. In most
     * cases, the returned {@link InputStream} should be closed using try-with-resources construct.
     * When the stream must keep being opened for the lifetime of a context it should be
     * {@link Env#registerOnDispose(Closeable) registered} for automatic close on context dispose.
     *
     * @param options the options specifying how the file should be opened
     * @return the created {@link InputStream}
     * @throws IOException in case of IO error
     * @throws IllegalArgumentException in case of invalid options combination
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public InputStream newInputStream(OpenOption... options) throws IOException {
        final Set openOptions = new HashSet<>();
        if (options.length > 0) {
            for (OpenOption option : options) {
                if (option == StandardOpenOption.APPEND || option == StandardOpenOption.WRITE) {
                    throw new IllegalArgumentException(String.format("Option %s is not allowed.", option));
                }
                openOptions.add(option);
            }
        }
        return Channels.newInputStream(newByteChannel(openOptions));
    }

    /**
     * Opens a file for reading returning a {@link BufferedReader} to access the file content. In
     * most cases, the returned {@link BufferedReader} should be closed using try-with-resources
     * construct. When the reader must keep being opened for the lifetime of a context it should be
     * {@link Env#registerOnDispose(Closeable) registered} for automatic close on context dispose.
     *
     * @param charset the file encoding
     * @return the created {@link BufferedReader}
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public BufferedReader newBufferedReader(Charset charset) throws IOException {
        return new BufferedReader(new InputStreamReader(newInputStream(), charset));
    }

    /**
     * Opens a file for reading returning a {@link BufferedReader} to access the file content. See
     * {@link #newBufferedReader(Charset)}.
     *
     * @return the created {@link BufferedReader}
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public BufferedReader newBufferedReader() throws IOException {
        return newBufferedReader(StandardCharsets.UTF_8);
    }

    /**
     * Reads a file content as bytes.
     *
     * @return the created byte[]
     * @throws IOException in case of IO error
     * @throws OutOfMemoryError if an array of a file size cannot be allocated
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public byte[] readAllBytes() throws IOException {
        try (SeekableByteChannel channel = newByteChannel(Collections.emptySet())) {
            long sizel = channel.size();
            if (sizel > MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("File size is too large.");
            }
            try (InputStream in = Channels.newInputStream(channel)) {
                int size = (int) sizel;
                byte[] buf = new byte[size];
                int read = 0;
                while (true) {
                    int n;
                    while ((n = in.read(buf, read, size - read)) > 0) {
                        read += n;
                    }
                    if (n < 0 || (n = in.read()) < 0) {
                        break;
                    }
                    if (size << 1 <= MAX_BUFFER_SIZE) {
                        size = Math.max(size << 1, BUFFER_SIZE);
                    } else if (size == MAX_BUFFER_SIZE) {
                        throw new OutOfMemoryError("Required array size too large");
                    } else {
                        size = MAX_BUFFER_SIZE;
                    }
                    buf = Arrays.copyOf(buf, size);
                    buf[read++] = (byte) n;
                }
                return size == read ? buf : Arrays.copyOf(buf, read);
            }
        } catch (IOException | OutOfMemoryError | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Opens a file for writing returning an {@link OutputStream}. In most cases, the returned
     * {@link OutputStream} should be closed using try-with-resources construct. When the stream
     * must keep being opened for the lifetime of a context it should be
     * {@link Env#registerOnDispose(Closeable) registered} for automatic close on context dispose.
     *
     * @param options the options specifying how the file should be opened
     * @return the created {@link OutputStream}
     * @throws IOException in case of IO error
     * @throws IllegalArgumentException in case of invalid options combination
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public OutputStream newOutputStream(OpenOption... options) throws IOException {
        final Set openOptions = new HashSet<>(Math.max(options.length, 2) + 1);
        openOptions.add(StandardOpenOption.WRITE);
        if (options.length == 0) {
            openOptions.add(StandardOpenOption.CREATE);
            openOptions.add(StandardOpenOption.TRUNCATE_EXISTING);
        } else {
            for (OpenOption option : options) {
                if (option == StandardOpenOption.READ) {
                    throw new IllegalArgumentException(String.format("Option %s is not allowed.", option));
                }
                openOptions.add(option);
            }
        }
        return Channels.newOutputStream(newByteChannel(openOptions));
    }

    /**
     * Opens a file for writing returning an {@link BufferedWriter}. In most cases, the returned
     * {@link BufferedWriter} should be closed using try-with-resources construct. When the writer
     * must keep being opened for the lifetime of a context it should be
     * {@link Env#registerOnDispose(Closeable) registered} for automatic close on context dispose.
     *
     * @param charset the file encoding
     * @param options the options specifying how the file should be opened
     * @return the created {@link BufferedWriter}
     * @throws IOException in case of IO error
     * @throws IllegalArgumentException in case of invalid options combination
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public BufferedWriter newBufferedWriter(Charset charset, OpenOption... options) throws IOException {
        return new BufferedWriter(new OutputStreamWriter(newOutputStream(options), charset));
    }

    /**
     * Opens a file for writing returning an {@link BufferedWriter}. See
     * {@link #newBufferedWriter(Charset, OpenOption...)}.
     *
     * @param options the options specifying how the file should be opened
     * @return the created {@link BufferedWriter}
     * @throws IOException in case of IO error
     * @throws IllegalArgumentException in case of invalid options combination
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public BufferedWriter newBufferedWriter(OpenOption... options) throws IOException {
        return newBufferedWriter(StandardCharsets.UTF_8, options);
    }

    /**
     * Creates a new empty file.
     *
     * @param attributes the optional attributes to set atomically when creating the new file
     * @throws FileAlreadyExistsException if the file already exists on given path
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException if the attributes contain an attribute which cannot be
     *             set atomically
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void createFile(FileAttribute... attributes) throws IOException {
        newByteChannel(
                        EnumSet. of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW),
                        attributes).close();
    }

    /**
     * Creates a new directory.
     *
     * @param attributes the optional attributes to set atomically when creating the new file
     * @throws FileAlreadyExistsException if the file or directory already exists on given path
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException if the attributes contain an attribute which cannot be
     *             set atomically
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void createDirectory(FileAttribute... attributes) throws IOException {
        try {
            checkFileOperationPreconditions();
            createDirectoryImpl(normalizedPath, attributes);
        } catch (IOException | UnsupportedOperationException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Creates a directory and all nonexistent parent directories. Unlike the
     * {@link #createDirectory} the {@link FileAlreadyExistsException} is not thrown if the
     * directory already exists.
     *
     * @param attributes the optional attributes to set atomically when creating the new file
     * @throws FileAlreadyExistsException if a file (not a directory) already exists on given path
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException if the attributes contain an attribute which cannot be
     *             set atomically
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void createDirectories(FileAttribute... attributes) throws IOException {
        try {
            checkFileOperationPreconditions();
            try {
                createDirAndCheck(normalizedPath, attributes);
                return;
            } catch (FileAlreadyExistsException faee) {
                throw faee;
            } catch (IOException ioe) {
                // Try to create parents
            }
            SecurityException notAllowed = null;
            Path absolutePath = normalizedPath;
            try {
                absolutePath = fileSystemContext.fileSystem.toAbsolutePath(absolutePath);
            } catch (SecurityException se) {
                notAllowed = se;
            }
            Path lastExisting = findExisting(absolutePath);
            if (lastExisting == null) {
                if (notAllowed != null) {
                    throw notAllowed;
                } else {
                    throw new FileSystemException(path.toString(), null, "Cannot determine root");
                }
            }
            for (Path pathElement : lastExisting.relativize(absolutePath)) {
                lastExisting = lastExisting.resolve(pathElement);
                createDirAndCheck(lastExisting, attributes);
            }
        } catch (IOException | UnsupportedOperationException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Deletes the file. If the {@link TruffleFile} denotes a directory, the directory must be empty
     * before deleting. If the {@link TruffleFile} denotes a symbolic link the symbolic link itself
     * is deleted not its target.
     *
     * @throws NoSuchFileException if the file does not exist
     * @throws DirectoryNotEmptyException if the {@link TruffleFile} denotes a non empty directory
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void delete() throws IOException {
        try {
            checkFileOperationPreconditions();
            fileSystemContext.fileSystem.delete(normalizedPath);
        } catch (IOException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Moves or renames the file.
     *
     * @param target the path of a target file
     * @param options the options specifying how the move should be performed, see
     *            {@link StandardCopyOption}
     * @throws UnsupportedOperationException if {@code options} contains unsupported option
     * @throws FileAlreadyExistsException if the target path already exists and the {@code options}
     *             don't contain {@link StandardCopyOption#REPLACE_EXISTING} option
     * @throws DirectoryNotEmptyException if the {@code options} contain
     *             {@link StandardCopyOption#REPLACE_EXISTING} but the {@code target} is a non empty
     *             directory
     * @throws AtomicMoveNotSupportedException if the {@code options} contain
     *             {@link StandardCopyOption#ATOMIC_MOVE} but file cannot be moved atomically
     * @throws IOException in case of IO error
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void move(TruffleFile target, CopyOption... options) throws IOException {
        try {
            checkFileOperationPreconditions();
            target.checkFileOperationPreconditions();
            fileSystemContext.fileSystem.move(normalizedPath, target.normalizedPath, options);
        } catch (IOException | UnsupportedOperationException | SecurityException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Returns the file's Posix permissions.
     *
     * @param linkOptions the options determining how the symbolic links should be handled
     * @return the the file's Posix permissions
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException when the Posix permissions are not supported by
     *             filesystem
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    @SuppressWarnings("unchecked")
    public Set getPosixPermissions(LinkOption... linkOptions) throws IOException {
        try {
            return (Set) getAttributeImpl(normalizedPath, "posix:permissions", linkOptions);
        } catch (IOException | SecurityException | UnsupportedOperationException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * Sets the file's Posix permissions.
     *
     * @param permissions the Posix permissions to set
     * @param linkOptions the options determining how the symbolic links should be handled
     * @throws IOException in case of IO error
     * @throws UnsupportedOperationException when the Posix permissions are not supported by
     *             filesystem
     * @throws SecurityException if the {@link FileSystem} denied the operation
     * @since 19.0
     */
    @TruffleBoundary
    public void setPosixPermissions(Set permissions, LinkOption... linkOptions) throws IOException {
        try {
            checkFileOperationPreconditions();
            fileSystemContext.fileSystem.setAttribute(normalizedPath, "posix:permissions", permissions, linkOptions);
        } catch (IOException | SecurityException | UnsupportedOperationException e) {
            throw e;
        } catch (Throwable t) {
            throw wrapHostException(t);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @since 19.0
     */
    @Override
    @TruffleBoundary
    public String toString() {
        return path.toString();
    }

    /**
     * {@inheritDoc}
     *
     * @since 19.0
     */
    @Override
    @TruffleBoundary
    public int hashCode() {
        int res = 17;
        res = res * 31 + fileSystemContext.hashCode();
        res = res * 31 + path.hashCode();
        return res;
    }

    /**
     * {@inheritDoc}
     *
     * @since 19.0
     */
    @Override
    @TruffleBoundary
    public boolean equals(final Object other) {
        if (this == other) {
            return true;
        }
        if (other == null || other.getClass() != TruffleFile.class) {
            return false;
        }
        final TruffleFile otherFile = (TruffleFile) other;
        return path.equals(otherFile.path) && fileSystemContext.equals(otherFile.fileSystemContext);
    }

    /**
     * Returns a {@link TruffleFile} with removed redundant name elements in it's path.
     *
     * @return the normalized {@link TruffleFile}
     * @since 19.0
     */
    @TruffleBoundary
    public TruffleFile normalize() {
        if (isNormalized()) {
            return this;
        }
        Path newPath;
        if (!isEmptyPath && isEmptyPath(normalizedPath)) {
            newPath = fileSystemContext.fileSystem.parsePath(".");
        } else {
            newPath = normalizedPath;
        }
        return new TruffleFile(fileSystemContext, newPath, normalizedPath, isEmptyPath);
    }

    /**
     * Creates a {@link TruffleFile} with a relative path between this {@link TruffleFile} and a
     * given {@link TruffleFile}.
     * 

* Relativization is the inverse of {@link #resolve(java.lang.String) resolution}. * Relativization constructs a {@link TruffleFile} with relative path that when * {@link #resolve(java.lang.String) resolved} against this {@link TruffleFile} yields a * {@link TruffleFile} locating the same file as given {@link TruffleFile}. A relative path * cannot be constructed if only one of the {@link TruffleFile}s is {@link #isAbsolute() * absolute}. * * @param other the {@link TruffleFile} to relativize against this {@link TruffleFile} * @return the {@link TruffleFile} with relative path between this and {@code other} * {@link TruffleFile}s * @throws IllegalArgumentException when {@code other} cannot be relativized against this * {@link TruffleFile} * @since 19.0 */ @TruffleBoundary public TruffleFile relativize(TruffleFile other) { try { return new TruffleFile(fileSystemContext, path.relativize(other.path)); } catch (IllegalArgumentException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Tests if this {@link TruffleFile} path starts with the given path. The path {@code foo/bar} * starts with {@code foo} and {@code foo/bar} but does not start with {@code f}. * * @param other the path * @return {@code true} if this {@link TruffleFile} path starts with given path * @throws IllegalArgumentException if the path cannot be parsed. * @since 19.0 */ @TruffleBoundary public boolean startsWith(String other) { try { return path.startsWith(other); } catch (IllegalArgumentException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Tests if this {@link TruffleFile} path starts with the given {@link TruffleFile} path. The * path {@code foo/bar} starts with {@code foo} and {@code foo/bar} but does not start with * {@code f}. * * @param other the {@link TruffleFile} * @return {@code true} if this {@link TruffleFile} path starts with given {@link TruffleFile} * path * @since 19.0 */ @TruffleBoundary public boolean startsWith(TruffleFile other) { try { return path.startsWith(other.path); } catch (Throwable t) { throw wrapHostException(t); } } /** * Tests if this {@link TruffleFile} path ends with the given path. The path {@code foo/bar} * ends with {@code bar} and {@code foo/bar} but does not end with {@code r}. * * @param other the path * @return {@code true} if this {@link TruffleFile} path ends with given path * @throws IllegalArgumentException if the path cannot be parsed. * @since 19.0 */ @TruffleBoundary public boolean endsWith(String other) { try { return path.endsWith(other); } catch (IllegalArgumentException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Tests if this {@link TruffleFile} path ends with the given {@link TruffleFile} path. The path * {@code foo/bar} ends with {@code bar} and {@code foo/bar} but does not end with {@code r}. * * @param other the {@link TruffleFile} * @return {@code true} if this {@link TruffleFile} path ends with given {@link TruffleFile} * path * @since 19.0 */ @TruffleBoundary public boolean endsWith(TruffleFile other) { try { return path.endsWith(other.path); } catch (Throwable t) { throw wrapHostException(t); } } /** * Creates a new link to an existing target (optional operation). * * @param target the existing file to link * @throws FileAlreadyExistsException if the file or directory already exists on given path * @throws IOException in case of IO error * @throws UnsupportedOperationException if the {@link FileSystem} implementation does not * support links * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public void createLink(TruffleFile target) throws IOException { try { checkFileOperationPreconditions(); fileSystemContext.fileSystem.createLink(normalizedPath, target.normalizedPath); } catch (IOException | SecurityException | UnsupportedOperationException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Creates a symbolic link to a target (optional operation). * * @param target the target of the symbolic link * @param attrs the optional attributes to set atomically when creating the symbolic link * @throws FileAlreadyExistsException if the file or directory already exists on given path * @throws IOException in case of IO error * @throws UnsupportedOperationException if the {@link FileSystem} implementation does not * support symbolic links or the attributes contain an attribute which cannot be set * atomically * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public void createSymbolicLink(TruffleFile target, FileAttribute... attrs) throws IOException { try { checkFileOperationPreconditions(); fileSystemContext.fileSystem.createSymbolicLink(normalizedPath, target.path, attrs); } catch (IOException | SecurityException | UnsupportedOperationException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Reads the target of a symbolic link. * * @return the {@link TruffleFile} representing the target of the symbolic link * @throws NotLinkException if the {@link TruffleFile} is not a symbolic link * @throws IOException in case of IO error * @throws UnsupportedOperationException if the {@link FileSystem} implementation does not * support symbolic links * @throws SecurityException if the {@link FileSystem} denied the operation * @since 20.3 */ @TruffleBoundary public TruffleFile readSymbolicLink() throws IOException { try { checkFileOperationPreconditions(); return new TruffleFile(fileSystemContext, fileSystemContext.fileSystem.readSymbolicLink(normalizedPath)); } catch (IOException | SecurityException | UnsupportedOperationException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Returns the owner of the file. * * @param options the options determining how the symbolic links should be handled * @return the file owner * @throws IOException in case of IO error * @throws UnsupportedOperationException if the {@link FileSystem} implementation does not * support owner attribute * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public UserPrincipal getOwner(LinkOption... options) throws IOException { try { return getAttributeImpl("posix:owner", UserPrincipal.class, options); } catch (IOException | SecurityException | UnsupportedOperationException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Returns the group owner of the file. * * @param options the options determining how the symbolic links should be handled * @return the file owner * @throws IOException in case of IO error * @throws UnsupportedOperationException if the {@link FileSystem} implementation does not * support group owner attribute * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public GroupPrincipal getGroup(LinkOption... options) throws IOException { try { return getAttributeImpl("posix:group", GroupPrincipal.class, options); } catch (IOException | SecurityException | UnsupportedOperationException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Opens a directory, returning a {@link DirectoryStream} to iterate over all entries in the * directory. *

* The {@link TruffleFile}s returned by the directory stream's {@link DirectoryStream#iterator * iterator} are created as if by {@link #resolve(java.lang.String) resolving} the name of the * directory entry against this {@link TruffleFile}. *

* In most cases, the returned {@link DirectoryStream} should be closed using try-with-resources * construct. When the stream must keep being opened for the lifetime of a context it should be * {@link Env#registerOnDispose(Closeable) registered} for automatic close on context dispose. * When not using the try-with-resources construct, then the directory stream's * {@link DirectoryStream#close() close} method should be called after iteration is completed. *

* The code which iterates over all files can use simpler {@link #list()} method. * * @return a new opened {@link DirectoryStream} object * @throws IOException in case of IO error * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public DirectoryStream newDirectoryStream() throws IOException { try { checkFileOperationPreconditions(); return new TruffleFileDirectoryStream(this, fileSystemContext.fileSystem.newDirectoryStream(normalizedPath, AllFiles.INSTANCE)); } catch (IOException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Visits this {@link TruffleFile} file tree. * *

* This method walks a file tree rooted at this {@link TruffleFile}. The file tree traversal is * depth-first. The appropriate method on give {@link FileVisitor} is invoked for each * met file. File tree traversal completes when all accessible files in the tree have been * visited, a {@link FileVisitor} visit method returns a {@link FileVisitResult#TERMINATE} or a * {@link FileVisitor} method terminates due to an uncaught exception. * *

* For each file encountered this method attempts to read its * {@link java.nio.file.attribute.BasicFileAttributes}. If the file is not a directory then the * {@link FileVisitor#visitFile visitFile} method is invoked with the file attributes. If the * file attributes cannot be read, due to an I/O exception, then the * {@link FileVisitor#visitFileFailed visitFileFailed} method is invoked with the I/O exception. * *

* Where the file is a directory, and the directory could not be opened, then the * {@code visitFileFailed} method is invoked with the I/O exception, after which, the file tree * walk continues, by default, at the next sibling of the directory. * *

* Where the directory is opened successfully, then the entries in the directory, and their * descendants are visited. When all entries have been visited, or an I/O error occurs * during iteration of the directory, then the directory is closed and the visitor's * {@link FileVisitor#postVisitDirectory postVisitDirectory} method is invoked. The file tree * walk then continues, by default, at the next sibling of the directory. * *

* By default, symbolic links are not automatically followed by this method. If the * {@code options} parameter contains the {@link FileVisitOption#FOLLOW_LINKS FOLLOW_LINKS} * option then symbolic links are followed. * *

* The {@code maxDepth} parameter is the maximum number of levels of directories to visit. A * value of {@code 0} means that only the starting file is visited. The {@code visitFile} method * is invoked for all files, including directories, encountered at {@code maxDepth}, unless the * basic file attributes cannot be read, in which case the {@code * visitFileFailed} method is invoked. * * @param visitor the {@link FileVisitor} to invoke for each file * @param maxDepth the maximum number of directory levels to visit, {@link Integer#MAX_VALUE * MAX_VALUE} may be used to indicate that all levels should be visited. * @param options the options configuring the file tree traversal * @throws IllegalArgumentException if the {@code maxDepth} parameter is negative * @throws IOException in case of IO error * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public void visit(FileVisitor visitor, int maxDepth, FileVisitOption... options) throws IOException { if (maxDepth < 0) { throw new IllegalArgumentException("The maxDepth must be >= 0"); } try { checkFileOperationPreconditions(); Walker walker = new Walker(this, maxDepth, options); for (Walker.Event event : walker) { FileVisitResult result; switch (event.type) { case PRE_VISIT_DIRECTORY: result = visitor.preVisitDirectory(event.file, event.attrs); if (result == FileVisitResult.SKIP_SUBTREE || result == FileVisitResult.SKIP_SIBLINGS) { walker.pop(); } break; case VISIT: IOException ioe = event.ioe; if (ioe == null) { result = visitor.visitFile(event.file, event.attrs); } else { result = visitor.visitFileFailed(event.file, ioe); } break; case POST_VISIT_DIRECTORY: result = visitor.postVisitDirectory(event.file, event.ioe); break; default: throw new IllegalStateException("Unexpected event type: " + event.type); } if (Objects.requireNonNull(result) != FileVisitResult.CONTINUE) { switch (result) { case SKIP_SIBLINGS: walker.skipRemainingSiblings(); break; case TERMINATE: return; } } } } catch (IOException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Copies the file. When the file is a directory the copy creates an empty directory in the * target location, the directory entries are not copied. This method can be used with the * {@link #visit visit} method to copy the whole sub-tree. * * @param target the path of a target file * @param options the options specifying how the copy should be performed, see * {@link StandardCopyOption} * @throws UnsupportedOperationException if {@code options} contains unsupported option * @throws FileAlreadyExistsException if the target path already exists and the {@code options} * don't contain {@link StandardCopyOption#REPLACE_EXISTING} option * @throws DirectoryNotEmptyException if the {@code options} contain * {@link StandardCopyOption#REPLACE_EXISTING} but the {@code target} is a non empty * directory * @throws IOException in case of IO error * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public void copy(TruffleFile target, CopyOption... options) throws IOException { try { checkFileOperationPreconditions(); target.checkFileOperationPreconditions(); fileSystemContext.fileSystem.copy(normalizedPath, target.normalizedPath, options); } catch (IOException | UnsupportedOperationException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Returns the {@link TruffleFile file} MIME type. * * @return the MIME type or {@code null} if the MIME type is not recognized * @throws IOException in case of IO error * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 * @deprecated use {@link #detectMimeType()} */ @TruffleBoundary @Deprecated(since = "20.2") public String getMimeType() throws IOException { return detectMimeType(null); } /** * Detects the {@link TruffleFile file} MIME type. * * @return the MIME type or {@code null} if the MIME type is not recognized * @throws SecurityException if the {@link FileSystem} denied the operation * @since 22.2 */ @TruffleBoundary public String detectMimeType() { return detectMimeType(null); } /** * Tests if this and the given {@link TruffleFile} refer to the same physical file. If both * {@code TruffleFile} objects are {@link TruffleFile#equals(Object) equal} then this method * returns {@code true} without any checks. If the {@link TruffleFile}s have different * filesystems then this method returns {@code false}. Otherwise, this method checks if both * {@link TruffleFile}s refer to the same physical file. Depending on the {@link FileSystem} * implementation it may require to read the files attributes. This implies: *

    *
  • Public and Internal files with disabled IO are never the same. *
  • Public and Internal files with allowed IO are potentially the same. *
  • Files created by different languages are potentially the same. *
  • Files created during the Context pre-initialization and files created during Context * execution are potentially the same. *
* * @param other the other {@link TruffleFile} * @return {@code true} if this and the given {@link TruffleFile} refer to the same physical * file * @throws IOException in case of IO error * @throws SecurityException if the {@link FileSystem} denied the operation * @since 20.2.0 */ @TruffleBoundary public boolean isSameFile(TruffleFile other, LinkOption... options) throws IOException { try { checkFileOperationPreconditions(); other.checkFileOperationPreconditions(); if (this.equals(other)) { return true; } if (!fileSystemContext.fileSystem.equals(other.fileSystemContext.fileSystem)) { return false; } return fileSystemContext.fileSystem.isSameFile(normalizedPath, other.normalizedPath, options); } catch (IOException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } @TruffleBoundary String detectMimeType(Set validMimeTypes) { try { if (validMimeTypes != null && validMimeTypes.isEmpty()) { return null; } checkFileOperationPreconditions(); String result = fileSystemContext.fileSystem.getMimeType(normalizedPath); if (result != null && (validMimeTypes == null || validMimeTypes.contains(result))) { return result; } for (FileTypeDetector detector : fileSystemContext.getFileTypeDetectors(validMimeTypes)) { try { result = detector.findMimeType(this); if (result != null && (validMimeTypes == null || validMimeTypes.contains(result))) { return result; } } catch (IOException ioe) { continue; } } return null; } catch (IOException ioe) { // invalid path return null; } catch (SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } Charset detectEncoding(String mimeType) { try { assert mimeType != null; checkFileOperationPreconditions(); Charset result = fileSystemContext.fileSystem.getEncoding(normalizedPath); if (result != null) { return result; } for (FileTypeDetector detector : fileSystemContext.getFileTypeDetectors(Collections.singleton(mimeType))) { try { result = detector.findEncoding(this); if (result != null) { return result; } } catch (IOException ioe) { continue; } } return null; } catch (IOException ioe) { // invalid path return null; } catch (UnsupportedOperationException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } static TruffleFile createTempFile(TruffleFile targetDirectory, String prefix, String suffix, boolean dir, FileAttribute... attrs) throws IOException { Objects.requireNonNull(targetDirectory, "TargetDirectory must be non null."); targetDirectory.checkFileOperationPreconditions(); String usePrefix = prefix != null ? prefix : ""; String useSuffix = suffix != null ? suffix : (dir ? "" : ".tmp"); while (true) { TruffleFile target; try { target = createUniquePath(targetDirectory, usePrefix, useSuffix); if (!target.exists()) { if (dir) { target.createDirectory(attrs); } else { target.createFile(attrs); } return target; } } catch (InvalidPathException e) { throw new IllegalArgumentException("Prefix (" + usePrefix + ") or suffix (" + useSuffix + ") are not valid file name components"); } catch (FileAlreadyExistsException e) { // retry with different name } } } private void checkFileOperationPreconditions() throws IOException { if (isEmptyPath) { throw new NoSuchFileException(""); } } private static TruffleFile createUniquePath(TruffleFile targetDirectory, String prefix, String suffix) { long n = TempFileRandomHolder.getRandom().nextLong(); n = n == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(n); String name = prefix + Long.toString(n) + suffix; TruffleFile result = targetDirectory.resolve(name); if (!targetDirectory.equals(result.getParent())) { throw new InvalidPathException(name, "Must be a simple name"); } return result; } private static boolean isEmptyPath(Path path) { if (path.isAbsolute()) { return false; } // On Windows all '', '/', '\' represent an empty path. // The path for '' has a single empty name as on Unix. // The path for '/' or '\' have no name. switch (path.getNameCount()) { case 0: return true; case 1: return path.getName(0).toString().isEmpty(); default: return false; } } private static final class TempFileRandomHolder { private static Random RANDOM; static Random getRandom() { if (RANDOM == null) { /* We don't want RANDOM seeds in the image heap. */ RANDOM = new Random(); } return RANDOM; } } private static final class AttributeGroup { static final AttributeGroup BASIC = new AttributeGroup("basic", null); static final AttributeGroup POSIX = new AttributeGroup("posix", BASIC); static final AttributeGroup UNIX = new AttributeGroup("unix", POSIX); final String name; private final AttributeGroup parent; AttributeGroup(String name, AttributeGroup parent) { this.name = name; this.parent = parent; } boolean contains(AttributeGroup other) { if (name.equals(other.name)) { return true; } if (parent != null) { return parent.contains(other); } return false; } @Override public String toString() { return name; } } /** * Represents a file's attribute. *

* Not all attributes are supported by all filesystems. When the filesystem does not support the * attribute the attribute read fails with {@link UnsupportedOperationException}. *

* Attributes can be read one by one by the * {@link TruffleFile#getAttribute(org.pkl.thirdparty.truffle.api.TruffleFile.AttributeDescriptor, java.nio.file.LinkOption...) * getAttribute} method or as a bulk read operation by * {@link TruffleFile#getAttributes(java.util.Collection, java.nio.file.LinkOption...) * getAttributes} method. When more attributes are needed the bulk read provides better * performance. * * @since 19.0 */ public static final class AttributeDescriptor { final AttributeGroup group; final String name; final Class clazz; AttributeDescriptor(AttributeGroup group, String name, Class clazz) { this.group = group; this.name = name; this.clazz = clazz; } @SuppressWarnings("unchecked") AttributeDescriptor(AttributeGroup group, Class rawType, String name) { this.group = group; this.clazz = (Class) rawType; this.name = name; } /** * * {@inheritDoc} * * @since 19.0 */ @Override public String toString() { return group + ":" + name; } } /** * A view over file's attributes values obtained by * {@link TruffleFile#getAttributes(java.util.Collection, java.nio.file.LinkOption...) * getAttributes}. * * @since 19.0 */ public static final class Attributes { private final Set> queriedAttributes; private final Map delegate; Attributes(Set> queriedAttributes, Map delegate) { assert queriedAttributes != null; assert delegate != null; this.queriedAttributes = queriedAttributes; this.delegate = delegate; } /** * Returns the attribute value. * * @param descriptor the attribute to obtain value for * @return the attribute value, may return {@code null} if the filesystem doesn't support * the attribute. * @throws IllegalArgumentException if the view was not created for the given attribute * @since 19.0 */ public T get(AttributeDescriptor descriptor) { Object value = delegate.get(descriptor.name); if (value != null) { return descriptor.clazz.cast(value); } else if (queriedAttributes.contains(descriptor)) { return null; } else { throw new IllegalArgumentException("The attribute: " + descriptor.toString() + " was not queried."); } } } /** * Reads a single file's attribute. * * @param attribute the attribute to read * @param linkOptions the options determining how the symbolic links should be handled * @return the attribute value * @throws IOException in case of IO error * @throws UnsupportedOperationException when the filesystem does not support required * attribute. * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public T getAttribute(AttributeDescriptor attribute, LinkOption... linkOptions) throws IOException { try { return getAttributeImpl(createAttributeString(attribute.group, Collections.singleton(attribute.name)), attribute.clazz, linkOptions); } catch (IOException | UnsupportedOperationException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Sets a single file's attribute. * * @param attribute the attribute to set * @param value the attribute value * @param linkOptions the options determining how the symbolic links should be handled * @throws IOException in case of IO error * @throws UnsupportedOperationException when the filesystem does not support given attribute * @throws IllegalArgumentException when the attribute value has an inappropriate value * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.1.0 */ @TruffleBoundary public void setAttribute(AttributeDescriptor attribute, T value, LinkOption... linkOptions) throws IOException { try { checkFileOperationPreconditions(); fileSystemContext.fileSystem.setAttribute( normalizedPath, createAttributeString(attribute.group, Collections.singleton(attribute.name)), value, linkOptions); } catch (IOException | UnsupportedOperationException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * Reads file's attributes as a bulk operation. * * @param attributes the attributes to read * @param linkOptions the options determining how the symbolic links should be handled * @return the {@link Attributes attributes view} * @throws IllegalArgumentException when no attributes are given * @throws IOException in case of IO error * @throws UnsupportedOperationException when the filesystem does not support some of the * required attributes. * @throws SecurityException if the {@link FileSystem} denied the operation * @since 19.0 */ @TruffleBoundary public Attributes getAttributes(Collection> attributes, LinkOption... linkOptions) throws IOException { Set> useAttributes = new HashSet<>(attributes); if (useAttributes.isEmpty()) { throw new IllegalArgumentException("No descriptors given."); } try { checkFileOperationPreconditions(); AttributeGroup group = null; List attributeNames = new ArrayList<>(); for (AttributeDescriptor descriptor : useAttributes) { if (group == null || !group.contains(descriptor.group)) { group = descriptor.group; } attributeNames.add(descriptor.name); } Map map = fileSystemContext.fileSystem.readAttributes(normalizedPath, createAttributeString(group, attributeNames), linkOptions); return new Attributes(useAttributes, map); } catch (IOException | UnsupportedOperationException | SecurityException e) { throw e; } catch (Throwable t) { throw wrapHostException(t); } } /** * A detector for finding {@link TruffleFile file}'s MIME type and encoding. * *

* The means by which the detector determines the MIME is highly implementation specific. A * simple implementation might detect the MIME type using {@link TruffleFile file} extension. In * other cases, the content of {@link TruffleFile file} needs to be examined to guess its file * type. *

* The implementations are registered using * {@link TruffleLanguage.Registration#fileTypeDetectors() TruffleLanguage registration}. * * @see TruffleFile#detectMimeType() * @see TruffleLanguage.Registration#fileTypeDetectors() * @since 19.0 */ public interface FileTypeDetector { /** * Finds a MIME type for given {@link TruffleFile}. * * @param file the {@link TruffleFile file} to find a MIME type for * @return the MIME type or {@code null} if the MIME type is not recognized * @throws IOException of an I/O error occurs * @throws SecurityException if the implementation requires an access the file and the * {@link FileSystem} denies the operation * @since 19.0 */ String findMimeType(TruffleFile file) throws IOException; /** * For a file containing an encoding information returns the encoding. * * @param file the {@link TruffleFile file} to find an encoding for. It's guaranteed that * the {@code file} has a MIME type supported by the language registering this * {@link FileTypeDetector}. * @return the file encoding or {@code null} if the file does not provide encoding * @throws IOException of an I/O error occurs * @throws SecurityException if the {@link FileSystem} denies the file access * @since 19.0 */ Charset findEncoding(TruffleFile file) throws IOException; } static final class FileSystemContext { // instance of PolyglotContextConfig or PolyglotSource.EmbedderFileSystemContext final Object engineObject; private volatile Map> fileTypeDetectors; final FileSystem fileSystem; FileSystemContext(Object engineFileSystemContext, FileSystem fileSystem) { Objects.requireNonNull(engineFileSystemContext); Objects.requireNonNull(fileSystem); this.engineObject = engineFileSystemContext; this.fileSystem = fileSystem; } Iterable getFileTypeDetectors(Set mimeTypes) { Map> result = fileTypeDetectors; if (result == null) { result = LanguageAccessor.engineAccess().getEngineFileTypeDetectors(engineObject); assert result != null; fileTypeDetectors = result; } Set filtered = new HashSet<>(); for (Map.Entry> e : result.entrySet()) { if (mimeTypes == null || mimeTypes.contains(e.getKey())) { filtered.addAll(e.getValue()); } } return filtered; } } private static String createAttributeString(AttributeGroup group, Iterable attributeNames) { String joinedNames = String.join(",", attributeNames); return group == AttributeGroup.BASIC ? joinedNames : group.name + ':' + joinedNames; } private boolean isNormalized() { return path == normalizedPath || path.equals(normalizedPath); } private Path[] toAbsolutePathImpl() { Path absolute = fileSystemContext.fileSystem.toAbsolutePath(path); Path normalizedAbsolute = fileSystemContext.fileSystem.toAbsolutePath(normalizedPath).normalize(); return new Path[]{absolute, normalizedAbsolute}; } private boolean checkAccess(AccessMode... modes) { final Set modesSet = EnumSet.noneOf(AccessMode.class); Collections.addAll(modesSet, modes); return checkAccess(modesSet); } private boolean checkAccess(Set modes, LinkOption... linkOptions) { try { checkFileOperationPreconditions(); fileSystemContext.fileSystem.checkAccess(normalizedPath, modes, linkOptions); return true; } catch (IOException ioe) { return false; } } private T getAttributeImpl(String attribute, Class type, LinkOption... options) throws IOException { return getAttributeImpl(normalizedPath, attribute, type, options); } private T getAttributeImpl(Path forPath, String attribute, Class type, LinkOption... options) throws IOException { final Object value = getAttributeImpl(forPath, attribute, options); return value == null ? null : type.cast(value); } private Object getAttributeImpl(final Path forPath, final String attribute, final LinkOption... options) throws IOException { checkFileOperationPreconditions(); final Map map = fileSystemContext.fileSystem.readAttributes(forPath, attribute, options); final int index = attribute.indexOf(':'); final String key = index < 0 ? attribute : attribute.substring(index + 1); return map.get(key); } private Path createDirectoryImpl(Path dir, FileAttribute... attrs) throws IOException { fileSystemContext.fileSystem.createDirectory(dir, attrs); return dir; } private Path createDirAndCheck(Path dir, FileAttribute... attrs) throws IOException { try { return createDirectoryImpl(dir, attrs); } catch (FileAlreadyExistsException faee) { try { if (getAttributeImpl(dir, "isDirectory", Boolean.class, LinkOption.NOFOLLOW_LINKS)) { return dir; } else { throw faee; } } catch (IOException ioe) { throw faee; } } } private Path findExisting(Path forPath) throws IOException { final Set mode = EnumSet.noneOf(AccessMode.class); for (Path p = forPath.getParent(); p != null; p = p.getParent()) { try { fileSystemContext.fileSystem.checkAccess(p, mode); return p; } catch (NoSuchFileException nsfe) { // Still does not exist } } return null; } private RuntimeException wrapHostException(T t) { throw wrapHostException(t, fileSystemContext); } static RuntimeException wrapHostException(T t, FileSystemContext fsContext) { if (LanguageAccessor.engineAccess().isInternal(fsContext.engineObject, fsContext.fileSystem)) { throw Env.engineToLanguageException(t); } throw LanguageAccessor.engineAccess().wrapHostException(null, LanguageAccessor.engineAccess().getCurrentHostContext(), t); } private static final class AllFiles implements DirectoryStream.Filter { static final DirectoryStream.Filter INSTANCE = new AllFiles(); private AllFiles() { } @Override public boolean accept(Path entry) throws IOException { return true; } } private static final class ByteChannelDecorator implements SeekableByteChannel { private final SeekableByteChannel delegate; ByteChannelDecorator(final SeekableByteChannel delegate) { this.delegate = delegate; } @Override public int read(ByteBuffer dst) throws IOException { return delegate.read(dst); } @Override public int write(ByteBuffer src) throws IOException { return delegate.write(src); } @Override public boolean isOpen() { return delegate.isOpen(); } @Override public void close() throws IOException { delegate.close(); } @Override public long position() throws IOException { return delegate.position(); } @Override public SeekableByteChannel position(long newPosition) throws IOException { delegate.position(newPosition); return this; } @Override public long size() throws IOException { return delegate.size(); } @Override public SeekableByteChannel truncate(long size) throws IOException { delegate.truncate(size); return this; } static SeekableByteChannel create(final SeekableByteChannel delegate) { Objects.requireNonNull(delegate, "Delegate must be non null."); return new ByteChannelDecorator(delegate); } } private static final class TruffleFileDirectoryStream implements DirectoryStream { private final TruffleFile directory; private final DirectoryStream delegate; TruffleFileDirectoryStream(TruffleFile directory, DirectoryStream delegate) { this.directory = directory; this.delegate = delegate; } @Override public Iterator iterator() { try { final Iterator delegateIterator = delegate.iterator(); final boolean normalized = directory.isNormalized(); return new IteratorImpl(directory, delegateIterator, normalized); } catch (Throwable t) { throw directory.wrapHostException(t); } } @Override public void close() throws IOException { try { this.delegate.close(); } catch (IOException e) { throw e; } catch (Throwable t) { throw directory.wrapHostException(t); } } private static final class IteratorImpl implements Iterator { private final TruffleFile directory; private final Iterator delegateIterator; private final boolean normalized; IteratorImpl(TruffleFile directory, Iterator delegateIterator, boolean normalized) { this.directory = directory; this.delegateIterator = delegateIterator; this.normalized = normalized; } @Override public boolean hasNext() { try { return delegateIterator.hasNext(); } catch (Throwable t) { throw directory.wrapHostException(t); } } @Override public TruffleFile next() { try { Path path = delegateIterator.next(); return new TruffleFile( directory.fileSystemContext, normalized ? path : directory.path.resolve(path.getFileName()), normalized ? path : directory.normalizedPath.resolve(path.getFileName()), false); } catch (DirectoryIteratorException e) { throw e; } catch (Throwable t) { throw directory.wrapHostException(t); } } } } private static final class Walker implements Iterable { private final TruffleFile start; private final int maxDepth; private final boolean followSymLinks; private IteratorImpl currentIterator; Walker(TruffleFile start, int maxDepth, FileVisitOption... options) { this.start = start; this.maxDepth = maxDepth; boolean followSymLinksTmp = false; for (FileVisitOption option : options) { if (option == FileVisitOption.FOLLOW_LINKS) { followSymLinksTmp = true; break; } } this.followSymLinks = followSymLinksTmp; } @Override public Iterator iterator() { if (currentIterator != null) { throw new IllegalStateException("Multiple iterators are not allowed."); } currentIterator = new IteratorImpl(start, maxDepth, followSymLinks); return currentIterator; } void pop() { if (!currentIterator.stack.isEmpty()) { try { currentIterator.stack.removeLast().close(); } catch (IOException ignored) { } } } void skipRemainingSiblings() { if (!currentIterator.stack.isEmpty()) { currentIterator.stack.peekLast().setSkipped(true); } } static class Event { final Type type; final TruffleFile file; final IOException ioe; final BasicFileAttributes attrs; Event(Type type, TruffleFile file, BasicFileAttributes attrs) { this.type = type; this.file = file; this.attrs = attrs; this.ioe = null; } Event(Type type, TruffleFile file, IOException ioe) { this.type = type; this.file = file; this.attrs = null; this.ioe = ioe; } enum Type { PRE_VISIT_DIRECTORY, VISIT, POST_VISIT_DIRECTORY } } private static class IteratorImpl implements Iterator { private final int maxDepth; private final LinkOption[] linkOptions; private final Deque

stack; private Event current; IteratorImpl(TruffleFile start, int maxDepth, boolean followSymLinks) { this.maxDepth = maxDepth; this.linkOptions = followSymLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; this.stack = new ArrayDeque<>(); this.current = enter(start); } @Override public boolean hasNext() { if (current == null) { Dir top = stack.peekLast(); if (top != null) { IOException ioe = null; TruffleFile file = null; if (!top.isSkipped()) { try { file = top.next(); } catch (DirectoryIteratorException x) { ioe = x.getCause(); } } if (file == null) { try { top.close(); } catch (IOException e) { if (ioe == null) { ioe = e; } else { ioe.addSuppressed(e); } } stack.removeLast(); current = new Event(Event.Type.POST_VISIT_DIRECTORY, top.directory, ioe); } else { current = enter(file); } } } return current != null; } @Override public Event next() { if (current == null) { throw new NoSuchElementException(); } Event res = current; current = null; return res; } private Event enter(TruffleFile file) { BasicFileAttributes attrs; try { attrs = new BasicFileAttributesImpl(file.fileSystemContext.fileSystem.readAttributes(file.normalizedPath, "*", linkOptions)); } catch (IOException ioe) { return new Event(Event.Type.VISIT, file, ioe); } int currentDepth = stack.size(); if (currentDepth >= maxDepth || !attrs.isDirectory()) { return new Event(Event.Type.VISIT, file, attrs); } DirectoryStream stream = null; try { stream = file.newDirectoryStream(); } catch (IOException ioe) { return new Event(Event.Type.VISIT, file, ioe); } stack.addLast(new Dir(file, stream)); return new Event(Event.Type.PRE_VISIT_DIRECTORY, file, attrs); } private static final class Dir implements Closeable { final TruffleFile directory; final DirectoryStream stream; private final Iterator iterator; private boolean skipped; Dir(TruffleFile directory, DirectoryStream stream) { this.directory = directory; this.stream = stream; this.iterator = stream.iterator(); } void setSkipped(boolean value) { skipped = value; } boolean isSkipped() { return skipped; } TruffleFile next() { return iterator.hasNext() ? iterator.next() : null; } @Override public void close() throws IOException { stream.close(); } } private static final class BasicFileAttributesImpl implements BasicFileAttributes { private Map attrsMap; BasicFileAttributesImpl(Map attrsMap) { this.attrsMap = Objects.requireNonNull(attrsMap); } @Override public FileTime lastModifiedTime() { return (FileTime) attrsMap.get("lastModifiedTime"); } @Override public FileTime lastAccessTime() { return (FileTime) attrsMap.get("lastAccessTime"); } @Override public FileTime creationTime() { return (FileTime) attrsMap.get("creationTime"); } @Override public boolean isRegularFile() { return (boolean) attrsMap.get("isRegularFile"); } @Override public boolean isDirectory() { return (boolean) attrsMap.get("isDirectory"); } @Override public boolean isSymbolicLink() { return (boolean) attrsMap.get("isSymbolicLink"); } @Override public boolean isOther() { return (boolean) attrsMap.get("isOther"); } @Override public long size() { return (long) attrsMap.get("size"); } @Override public Object fileKey() { return attrsMap.get("fileKey"); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy