package com.sora.util.akatsuki;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import android.os.Bundle;
import android.util.Log;
/**
* This is not the class you are looking for (unless you want to create your own
* instance of {@link BundleRetainer}) for testing
*/
@SuppressWarnings("unused")
class Internal {
static final String BUILDER_CLASS_NAME = "Builders";
static final String BUILDER_CLASS_SUFFIX = "Builder";
private static String getPackageNameString(Class<?> clazz) {
Package pkg = clazz.getPackage();
String packageName = null;
if (pkg != null) {
return pkg.getName();
} else {
String fqcn = clazz.getName();
int i = fqcn.lastIndexOf('.');
if (i != -1) {
return fqcn.substring(0, i);
}
}
return null;
}
/**
* Finds the {@link BundleRetainer} class and instantiate it. You would not
* normally need this, this method does not do any caching and is designed
* to be used from classes that require access to a fresh
* {@link BundleRetainer} instance
*
* @param <T>
* the type of the annotated instance
* @param clazz
* the {@link Class} of the instance
* @param type
* the annotation type for retainer lookup
* @return the {@link BundleRetainer}
*/
@SuppressWarnings("unchecked")
static <T> BundleRetainer<T> createRetainer(ClassLoader loader, RetainerCache cache,
Class<?> clazz, Class<? extends Annotation> type) {
if (clazz == null)
throw new NullPointerException("class == null");
if (type == null)
throw new NullPointerException("annotation type == null");
if (type != Retained.class && type != Arg.class) {
throw new AssertionError("Unable to create retainer for unknown class " + type);
}
Class<? extends BundleRetainer<?>> retainerClass = type == Retained.class
? findRetainedRetainerClass(loader, cache, clazz)
: findArgRetainerClass(loader, cache, clazz);
try {
return (BundleRetainer<T>) retainerClass.newInstance();
} catch (ClassCastException e) {
throw new AssertionError(
clazz + "does not implement BundleRetainer or has the wrong generic "
+ "parameter, this should not happen at all",
e);
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Unable to access/instantiate retainer class", e);
}
}
@SuppressWarnings("unchecked")
static <T> Class<? extends BundleRetainer<?>> findRetainedRetainerClass(ClassLoader loader,
RetainerCache cache, Class<?> clazz) {
final BundleRetainer<T> instance;
final String fqcn = clazz.getName();
Class<? extends BundleRetainer<T>> retainerClass = null;
// TODO we didn't cache Arg.class , implement it
if (cache != null)
retainerClass = cache.getCached(fqcn);
if (retainerClass == null) {
String className = generateRetainerClassName(fqcn);
try {
retainerClass = (Class<? extends BundleRetainer<T>>) Class.forName(className, true,
loader);
} catch (ClassNotFoundException ignored) {
Log.i(Akatsuki.TAG, "Retainer class does not exist for " + clazz
+ ", was looking for " + className + "; trying inheritance next");
// can't find it, moving on
}
}
// search the tree
if (retainerClass == null)
retainerClass = (Class<? extends BundleRetainer<T>>) findClass(loader, clazz);
if (retainerClass == null) {
throw new RuntimeException("Unable to find generated class for " + fqcn
+ " while traversing the class hierarchy."
+ "\nYou cannot call Akatsuki.save/restore with classes that does not have fields annotated with @Retained."
+ "\nIf proguard is turned on, please add the respective rules for Akatsuki.");
}
return retainerClass;
}
@SuppressWarnings("unchecked")
static <T> Class<? extends BundleRetainer<T>> findArgRetainerClass(ClassLoader loader,
RetainerCache cache, Class<T> clazz) {
final BundleRetainer<T> instance;
final String fqcn = clazz.getName();
Class<? extends BundleRetainer<T>> retainerClass;
Package pkg = clazz.getPackage();
String packageName = getPackageNameString(clazz);
// give up trying, we're not getting the package name
if (packageName == null) {
throw new RuntimeException("unable to obtain a package name from class " + clazz);
}
String name = clazz.getName();
// strip the package name from the fqcn
name = name.substring(packageName.length() + 1, name.length());
String builderClassName = name + "Builder";
//Log.i(Akatsuki.TAG, "clz simple -> " + builderClassName);
// if (!clazz.isMemberClass() || false) {
// int indexOfDollar = builderClassName.lastIndexOf('$');
// if (indexOfDollar != -1) {
// builderClassName = builderClassName.substring(indexOfDollar + 1,
// builderClassName.length());
// }
// }
String className = generateRetainerClassName(packageName + "." + BUILDER_CLASS_NAME + "$"
+ builderClassName + "$" + builderClassName);
//Log.i(Akatsuki.TAG, "bcn -> " + builderClassName);
try {
retainerClass = (Class<? extends BundleRetainer<T>>) Class.forName(className, true,
loader);
} catch (ClassNotFoundException e) {
throw new AssertionError("Builder's Retainer class does not exist for " + clazz
+ ", was looking for " + className + "; this should not happen at all", e);
}
return retainerClass;
}
/**
* Traverse the class hierarchy to find correct BundleRetainer
*/
private static Class<?> findClass(ClassLoader loader, Class<?> clazz) {
final String name = clazz.getName();
if (name.startsWith("android.") || name.startsWith("java."))
return null;
String generatedClassName = generateRetainerClassName(name);
try {
if (Akatsuki.loggingLevel == AkatsukiConfig.LoggingLevel.VERBOSE)
Log.i(Akatsuki.TAG, "traversing hierarchy to find retainer for class " + clazz);
return Class.forName(generatedClassName, true, loader);
} catch (ClassNotFoundException e) {
return findClass(loader, clazz.getSuperclass());
}
}
/**
* Create the name for the generated class. <b>This is not the method you
* are looking for</b>
*
* @param prefix
* the class name
*/
static String generateRetainerClassName(CharSequence prefix) {
return prefix + "$$" + BundleRetainer.class.getSimpleName();
}
public static abstract class ArgBuilder<T> {
protected Bundle bundle;
protected abstract Class<? super T> targetClass();
protected Bundle bundle() {
return bundle;
}
protected void check() {
// for subclass to do some checking
}
}
// TODO allow the use of final fields in @Arg and possibly @Retained?
static void setFieldUnsafe(Object object, Class<?> clazz, String fieldName, Object value) {
try {
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("unable to set field [" + fieldName + "] in class " + clazz
+ " on object " + object + " with value " + value, e);
}
}
public static class ClassArgBuilder<T> extends ArgBuilder<T> {
private final Class<? super T> targetClass;
public ClassArgBuilder(Bundle bundle, Class<? super T> targetClass) {
this.bundle = bundle;
this.targetClass = targetClass;
}
@Override
protected Class<? super T> targetClass() {
return targetClass;
}
}
}