blob: 638026872c2422563b68b6d40f0cf08d605fbc21 [file] [log] [blame]
// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package java.nio.channels;
import java.adapter.AndroidVersionTest;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class DesugarChannels {
/** Special conversion for Channel to answer a converted FileChannel if required. */
public static Channel convertMaybeLegacyChannelFromLibrary(Channel raw) {
if (raw == null) {
return null;
}
if (raw instanceof FileChannel) {
return convertMaybeLegacyFileChannelFromLibrary((FileChannel) raw);
}
return raw;
}
/**
* Below Api 24 FileChannel does not implement SeekableByteChannel. When we get one from the
* library, we wrap it to implement the interface.
*/
public static FileChannel convertMaybeLegacyFileChannelFromLibrary(FileChannel raw) {
if (raw == null) {
return null;
}
if (raw instanceof SeekableByteChannel) {
return raw;
}
return new WrappedFileChannel(raw);
}
/**
* We unwrap when going to the library since we cannot intercept the calls to final methods in the
* library.
*/
public static FileChannel convertMaybeLegacyFileChannelToLibrary(FileChannel raw) {
if (raw == null) {
return null;
}
if (raw instanceof WrappedFileChannel) {
return ((WrappedFileChannel) raw).delegate;
}
return raw;
}
static class WrappedFileChannel extends FileChannel implements SeekableByteChannel {
final FileChannel delegate;
private WrappedFileChannel(FileChannel delegate) {
this.delegate = delegate;
}
FileChannel convert(FileChannel raw) {
return new WrappedFileChannel(raw);
}
@Override
public int read(ByteBuffer dst) throws IOException {
return delegate.read(dst);
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
return delegate.read(dsts, offset, length);
}
@Override
public int write(ByteBuffer src) throws IOException {
return delegate.write(src);
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
return delegate.write(srcs, offset, length);
}
@Override
public long position() throws IOException {
return delegate.position();
}
@Override
public FileChannel position(long newPosition) throws IOException {
return convert(delegate.position(newPosition));
}
@Override
public long size() throws IOException {
return delegate.size();
}
@Override
public FileChannel truncate(long size) throws IOException {
return convert(delegate.truncate(size));
}
@Override
public void force(boolean metaData) throws IOException {
delegate.force(metaData);
}
@Override
public long transferTo(long position, long count, WritableByteChannel target)
throws IOException {
return delegate.transferTo(position, count, target);
}
@Override
public long transferFrom(ReadableByteChannel src, long position, long count)
throws IOException {
return delegate.transferFrom(src, position, count);
}
@Override
public int read(ByteBuffer dst, long position) throws IOException {
return delegate.read(dst, position);
}
@Override
public int write(ByteBuffer src, long position) throws IOException {
return delegate.write(src, position);
}
@Override
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
return delegate.map(mode, position, size);
}
@Override
public FileLock lock(long position, long size, boolean shared) throws IOException {
return delegate.lock(position, size, shared);
}
@Override
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
return delegate.tryLock(position, size, shared);
}
@Override
public void implCloseChannel() throws IOException {
// We cannot call the protected method, this should be effectively equivalent.
delegate.close();
}
}
/** The 2 open methods are present to be retargeted from FileChannel#open. */
public static FileChannel open(Path path, OpenOption... openOptions) throws IOException {
Set<OpenOption> openOptionSet = new HashSet<>();
Collections.addAll(openOptionSet, openOptions);
return open(path, openOptionSet);
}
public static FileChannel open(
Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
throws IOException {
if (AndroidVersionTest.is26OrAbove) {
return FileChannel.open(path, openOptions, attrs);
}
return openEmulatedFileChannel(path, openOptions, attrs);
}
/**
* All FileChannel creation go through the FileSystemProvider which then comes here if the Api is
* strictly below 26, and to the plaform FileSystemProvider if the Api is above or equal to 26.
*
* <p>Below Api 26 there is no way to create a FileChannel, so we create instead an emulated
* version using RandomAccessFile which tries, with a best effort, to support all settings.
*
* <p>The FileAttributes are ignored.
*/
public static FileChannel openEmulatedFileChannel(
Path path, Set<? extends OpenOption> openOptions, FileAttribute<?>... attrs)
throws IOException {
validateOpenOptions(path, openOptions);
RandomAccessFile randomAccessFile =
new RandomAccessFile(path.toFile(), getFileAccessModeText(openOptions));
if (openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
randomAccessFile.setLength(0);
}
if (!openOptions.contains(StandardOpenOption.APPEND)) {
// This one may be retargeted, below 24, to support SeekableByteChannel.
return randomAccessFile.getChannel();
}
// TODO(b/259056135): Consider subclassing UnsupportedOperationException for desugared library.
// RandomAccessFile does not support APPEND.
// We could hack a wrapper to support APPEND in simple cases such as Files.write().
throw new UnsupportedOperationException();
}
private static void validateOpenOptions(Path path, Set<? extends OpenOption> openOptions)
throws NoSuchFileException {
// Validations that resemble sun.nio.fs.UnixChannelFactory#newFileChannel.
if (openOptions.contains(StandardOpenOption.READ)
&& openOptions.contains(StandardOpenOption.APPEND)) {
throw new IllegalArgumentException("READ + APPEND not allowed");
}
if (openOptions.contains(StandardOpenOption.APPEND)
&& openOptions.contains(StandardOpenOption.TRUNCATE_EXISTING)) {
throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
}
if (openOptions.contains(StandardOpenOption.APPEND) && !path.toFile().exists()) {
throw new NoSuchFileException(path.toString());
}
}
private static String getFileAccessModeText(Set<? extends OpenOption> options) {
if (!options.contains(StandardOpenOption.WRITE)) {
return "r";
}
if (options.contains(StandardOpenOption.SYNC)) {
return "rws";
}
if (options.contains(StandardOpenOption.DSYNC)) {
return "rwd";
}
return "rw";
}
}