From 35ae5eaca0567949fb58c7763b73d66a32f7488b Mon Sep 17 00:00:00 2001
From: Stefan Niedermann <info@niedermann.it>
Date: Sun, 29 Sep 2024 11:01:45 +0200
Subject: [PATCH] chore(records)!: Migrate data classes to records

This is a breaking change, but records are the structures intended to be used for pure data objects.

Signed-off-by: Stefan Niedermann <info@niedermann.it>
---
 .../android/sso/AccountImporter.java          | 25 ++++----
 .../android/sso/FilesAppTypeRegistry.java     | 63 ++++++++++---------
 .../android/sso/api/AidlNetworkRequest.java   | 14 ++---
 .../sso/helper/VersionCheckHelper.java        |  8 +--
 .../android/sso/model/FilesAppType.java       | 15 ++---
 .../model/ocs/OcsCapabilitiesResponse.java    | 62 +++++++++---------
 .../android/sso/model/ocs/OcsResponse.java    | 25 ++++----
 .../android/sso/model/ocs/OcsUser.java        | 41 ++++++------
 .../android/sso/sample/MainActivity.java      |  4 +-
 .../nextcloud/android/sso/sample/OcsAPI.java  | 10 +--
 10 files changed, 137 insertions(+), 130 deletions(-)

diff --git a/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java b/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java
index 9c71f06a..2f1c5448 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/AccountImporter.java
@@ -9,6 +9,13 @@
  */
 package com.nextcloud.android.sso;
 
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
+import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
+import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
+import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;
+
 import android.Manifest;
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -24,6 +31,10 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
 import com.nextcloud.android.sso.exceptions.AccountImportCancelledException;
 import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
 import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
@@ -39,19 +50,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
 import io.reactivex.annotations.NonNull;
 import io.reactivex.annotations.Nullable;
 
-import static android.app.Activity.RESULT_CANCELED;
-import static android.app.Activity.RESULT_OK;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
-import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;
-
 public class AccountImporter {
 
     private static final String TAG = AccountImporter.class.getCanonicalName();
@@ -120,7 +121,7 @@ private static boolean appInstalledOrNot(Context context) {
         PackageManager pm = context.getPackageManager();
         for (final var appType : FilesAppTypeRegistry.getInstance().getTypes()) {
             try {
-                pm.getPackageInfo(appType.packageId, PackageManager.GET_ACTIVITIES);
+                pm.getPackageInfo(appType.packageId(), PackageManager.GET_ACTIVITIES);
                 returnValue = true;
                 break;
             } catch (PackageManager.NameNotFoundException e) {
@@ -365,7 +366,7 @@ private static Intent buildRequestAuthTokenIntent(Context context, Intent intent
             throw new NextcloudFilesAppAccountPermissionNotGrantedException(context);
         }
 
-        String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(account.type).packageId;
+        String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(account.type).packageId();
 
         Intent authIntent = new Intent();
         authIntent.setComponent(new ComponentName(componentName,
diff --git a/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java b/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java
index 2a1a9426..4d947004 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/FilesAppTypeRegistry.java
@@ -7,72 +7,77 @@
  */
 package com.nextcloud.android.sso;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.nextcloud.android.sso.model.FilesAppType;
 
+import java.util.Collection;
 import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 public class FilesAppTypeRegistry {
     private static final FilesAppTypeRegistry FILES_APP_TYPE_REGISTRY = new FilesAppTypeRegistry();
-    private final Set<FilesAppType> types = new HashSet<>();
+    private final Collection<FilesAppType> types = new HashSet<>();
 
     public FilesAppTypeRegistry() {
-        types.add(new FilesAppType("com.nextcloud.client", "nextcloud", FilesAppType.Type.PROD));
-        types.add(new FilesAppType("com.nextcloud.android.qa", "nextcloud.qa", FilesAppType.Type.QA));
-        types.add(new FilesAppType("com.nextcloud.android.beta", "nextcloud.beta", FilesAppType.Type.DEV));
+        types.add(new FilesAppType("com.nextcloud.client", "nextcloud", FilesAppType.Stage.PROD));
+        types.add(new FilesAppType("com.nextcloud.android.qa", "nextcloud.qa", FilesAppType.Stage.QA));
+        types.add(new FilesAppType("com.nextcloud.android.beta", "nextcloud.beta", FilesAppType.Stage.DEV));
     }
 
     public static FilesAppTypeRegistry getInstance() {
         return FILES_APP_TYPE_REGISTRY;
     }
 
-    public synchronized void init(FilesAppType type) {
-        types.clear();
-
-        if (type.type != FilesAppType.Type.PROD) {
-            throw new IllegalArgumentException("If only one FilesAppType added, this must be PROD!");
+    public synchronized void init(@NonNull FilesAppType type) {
+        if (type.stage() != FilesAppType.Stage.PROD) {
+            throw new IllegalArgumentException("If only one " + FilesAppType.class.getSimpleName() + " added, this must be " + FilesAppType.Stage.PROD.name() + "!");
         }
-        
+
+        types.clear();
         types.add(type);
     }
 
-    public synchronized void init(List<FilesAppType> types) {
-        this.types.clear();
-
-        Optional<FilesAppType> prod = types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst();
-        if (prod.isEmpty()) {
-            throw new IllegalArgumentException("One provided FilesAppType must be PROD!");
+    public synchronized void init(@NonNull Collection<FilesAppType> types) {
+        if (!types.stream().anyMatch(t -> t.stage() == FilesAppType.Stage.PROD)) {
+            throw new IllegalArgumentException("At least one provided " + FilesAppType.class.getSimpleName() + " must be " + FilesAppType.Stage.PROD.name() + "!");
         }
 
+        this.types.clear();
         this.types.addAll(types);
     }
 
-    public Set<FilesAppType> getTypes() {
+    @NonNull
+    public Collection<FilesAppType> getTypes() {
         return types;
     }
 
+    @NonNull
     public String[] getAccountTypes() {
-        return types.stream().map(a -> a.accountType).toArray(String[]::new);
+        return types
+            .stream()
+            .map(FilesAppType::accountType)
+            .toArray(String[]::new);
     }
 
 
     /**
-     * @return {@link FilesAppType.Type#PROD}, {@link FilesAppType.Type#QA}
-     * or {@link FilesAppType.Type#DEV} depending on {@param accountType}.
-     * Uses {@link FilesAppType.Type#PROD} as fallback.
+     * @return {@link FilesAppType.Stage#PROD}, {@link FilesAppType.Stage#QA}
+     * or {@link FilesAppType.Stage#DEV} depending on {@param accountType}.
+     * Uses {@link FilesAppType.Stage#PROD} as fallback.
      */
     @NonNull
     public FilesAppType findByAccountType(@Nullable String accountType) {
         for (final var type : types) {
-            if (type.accountType.equalsIgnoreCase(accountType)) {
+            if (type.accountType().equalsIgnoreCase(accountType)) {
                 return type;
             }
         }
-        return types.stream().filter(t -> t.type == FilesAppType.Type.PROD).findFirst().get();
+
+        return types
+            .stream()
+            .filter(t -> t.stage() == FilesAppType.Stage.PROD)
+            .findFirst()
+            .get();
     }
 }
diff --git a/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java b/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java
index dd2e1e74..776e7361 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java
@@ -9,6 +9,9 @@
  */
 package com.nextcloud.android.sso.api;
 
+import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom;
+import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -20,6 +23,9 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.google.gson.Gson;
 import com.google.gson.internal.LinkedTreeMap;
 import com.nextcloud.android.sso.FilesAppTypeRegistry;
@@ -38,12 +44,6 @@
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import static com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil.pipeFrom;
-import static com.nextcloud.android.sso.exceptions.SSOException.parseNextcloudCustomException;
-
 public class AidlNetworkRequest extends NetworkRequest {
     private static final String TAG = AidlNetworkRequest.class.getCanonicalName();
 
@@ -91,7 +91,7 @@ public void connect(String type) {
         Log.d(TAG, "[connect] Binding to AccountManagerService for type [" + type + "]");
         super.connect(type);
 
-        final String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(type).packageId;
+        final String componentName = FilesAppTypeRegistry.getInstance().findByAccountType(type).packageId();
 
         Log.d(TAG, "[connect] Component name is: [" + componentName + "]");
 
diff --git a/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java b/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java
index a60addc3..ee3b685d 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/helper/VersionCheckHelper.java
@@ -12,6 +12,8 @@
 import android.content.pm.PackageManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.nextcloud.android.sso.FilesAppTypeRegistry;
 import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
 import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
@@ -20,8 +22,6 @@
 
 import java.util.Optional;
 
-import androidx.annotation.NonNull;
-
 public final class VersionCheckHelper {
 
     private static final String TAG = VersionCheckHelper.class.getCanonicalName();
@@ -45,7 +45,7 @@ public static boolean verifyMinVersion(@NonNull Context context, int minVersion,
                     .getInstance()
                     .getTypes()
                     .stream()
-                    .filter(t -> t.type == FilesAppType.Type.DEV)
+                    .filter(t -> t.stage() == FilesAppType.Stage.DEV)
                     .findFirst();
                 if (dev.isPresent()) {
                     final int verCode = getNextcloudFilesVersionCode(context, dev.get());
@@ -66,7 +66,7 @@ public static boolean verifyMinVersion(@NonNull Context context, int minVersion,
     }
 
     public static int getNextcloudFilesVersionCode(@NonNull Context context, @NonNull FilesAppType appType) throws PackageManager.NameNotFoundException {
-        final var packageInfo = context.getPackageManager().getPackageInfo(appType.packageId, 0);
+        final var packageInfo = context.getPackageManager().getPackageInfo(appType.packageId(), 0);
         final int verCode = packageInfo.versionCode;
         Log.d("VersionCheckHelper", "Version Code: " + verCode);
         return verCode;
diff --git a/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java b/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java
index 5b45867a..48caca18 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/model/FilesAppType.java
@@ -9,18 +9,15 @@
 
 import androidx.annotation.NonNull;
 
-public class FilesAppType {
-    public final String packageId;
-    public final String accountType;
-    public final Type type;
+public record FilesAppType(@NonNull String packageId,
+                           @NonNull String accountType,
+                           @NonNull Stage stage) {
 
-    public FilesAppType(@NonNull String packageId, @NonNull String accountType, Type type) {
-        this.packageId = packageId;
-        this.accountType = accountType;
-        this.type = type;
+    public FilesAppType(@NonNull String accountType, @NonNull String packageId) {
+        this(packageId, accountType, Stage.PROD);
     }
 
-    public enum Type {
+    public enum Stage {
         PROD, QA, DEV
     }
 }
diff --git a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsCapabilitiesResponse.java b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsCapabilitiesResponse.java
index f32cb6d6..c1b9ac23 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsCapabilitiesResponse.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsCapabilitiesResponse.java
@@ -16,51 +16,53 @@
  * <pre>
  * {@code
  * @GET("/ocs/v2.php/cloud/capabilities?format=json")
- * Call<OcsResponse<OcsCapabilities>> getCapabilities();
+ * Call<OcsResponse < OcsCapabilities>> getCapabilities();
  * }
  * </pre>
  *
  * @see <a href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#capabilities-api">Capabilities API</a>
  */
 @SuppressWarnings("unused, SpellCheckingInspection")
-public class OcsCapabilitiesResponse {
-    public OcsVersion version;
-    public OcsCapabilities capabilities;
-
-    public static class OcsVersion {
-        public int major;
-        public int minor;
-        public int macro;
-        public String string;
-        public String edition;
-        public boolean extendedSupport;
+public record OcsCapabilitiesResponse(
+    OcsVersion version,
+    OcsCapabilities capabilities
+) {
+    public record OcsVersion(
+        int major,
+        int minor,
+        int macro,
+        String string,
+        String edition,
+        boolean extendedSupport
+    ) {
     }
 
-    public static class OcsCapabilities {
-        public OcsTheming theming;
-
-        public static class OcsTheming {
-            public String name;
-            public String url;
-            public String slogan;
-            public String color;
+    public record OcsCapabilities(
+        OcsTheming theming
+    ) {
+        public record OcsTheming(
+            String name,
+            String url,
+            String slogan,
+            String color,
             @SerializedName("color-text")
-            public String colorText;
+            String colorText,
             @SerializedName("color-element")
-            public String colorElement;
+            String colorElement,
             @SerializedName("color-element-bright")
-            public String colorElementBright;
+            String colorElementBright,
             @SerializedName("color-element-dark")
-            public String colorElementDark;
-            public String logo;
-            public String background;
+            String colorElementDark,
+            String logo,
+            String background,
             @SerializedName("background-plain")
-            public boolean backgroundPlain;
+            boolean backgroundPlain,
             @SerializedName("background-default")
-            public boolean backgroundDefault;
+            boolean backgroundDefault,
             @SerializedName("logoheader")
-            public String logoHeader;
-            public String favicon;
+            String logoHeader,
+            String favicon
+        ) {
         }
     }
 }
diff --git a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsResponse.java b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsResponse.java
index 940c67ae..9779208f 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsResponse.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsResponse.java
@@ -17,25 +17,26 @@
  * <pre>
  * {@code
  * @GET("/ocs/v2.php/cloud/capabilities?format=json")
- * Call<OcsResponse<OcsCapabilitiesResponse>> getCapabilities();
+ * Call<OcsResponse < OcsCapabilitiesResponse>> getCapabilities();
  * }
  * </pre>
  *
  * @param <T> defines the payload type of this {@link OcsResponse}.
  */
 @SuppressWarnings("unused, SpellCheckingInspection")
-public class OcsResponse<T> {
-    public OcsWrapper<T> ocs;
-
-    public static class OcsWrapper<T> {
-        public OcsMeta meta;
-        public T data;
-
-        public static class OcsMeta {
-            public String status;
+public record OcsResponse<T>(
+    OcsWrapper<T> ocs
+) {
+    public record OcsWrapper<T>(
+        OcsMeta meta,
+        T data
+    ) {
+        public record OcsMeta(
+            String status,
             @SerializedName("statuscode")
-            public int statusCode;
-            public String message;
+            int statusCode,
+            String message
+        ) {
         }
     }
 }
\ No newline at end of file
diff --git a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsUser.java b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsUser.java
index c94fa15b..816c2849 100644
--- a/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsUser.java
+++ b/lib/src/main/java/com/nextcloud/android/sso/model/ocs/OcsUser.java
@@ -16,32 +16,33 @@
  * <pre>
  * {@code
  * @GET("/ocs/v2.php/cloud/users/{search}?format=json")
- * Call<OcsResponse<OcsUser>> getUser(@Path("search") String userId);
+ * Call<OcsResponse < OcsUser>> getUser(@Path("search") String userId);
  * }
  * </pre>
  * @see <a href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata">User API</a>
  */
 @SuppressWarnings("SpellCheckingInspection")
-public class OcsUser {
-    public boolean enabled;
+public record OcsUser(
+    boolean enabled,
     @SerializedName("id")
-    public String userId;
-    public long lastLogin;
-    public OcsQuota quota;
-    public String email;
+    String userId,
+    long lastLogin,
+    OcsQuota quota,
+    String email,
     @SerializedName("displayname")
-    public String displayName;
-    public String phone;
-    public String address;
-    public String website;
-    public String twitter;
-    public String[] groups;
-    public String language;
-    public String locale;
-
-    public static class OcsQuota {
-        public long free;
-        public long used;
-        public long total;
+    String displayName,
+    String phone,
+    String address,
+    String website,
+    String twitter,
+    String[] groups,
+    String language,
+    String locale
+) {
+    public record OcsQuota(
+        long free,
+        long used,
+        long total
+    ) {
     }
 }
diff --git a/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java b/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java
index 9823b3c3..cb3fe8da 100644
--- a/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java
+++ b/sample/src/main/java/com/nextcloud/android/sso/sample/MainActivity.java
@@ -90,13 +90,13 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten
 
                     try {
                         /* Perform actual requests */
-                        final var user = ocsAPI.getUser(ssoAccount.userId).execute().body().ocs.data;
+                        final var user = ocsAPI.getUser(ssoAccount.userId).execute().body().ocs().data();
                         final var serverInfo = ocsAPI.getServerInfo().execute().body().ocs.data;
 
                         /* Show result on the UI thread */
                         runOnUiThread(() -> ((TextView) findViewById(R.id.result)).setText(
                                 getString(R.string.account_info,
-                                        user.displayName,
+                                        user.displayName(),
                                         serverInfo.capabilities.theming.name,
                                         serverInfo.version.semanticVersion))
                         );
diff --git a/sample/src/main/java/com/nextcloud/android/sso/sample/OcsAPI.java b/sample/src/main/java/com/nextcloud/android/sso/sample/OcsAPI.java
index d618c1b3..d5741edb 100644
--- a/sample/src/main/java/com/nextcloud/android/sso/sample/OcsAPI.java
+++ b/sample/src/main/java/com/nextcloud/android/sso/sample/OcsAPI.java
@@ -40,14 +40,14 @@ public interface OcsAPI {
     class CustomResponse {
         public OcsNode ocs;
 
-        static class OcsNode {
+        public static class OcsNode {
             public DataNode data;
 
-            static class DataNode {
+            public static class DataNode {
                 public VersionNode version;
                 public CapabilitiesNode capabilities;
 
-                static class VersionNode {
+                public static class VersionNode {
                     /**
                      * You can map the <code>JSON</code> attributes to other variable names using {@link SerializedName}.
                      * See <a href="https://github.com/google/gson"><code>Gson</code></a>- and <a href="https://square.github.io/retrofit/"><code>Retrofit</code></a>-Documentation for all possibilities.
@@ -56,10 +56,10 @@ static class VersionNode {
                     public String semanticVersion;
                 }
 
-                static class CapabilitiesNode {
+                public static class CapabilitiesNode {
                     public ThemingNode theming;
 
-                    static class ThemingNode {
+                    public static class ThemingNode {
                         public String name;
                     }
                 }
