Desugared library nio: Support Dos

Bug: b/260383569
Change-Id: Ia37fcdb0ad3229318ab4cf5451fdd12c198d5c58
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
new file mode 100644
index 0000000..899c1e0
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class DosFileAttributeView extends FileAttributeView {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
new file mode 100644
index 0000000..ae2200c
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class DosFileAttributes {}
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java
index 9c8a46a..24a8d31 100644
--- a/src/library_desugar/java/java/nio/file/FileApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -32,6 +32,9 @@
     if (attributesClass == java.nio.file.attribute.PosixFileAttributes.class) {
       return j$.nio.file.attribute.PosixFileAttributes.class;
     }
+    if (attributesClass == java.nio.file.attribute.DosFileAttributes.class) {
+      return j$.nio.file.attribute.DosFileAttributes.class;
+    }
     throw exception("java.nio.file.attribute.BasicFileAttributes", attributesClass);
   }
 
@@ -57,6 +60,9 @@
     if (attributeView == java.nio.file.attribute.FileOwnerAttributeView.class) {
       return j$.nio.file.attribute.FileOwnerAttributeView.class;
     }
+    if (attributeView == java.nio.file.attribute.DosFileAttributeView.class) {
+      return j$.nio.file.attribute.DosFileAttributeView.class;
+    }
     throw exception("java.nio.file.attribute.FileAttributeView", attributeView);
   }
 
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 244aa34..c102e5e 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -263,9 +263,11 @@
         "java.nio.file.attribute.FileStoreAttributeView",
         "java.nio.file.attribute.PosixFilePermission",
         "java.nio.file.attribute.BasicFileAttributes",
+        "java.nio.file.attribute.DosFileAttributes",
         "java.nio.file.attribute.PosixFileAttributes",
         "java.nio.file.attribute.FileAttributeView",
         "java.nio.file.attribute.FileOwnerAttributeView",
+        "java.nio.file.attribute.DosFileAttributeView",
         "java.nio.file.attribute.PosixFileAttributeView",
         "java.nio.file.attribute.BasicFileAttributeView"
       ],
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 5ae7b6c..5222f94 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
@@ -6,6 +6,8 @@
 
 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 static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -25,12 +27,17 @@
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.DosFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFileAttributes;
 import java.nio.file.attribute.PosixFilePermission;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -45,47 +52,45 @@
 @RunWith(Parameterized.class)
 public class FilesTest extends DesugaredLibraryTestBase {
 
-  private static final String END_EXPECTED_RESULT =
-      StringUtils.lines("tmp", "/", "true", "This", "is", "fun!");
-  private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM =
+  private static final String EXPECTED_RESULT_FORMAT =
       StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "null",
-              "true",
-              "unsupported",
-              "j$.nio.file.attribute")
-          + END_EXPECTED_RESULT;
-  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
-      StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "true",
-              "true",
-              "true",
-              "j$.nio.file.attribute")
-          + END_EXPECTED_RESULT;
-  private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM =
-      StringUtils.lines(
-              "bytes written: 11",
-              "String written: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "bytes read: 11",
-              "String read: Hello World",
-              "true",
-              "true",
-              "true",
-              "java.nio.file.attribute")
-          + END_EXPECTED_RESULT;
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "true",
+          "%s",
+          "null",
+          "true",
+          "%s",
+          "unsupported",
+          "tmp",
+          "/",
+          "true",
+          "This",
+          "is",
+          "fun!",
+          "%s",
+          "%s",
+          "%s",
+          "%s");
+  private static final List<String> EXPECTED_RESULT_POSIX =
+      ImmutableList.of(
+          "true",
+          "true",
+          "Succeeded with POSIX RO:false",
+          "Successfully set RO with POSIX",
+          "Succeeded with POSIX RO:true");
+  private static final List<String> EXPECTED_RESULT_DESUGARING_NON_POSIX =
+      ImmutableList.of(
+          "null",
+          "unsupported",
+          "Fail to understand if the file is read-only: class"
+              + " java.lang.UnsupportedOperationException",
+          "Fail to set file as read-only: class java.lang.UnsupportedOperationException",
+          "NotSet");
 
   private final TestParameters parameters;
   private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -113,13 +118,21 @@
     this.compilationSpecification = compilationSpecification;
   }
 
+  private static String computeExpectedResult(boolean supportPosix, boolean j$nioClasses) {
+    List<String> strings =
+        new ArrayList<>(
+            supportPosix ? EXPECTED_RESULT_POSIX : EXPECTED_RESULT_DESUGARING_NON_POSIX);
+    strings.add(j$nioClasses ? "j$.nio.file.attribute" : "java.nio.file.attribute");
+    return String.format(EXPECTED_RESULT_FORMAT, strings.toArray());
+  }
+
   private String getExpectedResult() {
     if (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
       return libraryDesugaringSpecification.hasNioFileDesugaring(parameters)
-          ? EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING
-          : EXPECTED_RESULT_PLATFORM_FILE_SYSTEM;
+          ? computeExpectedResult(true, true)
+          : computeExpectedResult(true, false);
     }
-    return EXPECTED_RESULT_DESUGARING_FILE_SYSTEM;
+    return computeExpectedResult(false, true);
   }
 
   @Test
@@ -141,9 +154,78 @@
       readThroughFileChannelAPI(path);
       attributeAccess(path);
       Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH));
-      fspMethodsWithGeneric(path);
       pathGeneric();
       lines(path);
+      readOnlyTest(path);
+      fspMethodsWithGeneric(path);
+    }
+
+    private static void readOnlyTest(Path path) {
+      isReadOnly(path);
+      if (setReadOnly(path)) {
+        isReadOnly(path);
+      } else {
+        System.out.println("NotSet");
+      }
+    }
+
+    private static boolean isReadOnly(Path path) {
+      try {
+        // DOS attempt.
+        try {
+          DosFileAttributeView dosFileAttributeView =
+              Files.getFileAttributeView(path, DosFileAttributeView.class);
+          if (dosFileAttributeView != null && dosFileAttributeView.readAttributes() != null) {
+            boolean readOnly = dosFileAttributeView.readAttributes().isReadOnly();
+            System.out.println("Succeeded with DOS RO:" + readOnly);
+            return readOnly;
+          }
+        } catch (IOException ignored) {
+        }
+        // Posix attempt.
+        Set<PosixFilePermission> posixFilePermissions = Files.getPosixFilePermissions(path);
+        boolean readOnly =
+            posixFilePermissions.contains(OWNER_READ)
+                && !posixFilePermissions.contains(OWNER_WRITE);
+        System.out.println("Succeeded with POSIX RO:" + readOnly);
+        return readOnly;
+
+      } catch (Throwable t) {
+        System.out.println("Fail to understand if the file is read-only: " + t.getClass());
+        return false;
+      }
+    }
+
+    /** Common pattern to set a file as read-only: Try on Dos, on failure, retry on Posix. */
+    private static boolean setReadOnly(Path path) {
+      try {
+
+        // DOS attempt.
+        try {
+          DosFileAttributeView dosFileAttributeView =
+              Files.getFileAttributeView(path, DosFileAttributeView.class);
+          if (dosFileAttributeView != null) {
+            dosFileAttributeView.setReadOnly(true);
+            System.out.println("Successfully set RO with DOS");
+            return true;
+          }
+        } catch (IOException ignored) {
+        }
+
+        // Posix attempt.
+        Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
+        List<PosixFilePermission> readPermissions = Arrays.asList(OWNER_READ);
+        List<PosixFilePermission> writePermissions = Arrays.asList(PosixFilePermission.OWNER_WRITE);
+        permissions.addAll(readPermissions);
+        permissions.removeAll(writePermissions);
+        Files.setPosixFilePermissions(path, permissions);
+        System.out.println("Successfully set RO with POSIX");
+        return true;
+
+      } catch (Throwable t) {
+        System.out.println("Fail to set file as read-only: " + t.getClass());
+        return false;
+      }
     }
 
     private static void pathGeneric() throws IOException {
@@ -163,14 +245,33 @@
     }
 
     private static void attributeAccess(Path path) throws IOException {
-      PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
-      if (view != null) {
-        System.out.println(
-            view.readAttributes().permissions().contains(PosixFilePermission.OWNER_READ));
+      BasicFileAttributeView basicView =
+          Files.getFileAttributeView(path, BasicFileAttributeView.class);
+      if (basicView != null) {
+        System.out.println(basicView.readAttributes().isRegularFile());
       } else {
         System.out.println("null");
       }
 
+      PosixFileAttributeView posixView =
+          Files.getFileAttributeView(path, PosixFileAttributeView.class);
+      if (posixView != null) {
+        System.out.println(posixView.readAttributes().permissions().contains(OWNER_READ));
+      } else {
+        System.out.println("null");
+      }
+
+      try {
+        DosFileAttributeView dosView = Files.getFileAttributeView(path, DosFileAttributeView.class);
+        if (dosView != null) {
+          System.out.println(dosView.readAttributes().isReadOnly());
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
       BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
       if (attributes != null) {
         System.out.println(attributes.isRegularFile());
@@ -181,8 +282,18 @@
       try {
         PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
         if (posixAttributes != null) {
-          System.out.println(
-              posixAttributes.permissions().contains(PosixFilePermission.OWNER_READ));
+          System.out.println(posixAttributes.permissions().contains(OWNER_READ));
+        } else {
+          System.out.println("null");
+        }
+      } catch (UnsupportedOperationException e) {
+        System.out.println("unsupported");
+      }
+
+      try {
+        DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class);
+        if (dosFileAttributes != null) {
+          System.out.println(dosFileAttributes.isReadOnly());
         } else {
           System.out.println("null");
         }