package com.alexvasilkov.android.commons.prefs; import android.content.SharedPreferences; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Date; import java.util.HashMap; class PreferencesManager<T> { private static final String PREFERENCES_NAME = "prefs_manager"; private static final String OBJECT_SAVED_KEY = ":object"; @SuppressWarnings("unchecked") static <T> void save(T obj) { new PreferencesManager<T>().initInternal((Class<? extends T>) obj.getClass()).saveInternal(obj); } static <T> T get(Class<T> clazz) { return new PreferencesManager<T>().initInternal(clazz).restoreInternal(clazz); } static <T> void remove(Class<T> clazz) { new PreferencesManager<T>().initInternal(clazz).removeInternal(clazz); } static <T> boolean exists(Class<T> clazz) { return getPreferencesForClass(clazz).getBoolean(OBJECT_SAVED_KEY, false); } private SharedPreferences mPrefs; private final HashMap<String, Field> mFieldsMap = new HashMap<String, Field>(); private final HashMap<Field, String> mKeysMap = new HashMap<Field, String>(); private PreferencesManager() { } private PreferencesManager<T> initInternal(Class<? extends T> clazz) { mPrefs = getPreferencesForClass(clazz); PreferenceField fieldAn; String key; for (Field f : clazz.getDeclaredFields()) { fieldAn = f.getAnnotation(PreferenceField.class); if (fieldAn == null) continue; key = fieldAn.value(); if (key == null || key.length() == 0) { throw new RuntimeException("\"key\" value of PreferenceField annotation cannot be empty"); } else if (mFieldsMap.containsKey(key)) { throw new RuntimeException("Duplicate key \"" + key + "\" of PreferenceField annotation"); } else { f.setAccessible(true); // removing private fields access restriction mFieldsMap.put(key, f); mKeysMap.put(f, key); } } return this; } private void saveInternal(T obj) { try { SharedPreferences.Editor editor = mPrefs.edit(); for (Field f : mKeysMap.keySet()) { setPrefsValue(f, obj, editor, mKeysMap.get(f)); } editor.putBoolean(OBJECT_SAVED_KEY, true); editor.commit(); } catch (IllegalAccessException e) { throw new RuntimeException("Can't access field value", e); } } private T restoreInternal(Class<? extends T> clazz) { if (!mPrefs.getBoolean(OBJECT_SAVED_KEY, false)) return null; try { Constructor<? extends T> constr = clazz.getConstructor(); T obj = constr.newInstance(); for (Field f : mKeysMap.keySet()) { setInstanceValue(f, obj, mPrefs, mKeysMap.get(f)); } return obj; } catch (Exception e) { throw new RuntimeException("Can't restore object", e); } } private void removeInternal(Class<? extends T> clazz) { if (!mPrefs.getBoolean(OBJECT_SAVED_KEY, false)) return; SharedPreferences.Editor editor = mPrefs.edit(); for (String key : mKeysMap.values()) { editor.remove(key); } editor.putBoolean(OBJECT_SAVED_KEY, false); editor.commit(); } private static SharedPreferences getPreferencesForClass(Class<?> clazz) { PreferenceObject objectAn = clazz.getAnnotation(PreferenceObject.class); if (objectAn == null) throw new RuntimeException("Specified object's class should be marked with PreferenceObject annotation"); String prefsName = objectAn.value(); if (prefsName.length() == 0) throw new RuntimeException("PreferenceObject annotation cannot have empty value"); return Preferences.prefs(PREFERENCES_NAME + ':' + prefsName); } private static void setPrefsValue(Field f, Object obj, SharedPreferences.Editor editor, String key) throws IllegalAccessException { Class<?> type = f.getType(); if (type.equals(Boolean.TYPE)) { editor.putBoolean(key, f.getBoolean(obj)); } else if (type.equals(Byte.TYPE)) { editor.putInt(key, f.getByte(obj)); } else if (type.equals(Character.TYPE)) { editor.putInt(key, f.getChar(obj)); } else if (type.equals(Double.TYPE)) { PreferencesHelper.putDouble(editor, key, f.getDouble(obj)); } else if (type.equals(Float.TYPE)) { editor.putFloat(key, f.getFloat(obj)); } else if (type.equals(Integer.TYPE)) { editor.putInt(key, f.getInt(obj)); } else if (type.equals(Long.TYPE)) { editor.putLong(key, f.getLong(obj)); } else if (type.equals(Short.TYPE)) { editor.putInt(key, f.getShort(obj)); } else if (type.equals(String.class)) { editor.putString(key, (String) f.get(obj)); } else if (type.equals(String[].class)) { PreferencesHelper.putStringArray(editor, key, (String[]) f.get(obj)); } else if (type.equals(Date.class)) { PreferencesHelper.putDate(editor, key, (Date) f.get(obj)); } else if (Serializable.class.isAssignableFrom(type)) { PreferencesHelper.putSerializable(editor, key, (Serializable) f.get(obj)); } else { throw new RuntimeException("Unsupported field type: " + f.getName() + ", " + type.getName()); } } private static void setInstanceValue(Field f, Object obj, SharedPreferences prefs, String key) throws IllegalArgumentException, IllegalAccessException { Class<?> type = f.getType(); if (type.equals(Boolean.TYPE)) { f.setBoolean(obj, prefs.getBoolean(key, false)); } else if (type.equals(Byte.TYPE)) { f.setByte(obj, (byte) prefs.getInt(key, 0)); } else if (type.equals(Character.TYPE)) { f.setChar(obj, (char) prefs.getInt(key, 0)); } else if (type.equals(Double.TYPE)) { f.setDouble(obj, PreferencesHelper.getDouble(prefs, key, 0D)); } else if (type.equals(Float.TYPE)) { f.setFloat(obj, prefs.getFloat(key, 0F)); } else if (type.equals(Integer.TYPE)) { f.setInt(obj, prefs.getInt(key, 0)); } else if (type.equals(Long.TYPE)) { f.setLong(obj, prefs.getLong(key, 0L)); } else if (type.equals(Short.TYPE)) { f.setShort(obj, (short) prefs.getInt(key, 0)); } else if (type.equals(String.class)) { f.set(obj, prefs.getString(key, null)); } else if (type.equals(String[].class)) { f.set(obj, PreferencesHelper.getStringArray(prefs, key)); } else if (type.equals(Date.class)) { f.set(obj, PreferencesHelper.getDate(prefs, key)); } else if (Serializable.class.isAssignableFrom(type)) { f.set(obj, PreferencesHelper.getSerializable(prefs, key)); } else { throw new RuntimeException("Unsupported field type: " + f.getName() + ", " + type.getSimpleName()); } } }