package com.sora.util.akatsuki; import java.lang.annotation.Annotation; import java.util.Map; import java.util.WeakHashMap; import android.app.Activity; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.util.Log; import android.view.View; import com.sora.util.akatsuki.AkatsukiConfig.LoggingLevel; /** * Contains API for working with {@link Retained} annotated fields. */ @SuppressWarnings("ALL") public class Akatsuki { private static final Map<String, BundleRetainer<?>> CLASS_CACHE = new WeakHashMap<>(); private static final Map<Class<? extends TypeConverter<?>>, TypeConverter<?>> CACHED_CONVERTERS = new WeakHashMap<>(); static final String RETAINER_CACHE_NAME = "AkatsukiMapping"; static final String RETAINER_CACHE_PACKAGE = "com.sora.util.akatsuki"; static final String TAG = "Akatsuki"; static LoggingLevel loggingLevel = AkatsukiConfig.LoggingLevel.ERROR_ONLY; private static RetainerCache retainerCache; static { Class<?> retainerCacheClass = null; try { retainerCacheClass = Class.forName(RETAINER_CACHE_PACKAGE + "." + RETAINER_CACHE_NAME); } catch (ClassNotFoundException ignored) { // we don't have it, that's fine } if (retainerCacheClass != null) { try { retainerCache = (RetainerCache) retainerCacheClass.newInstance(); } catch (Exception e) { // we have it but it's broken, not good throw new RuntimeException("Unable to instantiate RetainerCache", e); } } } /** * Sets the current logging level */ public static void setLoggingLevel(LoggingLevel level) { loggingLevel = level; } public static LoggingLevel loggingLevel() { return loggingLevel; } /** * Saves all fields annotated with {@link Retained} into the provided bundle * * @param instance * the object that contains the annotated fields * @param outState * the bundle for saving, not null */ public static void save(Object instance, Bundle outState) { save(classLoader(), instance, outState); } static void save(ClassLoader loader, Object instance, Bundle outState) { checkInstance(instance, "instance"); checkInstance(outState, "outState"); findRetainerInstance(loader, instance, Retained.class).save(instance, outState); } // all restores below work with @Retained and @Arg public static void restore(Fragment fragment, Bundle savedInstanceState) { restore(classLoader(), fragment, savedInstanceState); } static void restore(ClassLoader loader, Fragment fragment, Bundle savedInstanceState) { checkInstance(fragment, "fragment"); restore(loader, fragment, savedInstanceState, fragment.getArguments()); } public static void restore(android.app.Fragment fragment, Bundle savedInstanceState) { restore(classLoader(), fragment, savedInstanceState); } static void restore(ClassLoader loader, android.app.Fragment fragment, Bundle savedInstanceState) { checkInstance(fragment, "fragment"); restore(loader, fragment, savedInstanceState, fragment.getArguments()); } public static void restore(Activity activity, Bundle savedInstanceState) { restore(classLoader(), activity, savedInstanceState); } static void restore(ClassLoader loader, Activity activity, Bundle savedInstanceState) { checkInstance(activity, "activity"); Intent intent = activity.getIntent(); restore(loader, activity, savedInstanceState, intent != null ? intent.getExtras() : null); } /** * Restores the given intent to the service * * @param service * the given service * @param intent * the intent from * {@link Service#onStartCommand(Intent, int, int)} */ public static void restore(Service service, Intent intent) { restore(classLoader(), service, intent); } static void restore(ClassLoader loader, Service service, Intent intent) { checkInstance(service, "service"); findRetainerInstance(loader, service, Arg.class).restore(service, intent.getExtras()); } /** * Restores the arguments and states from the bundles to the given instance * * @param instance * @param state * @param argument */ public static void restore(Object instance, Bundle state, Bundle argument) { restore(classLoader(), instance, state, argument); } static void restore(ClassLoader loader, Object instance, Bundle state, Bundle argument) { checkInstance(instance, "instance"); if (state != null) findRetainerInstance(loader, instance, Retained.class).restore(instance, state); if (argument != null) findRetainerInstance(loader, instance, Arg.class).restore(instance, argument); } /** * Like {@link #save(Object, Bundle)} but included some View state aware * logic, use this if you want to save view states. Typical usage looks * like: * <p> * * <pre> * {@code * @Override * protected Parcelable onSaveInstanceState() { * return Akatsuki.save(this, super.onSaveInstanceState()); * } * } * </pre> * * @param view * the view containing annotated fields * @param parcelable * from {@code super.onSaveInstanceState()} * @return a parcelable to returned in {@link View#onSaveInstanceState()} */ public static Parcelable save(View view, Parcelable parcelable) { return save(classLoader(), view, parcelable); } static Parcelable save(ClassLoader loader, View view, Parcelable parcelable) { checkInstance(view, "view"); Bundle bundle = new Bundle(); bundle.putParcelable(view.getClass().getName(), parcelable); save(loader, (Object) view, bundle); return bundle; } /** * For restoring states saved by {@link #save(View, Parcelable)}.Typical * usage looks like: * <p> * * <pre> * {@code * @Override * protected void onRestoreInstanceState(Parcelable state) { * super.onRestoreInstanceState(Akatsuki.restore(this, state)); * } * } * </pre> * * @param view * the view that requires restoring * @param parcelable * restored state from the parameter of * {@link View#onRestoreInstanceState(Parcelable)} * @return a parcelable to be passed to * {@code super.onRestoreInstanceState()} */ public static Parcelable restore(View view, Parcelable parcelable) { return restore(classLoader(), view, parcelable); } static Parcelable restore(ClassLoader loader, View view, Parcelable parcelable) { if (parcelable instanceof Bundle) { final Bundle bundle = (Bundle) parcelable; restore(loader, (Object) view, bundle, null); return bundle.getParcelable(view.getClass().getName()); } else { throw new RuntimeException("View state of view " + view.getClass() + " is not saved with Akatsuki View.onSaveInstanceState()"); } } /** * Serializes the given instance into a Bundle */ public static Bundle serialize(Object instance) { return serialize(classLoader(), instance); } static Bundle serialize(ClassLoader loader, Object instance) { Bundle bundle = new Bundle(); save(instance, bundle); return bundle; } /** * Deserialize the given bundle into the original instance * * @param instance * the instantiated instance * @param bundle * the bundle * @return deserialized instance */ public static <T> T deserialize(T instance, Bundle bundle) { return deserialize(classLoader(), instance, bundle); } static <T> T deserialize(ClassLoader loader, T instance, Bundle bundle) { restore(loader, instance, bundle, null); return instance; } /** * Same as {@link #deserialize(Object, Bundle)} but with a * {@link InstanceSupplier} so that the instance can be instantiated without * creating an instance first * * @param supplier * the instance supplier * @param bundle * the bundle * @return deserialized instance */ public static <T> T deserialize(InstanceSupplier<T> supplier, Bundle bundle) { final T t = supplier.create(); return deserialize(t, bundle); } private static void checkInstance(Object object, String name) { if (object == null) throw new IllegalArgumentException(name + " == null!"); } /** * An interface that supplies {@link #deserialize(InstanceSupplier, Bundle)} * an working instance * * @param <T> * the type of the instance */ public interface InstanceSupplier<T> { /** * Creates the instance */ T create(); } static <T> BundleRetainer<T> findRetainerInstance(ClassLoader loader, T instance, Class<? extends Annotation> type) { final String fqcn = instance.getClass().getName(); String retainerKey = generateRetainerKey(instance.getClass(), type); if (loggingLevel == AkatsukiConfig.LoggingLevel.VERBOSE) Log.i(TAG, "looking through cache with key " + retainerKey); BundleRetainer<T> retainer = (BundleRetainer<T>) CLASS_CACHE.get(retainerKey); if (retainer == null) { retainer = Internal.createRetainer(loader, retainerCache, instance.getClass(), type); CLASS_CACHE.put(retainerKey, retainer); if (loggingLevel == AkatsukiConfig.LoggingLevel.VERBOSE) Log.i(TAG, "cache miss for class " + fqcn + " for type " + type + " retainer is " + retainer.getClass()); } else { if (loggingLevel == AkatsukiConfig.LoggingLevel.VERBOSE) Log.i(TAG, "cache hit for class " + fqcn + " for type " + type + " retainer is " + retainer.getClass()); } return retainer; } private static ClassLoader classLoader() { return Thread.currentThread().getContextClassLoader(); } private static String generateRetainerKey(Class<?> clazz, Class<? extends Annotation> type) { return clazz.getName() + "_" + type.getName(); } private static void discardCache() { CLASS_CACHE.clear(); } /** * Finds the converter from the cache or create one. <b>This is not the * method you are looking for</b> * * @param key * the class of the converter */ @SuppressWarnings("unchecked") public static <T> TypeConverter<T> converter(Class<? extends TypeConverter<T>> key) { TypeConverter<T> converter = (TypeConverter<T>) CACHED_CONVERTERS.get(key); if (converter == null) { try { converter = key.newInstance(); } catch (Exception e) { converter = new InvalidTypeConverter(e); } CACHED_CONVERTERS.put(key, converter); } return converter; } }