New desugared library nio FileChannel implementation
- Maintain prefix for SeekableByteChannel to avoid conversions
- Custom management of FileChannel on all Apis, but no rewritting or
or automatic wrappers for FileChannel
- Emulation of custom FileChannel with RandomAccessFile below 26
- Wrap FileChannel below 24 to support SeekableByteChannel interface on
library boundaries
Above 24 the FileChannel should be 100% supported, below 24 there may be some locking issues to address [isOpen(), close(), begin(), end(boolean)]
Change-Id: I11ea847f97f0317b444e595cc152843d0df805dc
diff --git a/build.gradle b/build.gradle
index f775822..bde2f60 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1091,6 +1091,7 @@
task rawBuildLibraryDesugarConversions(type: Zip, dependsOn: downloadDeps) {
from sourceSets.libraryDesugarConversions.output
include "java/**/*.class"
+ include "desugar/sun/nio/fs/DesugarAndroid*.class"
baseName 'library_desugar_conversions_raw'
destinationDir file('build/tmp/desugaredlibrary')
}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
new file mode 100644
index 0000000..8de3f76
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidDefaultFileSystemProvider.java
@@ -0,0 +1,25 @@
+// 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 desugar.sun.nio.fs;
+
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.spi.FileSystemProvider;
+
+public class DesugarAndroidDefaultFileSystemProvider {
+ private static final FileSystemProvider INSTANCE = DesugarAndroidFileSystemProvider.create();
+
+ private DesugarAndroidDefaultFileSystemProvider() {}
+
+ /** Returns the platform's default file system provider. */
+ public static FileSystemProvider instance() {
+ return INSTANCE;
+ }
+
+ /** Returns the platform's default file system. */
+ public static FileSystem theFileSystem() {
+ return INSTANCE.getFileSystem(URI.create("file:///"));
+ }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
new file mode 100644
index 0000000..027d74f
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -0,0 +1,49 @@
+// 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 desugar.sun.nio.fs;
+
+import java.adapter.AndroidVersionTest;
+import java.io.IOException;
+import java.nio.channels.DesugarChannels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Set;
+
+/** Linux implementation of {@link FileSystemProvider} for desugar support. */
+public class DesugarAndroidFileSystemProvider
+ extends desugar.sun.nio.fs.DesugarLinuxFileSystemProvider {
+
+ public static DesugarAndroidFileSystemProvider create() {
+ return new DesugarAndroidFileSystemProvider(System.getProperty("user.dir"), "/");
+ }
+
+ DesugarAndroidFileSystemProvider(String userDir, String rootDir) {
+ super(userDir, rootDir);
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(
+ Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ if (path.toFile().isDirectory()) {
+ throw new UnsupportedOperationException(
+ "The desugar library does not support creating a file channel on a directory: " + path);
+ }
+ // A FileChannel is a SeekableByteChannel.
+ return newFileChannel(path, options, attrs);
+ }
+
+ @Override
+ public FileChannel newFileChannel(
+ Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
+ if (AndroidVersionTest.is26OrAbove) {
+ throw new RuntimeException("Above Api 26, the platform FileSystemProvider should be used.");
+ }
+ return DesugarChannels.openEmulatedFileChannel(path, options, attrs);
+ }
+}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
deleted file mode 100644
index cf36b80..0000000
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarDefaultFileSystemProvider.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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 desugar.sun.nio.fs;
-
-import java.nio.file.spi.FileSystemProvider;
-
-public class DesugarDefaultFileSystemProvider {
-
- public static FileSystemProvider instance() {
- return null;
- }
-}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
new file mode 100644
index 0000000..566bf12
--- /dev/null
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarLinuxFileSystemProvider.java
@@ -0,0 +1,116 @@
+// 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 desugar.sun.nio.fs;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
+import java.util.Set;
+
+public class DesugarLinuxFileSystemProvider extends FileSystemProvider {
+
+ DesugarLinuxFileSystemProvider(String userDir, String rootDir) {
+ super();
+ }
+
+ @Override
+ public String getScheme() {
+ return null;
+ }
+
+ @Override
+ public FileSystem newFileSystem(URI uri, Map<String, ?> map) throws IOException {
+ return null;
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ return null;
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ return null;
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(
+ Path path, Set<? extends OpenOption> set, FileAttribute<?>... fileAttributes)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path path, Filter<? super Path> filter)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public void createDirectory(Path path, FileAttribute<?>... fileAttributes) throws IOException {}
+
+ @Override
+ public void delete(Path path) throws IOException {}
+
+ @Override
+ public void copy(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+ @Override
+ public void move(Path path, Path path1, CopyOption... copyOptions) throws IOException {}
+
+ @Override
+ public boolean isSameFile(Path path, Path path1) throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ return false;
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ return null;
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... accessModes) throws IOException {}
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(
+ Path path, Class<V> aClass, LinkOption... linkOptions) {
+ return null;
+ }
+
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(
+ Path path, Class<A> aClass, LinkOption... linkOptions) throws IOException {
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String s, LinkOption... linkOptions)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public void setAttribute(Path path, String s, Object o, LinkOption... linkOptions)
+ throws IOException {}
+}
diff --git a/src/library_desugar/java/java/adapter/AndroidVersionTest.java b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
new file mode 100644
index 0000000..6425791
--- /dev/null
+++ b/src/library_desugar/java/java/adapter/AndroidVersionTest.java
@@ -0,0 +1,24 @@
+// 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.adapter;
+
+public class AndroidVersionTest {
+
+ public static final boolean is26OrAbove = setUp("java.nio.file.FileSystems");
+ public static final boolean isHeadfull = setUp("android.os.Build");
+
+ /**
+ * Answers true if the class is present, implying the SDK is at least at the level where the class
+ * was introduced.
+ */
+ private static boolean setUp(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException ignored) {
+ }
+ return false;
+ }
+}
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index e34e571..eea074f 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -6,7 +6,7 @@
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
-import desugar.sun.nio.fs.DesugarDefaultFileSystemProvider;
+import desugar.sun.nio.fs.DesugarAndroidDefaultFileSystemProvider;
import j$.nio.file.FileSystems;
import java.net.URI;
import java.nio.file.FileSystem;
@@ -23,27 +23,18 @@
INSTANCE.getFileSystem(URI.create("file:///"));
private static FileSystemProvider getFileSystemProvider() {
- // Note: this fails on non Android devices.
- try {
+ if (AndroidVersionTest.is26OrAbove) {
// On API 26 and above, FileSystems is present.
- Class.forName("java.nio.file.FileSystems");
j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
- } catch (ClassNotFoundException ignored) {
- // We reach this path is API < 26.
}
- // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
- // We cannot set the ThreadPolicy in headless and it should not matter.
- // In headless, android.os is absent so the following line will throw.
- // In headfull, android.os is present and we set the thread policy.
- try {
- Class.forName("android.os.Build");
+ if (AndroidVersionTest.isHeadfull) {
+ // The DesugarDefaultFileSystemProvider requires the ThreadPolicy to be set to work correctly.
+ // We cannot set the ThreadPolicy in headless and it should not matter.
setThreadPolicy();
- } catch (ClassNotFoundException ignored) {
- // Headless mode.
}
- return DesugarDefaultFileSystemProvider.instance();
+ return DesugarAndroidDefaultFileSystemProvider.instance();
}
private static void setThreadPolicy() {
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index da93736..8980540 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -17,13 +17,9 @@
private HybridFileTypeDetector() {}
public static FileTypeDetector create() {
- try {
- // On API 26 and above, java.nio.file.Files is present.
- Class.forName("java.nio.file.Files");
- return new PlatformFileTypeDetector();
- } catch (ClassNotFoundException ignored) {
- return DesugarDefaultFileTypeDetector.create();
- }
+ return AndroidVersionTest.is26OrAbove
+ ? new PlatformFileTypeDetector()
+ : DesugarDefaultFileTypeDetector.create();
}
static class PlatformFileTypeDetector extends FileTypeDetector {
diff --git a/src/library_desugar/java/java/nio/channels/DesugarChannels.java b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
new file mode 100644
index 0000000..6380268
--- /dev/null
+++ b/src/library_desugar/java/java/nio/channels/DesugarChannels.java
@@ -0,0 +1,239 @@
+// 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";
+ }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index c97bd79..3295333 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -166,6 +166,7 @@
"java.nio.channels.AsynchronousChannel": "j$.nio.channels.AsynchronousChannel",
"java.nio.channels.AsynchronousFileChannel": "j$.nio.channels.AsynchronousFileChannel",
"java.nio.channels.CompletionHandler": "j$.nio.channels.CompletionHandler",
+ "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
"java.nio.file.": "j$.nio.file."
},
"dont_rewrite_prefix": [
@@ -230,7 +231,8 @@
"java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"],
"java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"],
"java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"],
- "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"]
+ "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"],
+ "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"]
},
"wrapper_conversion": [
"java.nio.channels.CompletionHandler",
@@ -279,8 +281,6 @@
"api_level_below_or_equal": 23,
"rewrite_prefix": {
"java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
- "java.nio.channels.FileChannel": "j$.nio.channels.FileChannel",
- "java.nio.channels.SeekableByteChannel": "j$.nio.channels.SeekableByteChannel",
"java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
"java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
"java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -294,12 +294,10 @@
"java.util.concurrent.atomic.DesugarAtomic": "j$.util.concurrent.atomic.DesugarAtomic",
"java.util.stream.": "j$.util.stream."
},
- "dont_rewrite_prefix": [
- "java.nio.channels.FileChannel$MapMode"
- ],
"maintain_prefix": [
"java.util.function.",
- "java.io.UncheckedIOException"
+ "java.io.UncheckedIOException",
+ "java.nio.channels.SeekableByteChannel"
],
"emulate_interface": {
"java.lang.Iterable": "j$.lang.Iterable",
@@ -347,13 +345,18 @@
"java.util.stream.IntStream java.util.stream.IntStream#flatMap(java.util.function.IntFunction)": [0, "java.util.function.IntFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.IntFunction)"],
"java.util.stream.LongStream java.util.stream.Stream#flatMapToLong(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.Function)"],
"java.util.stream.LongStream java.util.stream.LongStream#flatMap(java.util.function.LongFunction)": [0, "java.util.function.LongFunction java.util.stream.FlatMapApiFlips#flipFunctionReturningStream(java.util.function.LongFunction)"],
- "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"]
+ "java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)": [0, "java.util.function.Function java.util.stream.StackWalkerApiFlips#flipFunctionStream(java.util.function.Function)"],
+ "java.nio.channels.FileChannel java.nio.channels.FileLock#channel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+ "java.nio.channels.Channel java.nio.channels.FileLock#acquiredBy()": [-1, "java.nio.channels.Channel java.nio.channels.DesugarChannels#convertMaybeLegacyChannelFromLibrary(java.nio.channels.Channel)"],
+ "java.nio.channels.FileChannel java.io.RandomAccessFile#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+ "java.nio.channels.FileChannel java.io.FileInputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+ "java.nio.channels.FileChannel java.io.FileOutputStream#getChannel()": [-1, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelFromLibrary(java.nio.channels.FileChannel)"],
+ "void java.nio.channels.FileLock#<init>(java.nio.channels.FileChannel,long, long, boolean)": [0, "java.nio.channels.FileChannel java.nio.channels.DesugarChannels#convertMaybeLegacyFileChannelToLibrary(java.nio.channels.FileChannel)"]
},
"never_outline_api": [
"java.lang.Object java.lang.StackWalker#walk(java.util.function.Function)"
],
"wrapper_conversion": [
- "java.nio.channels.SeekableByteChannel",
"java.util.PrimitiveIterator$OfDouble",
"java.util.PrimitiveIterator$OfInt",
"java.util.PrimitiveIterator$OfLong",
@@ -370,18 +373,6 @@
"java.util.stream.LongStream",
"java.util.stream.Stream"
],
- "wrapper_conversion_excluding": {
- "java.nio.channels.FileChannel": [
- "long java.nio.channels.FileChannel#read(java.nio.ByteBuffer[])",
- "long java.nio.channels.FileChannel#write(java.nio.ByteBuffer[])",
- "java.nio.channels.FileLock java.nio.channels.FileChannel#lock()",
- "java.nio.channels.FileLock java.nio.channels.FileChannel#tryLock()",
- "void java.nio.channels.spi.AbstractInterruptibleChannel#close()",
- "boolean java.nio.channels.spi.AbstractInterruptibleChannel#isOpen()",
- "void java.nio.channels.spi.AbstractInterruptibleChannel#begin()",
- "void java.nio.channels.spi.AbstractInterruptibleChannel#end(boolean)"
- ]
- },
"custom_conversion": {
"java.util.DoubleSummaryStatistics": "java.util.DoubleSummaryStatisticsConversions",
"java.util.IntSummaryStatistics": "java.util.IntSummaryStatisticsConversions",
@@ -442,7 +433,9 @@
"java.time.Instant java.util.Calendar#toInstant()": "java.util.DesugarCalendar",
"java.util.Date java.util.Date#from(java.time.Instant)": "java.util.DesugarDate",
"java.util.GregorianCalendar java.util.GregorianCalendar#from(java.time.ZonedDateTime)": "java.util.DesugarGregorianCalendar",
- "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone"
+ "java.util.TimeZone java.util.TimeZone#getTimeZone(java.lang.String)": "java.util.DesugarTimeZone",
+ "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.nio.file.OpenOption[])": "java.nio.channels.DesugarChannels",
+ "java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])": "java.nio.channels.DesugarChannels"
}
},
{
@@ -483,7 +476,6 @@
{
"api_level_below_or_equal": 25,
"rewrite_prefix": {
- "java.nio.channels.Desugar": "j$.nio.channels.Desugar",
"jdk.internal.": "j$.jdk.internal.",
"sun.misc.Desugar": "j$.sun.misc.Desugar",
"sun.nio.cs.": "j$.sun.nio.cs.",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 178255b..40f9d67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -170,7 +170,9 @@
private DexMethod ensureApiGenericConversion(
DexMethod conversion, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer) {
- assert !appView.options().isDesugaredLibraryCompilation();
+ if (appView.appInfoForDesugaring().resolveMethod(conversion, false).isSingleResolution()) {
+ return conversion;
+ }
ClasspathMethod classpathMethod =
appView
.getSyntheticItems()
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
index c6ea5b0..2acd508 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LibraryValidator.java
@@ -27,7 +27,7 @@
} else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.R)) {
levelType = app.dexItemFactory.createType("Ljava/util/concurrent/Flow;");
} else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.N)) {
- levelType = app.dexItemFactory.createType("Ljava/util/function/Supplier;");
+ levelType = app.dexItemFactory.createType("Ljava/util/StringJoiner;");
} else if (requiredCompilationAPILevel.isEqualTo(AndroidApiLevel.T)) {
levelType = app.dexItemFactory.createType("Ljava/lang/invoke/VarHandle;");
} else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
index 76bd656..2c99723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryContentTest.java
@@ -80,7 +80,11 @@
"java.util.stream.DoubleStream"
+ " java.util.stream.DoubleStream.dropWhile(java.util.function.DoublePredicate)",
// FileStore.getBlockSize() was added in 33 which confuses the required library (30).
- "long java.nio.file.FileStore.getBlockSize()");
+ "long java.nio.file.FileStore.getBlockSize()",
+ // The call is present but unreachable above 26.
+ "java.nio.channels.FileChannel"
+ + " java.nio.channels.DesugarChannels.openEmulatedFileChannel(java.nio.file.Path,"
+ + " java.util.Set, java.nio.file.attribute.FileAttribute[])");
private final TestParameters parameters;
private final CompilationSpecification compilationSpecification;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index a68f3ce..4602235 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -514,8 +514,7 @@
if (clazzType.startsWith("java.")
&& !doesNotNeedWrapper(clazzType, customConversions, maintainType)
// FileChannel is there since B but it needs wrapping due to recently added interfaces.
- && (!preDesugarTypes.contains(clazz)
- || clazzType.equals("java.nio.channels.FileChannel"))) {
+ && !preDesugarTypes.contains(clazz)) {
additions.accept(clazz);
return true;
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
new file mode 100644
index 0000000..c39147d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FileChannelTest.java
@@ -0,0 +1,211 @@
+// 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 com.android.tools.r8.desugar.desugaredlibrary.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FileChannelTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Hello World! ",
+ "Hello World! ",
+ "Bye bye. ",
+ "Hello World! ",
+ "Bye bye. ",
+ "Hello World! ",
+ "The monkey eats...",
+ "Bananas!",
+ "Bananas!",
+ "Bananas!");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ getTestParameters()
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public FileChannelTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void test() throws Throwable {
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) throws IOException {
+ fisTest();
+ fosTest();
+ fileChannelOpen();
+ }
+
+ private static void fosTest() throws IOException {
+ String toWrite = "The monkey eats...";
+ Path tmp = Files.createTempFile("fos", ".txt");
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
+ FileOutputStream fos = new FileOutputStream(tmp.toFile());
+ FileChannel channel = fos.getChannel();
+ channel.write(byteBuffer);
+
+ List<String> lines = Files.readAllLines(tmp);
+ System.out.println(lines.get(0));
+ }
+
+ private static void fileChannelOpen() throws IOException {
+ fileChannelOpenTest();
+ fileChannelOpenSetTest();
+ fileChannelOpenLockTest();
+ }
+
+ private static void fileChannelOpenLockTest() throws IOException {
+ Path tmp = Files.createTempFile("lock", ".txt");
+ String contents = "Bananas!";
+ Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+ FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+ fc.read(byteBuffer);
+ System.out.println(new String(byteBuffer.array()));
+ fc.close();
+ }
+
+ private static void fileChannelOpenTest() throws IOException {
+ Path tmp = Files.createTempFile("a", ".txt");
+ String contents = "Bananas!";
+ Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+ FileChannel fc = FileChannel.open(tmp, StandardOpenOption.READ, StandardOpenOption.WRITE);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+ // Extra indirection through the lock.
+ fc.lock().channel().read(byteBuffer);
+ System.out.println(new String(byteBuffer.array()));
+ fc.close();
+ }
+
+ private static void fileChannelOpenSetTest() throws IOException {
+ Path tmp = Files.createTempFile("b", ".txt");
+ String contents = "Bananas!";
+ Files.write(tmp, contents.getBytes(StandardCharsets.UTF_8));
+ Set<OpenOption> options = new HashSet<>();
+ options.add(StandardOpenOption.READ);
+ FileChannel fc = FileChannel.open(tmp, options);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length());
+ fc.read(byteBuffer);
+ System.out.println(new String(byteBuffer.array()));
+ fc.close();
+ }
+
+ private static void fisTest() throws IOException {
+ fisOwner();
+ fisNotOwner(true);
+ fisNotOwner(false);
+ fisOwnerTryResources();
+ }
+
+ private static void fisNotOwner(boolean closeFirst) throws IOException {
+ String toWrite = "Hello World! ";
+ String toWriteFIS = "Bye bye. ";
+ Path tmp = Files.createTempFile("tmp", ".txt");
+ Files.write(tmp, (toWrite + toWriteFIS).getBytes(StandardCharsets.UTF_8));
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+ ByteBuffer byteBufferFIS = ByteBuffer.allocate(toWriteFIS.length());
+ FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+ FileDescriptor fd = fileInputStream.getFD();
+ FileInputStream fis2 = new FileInputStream(fd);
+ fileInputStream.getChannel().read(byteBuffer);
+ fis2.getChannel().read(byteBufferFIS);
+
+ if (closeFirst) {
+ fileInputStream.close();
+ fis2.close();
+ } else {
+ fis2.close();
+ fileInputStream.close();
+ }
+
+ System.out.println(new String(byteBuffer.array()));
+ System.out.println(new String(byteBufferFIS.array()));
+ }
+
+ private static void fisOwner() throws IOException {
+ String toWrite = "Hello World! ";
+ Path tmp = Files.createTempFile("tmp", ".txt");
+ Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+ FileInputStream fileInputStream = new FileInputStream(tmp.toFile());
+ fileInputStream.getChannel().read(byteBuffer);
+ fileInputStream.close();
+
+ System.out.println(new String(byteBuffer.array()));
+ }
+
+ private static void fisOwnerTryResources() throws IOException {
+ String toWrite = "Hello World! ";
+ Path tmp = Files.createTempFile("tmp", ".txt");
+ Files.write(tmp, toWrite.getBytes(StandardCharsets.UTF_8));
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(toWrite.length());
+ try (FileInputStream fileInputStream = new FileInputStream(tmp.toFile())) {
+ fileInputStream.getChannel().read(byteBuffer);
+ }
+
+ System.out.println(new String(byteBuffer.array()));
+ }
+ }
+}