Desugared lib: Fix DirectoryStream apis

Change-Id: I8de39a96a730ce6198e083de445006c2cf8707e7
diff --git a/src/library_desugar/java/j$/nio/file/Path.java b/src/library_desugar/java/j$/nio/file/Path.java
index 781ab5f..6030764 100644
--- a/src/library_desugar/java/j$/nio/file/Path.java
+++ b/src/library_desugar/java/j$/nio/file/Path.java
@@ -9,4 +9,8 @@
   public static j$.nio.file.Path wrap_convert(java.nio.file.Path path) {
     return null;
   }
+
+  public static java.nio.file.Path wrap_convert(j$.nio.file.Path path) {
+    return null;
+  }
 }
diff --git a/src/library_desugar/java/java/nio/file/PathApiFlips.java b/src/library_desugar/java/java/nio/file/PathApiFlips.java
new file mode 100644
index 0000000..f7009ca
--- /dev/null
+++ b/src/library_desugar/java/java/nio/file/PathApiFlips.java
@@ -0,0 +1,136 @@
+// 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.file;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.function.Consumer;
+
+/** For all wrappers, the spliterator is based on the iterator so no need to wrap it. */
+public class PathApiFlips {
+
+  public static Iterator<?> flipIteratorPath(Iterator<?> iterator) {
+    return new IteratorPathWrapper<>(iterator);
+  }
+
+  public static Iterable<?> flipIterablePath(Iterable<?> iterable) {
+    return new IterablePathWrapper<>(iterable);
+  }
+
+  public static java.nio.file.DirectoryStream<?> flipDirectoryStreamPath(
+      java.nio.file.DirectoryStream<?> directoryStream) {
+    return new DirectoryStreamPathWrapper<>(directoryStream);
+  }
+
+  public static java.nio.file.DirectoryStream.Filter<?> flipDirectoryStreamFilterPath(
+      java.nio.file.DirectoryStream.Filter<?> filter) {
+    return new DirectoryStreamFilterWrapper<>(filter);
+  }
+
+  /**
+   * The generic types inherit from java.lang.Object even if in practice it seems they are mainly
+   * used with Path. To be conservative, we use
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T convertPath(T maybePath) {
+    if (maybePath == null) {
+      return null;
+    }
+    if (maybePath instanceof java.nio.file.Path) {
+      return (T) j$.nio.file.Path.wrap_convert((java.nio.file.Path) maybePath);
+    }
+    if (maybePath instanceof j$.nio.file.Path) {
+      return (T) j$.nio.file.Path.wrap_convert((j$.nio.file.Path) maybePath);
+    }
+    return maybePath;
+  }
+
+  public static class DirectoryStreamFilterWrapper<T>
+      implements java.nio.file.DirectoryStream.Filter<T> {
+
+    private final java.nio.file.DirectoryStream.Filter<T> filter;
+
+    public DirectoryStreamFilterWrapper(java.nio.file.DirectoryStream.Filter<T> filter) {
+      this.filter = filter;
+    }
+
+    @Override
+    public boolean accept(T t) throws IOException {
+      return filter.accept(convertPath(t));
+    }
+  }
+
+  public static class IterablePathWrapper<T> implements Iterable<T> {
+
+    private final Iterable<T> iterable;
+
+    public IterablePathWrapper(Iterable<T> iterable) {
+      this.iterable = iterable;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+      return new IteratorPathWrapper<>(iterable.iterator());
+    }
+
+    @Override
+    public void forEach(Consumer<? super T> action) {
+      iterable.forEach(path -> action.accept(convertPath(path)));
+    }
+  }
+
+  public static class IteratorPathWrapper<T> implements Iterator<T> {
+
+    private final Iterator<T> iterator;
+
+    public IteratorPathWrapper(Iterator<T> iterator) {
+      this.iterator = iterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+
+    @Override
+    public T next() {
+      return convertPath(iterator.next());
+    }
+
+    @Override
+    public void remove() {
+      iterator.remove();
+    }
+
+    @Override
+    public void forEachRemaining(Consumer<? super T> action) {
+      iterator.forEachRemaining(path -> action.accept(convertPath(path)));
+    }
+  }
+
+  public static class DirectoryStreamPathWrapper<T> implements DirectoryStream<T> {
+
+    private final DirectoryStream<T> directoryStream;
+
+    public DirectoryStreamPathWrapper(DirectoryStream<T> directoryStream) {
+      this.directoryStream = directoryStream;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+      return new IteratorPathWrapper<>(directoryStream.iterator());
+    }
+
+    @Override
+    public void forEach(Consumer<? super T> action) {
+      directoryStream.forEach(path -> action.accept(convertPath(path)));
+    }
+
+    @Override
+    public void close() throws IOException {
+      directoryStream.close();
+    }
+  }
+}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 9bc2704..090d1f2 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -152,6 +152,7 @@
         "java.nio.file.ClosedWatchServiceException",
         "java.nio.file.DirectoryIteratorException",
         "java.nio.file.DirectoryNotEmptyException",
+        "java.nio.file.DirectoryStream",
         "java.nio.file.FileAlreadyExistsException",
         "java.nio.file.FileSystemAlreadyExistsException",
         "java.nio.file.FileSystemException",
@@ -173,6 +174,7 @@
         "java.nio.file.ClosedWatchServiceException",
         "java.nio.file.DirectoryIteratorException",
         "java.nio.file.DirectoryNotEmptyException",
+        "java.nio.file.DirectoryStream",
         "java.nio.file.FileAlreadyExistsException",
         "java.nio.file.FileSystemAlreadyExistsException",
         "java.nio.file.FileSystemException",
@@ -199,7 +201,10 @@
         "java.nio.file.attribute.BasicFileAttributes java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.Class, java.nio.file.LinkOption[])": [1, "java.lang.Class java.nio.file.FileApiFlips#flipFileAttributes(java.lang.Class)"],
         "java.util.Set java.nio.file.attribute.PosixFileAttributes#permissions()": [-1, "java.util.Set java.nio.file.FileApiFlips#flipPosixPermissionSet(java.util.Set)"],
         "void java.nio.file.attribute.PosixFileAttributeView#setPermissions(java.util.Set)": [0, "java.util.Set java.nio.file.FileApiFlips#flipPosixPermissionSet(java.util.Set)"],
-        "java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"]
+        "java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"],
+        "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)"]
       },
       "wrapper_conversion": [
         "java.nio.channels.CompletionHandler",
@@ -225,8 +230,6 @@
         "java.nio.file.attribute.UserPrincipal",
         "java.nio.file.FileStore",
         "java.nio.file.attribute.FileStoreAttributeView",
-        "java.nio.file.DirectoryStream$Filter",
-        "java.nio.file.DirectoryStream",
         "java.nio.file.attribute.PosixFilePermission",
         "java.nio.file.attribute.BasicFileAttributes",
         "java.nio.file.attribute.PosixFileAttributes",
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 b5ca98b..a68f3ce 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
@@ -123,13 +123,7 @@
 
   // TODO(b/238179854): Investigate how to fix these.
   private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_PATH =
-      ImmutableSet.of(
-          "java.lang.Iterable java.nio.file.FileSystem.getFileStores()",
-          "java.lang.Iterable java.nio.file.FileSystem.getRootDirectories()",
-          "java.util.Iterator java.nio.file.Path.iterator()",
-          "java.nio.file.DirectoryStream"
-              + " java.nio.file.spi.FileSystemProvider.newDirectoryStream(java.nio.file.Path,"
-              + " java.nio.file.DirectoryStream$Filter)");
+      ImmutableSet.of("java.lang.Iterable java.nio.file.FileSystem.getFileStores()");
 
   // TODO(b/238179854): Investigate how to fix these.
   private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_FLOW =
@@ -245,6 +239,18 @@
             .collect(Collectors.toSet());
     Set<String> maintainTypeInSet =
         specification.getMaintainType().stream().map(DexType::toString).collect(Collectors.toSet());
+    Map<String, boolean[]> genericConversionsInSpec = new HashMap<>();
+    specification
+        .getApiGenericConversion()
+        .forEach(
+            (method, generics) -> {
+              boolean[] indexes = new boolean[generics.length];
+              for (int i = 0; i < generics.length; i++) {
+                indexes[i] = generics[i] != null;
+              }
+              genericConversionsInSpec.put(method.toString(), indexes);
+            });
+
     assertEquals(
         Collections.emptySet(), Sets.intersection(wrappersInSpec, customConversionsInSpec));
 
@@ -257,6 +263,7 @@
             nonDesugaredJar,
             customConversionsInSpec,
             maintainTypeInSet,
+            genericConversionsInSpec,
             genericDependencies);
     Map<ClassReference, Set<ClassReference>> indirectWrappers =
         getIndirectlyReferencedWrapperTypes(
@@ -266,6 +273,7 @@
             customConversionsInSpec,
             maintainTypeInSet,
             specification.getWrappers(),
+            genericConversionsInSpec,
             genericDependencies);
     {
       Set<String> missingGenericDependency = new HashSet<>();
@@ -339,6 +347,7 @@
       CodeInspector nonDesugaredJar,
       Set<String> customConversions,
       Set<String> maintainType,
+      Map<String, boolean[]> genericConversionsInSpec,
       Set<DexEncodedMethod> genericDependencies) {
     Map<ClassReference, Set<MethodReference>> directWrappers = new HashMap<>();
     nonDesugaredJar.forAllClasses(
@@ -364,6 +373,7 @@
                 forEachType(
                     method,
                     t -> addType(adder, t, preDesugarTypes, customConversions, maintainType),
+                    genericConversionsInSpec,
                     genericDependencies);
               });
         });
@@ -373,12 +383,20 @@
   private void forEachType(
       FoundMethodSubject subject,
       Function<String, Boolean> process,
+      Map<String, boolean[]> genericConversionsInSpec,
       Set<DexEncodedMethod> generics) {
+    boolean[] genericConversions = genericConversionsInSpec.get(subject.toString());
     MethodSignature signature = subject.getFinalSignature().asMethodSignature();
-    process.apply(signature.type);
-    for (String parameter : signature.parameters) {
-      process.apply(parameter);
+    if (genericConversions == null || !genericConversions[genericConversions.length - 1]) {
+      process.apply(signature.type);
     }
+    for (int i = 0; i < signature.parameters.length; i++) {
+      if (genericConversions == null || !genericConversions[i]) {
+        process.apply(signature.parameters[i]);
+      }
+    }
+    // Even if the genericConversions are present, we check the generic types since conversions
+    // on such types will happen through the hand written custom wrappers.
     MethodTypeSignature genericSignature = subject.getMethod().getGenericSignature();
     if (genericSignature != null) {
       TypeSignature[] typeSignatures = new TypeSignature[signature.parameters.length + 1];
@@ -387,15 +405,17 @@
       }
       typeSignatures[signature.parameters.length] = genericSignature.returnType().typeSignature();
       for (TypeSignature typeSignature : typeSignatures) {
-        if ((typeSignature instanceof ClassTypeSignature)) {
-          for (FieldTypeSignature typeArgument :
-              ((ClassTypeSignature) typeSignature).typeArguments()) {
-            if (typeArgument instanceof ClassTypeSignature) {
-              String type = descriptorToJavaType(typeArgument.toString()).split("<")[0];
-              if (!GENERIC_NOT_NEEDED.contains(type)) {
-                boolean added = process.apply(type);
-                if (added) {
-                  generics.add(subject.getMethod());
+        if (typeSignature != null) {
+          if ((typeSignature instanceof ClassTypeSignature)) {
+            for (FieldTypeSignature typeArgument :
+                ((ClassTypeSignature) typeSignature).typeArguments()) {
+              if (typeArgument instanceof ClassTypeSignature) {
+                String type = descriptorToJavaType(typeArgument.toString()).split("<")[0];
+                if (!GENERIC_NOT_NEEDED.contains(type)) {
+                  boolean added = process.apply(type);
+                  if (added) {
+                    generics.add(subject.getMethod());
+                  }
                 }
               }
             }
@@ -412,6 +432,7 @@
       Set<String> customConversions,
       Set<String> maintainType,
       Map<DexType, WrapperDescriptor> wrapperDescriptorMap,
+      Map<String, boolean[]> genericConversionsInSpec,
       Set<DexEncodedMethod> genericDependencies) {
     Map<ClassReference, Set<ClassReference>> indirectWrappers = new HashMap<>();
     WorkList<ClassReference> worklist = WorkList.newEqualityWorkList(directWrappers.keySet());
@@ -435,6 +456,7 @@
             forEachType(
                 method,
                 t -> addType(adder, t, existing, customConversions, maintainType),
+                genericConversionsInSpec,
                 genericDependencies);
           });
       WrapperDescriptor descriptor = wrapperDescriptorMap.get(clazz.getDexProgramClass().getType());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
index 9508e8c..cef3785 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -18,6 +18,7 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
@@ -28,6 +29,7 @@
 import java.nio.file.attribute.PosixFileAttributes;
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -50,7 +52,10 @@
           "null",
           "true",
           "unsupported",
-          "j$.nio.file.attribute");
+          "j$.nio.file.attribute",
+          "tmp",
+          "/",
+          "true");
   private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM_PLATFORM_CHANNEL =
       StringUtils.lines(
           "bytes written: 11",
@@ -62,7 +67,10 @@
           "null",
           "true",
           "unsupported",
-          "j$.nio.file.attribute");
+          "j$.nio.file.attribute",
+          "tmp",
+          "/",
+          "true");
   private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
       StringUtils.lines(
           "bytes written: 11",
@@ -74,7 +82,10 @@
           "true",
           "true",
           "true",
-          "j$.nio.file.attribute");
+          "j$.nio.file.attribute",
+          "tmp",
+          "/",
+          "true");
   private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM =
       StringUtils.lines(
           "bytes written: 11",
@@ -86,7 +97,10 @@
           "true",
           "true",
           "true",
-          "java.nio.file.attribute");
+          "java.nio.file.attribute",
+          "tmp",
+          "/",
+          "true");
 
   private final TestParameters parameters;
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -144,6 +158,18 @@
       readThroughFileChannelAPI(path);
       attributeAccess(path);
       fspMethodsWithGeneric(path);
+      pathGeneric();
+    }
+
+    private static void pathGeneric() throws IOException {
+      Path tmpDict = Files.createTempDirectory("tmpDict");
+      Path tmpFile = Files.createFile(tmpDict.resolve("tmpFile"));
+      Iterator<Path> iterator = tmpDict.iterator();
+      System.out.println(iterator.next());
+      Iterable<Path> rootDirectories = tmpFile.getFileSystem().getRootDirectories();
+      System.out.println(rootDirectories.iterator().next());
+      DirectoryStream<Path> paths = Files.newDirectoryStream(tmpDict);
+      System.out.println(paths.iterator().hasNext());
     }
 
     private static void fspMethodsWithGeneric(Path path) throws IOException {