com.wit.android.preference.SharedPreference.java Source code

Java tutorial

Introduction

Here is the source code for com.wit.android.preference.SharedPreference.java

Source

/*
 * =================================================================================================
 *                 Copyright (C) 2013 - 2014 Martin Albedinsky [Wolf-ITechnologies]
 * =================================================================================================
 *         Licensed under the Apache License, Version 2.0 or later (further "License" only).
 * -------------------------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy of this License
 * you may obtain at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * You can redistribute, modify or publish any part of the code written within this file but as it
 * is described in the License, the software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND.
 *
 * See the License for the specific language governing permissions and limitations under the License.
 * =================================================================================================
 */
package com.wit.android.preference;

import android.content.SharedPreferences;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
 * <h3>Class Overview</h3>
 * todo: description
 *
 * @param <Type> A type of the value which this implementation of preference holds and manages its saving/obtaining.
 * @author Martin Albedinsky
 * @see com.wit.android.preference.SharedPreference.StringPreference
 * @see com.wit.android.preference.SharedPreference.BooleanPreference
 * @see com.wit.android.preference.SharedPreference.IntegerPreference
 * @see com.wit.android.preference.SharedPreference.LongPreference
 * @see com.wit.android.preference.SharedPreference.FloatPreference
 * @see com.wit.android.preference.SharedPreference.EnumPreference
 * @see com.wit.android.preference.SharedPreference.ListPreference
 */
public abstract class SharedPreference<Type> {

    /**
     * Interface ===================================================================================
     */

    /**
     * <h3>Interface Overview</h3>
     * todo: description
     *
     * @param <PreferenceType> Type of the preference, for which is this callback created.
     * @author Martin Albedinsky
     */
    public static interface PreferenceChangeCallback<PreferenceType> {

        /**
         * Fired when the value of the passed <var>preference</var> was changed. The passed
         * preference already contains new value parsed from shared preferences.
         *
         * @param preference The preference, of which value was changed.
         */
        public void onPreferenceChanged(SharedPreference<PreferenceType> preference);
    }

    /**
     * Constants ===================================================================================
     */

    /**
     * Log TAG.
     */
    // private static final String TAG = "SharedPreference";

    /**
     * Flag indicating whether the debug output trough log-cat is enabled or not.
     */
    // private static final boolean DEBUG_ENABLED = true;

    /**
     * Flag indicating whether the output trough log-cat is enabled or not.
     */
    // private static final boolean LOG_ENABLED = true;

    /**
     * Static members ==============================================================================
     */

    /**
     * Members =====================================================================================
     */

    /**
     * The key under which will be the value of this preference mapped within shared preferences.
     */
    String mKey;

    /**
     * Default value of this preference for case, when there is no value saved withing
     * shared preference yet.
     */
    Type mDefaultValue;

    /**
     * Actual value of this shared preference to save.
     */
    Type mActualValue;

    /**
     * Xml resource
     */
    private int mKeyRes = -1;

    /**
     * Flag indicating whether the {@link #mActualValue} is same as value saved within shared
     * preferences.
     */
    private boolean mAlreadyParsed;

    /**
     * Constructors ================================================================================
     */

    /**
     * Creates a new instance of SharedPreference with the given key and default value.
     *
     * @param key      The key under which will be the value of this preference mapped within
     *                 shared preferences.
     * @param defValue Default value of this preference to return in case, when there is no value
     *                 saved withing shared preference yet.
     * @see #setUpKey(android.content.res.Resources)
     */
    protected SharedPreference(@NonNull String key, @Nullable Type defValue) {
        this.mKey = key;
        this.mDefaultValue = mActualValue = defValue;
    }

    /**
     * Creates a new instance of SharedPreference with the given key resource and default value.
     *
     * @param keyResId     XXml resource of the key under which will be the value of this preference
     *                     mapped within shared preferences.
     * @param defaultValue Default value of this preference for case, when there is no value saved withing
     *                     shared preference yet.
     */
    protected SharedPreference(@StringRes int keyResId, @Nullable Type defaultValue) {
        this.mKeyRes = keyResId;
        this.mDefaultValue = defaultValue;
    }

    /**
     * Methods =====================================================================================
     */

    /**
     * Public --------------------------------------------------------------------------------------
     */

    /**
     * Obtains the key for this preference from the given resources using the current key's xml resource
     * id passed to {@link #SharedPreference(int, Object)}.
     *
     * @param resources An application's resources to obtain key.
     * @return This preference.
     */
    public SharedPreference<Type> setUpKey(@NonNull Resources resources) {
        this.obtainKeyFromResources(resources);
        return this;
    }

    /**
     * Parses the current value of this preference from shared preferences.
     *
     * @param manager The preferences manager which can provide the instance of shared preferences
     *                into which was the value of this preference before saved.
     * @return This preference.
     * @see #parse(android.content.SharedPreferences)
     * @see #getActualValue()
     */
    public SharedPreference<Type> parse(@NonNull PreferencesManager manager) {
        return parse(manager.getSharedPreferences());
    }

    /**
     * Parses the current value of this preference from the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which was the value of this preference
     *                    before saved.
     * @return This preference.
     * @see #parse(PreferencesManager)
     * @see #getActualValue()
     */
    public SharedPreference<Type> parse(@NonNull SharedPreferences preferences) {
        this.mActualValue = performObtainFromPreferences(preferences);
        return this;
    }

    /**
     * Same as {@link #save(android.content.SharedPreferences)} where the instance of shared
     * preferences will be obtained from the given <var>manager</var>.
     *
     * @param manager The preferences manager which provides the instance of shared preferences into
     *                which should be the current value of this preference saved.
     * @return {@code True} if saving operation succeed, {@code false} otherwise.
     * @see #save(android.content.SharedPreferences)
     * @see #updateValue(Object)
     */
    public boolean save(@NonNull PreferencesManager manager) {
        return performPutIntoPreferences(manager.getSharedPreferences());
    }

    /**
     * Saves the current value of this preference into the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which will be the current value of
     *                    this preference saved.
     * @return {@code True} if saving operation succeed, {@code false} otherwise.
     * @see #save(PreferencesManager)
     * @see #updateValue(Object)
     */
    public boolean save(@NonNull SharedPreferences preferences) {
        return performPutIntoPreferences(preferences);
    }

    /**
     * Creates new instance of {@link SharedPreferences.OnSharedPreferenceChangeListener} which can
     * be used to listen on changes provided up on the value of this preference within shared preferences.
     * <p>
     * <b>Note</b>, that here created listener will fire the given callback only in case, that the
     * key received in the callback from shared preferences will match the key of this preference.
     *
     * @param callback Callback to be invoked when the value of this preference changes.
     * @return New instance of OnSharedPreferenceListener.
     * @see android.content.SharedPreferences
     */
    @NonNull
    public SharedPreferences.OnSharedPreferenceChangeListener createOnChangeListener(
            @NonNull final PreferenceChangeCallback<Type> callback) {
        return new SharedPreferences.OnSharedPreferenceChangeListener() {

            /**
             */
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                if (mKey.equals(key)) {
                    // Parse actual value.
                    parse(sharedPreferences);

                    // Invoke callback.
                    callback.onPreferenceChanged(SharedPreference.this);
                }
            }
        };
    }

    /**
     * Getters + Setters ---------------------------------------------------------------------------
     */

    /**
     * Returns the key of this preference.
     *
     * @return Same key as passed to {@link #SharedPreference(String, Object)}.
     */
    @NonNull
    public final String getKey() {
        return mKey;
    }

    /**
     * Returns the default value of this preference.
     *
     * @return Same value as passed to {@link #SharedPreference(String, Object)}.
     */
    public final Type getDefaultValue() {
        return mDefaultValue;
    }

    /**
     * Returns the value, which is currently being hold by this preference object. <b>Note</b>, that
     * this isn't current value from shared preferences if this preference wasn't parsed
     * ({@link #parse(PreferencesManager)} or {@link #parse(android.content.SharedPreferences)})
     * before this call.
     * <p>
     * To obtain always an actual value from shared preferences, perform this calls on the instance
     * of SharedPreference of which value you want to obtain:
     * <pre>
     * {@code <b>SharedPreference.parse(SharedPreferences).getActualValue()</b>} or
     * {@code <b>SharedPreference.parse(PreferencesManager).getActualValue()</b>}
     * </pre>
     *
     * @return The actual value of this preference or {@code null} if this preference's key is
     * invalid.
     * @see #parse(PreferencesManager)
     * @see #parse(android.content.SharedPreferences)
     */
    public final Type getActualValue() {
        return mActualValue;
    }

    /**
     * Updates the actual value of this preference to the given one. <b>Note</b>, that
     * this doesn't updates the value within shared preferences.
     * <p>
     * To immediately save the given new value of this preference into shared preferences, perform
     * this calls on the instance of SharedPreference of which value you want to save:
     * <pre>
     * {@code <b>SharedPreference.updateValue(Type).save(SharedPreferences)</b>} or
     * {@code <b>SharedPreference.updateValue(Type).save(PreferencesManager)</b>}
     * </pre>
     *
     * @param newValue New value for this preference.
     * @return This preference.
     * @see #save(PreferencesManager)
     * @see #save(android.content.SharedPreferences)
     */
    public SharedPreference<Type> updateValue(@Nullable Type newValue) {
        this.mAlreadyParsed = mActualValue != null && mActualValue.equals(newValue);
        this.mActualValue = newValue;
        return this;
    }

    /**
     * Protected -----------------------------------------------------------------------------------
     */

    /**
     * Performs saving of the actual value of this preference into the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which should be the current value of
     *                    this preference saved.
     * @return {@code True} if saving operation succeed, {@code false} otherwise.
     */
    final boolean performPutIntoPreferences(SharedPreferences preferences) {
        return mAlreadyParsed = (mKey != null && !mKey.equals("") && onPutIntoPreferences(preferences));
    }

    /**
     * Invoked to save the actual value into the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which should be the current value of
     *                    this preference saved.
     * @return {@code True} if saving operation succeed, {@code false} otherwise.
     */
    protected abstract boolean onPutIntoPreferences(@NonNull SharedPreferences preferences);

    /**
     * Performs obtaining of the actual value of this preference from the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which was the value of this preference
     *                    before saved.
     * @return The actual value obtained from the given shared preferences or {@code null} if this
     * preference's key is invalid.
     */
    final Type performObtainFromPreferences(SharedPreferences preferences) {
        Type value = null;
        if (mAlreadyParsed) {
            value = mActualValue;
        } else if (mKey != null && !mKey.equals("")) {
            value = onObtainFromPreferences(preferences);
            this.mAlreadyParsed = true;
        }
        return value;
    }

    /**
     * Invoked to obtain the actual value of this preference from the given shared preferences.
     *
     * @param preferences The instance of shared preferences into which was the value of this preference
     *                    before saved.
     * @return The actual value of this preference from the given shared preferences.
     */
    @Nullable
    protected abstract Type onObtainFromPreferences(@NonNull SharedPreferences preferences);

    /**
     * Clears the actual value of this preference.
     */
    final void clear() {
        this.mActualValue = null;
        this.mAlreadyParsed = false;
    }

    /**
     * Private -------------------------------------------------------------------------------------
     */

    /**
     * If the current <var>mKeyRes</var> is valid resource, this will obtain the string value of this
     * key's resource from the given resources.
     *
     * @param resources An application's resources to obtain key.
     */
    private void obtainKeyFromResources(Resources resources) {
        if (mKey == null && mKeyRes > 0) {
            this.mKey = resources.getString(mKeyRes);
        }
    }

    /**
     * Inner classes ===============================================================================
     */

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.String} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class StringPreference extends SharedPreference<String> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of StringPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public StringPreference(@NonNull String key, @Nullable String defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of StringPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public StringPreference(@StringRes int keyResId, @Nullable String defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putString(mKey, mActualValue).commit();
        }

        /**
         */
        @Nullable
        @Override
        protected String onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return preferences.getString(mKey, mDefaultValue);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.Boolean} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class BooleanPreference extends SharedPreference<Boolean> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of BooleanPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public BooleanPreference(@NonNull String key, @NonNull Boolean defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of BooleanPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public BooleanPreference(@StringRes int keyResId, @NonNull Boolean defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putBoolean(mKey, mActualValue).commit();
        }

        /**
         */
        @Nullable
        @Override
        protected Boolean onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return preferences.getBoolean(mKey, mDefaultValue);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.Integer} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class IntegerPreference extends SharedPreference<Integer> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of IntegerPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public IntegerPreference(@NonNull String key, @NonNull Integer defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of IntegerPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public IntegerPreference(@StringRes int keyResId, @NonNull Integer defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putInt(mKey, mActualValue).commit();
        }

        /**
         */
        @Nullable
        @Override
        protected Integer onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return preferences.getInt(mKey, mDefaultValue);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.Float} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class FloatPreference extends SharedPreference<Float> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of FloatPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public FloatPreference(@NonNull String key, @NonNull Float defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of FloatPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public FloatPreference(@StringRes int keyResId, @NonNull Float defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putFloat(mKey, mActualValue).commit();
        }

        /**
         */
        @Nullable
        @Override
        protected Float onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return preferences.getFloat(mKey, mDefaultValue);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.Long} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class LongPreference extends SharedPreference<Long> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of LongPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public LongPreference(@NonNull String key, @NonNull Long defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of LongPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public LongPreference(@StringRes int keyResId, @NonNull Long defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putLong(mKey, mActualValue).commit();
        }

        /**
         */
        @Nullable
        @Override
        protected Long onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return preferences.getLong(mKey, mDefaultValue);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.lang.Enum} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @param <E> A type of the enum implementation of which value will this preference manage.
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class EnumPreference<E extends Enum> extends SharedPreference<E> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of EnumPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public EnumPreference(@NonNull String key, @Nullable E defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of EnumPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public EnumPreference(@StringRes int keyResId, @Nullable E defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return preferences.edit().putString(mKey, mActualValue.name()).commit();
        }

        /**
         */
        @Nullable
        @Override
        @SuppressWarnings("unchecked")
        protected E onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            final String enumName = preferences.getString(mKey, mDefaultValue.name());
            return (E) E.valueOf(mDefaultValue.getClass(), enumName);
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of <b>array</b> within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @param <A> A type of the array of which value will this preference manage.
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     * @see com.wit.android.preference.SharedPreference.ListPreference
     */
    public static final class ArrayPreference<A> extends SharedPreference<A> {

        /**
         * Constants ===============================================================================
         */

        /**
         * Log TAG.
         */
        private static final String TAG = "ArrayPreference";

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of ArrayPreference.
         *
         * @throws java.lang.IllegalArgumentException If the given <var>defValue</var> is not actually
         *                                            an array.
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public ArrayPreference(@NonNull String key, @Nullable A defValue) {
            super(key, defValue);
            // Only array object is allowed.
            if (defValue != null && !defValue.getClass().isArray()) {
                throw new IllegalArgumentException("Not an array (" + defValue.getClass().getSimpleName() + ").");
            }
        }

        /**
         * Creates a new instance of ArrayPreference.
         *
         * @throws java.lang.IllegalArgumentException If the given <var>defValue</var> is not actually
         *                                            an array.
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public ArrayPreference(@StringRes int keyResId, @Nullable A defValue) {
            super(keyResId, defValue);
            // Only array object is allowed.
            if (defValue != null && !defValue.getClass().isArray()) {
                throw new IllegalArgumentException("Not an array (" + defValue.getClass().getSimpleName() + ").");
            }
        }

        /**
         * Methods =================================================================================
         */

        /**
         * Saves the given <var>array</var> into the given shared <var>preferences</var>.
         *
         * @param preferences The instance of shared preferences into which will be the given array saved.
         * @param key         The key under which will be the saved array mapped in the shared preferences.
         * @param array       Array to save into preferences.
         * @return {@code True} if saving succeeded, {@code false} otherwise.
         * @throws java.lang.IllegalArgumentException If the given <var>array</var> is not actually an array.
         */
        public static boolean putIntoPreferences(@NonNull SharedPreferences preferences, @NonNull String key,
                @Nullable Object array) {
            if (array == null) {
                return preferences.edit().putString(key, null).commit();
            }

            // Only array object is allowed.
            if (!array.getClass().isArray()) {
                throw new IllegalArgumentException("Not an array (" + array.getClass().getSimpleName() + ").");
            }

            final int n = Array.getLength(array);
            final JSONArray jsonArray = new JSONArray();
            for (int i = 0; i < n; i++) {
                jsonArray.put(Array.get(array, i));
            }
            // Save also class of array, so when obtaining array we will know exactly of which type it is.
            final Class<?> classOfArray = resolveArrayClass(array);
            if (classOfArray == null) {
                throw new IllegalArgumentException("Failed to save array of('" + array.getClass() + "')."
                        + "Can only save array of primitive types or theirs boxed representations, Object or String array.");
            }
            return preferences.edit().putString(key, classOfArray.getSimpleName() + ":" + jsonArray.toString())
                    .commit();
        }

        /**
         * Returns an <b>array</b> mapped in the given shared <var>preferences</var> under the
         * specified <var>key</var>.
         *
         * @param preferences The instance of shared preferences into which was the requested array
         *                    before saved.
         * @param key         The key under which is the saved list mapped in the shared preferences.
         * @param defValue    Default array to return if there is no mapping for the specified <var>key</var>
         *                    yet.
         * @param <A>         The type of an array to obtain.
         * @return An instance of the requested array or <var>defValue</var> if there is no mapping
         * for the specified key.
         */
        @SuppressWarnings("unchecked")
        public static <A> A obtainFromPreferences(@NonNull SharedPreferences preferences, @NonNull String key,
                @Nullable Object defValue) {
            Object array = defValue;
            final String arrayAsString = preferences.getString(key, null);
            if (!TextUtils.isEmpty(arrayAsString)) {
                final String[] parts = arrayAsString.split(":");
                if (parts.length > 1) {
                    final Class<?> classOfArray = resolveArrayClassByName(parts[0]);
                    if (classOfArray != null) {
                        JSONArray jsonArray;
                        try {
                            jsonArray = new JSONArray(parts[1]);
                        } catch (JSONException ignored) {
                            Log.e(TAG, "Failed to obtain array for key(" + key + ") from shared preferences.");
                            return (A) defValue;
                        }

                        final int n = jsonArray.length();
                        array = resolveArrayForClass(classOfArray, n);
                        // Fill the resolved array.
                        for (int i = 0; i < n; i++) {
                            Array.set(array, i, jsonArray.opt(i));
                        }
                    } else {
                        Log.e(TAG, "Unknown class('" + parts[0] + "') of array.");
                    }
                } else {
                    throw new IllegalStateException(
                            "Trying to obtain an array not saved by this preferences framework.");
                }
            }
            return (A) array;
        }

        /**
         * @see #putIntoPreferences(android.content.SharedPreferences, String, Object)
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return putIntoPreferences(preferences, mKey, mActualValue);
        }

        /**
         * @see #obtainFromPreferences(android.content.SharedPreferences, String, Object)
         */
        @Nullable
        @Override
        @SuppressWarnings("unchecked")
        protected A onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return obtainFromPreferences(preferences, mKey, mDefaultValue);
        }

        /**
         * Returns class of an items presented within the given <var>array</var>.
         *
         * @param array The array of which class should be resolved.
         * @return Class of an items within the given array or {@code null} if the given array
         * does not matches supported array types.
         */
        static Class<?> resolveArrayClass(Object array) {
            if (array instanceof boolean[]) {
                return boolean.class;
            } else if (array instanceof byte[]) {
                return byte.class;
            } else if (array instanceof char[]) {
                return char.class;
            } else if (array instanceof short[]) {
                return short.class;
            } else if (array instanceof int[]) {
                return int.class;
            } else if (array instanceof float[]) {
                return float.class;
            } else if (array instanceof long[]) {
                return long.class;
            } else if (array instanceof double[]) {
                return double.class;
            } else if (array instanceof Boolean[]) {
                return Boolean.class;
            } else if (array instanceof Byte[]) {
                return Byte.class;
            } else if (array instanceof Short[]) {
                return Short.class;
            } else if (array instanceof Integer[]) {
                return Integer.class;
            } else if (array instanceof Float[]) {
                return Float.class;
            } else if (array instanceof Long[]) {
                return Long.class;
            } else if (array instanceof Double[]) {
                return Double.class;
            } else if (array instanceof String[]) {
                return String.class;
            } else if (array instanceof Object[]) {
                return Object.class;
            }
            return null;
        }

        /**
         * Returns class for the requested <var>arrayClassName</var>.
         *
         * @param arrayClassName Name of the class for which is its name requested.
         * @return Name of the requested class or {@code Object.class} if the given name does
         * not matches the supported types.
         */
        static Class<?> resolveArrayClassByName(String arrayClassName) {
            switch (arrayClassName) {
            case "boolean":
                return boolean.class;
            case "byte":
                return byte.class;
            case "char":
                return char.class;
            case "short":
                return short.class;
            case "int":
                return int.class;
            case "float":
                return float.class;
            case "long":
                return long.class;
            case "double":
                return double.class;
            case "Boolean":
                return Boolean.class;
            case "Byte":
                return Byte.class;
            case "Short":
                return Short.class;
            case "Integer":
                return Integer.class;
            case "Float":
                return Float.class;
            case "Long":
                return Long.class;
            case "Double":
                return Double.class;
            case "String":
                return String.class;
            }
            return Object.class;
        }

        /**
         * Creates a new instance of array of the requested <var>arrayClass</var> type.
         *
         * @param arrayClass Class of array to create.
         * @param size       Size of array.
         * @return New instance of array type of the requested class.
         */
        static Object resolveArrayForClass(Class<?> arrayClass, int size) {
            if (boolean.class.equals(arrayClass)) {
                return new boolean[size];
            } else if (byte.class.equals(arrayClass)) {
                return new byte[size];
            } else if (char.class.equals(arrayClass)) {
                return new char[size];
            } else if (short.class.equals(arrayClass)) {
                return new short[size];
            } else if (int.class.equals(arrayClass)) {
                return new int[size];
            } else if (float.class.equals(arrayClass)) {
                return new float[size];
            } else if (long.class.equals(arrayClass)) {
                return new long[size];
            } else if (double.class.equals(arrayClass)) {
                return new double[size];
            } else if (Boolean.class.equals(arrayClass)) {
                return new Boolean[size];
            } else if (Byte.class.equals(arrayClass)) {
                return new Byte[size];
            } else if (Short.class.equals(arrayClass)) {
                return new Short[size];
            } else if (Integer.class.equals(arrayClass)) {
                return new Integer[size];
            } else if (Float.class.equals(arrayClass)) {
                return new Float[size];
            } else if (Long.class.equals(arrayClass)) {
                return new Long[size];
            } else if (Double.class.equals(arrayClass)) {
                return new Double[size];
            } else if (String.class.equals(arrayClass)) {
                return new String[size];
            }
            return new Object[size];
        }
    }

    /**
     * <h3>Class Overview</h3>
     * Shared preference which can manage value of {@link java.util.List} within {@link SharedPreferences}.
     * See also other implementations of {@link SharedPreference} for different types which
     * can be saved/obtained into/from shared preferences.
     *
     * @param <T> A type of an items which are presented within List of which value will this
     *            preference manage.
     * @author Martin Albedinsky
     * @see com.wit.android.preference.SharedPreference.BooleanPreference
     * @see com.wit.android.preference.SharedPreference.IntegerPreference
     * @see com.wit.android.preference.SharedPreference.LongPreference
     * @see com.wit.android.preference.SharedPreference.StringPreference
     * @see com.wit.android.preference.SharedPreference.FloatPreference
     * @see com.wit.android.preference.SharedPreference.EnumPreference
     */
    public static final class ListPreference<T> extends SharedPreference<List<T>> {

        /**
         * Constructors ============================================================================
         */

        /**
         * Creates a new instance of ListPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(String, Object)
         */
        public ListPreference(@NonNull String key, @Nullable List<T> defValue) {
            super(key, defValue);
        }

        /**
         * Creates a new instance of ListPreference.
         *
         * @see com.wit.android.preference.SharedPreference#SharedPreference(int, Object)
         */
        public ListPreference(@StringRes int keyResId, @Nullable List<T> defValue) {
            super(keyResId, defValue);
        }

        /**
         * Methods =================================================================================
         */

        /**
         * Saves the given <var>list</var> into the given shared <var>preferences</var>.
         *
         * @param preferences The instance of shared preferences into which will be the given list saved.
         * @param key         The key under which will be the saved list mapped in the shared preferences.
         * @param list        List to save into preferences.
         * @param <T>         Type of an items which are presented within the given list.
         * @return {@code True} if saving succeeded, {@code false} otherwise.
         */
        public static <T> boolean putIntoPreferences(@NonNull SharedPreferences preferences, @NonNull String key,
                @Nullable List<T> list) {
            return preferences.edit().putString(key, new JSONArray(list).toString()).commit();
        }

        /**
         * Returns a {@link java.util.List} mapped in the given shared <var>preferences</var> under
         * the specified <var>key</var>.
         *
         * @param preferences The instance of shared preferences into which was the requested list
         *                    before saved.
         * @param key         The key under which is the saved list mapped in the shared preferences.
         * @param defValue    Default list to return if there is no mapping for the specified <var>key</var>
         *                    yet.
         * @param <T>         Type of an items which should be presented within the obtained list.
         * @return An instance of the requested list or <var>defValue</var> if there is no mapping
         * for the specified key.
         */
        @SuppressWarnings("unchecked")
        public static <T> List<T> obtainFromPreferences(@NonNull SharedPreferences preferences, @NonNull String key,
                @Nullable List<T> defValue) {
            List<T> list = defValue;
            final String jsonArray = preferences.getString(key, null);
            if (jsonArray != null) {
                try {
                    final JSONArray array = new JSONArray(jsonArray);
                    final int n = array.length();
                    list = new ArrayList<>(n);
                    for (int i = 0; i < n; i++) {
                        list.add((T) array.get(i));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            return list;
        }

        /**
         * @see #putIntoPreferences(android.content.SharedPreferences, String, java.util.List)
         */
        @Override
        protected boolean onPutIntoPreferences(@NonNull SharedPreferences preferences) {
            return putIntoPreferences(preferences, mKey, mActualValue);
        }

        /**
         * @see #obtainFromPreferences(android.content.SharedPreferences, String, java.util.List)
         */
        @Nullable
        @Override
        protected List<T> onObtainFromPreferences(@NonNull SharedPreferences preferences) {
            return obtainFromPreferences(preferences, mKey, mDefaultValue);
        }
    }
}