Android Open Source - retention-magic Retention Magic






From Project

Back to project page retention-magic.

License

The source code is released under:

Apache License

If you think the Android project retention-magic listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2013 Marten Gajda <marten@dmfs.org>
 */* w ww. j  a v  a  2s.  c o  m*/
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package org.dmfs.android.retentionmagic;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.dmfs.android.retentionmagic.annotations.Parameter;
import org.dmfs.android.retentionmagic.annotations.ParameterArrayList;
import org.dmfs.android.retentionmagic.annotations.Retain;
import org.dmfs.android.retentionmagic.annotations.RetainArrayList;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.util.SparseArray;


/**
 * Helper to store and restore instance values.
 * 
 * @author Marten Gajda <marten@dmfs.org>
 */
public final class RetentionMagic
{
  /**
   * Map of final classes to their respective {@link PersistenceHelper}s. Since we don't have to expect subclasses of these classes we can get the helpers
   * with a simple <code>get()</code>.
   * <p>
   * Since we're always called from the UI thread, there is no need to synchronize access to this map.
   * </p>
   */
  private final static Map<Class<?>, PersistenceHelper> FINAL_CLASS_HELPERS = new HashMap<Class<?>, PersistenceHelper>();

  /**
   * Map of non-final classes and interfaces to their respective {@link PersistenceHelper}s. A simple <code>get()</code> won't match, so we have to check each
   * key separately here.
   * <p>
   * Since we're always called from the UI thread, there is no need to synchronize access to this map.
   * </p>
   */
  private final static Map<Class<?>, PersistenceHelper> OTHER_CLASS_HELPERS = new HashMap<Class<?>, PersistenceHelper>();

  /**
   * Map of final generic type classes to their respective {@link PersistenceHelper}s. Since we don't have to expect subclasses of these classes we can get
   * the helpers with a simple <code>get()</code>.
   * <p>
   * Since we're always called from the UI thread, there is no need to synchronize access to this map.
   * </p>
   */
  private final static Map<Class<?>, PersistenceHelper> ARRAYLIST_FINAL_CLASS_HELPERS = new HashMap<Class<?>, PersistenceHelper>();

  /**
   * Map of non-final generic type classes and interfaces to their respective {@link PersistenceHelper}s. A simple <code>get()</code> won't match, so we have
   * to check each key
   * <p>
   * Since we're always called from the UI thread, there is no need to synchronize access to this map.
   * </p>
   */
  private final static Map<Class<?>, PersistenceHelper> ARRAYLIST_OTHER_CLASS_HELPERS = new HashMap<Class<?>, PersistenceHelper>();

  /**
   * Maps Activity and Fragment classes to a maps of fields to their respective {@link PersistenceHelper}s.
   */
  private final static Map<Class<?>, Map<Field, PersistenceHelper>> CLASS_CACHE = new HashMap<Class<?>, Map<Field, PersistenceHelper>>();

  static
  {
    FINAL_CLASS_HELPERS.put(boolean.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setBoolean(instance, bundle.getBoolean(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putBoolean(key, field.getBoolean(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setBoolean(instance, prefs.getBoolean(key, field.getBoolean(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putBoolean(key, field.getBoolean(instance));
      }
    });

    // TODO: support storing of boolean arrays as base64 encoded bit fields in SharedPreferences
    FINAL_CLASS_HELPERS.put(boolean[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getBooleanArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putBooleanArray(key, (boolean[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(byte.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setByte(instance, bundle.getByte(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putByte(key, field.getByte(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setByte(instance, (byte) (prefs.getInt(key, field.getByte(instance)) & 0xff));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putInt(key, field.getByte(instance));
      }
    });

    // TODO: support storing byte arrays as Base64 encoded arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(byte[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getByteArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putByteArray(key, (byte[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(short.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setShort(instance, bundle.getShort(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putShort(key, field.getShort(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setShort(instance, (short) prefs.getInt(key, field.getShort(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putInt(key, field.getShort(instance));
      }
    });

    // TODO: support storing short arrays as Base64 encoded arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(short[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getShortArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putShortArray(key, (short[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(char.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setChar(instance, bundle.getChar(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putChar(key, field.getChar(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setChar(instance, (char) prefs.getInt(key, field.getChar(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putInt(key, field.getChar(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(char[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getCharArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putCharArray(key, (char[]) field.get(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.set(instance, prefs.getString(key, new String((char[]) field.get(instance))).toCharArray());
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putString(key, new String((char[]) field.get(instance)));
      }

    });

    FINAL_CLASS_HELPERS.put(int.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setInt(instance, bundle.getInt(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putInt(key, field.getInt(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setInt(instance, prefs.getInt(key, field.getInt(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putInt(key, field.getInt(instance));
      }

    });

    // TODO: support storing integer arrays as Base64 encoded arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(int[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getIntArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putIntArray(key, (int[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(long.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setLong(instance, bundle.getLong(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putLong(key, field.getLong(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setLong(instance, prefs.getLong(key, field.getLong(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putLong(key, field.getLong(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(long[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getLongArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putLongArray(key, (long[]) field.get(instance));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, Editor editor) throws IllegalAccessException
      {
        StringBuilder arrayStringBuilder = new StringBuilder(1024);
        long[] longArray = (long[]) field.get(instance);

        if (longArray != null)
        {
          boolean first = true;
          for (int i = 0; i < longArray.length; i++)
          {
            if (first)
            {
              first = !first;
            }
            else
            {
              arrayStringBuilder.append(",");
            }
            arrayStringBuilder.append(longArray[i]);
          }
          editor.putString(key, arrayStringBuilder.toString());
        }
        else
        {
          editor.putString(key, null);
        }
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        String longArrayPref = prefs.getString(key, (String) field.get(instance));
        long[] longArray = null;

        if (longArrayPref != null)
        {
          if (longArrayPref.length() > 0)
          {
            String[] arrayString = longArrayPref.split(",");
            longArray = new long[arrayString.length];
            for (int i = 0; i < longArray.length; i++)
            {
              longArray[i] = Long.valueOf(arrayString[i]);
            }
          }
          else
          {
            longArray = new long[0];
          }
        }
        field.set(instance, longArray);
      }
    });

    FINAL_CLASS_HELPERS.put(float.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setFloat(instance, bundle.getFloat(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putFloat(key, field.getFloat(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.setFloat(instance, prefs.getFloat(key, field.getFloat(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putFloat(key, field.getFloat(instance));
      }
    });

    // TODO: support storing float arrays as Base64 encoded arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(float[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getFloatArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putFloatArray(key, (float[]) field.get(instance));
      }
    });

    // TODO: support douple in SharedPreferences
    FINAL_CLASS_HELPERS.put(double.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.setDouble(instance, bundle.getDouble(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putDouble(key, field.getDouble(instance));
      }
    });

    // TODO: support storing double arrays as Base64 encoded arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(double[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getDoubleArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putDoubleArray(key, (double[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(String.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getString(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putString(key, (String) field.get(instance));
      }


      @Override
      public void restoreFromPreferences(Field field, Object instance, String key, SharedPreferences prefs) throws IllegalAccessException
      {
        field.set(instance, prefs.getString(key, (String) field.get(instance)));
      }


      @Override
      public void storeInPreferences(Field field, Object instance, String key, SharedPreferences.Editor editor) throws IllegalAccessException
      {
        editor.putString(key, (String) field.get(instance));
      }

    });

    // TODO: support storing string arrays in SharedPreferences
    FINAL_CLASS_HELPERS.put(String[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getStringArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putStringArray(key, (String[]) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(Bundle.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getBundle(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putBundle(key, (Bundle) field.get(instance));
      }
    });

    FINAL_CLASS_HELPERS.put(SparseArray.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getSparseParcelableArray(key));
      }


      @SuppressWarnings("unchecked")
      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putSparseParcelableArray(key, (SparseArray<Parcelable>) field.get(instance));
      }
    });

    ARRAYLIST_FINAL_CLASS_HELPERS.put(Integer.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getIntegerArrayList(key));
      }


      @SuppressWarnings("unchecked")
      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putIntegerArrayList(key, (ArrayList<Integer>) field.get(instance));
      }
    });

    ARRAYLIST_FINAL_CLASS_HELPERS.put(String.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getStringArrayList(key));
      }


      @SuppressWarnings("unchecked")
      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putStringArrayList(key, (ArrayList<String>) field.get(instance));
      }
    });

    if (VERSION.SDK_INT >= VERSION_CODES.FROYO)
    {
      // Bundle doesn't support CharSequence ArrayLists prior to SDK version 8
      ARRAYLIST_OTHER_CLASS_HELPERS.put(CharSequence.class, new PersistenceHelper()
      {

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          field.set(instance, bundle.getCharSequenceArrayList(key));
        }


        @TargetApi(Build.VERSION_CODES.FROYO)
        @SuppressWarnings("unchecked")
        @Override
        public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          bundle.putCharSequenceArrayList(key, (ArrayList<CharSequence>) field.get(instance));
        }
      });
    }

    ARRAYLIST_OTHER_CLASS_HELPERS.put(Parcelable.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getParcelableArrayList(key));
      }


      @SuppressWarnings("unchecked")
      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putParcelableArrayList(key, (ArrayList<Parcelable>) field.get(instance));
      }
    });

    OTHER_CLASS_HELPERS.put(CharSequence.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getCharSequence(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putCharSequence(key, (CharSequence) field.get(instance));
      }
    });

    if (VERSION.SDK_INT >= VERSION_CODES.FROYO)
    {
      // Bundle doesn't support CharSequence arrays prior to SDK version 8
      OTHER_CLASS_HELPERS.put(CharSequence[].class, new PersistenceHelper()
      {

        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          field.set(instance, bundle.getCharSequenceArray(key));
        }


        @TargetApi(Build.VERSION_CODES.FROYO)
        @Override
        public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          bundle.putCharSequenceArray(key, (CharSequence[]) field.get(instance));
        }
      });
    }

    OTHER_CLASS_HELPERS.put(Parcelable.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getParcelable(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putParcelable(key, (Parcelable) field.get(instance));
      }
    });

    OTHER_CLASS_HELPERS.put(Parcelable[].class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getParcelableArray(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putParcelableArray(key, (Parcelable[]) field.get(instance));
      }
    });

    OTHER_CLASS_HELPERS.put(Serializable.class, new PersistenceHelper()
    {

      @Override
      public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        field.set(instance, bundle.getSerializable(key));
      }


      @Override
      public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
      {
        bundle.putSerializable(key, (Serializable) field.get(instance));
      }
    });

    if (Build.VERSION.SDK_INT >= 18)
    {
      // Bundle doesn't support IBinders prior to SDK version 18

      OTHER_CLASS_HELPERS.put(IBinder.class, new PersistenceHelper()
      {

        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void restoreFromBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          field.set(instance, bundle.getBinder(key));
        }


        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void storeInBundle(Field field, Object instance, String key, Bundle bundle) throws IllegalAccessException
        {
          bundle.putBinder(key, (IBinder) field.get(instance));
        }
      });
    }
  }


  /**
   * Don't allow instances.
   */
  private RetentionMagic()
  {
  }


  /**
   * Store all retainable fields of an Activity in a {@link Bundle}.
   * 
   * @param activity
   *            The {@link Activity}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void store(final Activity activity, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(activity.getClass(), activity, instanceState, true /* store */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  /**
   * Store all retainable fields of a {@link Fragment} in a {@link Bundle}.
   * 
   * @param fragment
   *            The {@link Fragment}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void store(final Fragment fragment, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(fragment.getClass(), fragment, instanceState, true /* store */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  /**
   * Store all retainable fields of a {@link android.support.v4.app.Fragment} in a {@link Bundle}.
   * 
   * @param fragment
   *            The {@link android.support.v4.app.Fragment}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void store(final android.support.v4.app.Fragment fragment, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(fragment.getClass(), fragment, instanceState, true /* store */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  /**
   * Restore all retainable fields of an Activity from a {@link Bundle}.
   * 
   * @param activity
   *            The {@link Activity}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void restore(final Activity activity, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(activity.getClass(), activity, instanceState, false /* restore */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  /**
   * Restore all retainable fields of a {@link Fragment} from a {@link Bundle}.
   * 
   * @param fragment
   *            The {@link Fragment}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void restore(final Fragment fragment, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(fragment.getClass(), fragment, instanceState, false /* restore */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  /**
   * Restore all retainable fields of a {@link android.support.v4.app.Fragment} from a {@link Bundle}.
   * 
   * @param fragment
   *            The {@link android.support.v4.app.Fragment}.
   * @param instanceState
   *            The {@link Bundle} to store the state in.
   */
  public static void restore(final android.support.v4.app.Fragment fragment, final Bundle instanceState)
  {
    try
    {
      storeAndRestore(fragment.getClass(), fragment, instanceState, false /* restore */);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  private static void storeAndRestore(final Class<?> classInstance, final Object instance, final Bundle instanceState, final boolean store)
    throws IllegalAccessException
  {
    if (instanceState == null)
    {
      // nothing to do
      return;
    }

    Map<Field, PersistenceHelper> helperCache = CLASS_CACHE.get(classInstance);

    if (helperCache == null)
    {
      helperCache = new HashMap<Field, PersistenceHelper>();
      for (Field field : classInstance.getDeclaredFields())
      {
        Retain retain = field.getAnnotation(Retain.class);
        if (retain != null && !ArrayList.class.isAssignableFrom(field.getType()))
        {
          field.setAccessible(true);

          String key = retain.key();
          if (key == null || key.length() == 0)
          {
            key = field.getName();
          }

          PersistenceHelper helper = getHelper(field.getType());
          if (helper != null)
          {
            if (store)
            {
              helper.storeInBundle(field, instance, key, instanceState);
            }
            else
            {
              helper.restoreFromBundle(field, instance, key, instanceState);
            }
            helperCache.put(field, helper);
          }
          else
          {
            throw new UnsupportedOperationException("field of class " + field.getType().getCanonicalName() + " not supported");
          }
        }
        else if (retain != null)
        {
          throw new UnsupportedOperationException("@Retain does not support ArrayLists, use @RetainArrayList instead");
        }
        else
        {
          RetainArrayList retainList = field.getAnnotation(RetainArrayList.class);
          if (retainList != null && ArrayList.class.isAssignableFrom(field.getType()))
          {
            field.setAccessible(true);
            String key = retainList.key();
            if (key == null || key.length() == 0)
            {
              key = field.getName();
            }

            PersistenceHelper helper = getArrayListHelper(retainList.genericType());
            if (helper != null)
            {
              if (store)
              {
                helper.storeInBundle(field, instance, key, instanceState);
              }
              else
              {
                helper.restoreFromBundle(field, instance, key, instanceState);
              }
              helperCache.put(field, helper);
            }
            else
            {
              throw new UnsupportedOperationException("list with generic type of " + field.getType().getCanonicalName() + " not supported");
            }
          }
          else if (retainList != null)
          {
            throw new UnsupportedOperationException("@RetainArrayList supports only ArrayList fields, use @Retain instead");
          }
        }
      }
      CLASS_CACHE.put(classInstance, helperCache);
    }
    else
    {
      for (Entry<Field, PersistenceHelper> entry : helperCache.entrySet())
      {
        PersistenceHelper helper = entry.getValue();
        Field field = entry.getKey();
        if (helper != null)
        {
          Retain retain = field.getAnnotation(Retain.class);
          String key;
          if (retain != null)
          {
            key = retain.key();
          }
          else
          {
            key = field.getAnnotation(RetainArrayList.class).key();
          }

          if (key == null || key.length() == 0)
          {
            key = field.getName();
          }

          if (store)
          {
            helper.storeInBundle(field, instance, key, instanceState);
          }
          else
          {
            helper.restoreFromBundle(field, instance, key, instanceState);
          }
        }
      }
    }
  }


  public static void init(final Activity activity, final SharedPreferences prefs)
  {
    try
    {
      init(activity.getClass(), activity, prefs);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void init(final Fragment fragment, final SharedPreferences prefs)
  {
    try
    {
      init(fragment.getClass(), fragment, prefs);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void init(final android.support.v4.app.Fragment fragment, final SharedPreferences prefs)
  {
    try
    {
      init(fragment.getClass(), fragment, prefs);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void init(final Activity activity, final Bundle extras)
  {
    try
    {
      init(activity.getClass(), activity, extras);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void init(final Fragment fragment, final Bundle arguments)
  {
    try
    {
      init(fragment.getClass(), fragment, arguments);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void init(final android.support.v4.app.Fragment fragment, final Bundle arguments)
  {
    try
    {
      init(fragment.getClass(), fragment, arguments);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
  public static void persist(final Activity activity, final SharedPreferences prefs)
  {
    SharedPreferences.Editor editor = prefs.edit();

    persist(activity, editor);
    if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD)
    {
      // write out asynchronously on newer platforms
      editor.apply();
    }
    else
    {
      // use the synchronous call on older platforms
      editor.commit();
    }
  }


  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
  public static void persist(final Fragment fragment, final SharedPreferences prefs)
  {
    SharedPreferences.Editor editor = prefs.edit();

    persist(fragment, editor);
    if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD)
    {
      // write out asynchronously on newer platforms
      editor.apply();
    }
    else
    {
      // use the synchronous call on older platforms
      editor.commit();
    }
  }


  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
  public static void persist(final android.support.v4.app.Fragment fragment, final SharedPreferences prefs)
  {
    SharedPreferences.Editor editor = prefs.edit();

    persist(fragment, editor);
    if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD)
    {
      // write out asynchronously on newer platforms
      editor.apply();
    }
    else
    {
      // use the synchronous call on older platforms
      editor.commit();
    }
  }


  public static void persist(final Activity activity, final SharedPreferences.Editor editor)
  {
    try
    {
      persist(activity.getClass(), activity, editor);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void persist(final Fragment fragment, final SharedPreferences.Editor editor)
  {
    try
    {
      persist(fragment.getClass(), fragment, editor);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  public static void persist(final android.support.v4.app.Fragment fragment, final SharedPreferences.Editor editor)
  {
    try
    {
      persist(fragment.getClass(), fragment, editor);
    }
    catch (IllegalAccessException e)
    {
      e.printStackTrace();
    }
  }


  private static void init(final Class<?> classInstance, final Object instance, final SharedPreferences prefs) throws IllegalAccessException
  {
    Map<Field, PersistenceHelper> helperCache = CLASS_CACHE.get(classInstance);

    if (helperCache == null)
    {
      for (Field field : classInstance.getDeclaredFields())
      {
        Retain retain = field.getAnnotation(Retain.class);
        if (retain != null && !ArrayList.class.isAssignableFrom(field.getType()))
        {
          if (!retain.permanent())
          {
            continue;
          }

          field.setAccessible(true);

          String key = retain.key();
          key = getTag(classInstance, retain.instanceNSField(), retain.classNS(), instance).append(
            key == null || key.length() == 0 ? field.getName() : key).toString();

          PersistenceHelper helper = getHelper(field.getType());
          if (helper != null)
          {
            helper.restoreFromPreferences(field, instance, key, prefs);
          }
          else
          {
            throw new UnsupportedOperationException("field of class " + field.getType().getCanonicalName() + " not supported for permanent storage");
          }
        }
        else if (retain != null)
        {
          throw new UnsupportedOperationException("@Retain does not support ArrayLists, use @RetainArrayList instead");
        }
      }
    }
    else
    {
      for (Entry<Field, PersistenceHelper> entry : helperCache.entrySet())
      {
        PersistenceHelper helper = entry.getValue();
        Field field = entry.getKey();
        if (helper != null)
        {
          Retain retain = field.getAnnotation(Retain.class);
          if (retain == null || !retain.permanent())
          {
            continue;
          }
          String key = retain.key();
          key = getTag(classInstance, retain.instanceNSField(), retain.classNS(), instance).append(
            key == null || key.length() == 0 ? field.getName() : key).toString();

          helper.restoreFromPreferences(field, instance, key, prefs);
        }
      }
    }
  }


  private static void persist(final Class<?> classInstance, final Object instance, final SharedPreferences.Editor editor) throws IllegalAccessException
  {
    Map<Field, PersistenceHelper> helperCache = CLASS_CACHE.get(classInstance);

    if (helperCache == null)
    {
      for (Field field : classInstance.getDeclaredFields())
      {
        Retain retain = field.getAnnotation(Retain.class);
        if (retain != null && !ArrayList.class.isAssignableFrom(field.getType()))
        {
          if (!retain.permanent())
          {
            continue;
          }

          field.setAccessible(true);

          String key = retain.key();
          key = getTag(classInstance, retain.instanceNSField(), retain.classNS(), instance).append(
            key == null || key.length() == 0 ? field.getName() : key).toString();

          PersistenceHelper helper = getHelper(field.getType());
          if (helper != null)
          {
            helper.storeInPreferences(field, instance, key, editor);
          }
          else
          {
            throw new UnsupportedOperationException("field of class " + field.getType().getCanonicalName() + " not supported for permanent storage");
          }
        }
        else if (retain != null)
        {
          throw new UnsupportedOperationException("@Retain does not support ArrayLists, use @RetainArrayList instead");
        }
      }
    }
    else
    {
      for (Entry<Field, PersistenceHelper> entry : helperCache.entrySet())
      {
        PersistenceHelper helper = entry.getValue();
        Field field = entry.getKey();
        if (helper != null)
        {
          Retain retain = field.getAnnotation(Retain.class);
          if (retain == null || !retain.permanent())
          {
            continue;
          }

          String key = retain.key();
          key = getTag(classInstance, retain.instanceNSField(), retain.classNS(), instance).append(
            key == null || key.length() == 0 ? field.getName() : key).toString();

          helper.storeInPreferences(field, instance, key, editor);
        }
      }
    }
  }


  private static void init(final Class<?> classInstance, final Object instance, final Bundle bundle) throws IllegalAccessException
  {
    if (bundle == null || bundle.size() == 0)
    {
      return;
    }

    for (Field field : classInstance.getDeclaredFields())
    {
      Parameter param = field.getAnnotation(Parameter.class);
      if (param != null && !ArrayList.class.isAssignableFrom(field.getType()))
      {
        field.setAccessible(true);

        String key = param.key();
        if (key == null || key.length() == 0)
        {
          key = field.getName();
        }

        PersistenceHelper helper = getHelper(field.getType());
        if (helper != null)
        {
          helper.restoreFromBundle(field, instance, key, bundle);
        }
        else
        {
          throw new UnsupportedOperationException("field of class " + field.getType().getCanonicalName()
            + " not supported for initialization from a Bundle");
        }
      }
      else if (param != null)
      {
        throw new UnsupportedOperationException("@Parameter does not support ArrayLists, use @ParameterArrayList instead");
      }
      else
      {
        ParameterArrayList paramList = field.getAnnotation(ParameterArrayList.class);
        if (paramList != null && ArrayList.class.isAssignableFrom(field.getType()))
        {
          field.setAccessible(true);
          String key = paramList.value();
          if (key == null || key.length() == 0)
          {
            key = field.getName();
          }

          PersistenceHelper helper = getArrayListHelper(paramList.genericType());
          if (helper != null)
          {
            helper.restoreFromBundle(field, instance, key, bundle);
          }
          else
          {
            throw new UnsupportedOperationException("list with generic type of " + field.getType().getCanonicalName() + " not supported");
          }
        }
        else if (paramList != null)
        {
          throw new UnsupportedOperationException("@ParameterArrayList supports only ArrayList fields, use @Parameter instead");
        }
      }

    }
  }


  private static PersistenceHelper getHelper(final Class<?> fieldType)
  {
    return getHelper(fieldType, FINAL_CLASS_HELPERS, OTHER_CLASS_HELPERS);
  }


  private static PersistenceHelper getArrayListHelper(final Class<?> genericArrayListType)
  {
    return getHelper(genericArrayListType, ARRAYLIST_FINAL_CLASS_HELPERS, ARRAYLIST_OTHER_CLASS_HELPERS);
  }


  private static PersistenceHelper getHelper(final Class<?> genericType, final Map<Class<?>, PersistenceHelper> finalClassHelper,
    final Map<Class<?>, PersistenceHelper> otherClassHelper)
  {
    PersistenceHelper result = finalClassHelper.get(genericType);
    if (result != null)
    {
      return result;
    }

    for (Class<?> classClass : otherClassHelper.keySet())
    {
      if (classClass.isAssignableFrom(genericType))
      {
        return otherClassHelper.get(classClass);
      }
    }
    return null;
  }


  private static StringBuilder getTag(final Class<?> classType, String instanceTag, String classTag, Object instance) throws IllegalAccessException
  {
    StringBuilder result = new StringBuilder(256);

    if (classTag != null && classTag.length() > 0)
    {
      if (classTag.length() == 1 && classTag.charAt(0) == '.')
      {
        try
        {
          Field tagField = classType.getDeclaredField("TAG");
          tagField.setAccessible(true);
          result.append(tagField.get(instance).toString());
        }
        catch (Exception e)
        {
          result.append(classType.getCanonicalName());
        }
      }
      else
      {
        result.append(classTag);
      }
      result.append('.');
    }

    if (instanceTag != null && instanceTag.length() > 0)
    {
      try
      {
        Field tagField = classType.getDeclaredField(instanceTag);
        tagField.setAccessible(true);
        Object value = tagField.get(instance);
        if (value != null)
        {
          result.append(value.toString());
          result.append('.');
        }
      }
      catch (NoSuchFieldException e)
      {
        // ignore
      }
      catch (SecurityException e)
      {
        // ignore
      }
    }

    return result;
  }
}




Java Source Code List

org.dmfs.android.retentionmagic.Activity.java
org.dmfs.android.retentionmagic.DialogFragment.java
org.dmfs.android.retentionmagic.FragmentActivity.java
org.dmfs.android.retentionmagic.Fragment.java
org.dmfs.android.retentionmagic.ListFragment.java
org.dmfs.android.retentionmagic.PersistenceHelper.java
org.dmfs.android.retentionmagic.RetentionMagic.java
org.dmfs.android.retentionmagic.SupportDialogFragment.java
org.dmfs.android.retentionmagic.SupportFragment.java
org.dmfs.android.retentionmagic.SupportListFragment.java
org.dmfs.android.retentionmagic.annotations.ParameterArrayList.java
org.dmfs.android.retentionmagic.annotations.Parameter.java
org.dmfs.android.retentionmagic.annotations.RetainArrayList.java
org.dmfs.android.retentionmagic.annotations.Retain.java
org.dmfs.android.retentionmagic.demo.DemoActivity.java