From 59c99f7de45e0ed35761f675852c60b5cb8a9714 Mon Sep 17 00:00:00 2001 From: Stefan Niedermann Date: Tue, 11 May 2021 20:28:50 +0200 Subject: [PATCH] Enhance handling, parsing, sanitizing and serialization of supported ApiVersions --- .../owncloud/notes/edit/BaseNoteFragment.java | 4 +- .../notes/persistence/NotesRepository.java | 56 ++--- .../persistence/NotesServerSyncTask.java | 21 +- .../notes/persistence/entity/Account.java | 25 -- .../sync/CapabilitiesDeserializer.java | 3 +- .../notes/shared/model/ApiVersion.java | 3 + .../notes/shared/util/ApiVersionUtil.java | 107 +++++++++ .../persistence/NotesRepositoryTest.java | 22 +- .../notes/shared/model/CapabilitiesTest.java | 2 +- .../notes/shared/util/ApiVersionUtilTest.java | 227 ++++++++++++++++++ 10 files changed, 381 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java create mode 100644 app/src/test/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtilTest.java diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java index b1cf1d54..78f20b17 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java @@ -47,6 +47,7 @@ import it.niedermann.owncloud.notes.persistence.entity.Note; import it.niedermann.owncloud.notes.shared.model.ApiVersion; import it.niedermann.owncloud.notes.shared.model.DBStatus; import it.niedermann.owncloud.notes.shared.model.ISyncCallback; +import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil; import it.niedermann.owncloud.notes.shared.util.NoteUtil; import it.niedermann.owncloud.notes.shared.util.NotesColorUtil; import it.niedermann.owncloud.notes.shared.util.ShareUtil; @@ -193,7 +194,8 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego if (note != null) { prepareFavoriteOption(menu.findItem(R.id.menu_favorite)); - menu.findItem(R.id.menu_title).setVisible(localAccount.getPreferredApiVersion() != null && localAccount.getPreferredApiVersion().compareTo(ApiVersion.API_VERSION_1_0) >= 0); + final ApiVersion preferredApiVersion = ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion()); + menu.findItem(R.id.menu_title).setVisible(preferredApiVersion != null && preferredApiVersion.compareTo(ApiVersion.API_VERSION_1_0) >= 0); menu.findItem(R.id.menu_delete).setVisible(!isNew); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java index 861987f0..1f2a2e06 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java @@ -30,11 +30,9 @@ import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.nextcloud.android.sso.model.SingleSignOnAccount; -import org.json.JSONArray; -import org.json.JSONException; - import java.util.ArrayList; import java.util.Calendar; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -61,6 +59,7 @@ import it.niedermann.owncloud.notes.shared.model.IResponseCallback; import it.niedermann.owncloud.notes.shared.model.ISyncCallback; import it.niedermann.owncloud.notes.shared.model.NavigationCategory; import it.niedermann.owncloud.notes.shared.model.SyncResultStatus; +import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil; import it.niedermann.owncloud.notes.shared.util.NoteUtil; import it.niedermann.owncloud.notes.shared.util.SSOUtil; @@ -462,8 +461,7 @@ public class NotesRepository { final Note newNote; // Re-read the up to date remoteId from the database because the UI might not have the state after synchronization yet // https://github.com/stefan-niedermann/nextcloud-notes/issues/1198 - @Nullable - final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId()); + @Nullable final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId()); if (newContent == null) { newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY()); } else { @@ -471,7 +469,8 @@ public class NotesRepository { if (newTitle != null) { title = newTitle; } else { - if ((remoteId == null || localAccount.getPreferredApiVersion() == null || localAccount.getPreferredApiVersion().compareTo(ApiVersion.API_VERSION_1_0) < 0) && + final ApiVersion preferredApiVersion = ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion()); + if ((remoteId == null || preferredApiVersion == null || preferredApiVersion.compareTo(ApiVersion.API_VERSION_1_0) < 0) && (defaultNonEmptyTitle.equals(oldNote.getTitle()))) { title = NoteUtil.generateNonEmptyNoteTitle(newContent, context); } else { @@ -573,40 +572,23 @@ public class NotesRepository { } /** - * @param apiVersion has to be a JSON array as a string ["0.2", "1.0", ...] - * @return whether or not the given {@link ApiVersion} has been written to the database - * @throws IllegalArgumentException if the apiVersion does not match the expected format + * @param raw has to be a JSON array as a string ["0.2", "1.0", ...] */ - public boolean updateApiVersion(long accountId, @Nullable String apiVersion) throws IllegalArgumentException { - if (apiVersion != null) { - try { - JSONArray apiVersions = new JSONArray(apiVersion); - for (int i = 0; i < apiVersions.length(); i++) { - ApiVersion.of(apiVersions.getString(i)); - } - if (apiVersions.length() > 0) { - final int updatedRows = db.getAccountDao().updateApiVersion(accountId, apiVersion); - if (updatedRows == 0) { - Log.d(TAG, "ApiVersion not updated, because it did not change"); - } else if (updatedRows == 1) { - Log.i(TAG, "Updated apiVersion to \"" + apiVersion + "\" for accountId = " + accountId); - ApiProvider.invalidateAPICache(); - } else { - Log.w(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + apiVersion + "\""); - } - return true; - } else { - Log.i(TAG, "Given API version is a valid JSON array but does not contain any valid API versions. Do not update database."); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("API version does contain a non-valid version: " + apiVersion); - } catch (JSONException e) { - throw new IllegalArgumentException("API version must contain be a JSON array: " + apiVersion); + public void updateApiVersion(long accountId, @Nullable String raw) { + final Collection apiVersions = ApiVersionUtil.parse(raw); + if (apiVersions.size() > 0) { + final int updatedRows = db.getAccountDao().updateApiVersion(accountId, ApiVersionUtil.serialize(apiVersions)); + if (updatedRows == 0) { + Log.d(TAG, "ApiVersion not updated, because it did not change"); + } else if (updatedRows == 1) { + Log.i(TAG, "Updated apiVersion to \"" + raw + "\" for accountId = " + accountId); + ApiProvider.invalidateAPICache(); + } else { + Log.w(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + raw + "\""); } } else { - Log.v(TAG, "Given API version is null. Do not update database"); + Log.v(TAG, "Could not extract any version from the given String: " + raw); } - return false; } /** @@ -785,7 +767,7 @@ public class NotesRepository { * * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server. */ - public synchronized void scheduleSync(Account account, boolean onlyLocalChanges) { + public synchronized void scheduleSync(@Nullable Account account, boolean onlyLocalChanges) { if (account == null) { Log.i(TAG, SingleSignOnAccount.class.getSimpleName() + " is null. Is this a local account?"); } else { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java index a40342bf..2a1855ce 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import it.niedermann.owncloud.notes.persistence.entity.Account; @@ -29,6 +28,7 @@ import it.niedermann.owncloud.notes.persistence.sync.NotesAPI; import it.niedermann.owncloud.notes.shared.model.DBStatus; import it.niedermann.owncloud.notes.shared.model.ISyncCallback; import it.niedermann.owncloud.notes.shared.model.SyncResultStatus; +import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil; import retrofit2.Response; import static it.niedermann.owncloud.notes.shared.model.DBStatus.LOCAL_DELETED; @@ -82,7 +82,7 @@ abstract class NotesServerSyncTask extends Thread { public void run() { onPreExecute(); - notesAPI = ApiProvider.getNotesAPI(context, ssoAccount, localAccount.getPreferredApiVersion()); + notesAPI = ApiProvider.getNotesAPI(context, ssoAccount, ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion())); Log.i(TAG, "STARTING SYNCHRONIZATION"); @@ -249,19 +249,10 @@ abstract class NotesServerSyncTask extends Thread { repo.updateETag(localAccount.getId(), localAccount.getETag()); repo.updateModified(localAccount.getId(), localAccount.getModified().getTimeInMillis()); - - String supportedApiVersions = null; - final String supportedApiVersionsHeader = fetchResponse.getHeaders().get(HEADER_KEY_X_NOTES_API_VERSIONS); - if (supportedApiVersionsHeader != null) { - supportedApiVersions = "[" + Objects.requireNonNull(supportedApiVersionsHeader) + "]"; - } - try { - if (repo.updateApiVersion(localAccount.getId(), supportedApiVersions)) { - localAccount.setApiVersion(supportedApiVersions); - } - } catch (Exception e) { - exceptions.add(e); - } + final String newApiVersion = ApiVersionUtil.sanitize(fetchResponse.getHeaders().get(HEADER_KEY_X_NOTES_API_VERSIONS)); + localAccount.setApiVersion(newApiVersion); + repo.updateApiVersion(localAccount.getId(), newApiVersion); + Log.d(TAG, "ApiVersion: " + newApiVersion); return true; } catch (Throwable t) { final Throwable cause = t.getCause(); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java index 07d91afc..847d2247 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Account.java @@ -73,31 +73,6 @@ public class Account implements Serializable { setCapabilities(capabilities); } - @Nullable - public ApiVersion getPreferredApiVersion() { - // TODO move this logic to NotesClient? - try { - if (apiVersion == null) { - return null; - } - final JSONArray versionsArray = new JSONArray(apiVersion); - final Collection supportedApiVersions = new HashSet<>(versionsArray.length()); - for (int i = 0; i < versionsArray.length(); i++) { - final ApiVersion parsedApiVersion = ApiVersion.of(versionsArray.getString(i)); - for (ApiVersion temp : ApiVersion.SUPPORTED_API_VERSIONS) { - if (temp.equals(parsedApiVersion)) { - supportedApiVersions.add(parsedApiVersion); - break; - } - } - } - return Collections.max(supportedApiVersions); - } catch (JSONException | NoSuchElementException e) { - e.printStackTrace(); - return null; - } - } - public void setCapabilities(@NonNull Capabilities capabilities) { capabilitiesETag = capabilities.getETag(); apiVersion = capabilities.getApiVersion(); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java index b3074e11..141443e3 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java @@ -41,8 +41,7 @@ public class CapabilitiesDeserializer implements JsonDeserializer if (capabilities.has(CAPABILITIES_NOTES)) { final JsonObject notes = capabilities.getAsJsonObject(CAPABILITIES_NOTES); if (notes.has(CAPABILITIES_NOTES_API_VERSION)) { - final JsonElement apiVersion = notes.get(CAPABILITIES_NOTES_API_VERSION); - response.setApiVersion(apiVersion.isJsonArray() ? apiVersion.toString() : null); + response.setApiVersion(notes.get(CAPABILITIES_NOTES_API_VERSION).toString()); } } if (capabilities.has(CAPABILITIES_THEMING)) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java index 94c5408d..0f8b7c1c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/ApiVersion.java @@ -83,6 +83,9 @@ public class ApiVersion implements Comparable { return 0; } + /** + * Checks only the {@link #major} version. + */ @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java new file mode 100644 index 00000000..50d1b4ea --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtil.java @@ -0,0 +1,107 @@ +package it.niedermann.owncloud.notes.shared.util; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.stream.Collectors; + +import it.niedermann.owncloud.notes.shared.model.ApiVersion; + +public class ApiVersionUtil { + + private static final String TAG = ApiVersionUtil.class.getSimpleName(); + + private ApiVersionUtil() { + + } + + /** + * @return a {@link Collection} of all valid {@link ApiVersion}s which have been found in {@param raw}. + */ + @NonNull + public static Collection parse(@Nullable String raw) { + if (TextUtils.isEmpty(raw)) { + return Collections.emptyList(); + } + + JSONArray a; + try { + a = new JSONArray(raw); + } catch (JSONException e) { + try { + a = new JSONArray("[" + raw + "]"); + } catch (JSONException e1) { + return Collections.emptyList(); + } + } + + final Collection result = new ArrayList<>(); + for (int i = 0; i < a.length(); i++) { + try { + final ApiVersion version = ApiVersion.of(a.getString(i)); + if (version.getMajor() != 0 || version.getMinor() != 0) { + result.add(version); + } + } catch (Exception ignored) { + } + } + return result; + } + + /** + * @return a serialized {@link String} of the given {@param apiVersions} or null. + */ + @Nullable + public static String serialize(@Nullable Collection apiVersions) { + if (apiVersions == null || apiVersions.isEmpty()) { + return null; + } + return "[" + + apiVersions + .stream() + .filter(Objects::nonNull) + .map(v -> v.getMajor() + "." + v.getMinor()) + .collect(Collectors.joining(",")) + + "]"; + } + + @Nullable + public static String sanitize(@Nullable String raw) { + return serialize(parse(raw)); + } + + /** + * @return the highest {@link ApiVersion} that is supported by the server according to {@param raw}, + * whose major version is also supported by this app (see {@link ApiVersion#SUPPORTED_API_VERSIONS}). + * Returns null if no better version could be found. + */ + @Nullable + public static ApiVersion getPreferredApiVersion(@Nullable String raw) { + return parse(raw) + .stream() + .filter(version -> Arrays.asList(ApiVersion.SUPPORTED_API_VERSIONS).contains(version)) + .max((o1, o2) -> { + if (o2.getMajor() > o1.getMajor()) { + return -1; + } else if (o2.getMajor() < o1.getMajor()) { + return 1; + } else if (o2.getMinor() > o1.getMinor()) { + return -1; + } else if (o2.getMinor() < o1.getMinor()) { + return 1; + } + return 0; + }) + .orElse(null); + } +} diff --git a/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java b/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java index 230da86a..fca1ce4a 100644 --- a/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java +++ b/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java @@ -156,23 +156,29 @@ public class NotesRepositoryTest { @Test public void updateApiVersion() { - assertThrows(IllegalArgumentException.class, () -> repo.updateApiVersion(account.getId(), "")); - assertThrows(IllegalArgumentException.class, () -> repo.updateApiVersion(account.getId(), "asdf")); - assertThrows(IllegalArgumentException.class, () -> repo.updateApiVersion(account.getId(), "{}")); + repo.updateApiVersion(account.getId(), ""); + assertNull(repo.getAccountById(account.getId()).getApiVersion()); + + repo.updateApiVersion(account.getId(), "foo"); + assertNull(repo.getAccountById(account.getId()).getApiVersion()); + + repo.updateApiVersion(account.getId(), "{}"); + assertNull(repo.getAccountById(account.getId()).getApiVersion()); repo.updateApiVersion(account.getId(), null); assertNull(repo.getAccountById(account.getId()).getApiVersion()); + repo.updateApiVersion(account.getId(), "[]"); assertNull(repo.getAccountById(account.getId()).getApiVersion()); repo.updateApiVersion(account.getId(), "[1.0]"); assertEquals("[1.0]", repo.getAccountById(account.getId()).getApiVersion()); - repo.updateApiVersion(account.getId(), "[0.2, 1.0]"); - assertEquals("[0.2, 1.0]", repo.getAccountById(account.getId()).getApiVersion()); - // TODO is this really indented? - repo.updateApiVersion(account.getId(), "[0.2, abc]"); - assertEquals("[0.2, abc]", repo.getAccountById(account.getId()).getApiVersion()); + repo.updateApiVersion(account.getId(), "[0.2, 1.0]"); + assertEquals("[0.2,1.0]", repo.getAccountById(account.getId()).getApiVersion()); + + repo.updateApiVersion(account.getId(), "[0.2, foo]"); + assertEquals("[0.2]", repo.getAccountById(account.getId()).getApiVersion()); } @Test diff --git a/app/src/test/java/it/niedermann/owncloud/notes/shared/model/CapabilitiesTest.java b/app/src/test/java/it/niedermann/owncloud/notes/shared/model/CapabilitiesTest.java index 3977e488..787bcf77 100644 --- a/app/src/test/java/it/niedermann/owncloud/notes/shared/model/CapabilitiesTest.java +++ b/app/src/test/java/it/niedermann/owncloud/notes/shared/model/CapabilitiesTest.java @@ -107,7 +107,7 @@ public class CapabilitiesTest { "}"; final Capabilities capabilities = new CapabilitiesDeserializer().deserialize(JsonParser.parseString(response), null, null); assertNull(capabilities.getETag()); - assertNull(capabilities.getApiVersion()); + assertEquals("\"1.0\"", capabilities.getApiVersion()); assertEquals(Color.parseColor("#1E4164"), capabilities.getColor()); assertEquals(Color.parseColor("#ffffff"), capabilities.getTextColor()); } diff --git a/app/src/test/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtilTest.java b/app/src/test/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtilTest.java new file mode 100644 index 00000000..15ef19c4 --- /dev/null +++ b/app/src/test/java/it/niedermann/owncloud/notes/shared/util/ApiVersionUtilTest.java @@ -0,0 +1,227 @@ +package it.niedermann.owncloud.notes.shared.util; + +import android.os.Build; + +import junit.framework.TestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import it.niedermann.owncloud.notes.shared.model.ApiVersion; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = {Build.VERSION_CODES.P}) +public class ApiVersionUtilTest extends TestCase { + + @Test + public void testSanitizeVersionString_invalid_one() { + assertEquals(0, ApiVersionUtil.parse(null).size()); + assertEquals(0, ApiVersionUtil.parse("").size()); + assertEquals(0, ApiVersionUtil.parse(" ").size()); + assertEquals(0, ApiVersionUtil.parse("{}").size()); + assertEquals(0, ApiVersionUtil.parse("[]").size()); + } + + @Test + public void testSanitizeVersionString_valid_one() { + Collection result; + ApiVersion current; + + result = ApiVersionUtil.parse("[0.2]"); + assertEquals(1, result.size()); + current = result.iterator().next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + + result = ApiVersionUtil.parse("[1.0]"); + assertEquals(1, result.size()); + current = result.iterator().next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[\"0.2\"]"); + assertEquals(1, result.size()); + current = result.iterator().next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + + result = ApiVersionUtil.parse("[\"1.0\"]"); + assertEquals(1, result.size()); + current = result.iterator().next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("1.0"); + assertEquals(1, result.size()); + current = result.iterator().next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + } + + @Test + public void testSanitizeVersionString_invalid_many() { + Collection result; + ApiVersion current; + Iterator iterator; + + result = ApiVersionUtil.parse("[0.2, foo]"); + assertEquals(1, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + + result = ApiVersionUtil.parse("[foo, 1.1]"); + assertEquals(1, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(1, current.getMinor()); + + assertEquals(0, ApiVersionUtil.parse("[foo, bar]").size()); + + result = ApiVersionUtil.parse("[, 1.1]"); + assertEquals(1, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(1, current.getMinor()); + + result = ApiVersionUtil.parse("[1.1, ?]"); + assertEquals(1, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(1, current.getMinor()); + } + + @Test + public void testSanitizeVersionString_valid_many() { + Collection result; + ApiVersion current; + Iterator iterator; + + result = ApiVersionUtil.parse("[0.2, 1.0]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[\"0.2\", \"1.0\"]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[0.2,1.0]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[\"0.2\",\"1.0\"]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[0.2, \"1.0\"]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + + result = ApiVersionUtil.parse("[0.2,\"1.0\"]"); + assertEquals(2, result.size()); + iterator = result.iterator(); + current = iterator.next(); + assertEquals(0, current.getMajor()); + assertEquals(2, current.getMinor()); + current = iterator.next(); + assertEquals(1, current.getMajor()); + assertEquals(0, current.getMinor()); + } + + @Test + public void testSerialize() { + assertNull(ApiVersionUtil.serialize(null)); + assertNull(ApiVersionUtil.serialize(Collections.emptyList())); + + assertEquals("[0.2]", ApiVersionUtil.serialize(Collections.singleton(ApiVersion.API_VERSION_0_2))); + assertEquals("[1.0]", ApiVersionUtil.serialize(Collections.singleton(ApiVersion.API_VERSION_1_0))); + + assertEquals("[1.0]", ApiVersionUtil.serialize(Arrays.asList(ApiVersion.API_VERSION_1_0, null))); + assertEquals("[1.0]", ApiVersionUtil.serialize(Arrays.asList(null, ApiVersion.API_VERSION_1_0))); + + assertEquals("[0.2,1.0]", ApiVersionUtil.serialize(Arrays.asList(ApiVersion.API_VERSION_0_2, ApiVersion.API_VERSION_1_0))); + + // TODO sure...? + assertEquals("[1.0,1.0]", ApiVersionUtil.serialize(Arrays.asList(ApiVersion.API_VERSION_1_0, ApiVersion.API_VERSION_1_0))); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testGetPreferredApiVersion() { + assertNull(ApiVersionUtil.getPreferredApiVersion(null)); + assertNull(ApiVersionUtil.getPreferredApiVersion("")); + assertNull(ApiVersionUtil.getPreferredApiVersion("[]")); + assertNull(ApiVersionUtil.getPreferredApiVersion("foo")); + + ApiVersion result; + + result = ApiVersionUtil.getPreferredApiVersion("[0.2]"); + assertEquals(0, result.getMajor()); + assertEquals(2, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[1.1]"); + assertEquals(1, result.getMajor()); + assertEquals(1, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[0.2,1.1]"); + assertEquals(1, result.getMajor()); + assertEquals(1, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[1.1,0.2]"); + assertEquals(1, result.getMajor()); + assertEquals(1, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[10.0,1.1,1.0,0.2]"); + assertEquals(1, result.getMajor()); + assertEquals(1, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[1.1,1.5,1.0]"); + assertEquals(1, result.getMajor()); + assertEquals(5, result.getMinor()); + + result = ApiVersionUtil.getPreferredApiVersion("[1.1,,foo,1.0]"); + assertEquals(1, result.getMajor()); + assertEquals(1, result.getMinor()); + } +} \ No newline at end of file