Skip to content

Commit

Permalink
feat: provide string format and wrapToString extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
tynn committed Nov 28, 2023
1 parent 5499f35 commit ed62b7c
Show file tree
Hide file tree
Showing 25 changed files with 1,358 additions and 626 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,21 @@ There are several _standard_ implementations of `AString` for:
* `getText()` and `getQuantityText()` delegates

* the `getPackageName()` delegate
* the `versionName` resolver
* the `PackageInfo.versionName` resolver


### `AString` string format and `toString()` wrapper

While `AString` represent any generic `CharSequence`, it might be useful to use
it as a `String` only or even use it as a format string.

aString.format(1, "2")
aString.format(Locale.UK, 1, "2")

aString.wrapToString()

This behavior is similar to the approaches taken for `getText(int)`,
`getString(int)` and `getString(int, Object...)` string resource accessors.


### _Jetpack_ Compose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,142 @@
import static xyz.tynn.astring.test.AStringAssert.assertParcelableAStringIdentity;
import static xyz.tynn.astring.test.AStringAssert.assertParcelableAStringInvocation;

import android.content.Context;
import android.os.Parcel;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.junit.Test;

import java.util.Date;
import java.util.Locale;

public class ParcelableAStringTest {

public static final int RES_ID = 123;
public static final int QUANTITY = 456;
public static final Object[] FORMAT_ARGS = {"arg1", 2, 3L, 4.5, 6F, new Date()};
private static final Locale LOCALE = Locale.UK;
private static final int RES_ID = 123;
private static final int QUANTITY = 456;
private static final AString FORMAT = new Format();
private static final Object[] FORMAT_ARGS = {"arg1", 2, 3L, 4.5, 6F, new Date()};

@Test
public void Wrapper_should_implement_parcelable() {
assertParcelableAStringEquality(Wrapper.wrap("test-string"));
assertParcelableAStringInvocation(Wrapper.wrap("test-string"));
}

@Test
public void Wrapper_of_null_should_implement_parcelable() {
assertParcelableAStringEquality(Wrapper.wrap(null));
assertParcelableAStringInvocation(Wrapper.wrap(null));
}

@Test
public void Wrapper_NULL_should_implement_parcelable() {
assertParcelableAStringIdentity(Wrapper.NULL);
assertParcelableAStringInvocation(Wrapper.NULL);
}

@Test
public void CharSequenceWrapper_should_implement_parcelable() {
assertParcelableAStringEquality(new CharSequenceWrapper("test-string"));
assertParcelableAStringInvocation(new CharSequenceWrapper("test-string"));
public void ToString_should_implement_parcelable() {
assertParcelableAStringEquality(ToString.wrap(FORMAT, LOCALE, FORMAT_ARGS));
assertParcelableAStringInvocation(ToString.wrap(FORMAT, LOCALE, FORMAT_ARGS));
}

@Test
public void CharSequenceWrapper_of_null_should_implement_parcelable() {
assertParcelableAStringEquality(new CharSequenceWrapper(null));
assertParcelableAStringInvocation(new CharSequenceWrapper(null));
public void ToString_without_locale_should_implement_parcelable() {
assertParcelableAStringEquality(ToString.wrap(FORMAT, null, FORMAT_ARGS));
assertParcelableAStringInvocation(ToString.wrap(FORMAT, null, FORMAT_ARGS));
}

@Test
public void CharSequenceWrapper_NULL_should_implement_parcelable() {
assertParcelableAStringIdentity(CharSequenceWrapper.NULL);
assertParcelableAStringInvocation(CharSequenceWrapper.NULL);
public void ToString_without_args_should_implement_parcelable() {
assertParcelableAStringEquality(ToString.wrap(FORMAT, LOCALE, null));
assertParcelableAStringInvocation(ToString.wrap(FORMAT, LOCALE, null));
}

@Test
public void ResourceDelegate_quantityString_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.quantityString(RES_ID, QUANTITY, FORMAT_ARGS));
public void ToString_without_args_and_locale_should_implement_parcelable() {
assertParcelableAStringEquality(ToString.wrap(FORMAT, null, null));
assertParcelableAStringInvocation(ToString.wrap(FORMAT, null, null));
}

@Test
public void ResourceDelegate_quantityString_without_args_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.quantityString(RES_ID, QUANTITY, null));
public void Resource_quantity_text_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY));
}

@Test
public void ResourceDelegate_quantityText_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.quantityText(RES_ID, QUANTITY));
public void Resource_quantity_string_with_args_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY, FORMAT_ARGS));
}

@Test
public void ResourceDelegate_string_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.string(RES_ID, FORMAT_ARGS));
public void Resource_quantity_string_without_args_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, QUANTITY, null));
}

@Test
public void ResourceDelegate_string_without_args_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.string(RES_ID, null));
public void Resource_text_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, null));
}

@Test
public void ResourceDelegate_text_should_implement_parcelable() {
assertParcelableAStringEquality(ResourceDelegate.text(RES_ID));
public void Resource_string_with_args_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, null, FORMAT_ARGS));
}

@Test
public void ContextValueProvider_AppIdProvider_should_implement_parcelable() {
assertParcelableAStringIdentity(ContextValueProvider.AppIdProvider);
assertParcelableAStringInvocation(ContextValueProvider.AppIdProvider);
public void Resource_string_without_args_should_implement_parcelable() {
assertParcelableAStringEquality(Resource.wrap(RES_ID, null, null));
}

@Test
public void ContextValueProvider_AppVersionProvider_should_implement_parcelable() {
assertParcelableAStringIdentity(ContextValueProvider.AppVersionProvider);
assertParcelableAStringInvocation(ContextValueProvider.AppVersionProvider);
public void AppIdProvider_should_implement_parcelable() {
assertParcelableAStringIdentity(Provider.AppIdProvider);
assertParcelableAStringInvocation(Provider.AppIdProvider);
}

@Test
public void AppVersionProvider_should_implement_parcelable() {
assertParcelableAStringIdentity(Provider.AppVersionProvider);
assertParcelableAStringInvocation(Provider.AppVersionProvider);
}

private static class Format implements AString {

@Nullable
@Override
public String invoke(@NonNull Context context) {
return "%s%d%d%f%f%s";
}

@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
}

@Override
public boolean equals(@Nullable Object obj) {
return obj instanceof Format;
}

@Override
public int hashCode() {
return 43;
}

public static final Creator<Format> CREATOR = new Creator<>() {

@Override
public Format createFromParcel(Parcel source) {
return new Format();
}

@Override
public Format[] newArray(int size) {
return new Format[size];
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,21 @@

package xyz.tynn.astring;

import static android.content.pm.PackageManager.PackageInfoFlags.of;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.TIRAMISU;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Parcel;

import androidx.annotation.NonNull;

enum ContextValueProvider implements AString {
enum Provider implements AString {

/**
* Implementation providing {@link Context#getPackageName()}
*/
AppIdProvider {
@Override
public CharSequence invoke(@NonNull Context context) {
public String invoke(@NonNull Context context) {
return context.getPackageName();
}

Expand All @@ -37,14 +33,11 @@ public String toString() {
*/
AppVersionProvider {
@Override
@SuppressWarnings("deprecation")
public CharSequence invoke(@NonNull Context context) {
public String invoke(@NonNull Context context) {
try {
String packageName = context.getPackageName();
PackageManager packageManager = context.getPackageManager();
if (SDK_INT < TIRAMISU)
return packageManager.getPackageInfo(packageName, 0).versionName;
return packageManager.getPackageInfo(packageName, of(0)).versionName;
return packageManager.getPackageInfo(packageName, 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e);
}
Expand All @@ -64,16 +57,16 @@ public void writeToParcel(@NonNull Parcel parcel, int i) {
parcel.writeString(name());
}

public static final Creator<ContextValueProvider> CREATOR = new Creator<>() {
public static final Creator<Provider> CREATOR = new Creator<>() {

@Override
public ContextValueProvider createFromParcel(Parcel source) {
return ContextValueProvider.valueOf(source.readString());
public Provider createFromParcel(Parcel source) {
return Provider.valueOf(source.readString());
}

@Override
public ContextValueProvider[] newArray(int size) {
return new ContextValueProvider[size];
public Provider[] newArray(int size) {
return new Provider[size];
}
};
}
90 changes: 90 additions & 0 deletions astring/src/main/java/xyz/tynn/astring/Resource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2023 Christian Schmitz
// SPDX-License-Identifier: Apache-2.0

package xyz.tynn.astring;

import static android.content.res.Resources.ID_NULL;
import static androidx.core.os.ParcelCompat.readBoolean;
import static androidx.core.os.ParcelCompat.writeBoolean;
import static xyz.tynn.astring.Wrapper.NULL;

import android.content.Context;
import android.os.Parcel;

import androidx.annotation.NonNull;

import java.util.Objects;

/**
* Implementation delegating to a resource with or without format arguments
*/
final class Resource implements AString {

private final int resId;
private final Integer quantity;

private Resource(int resId, Integer quantity) {
this.resId = resId;
this.quantity = quantity;
}

static AString wrap(int resId, Integer quantity) {
if (resId == ID_NULL) return NULL;
return new Resource(resId, quantity);
}

static AString wrap(int resId, Integer quantity, Object[] formatArgs) {
if (resId == ID_NULL) return NULL;
return ToString.wrap(new Resource(resId, quantity), null, formatArgs);
}

@Override
public CharSequence invoke(@NonNull Context context) {
return quantity == null ? context.getText(resId) :
context.getResources().getQuantityText(resId, quantity);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Resource that = (Resource) o;
return resId == that.resId && Objects.equals(quantity, that.quantity);
}

@Override
public int hashCode() {
return Objects.hash(resId, quantity);
}

@NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder("AString(");
if (quantity != null) sb.append("Quantity");
sb.append("TextResource(").append(resId);
if (quantity != null) sb.append(',').append(quantity);
return sb.append("))").toString();
}

@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(resId);
writeBoolean(dest, quantity != null);
if (quantity != null) dest.writeInt(quantity);
}

public static final Creator<Resource> CREATOR = new Creator<>() {

@Override
public Resource createFromParcel(Parcel source) {
return new Resource(source.readInt(),
readBoolean(source) ? source.readInt() : null);
}

@Override
public Resource[] newArray(int size) {
return new Resource[size];
}
};
}
Loading

0 comments on commit ed62b7c

Please sign in to comment.