com.oracle.truffle.api.TruffleFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of truffle-api Show documentation
Show all versions of truffle-api Show documentation
Truffle is a multi-language framework for executing dynamic languages
that achieves high performance when combined with Graal.
The newest version!
/*
* Copyright (c) 2018, 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 com.oracle.truffle.api;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
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.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystemException;
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.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.graalvm.polyglot.io.FileSystem;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
/**
* An abstract representation of a file used by Truffle languages.
*
* @since 1.0
*/
public final class TruffleFile {
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private static final int BUFFER_SIZE = 8192;
private final FileSystem fileSystem;
private final Path path;
private final Path normalizedPath;
TruffleFile(final FileSystem fileSystem, final Path path) {
this(fileSystem, path, path.normalize());
}
TruffleFile(final FileSystem fileSystem, final Path path, final Path normalizedPath) {
Objects.requireNonNull(fileSystem, "FileSystem must not be null.");
Objects.requireNonNull(path, "Path must not be null.");
Objects.requireNonNull(normalizedPath, "NormalizedPath must not be null.");
this.fileSystem = fileSystem;
this.path = path;
this.normalizedPath = normalizedPath;
}
/**
* 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 1.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 1.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 1.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 1.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 1.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 1.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 1.0
*/
@TruffleBoundary
public boolean isSymbolicLink() {
try {
return getAttributeImpl("isSymbolicLink", Boolean.class);
} 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 1.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}
* @since 1.0
*/
@TruffleBoundary
public String getName() {
try {
return path.getFileName().toString();
} catch (Throwable t) {
throw wrapHostException(t);
}
}
/**
* Returns the string representation of this {@link TruffleFile}.
*
* @return the path of this {@link TruffleFile}
* @since 1.0
*/
@TruffleBoundary
public String getPath() {
try {
return path.toString();
} catch (Throwable t) {
throw wrapHostException(t);
}
}
/**
* Returns the {@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 1.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);
}
}
/**
* 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 1.0
*/
@TruffleBoundary
public TruffleFile getAbsoluteFile() {
if (path.isAbsolute()) {
return this;
}
try {
Path[] absolutePaths = toAbsolutePathImpl();
return new TruffleFile(fileSystem, absolutePaths[0], absolutePaths[1]);
} 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 1.0
*/
@TruffleBoundary
public TruffleFile getCanonicalFile(LinkOption... options) throws IOException {
try {
return new TruffleFile(fileSystem, fileSystem.toRealPath(path, options));
} 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 1.0
*/
@TruffleBoundary
public TruffleFile getParent() {
try {
final Path parent = path.getParent();
return parent == null ? null : new TruffleFile(fileSystem, 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 1.0
*/
@TruffleBoundary
public TruffleFile resolve(String name) {
try {
return new TruffleFile(fileSystem, 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 1.0
*/
@TruffleBoundary
public TruffleFile resolveSibling(String name) {
try {
return new TruffleFile(fileSystem, 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 1.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 1.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 1.0
*/
@TruffleBoundary
public void setLastModifiedTime(FileTime time, LinkOption... options) throws IOException {
try {
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 1.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 1.0
*/
@TruffleBoundary
public void setLastAccessTime(FileTime time, LinkOption... options) throws IOException {
try {
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 1.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 1.0
*/
@TruffleBoundary
public void setCreationTime(FileTime time, LinkOption... options) throws IOException {
try {
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 1.0
*/
@TruffleBoundary
public Collection list() throws IOException {
try {
final Collection result = new ArrayList<>();
final boolean normalized = isNormalized();
try (DirectoryStream stream = fileSystem.newDirectoryStream(normalizedPath, AllFiles.INSTANCE)) {
for (Path p : stream) {
result.add(new TruffleFile(fileSystem, normalized ? p : path.resolve(p.getFileName()), p));
}
}
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.
*
* @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 1.0
*/
@TruffleBoundary
public SeekableByteChannel newByteChannel(Set extends OpenOption> options, FileAttribute>... attributes) throws IOException {
try {
return ByteChannelDecorator.create(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.
*
* @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 1.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.
*
* @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 1.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.
*
* @return the created {@link BufferedReader}
* @throws IOException in case of IO error
* @throws SecurityException if the {@link FileSystem} denied the operation
* @since 1.0
*/
@TruffleBoundary
public BufferedReader newBufferedReader() throws IOException {
return newBufferedReader(StandardCharsets.UTF_8);
}
/**
* Reads a file content as bytes.
*
* @return the created {@link BufferedReader}
* @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 1.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}.
*
* @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 1.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}.
*
* @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 1.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}.
*
* @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 1.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 1.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 1.0
*/
@TruffleBoundary
public void createDirectory(FileAttribute>... attributes) throws IOException {
try {
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 1.0
*/
@TruffleBoundary
public void createDirectories(FileAttribute>... attributes) throws IOException {
try {
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 = 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 1.0
*/
@TruffleBoundary
public void delete() throws IOException {
try {
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 1.0
*/
@TruffleBoundary
public void move(TruffleFile target, CopyOption... options) throws IOException {
try {
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 1.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 1.0
*/
@TruffleBoundary
public void setPosixPermissions(Set extends PosixFilePermission> permissions, LinkOption... linkOptions) throws IOException {
try {
fileSystem.setAttribute(normalizedPath, "posix:permissions", permissions, linkOptions);
} catch (IOException | SecurityException | UnsupportedOperationException e) {
throw e;
} catch (Throwable t) {
throw wrapHostException(t);
}
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
@TruffleBoundary
public String toString() {
return path.toString();
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
@TruffleBoundary
public int hashCode() {
int res = 17;
res = res * 31 + fileSystem.hashCode();
res = res * 31 + path.hashCode();
return res;
}
/**
* {@inheritDoc}
*
* @since 1.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) && fileSystem.equals(otherFile.fileSystem);
}
/**
* Returns a {@link TruffleFile} with removed redundant name elements in it's path.
*
* @return the normalized {@link TruffleFile}
* @since 1.0
*/
@TruffleBoundary
public TruffleFile normalize() {
if (isNormalized()) {
return this;
}
return new TruffleFile(fileSystem, normalizedPath, normalizedPath);
}
private boolean isNormalized() {
return path == normalizedPath || path.equals(normalizedPath);
}
private Path[] toAbsolutePathImpl() {
Path normalizedAbsolute = fileSystem.toAbsolutePath(normalizedPath);
if (isNormalized()) {
return new Path[]{normalizedAbsolute, normalizedAbsolute};
} else {
Path root = fileSystem.parsePath("/");
boolean emptyPath = normalizedPath.getFileName().getNameCount() == 1 && normalizedPath.getFileName().toString().isEmpty();
Path absolute = root.resolve(normalizedAbsolute.subpath(0, normalizedAbsolute.getNameCount() - (emptyPath ? 0 : normalizedPath.getNameCount()))).resolve(path);
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 extends AccessMode> modes, LinkOption... linkOptions) {
try {
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 {
final Map map = 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 {
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 {
fileSystem.checkAccess(p, mode);
return p;
} catch (NoSuchFileException nsfe) {
// Still does not exist
}
}
return null;
}
private RuntimeException wrapHostException(T t) {
if (TruffleLanguage.AccessAPI.engineAccess().isDefaultFileSystem(fileSystem)) {
throw sthrow(t);
}
throw TruffleLanguage.AccessAPI.engineAccess().wrapHostException(null, t);
}
@SuppressWarnings("unchecked")
private static T sthrow(final Throwable t) throws T {
throw (T) 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);
}
}
@SuppressWarnings("serial")
static final class FileAdapter extends File {
private final TruffleFile truffleFile;
FileAdapter(TruffleFile truffleFile) {
super(truffleFile.getPath());
this.truffleFile = truffleFile;
}
TruffleFile getTruffleFile() {
return truffleFile;
}
@Override
public String getName() {
return truffleFile.getName();
}
@Override
public String getPath() {
return truffleFile.getPath();
}
@Override
public File getAbsoluteFile() {
return new FileAdapter(truffleFile.getAbsoluteFile());
}
@Override
public File getCanonicalFile() throws IOException {
return new FileAdapter(truffleFile.getCanonicalFile());
}
@Override
public URI toURI() {
return truffleFile.toUri();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy