package com.sora.util.akatsuki; import java.io.Serializable; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import java.util.function.Function; import javax.lang.model.element.Modifier; import org.junit.Test; import android.accounts.Account; import android.app.Activity; import android.app.Notification; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.PointF; import android.location.Location; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; import android.text.Editable; import android.text.Spannable; import android.text.Spanned; import android.util.Size; import android.util.SizeF; import android.util.SparseArray; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Primitives; import com.sora.util.akatsuki.RetainedStateTestEnvironment.BundleRetainerTester; import com.sora.util.akatsuki.RetainedStateTestEnvironment.BundleRetainerTester.AccessorKeyPair; import com.squareup.javapoet.ClassName; public class SupportedTypeIntegrationTest extends RetainedStateIntegrationTestBase { public static class MySerializable implements Serializable { } public static final Class<?>[] PRIMITIVES = new Class<?>[] { boolean.class, byte.class, short.class, int.class, long.class, char.class, float.class, double.class }; // these classes are just some random parcelable subclasses chosen // based on intuition :) @SuppressWarnings("unchecked") public static final Class<? extends Parcelable>[] PARCELABLES_CLASSES = new Class[] { Parcelable.class, Account.class, Location.class, Bitmap.class, Intent.class, Notification.class, Point.class, PointF.class }; public static final Class<?>[] SUPPORTED_ARRAY_CLASSES = { Parcelable[].class, CharSequence[].class, String[].class }; public static final Class<?>[] SUPPORTED_SIMPLE_CLASSES = { Size.class, SizeF.class, String.class, CharSequence.class, IBinder.class, Bundle.class, Serializable.class }; public static final ImmutableMap<Class<?>, Class<?>> SUPPORTED_SIMPLE_SUBCLASSES_MAP = ImmutableMap .<Class<?>, Class<?>> builder().put(StringBuilder.class, CharSequence.class) .put(Spannable.class, CharSequence.class).put(Binder.class, IBinder.class) .put(MySerializable.class, Serializable.class).build(); @Test(expected = RuntimeException.class) public void testUnsupportedType() { final TestSource source = new TestSource(TEST_PACKAGE, generateClassName(), Modifier.PUBLIC) .appendFields(field(ClassName.get(Activity.class), "badType", Retained.class)); new RetainedStateTestEnvironment(this, source); } @Test public void testPrimitives() { testSimpleTypes(t -> true, BundleRetainerTester.CLASS_EQ, Function.identity(), PRIMITIVES); } @Test public void testBoxedPrimitives() { // boxed primitives cannot be null otherwise we get NPE when unboxing ImmutableMap<Class<?>, String> defaultValueMap = ImmutableMap.<Class<?>, String> builder() .put(Byte.class, "0").put(Short.class, "0").put(Integer.class, "0") .put(Long.class, "0L").put(Float.class, "0.0F").put(Double.class, "0.0D") .put(Character.class, "\'a\'").put(Boolean.class, "false").build(); RetainedTestField[] fields = defaultValueMap.entrySet() .stream().map(ent -> new RetainedTestField(ent.getKey(), "_" + ent.getKey().getSimpleName(), ent.getValue())) .toArray(RetainedTestField[]::new); // comparison between primitives and boxed primitives does not work, // manually wrap it testTypes(ALWAYS, (field, type, arguments) -> type.isPrimitive() && field.clazz == Primitives.wrap(type), f->1, fields); } @Test public void testPrimitiveArrays() { final Class<?>[] classes = Arrays.stream(PRIMITIVES).map(this::toArrayClass) .toArray(Class<?>[]::new); testSimpleTypes(n -> n.contains("Array"), BundleRetainerTester.CLASS_EQ, Function.identity(), classes); } @Test public void testSupportedSimpleTypes() { testSimpleTypes(n -> true, BundleRetainerTester.CLASS_EQ, Function.identity(), SUPPORTED_SIMPLE_CLASSES); } @Test public void testSubclassOfSupportedTypes() { for (Entry<Class<?>, Class<?>> entry : SUPPORTED_SIMPLE_SUBCLASSES_MAP.entrySet()) { testSimpleTypes(n -> true, (f, t, a) -> t.equals(entry.getValue()), Function.identity(), entry.getKey()); } } @Test public void testParcelableAndParcelableSubclassTypes() { // parcelable requires special care because the get accessor returns a // <T> instead of Parcelable for (Class<? extends Parcelable> type : PARCELABLES_CLASSES) { // filter out the method [get|set]Parcelable and work with that only testSimpleTypes(n -> n.endsWith("Parcelable"), BundleRetainerTester.ALWAYS, Function.identity(), type); } } @Test public void testSupportedArrayTypes() { testSimpleTypes(n -> n.contains("Array"), BundleRetainerTester.CLASS_EQ, Function.identity(), SUPPORTED_ARRAY_CLASSES); } @Test public void testSupportedArraySubclassTypes() { List<Class<?>> classes = new ArrayList<>(); classes.addAll(Arrays.asList(PARCELABLES_CLASSES)); classes.addAll(Arrays.asList(StringBuilder.class, CharBuffer.class, Spannable.class, Editable.class, Spanned.class)); for (Class<?> type : classes) { // the type of the accessor must be assignable to the field's testSimpleTypes(n -> n.contains("Array"), BundleRetainerTester.ASSIGNABLE, Function.identity(), toArrayClass(type)); } } @Test public void testSparseArrayParcelableType() { testParameterizedTypes(n -> n.contains("SparseParcelableArray"), BundleRetainerTester.CLASS_EQ, SparseArray.class, PARCELABLES_CLASSES); } @Test public void testParcelableArrayListType() { // parcelable arraylist also requires special care because the generic // argument of the setter is a wildcard (<? extends Parcelable>) testParameterizedTypes(n -> n.contains("ParcelableArrayList"), BundleRetainerTester.ALWAYS, ArrayList.class, PARCELABLES_CLASSES); } @Test public void testSupportedArrayListTypes() { testParameterizedTypes(n -> n.contains("ArrayList"), (f, t, a) -> t.equals(ArrayList.class) && Arrays.equals(a, f.parameters), ArrayList.class, String.class, Integer.class, CharSequence.class); } @Test public void testSupportedCollectionTypes() { testParameterizedTypes(n -> n.contains("ArrayList"), (f, t, a) -> t.equals(ArrayList.class) && Arrays.equals(a, f.parameters), ArrayList.class, String.class, Integer.class, CharSequence.class); } @Test public void testSimpleInheritance1() { testInheritance(true, new RetainedTestField(String.class, "a"), new RetainedTestField(int.class, "b")); } @Test public void testSimpleInheritance2() { testInheritance(true, new RetainedTestField(CharSequence.class, "a"), new RetainedTestField(Parcelable.class, "b")); } @Test public void testSimpleInheritance3() { testInheritance(true, new RetainedTestField(float.class, "a"), new RetainedTestField(double.class, "b")); } @Test public void testMultiLevelInheritance1() { testInheritance(true, new RetainedTestField(String.class, "a"), new RetainedTestField(int.class, "b"), new RetainedTestField(long.class, "c")); } @Test public void testDeepInheritance() { RetainedTestField[] fields = new RetainedTestField[SUPPORTED_SIMPLE_CLASSES.length]; for (int i = 0; i < fields.length; i++) { fields[i] = new RetainedTestField(SUPPORTED_SIMPLE_CLASSES[i], "s" + i); } testInheritance(true, new RetainedTestField(int.class, "first"), fields); } @Test public void testMultiLevelInheritance2() { testInheritance(true, new RetainedTestField(int.class, "d"), new RetainedTestField(String.class, "e"), new RetainedTestField(long.class, "f"), new RetainedTestField(Parcelable.class, "g")); } @Test public void testMultiLevelInheritanceWithGap() { testInheritance(true, new RetainedTestField(String.class, "h"), new TestField(int.class, "i"), new RetainedTestField(long.class, "j")); } @Test public void testInheritanceWithoutAnnotations() { testInheritance(true, new TestField(int.class, "a"), new RetainedTestField(String.class, "b")); } @Test public void testInheritanceWithoutAnnotationsAndCache() { testInheritance(false, new TestField(int.class, "a"), new RetainedTestField(String.class, "b")); } @Test public void testFieldHiding() { final RetainedTestField first = new RetainedTestField(String.class, "a"); final RetainedTestField second = new RetainedTestField(int.class, "a"); final RetainedStateTestEnvironment environment = testFieldHiding(first, second); environment.tester().invokeSaveAndRestore(); final AccessorKeyPair firstKeys = environment.tester().captureTestCaseKeysWithField(first, n -> true, BundleRetainerTester.CLASS_EQ); final AccessorKeyPair secondKeys = environment.tester().captureTestCaseKeysWithField(second, n -> true, BundleRetainerTester.CLASS_EQ); firstKeys.assertSameKeyUsed(); secondKeys.assertSameKeyUsed(); firstKeys.assertNotTheSame(secondKeys); } @Test public void testGenericTypeOfT() { for (Class<?> clazz : SUPPORTED_SIMPLE_CLASSES) { testGenericType("T", clazz); } } @Test public void testGenericTypeOfSomethingLong() { for (Class<?> clazz : SUPPORTED_SIMPLE_CLASSES) { testGenericType("SomeVeryLongParameterNameThatMightBreak", clazz); } } @Test public void testIntersectionType() { testGenericType("T", Parcelable.class, Serializable.class); } }