/**
* Copyright 2014 Daum Kakao Corp.
*
* Redistribution and modification in source or binary forms are not permitted without specific prior written permission.
*
* 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 com.kakao.helper;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
public class SharedPreferencesCache {
private static final String JSON_VALUE_TYPE = "valueType";
private static final String JSON_VALUE = "value";
private static final String JSON_VALUE_ENUM_TYPE = "enumType";
private static final String TYPE_BOOLEAN = "bool";
private static final String TYPE_BOOLEAN_ARRAY = "bool[]";
private static final String TYPE_BYTE = "byte";
private static final String TYPE_BYTE_ARRAY = "byte[]";
private static final String TYPE_SHORT = "short";
private static final String TYPE_SHORT_ARRAY = "short[]";
private static final String TYPE_INTEGER = "int";
private static final String TYPE_INTEGER_ARRAY = "int[]";
private static final String TYPE_LONG = "long";
private static final String TYPE_LONG_ARRAY = "long[]";
private static final String TYPE_FLOAT = "float";
private static final String TYPE_FLOAT_ARRAY = "float[]";
private static final String TYPE_DOUBLE = "double";
private static final String TYPE_DOUBLE_ARRAY = "double[]";
private static final String TYPE_CHAR = "char";
private static final String TYPE_CHAR_ARRAY = "char[]";
private static final String TYPE_STRING = "string";
private static final String TYPE_STRING_LIST = "stringList";
private static final String TYPE_ENUM = "enum";
private final SharedPreferences file;
private final Bundle memory = new Bundle();
public SharedPreferencesCache(Context context, final String cacheName) {
Utility.notNull(context, "context");
Utility.notNull(cacheName, "cacheName");
Context applicationContext = context.getApplicationContext();
context = applicationContext != null ? applicationContext : context;
this.file = context.getSharedPreferences(cacheName, Context.MODE_PRIVATE);
}
public synchronized void reloadAll() {
Map<String, ?> allCachedEntries = file.getAll();
for (String key : allCachedEntries.keySet()) {
try {
deserializeKey(key);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.reloadAll" , "Error reading cached value for key: " + key + ", e = " + e);
}
}
}
public synchronized void save(final Bundle bundle) {
Utility.notNull(bundle, "bundle");
SharedPreferences.Editor editor = file.edit();
for (String key : bundle.keySet()) {
try {
serializeKey(key, bundle, editor);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.save", "Error serializing value for key: " + key + ", e = " + e);
return;
}
}
boolean successfulCommit = editor.commit();
if (!successfulCommit) {
Logger.getInstance().w("SharedPreferences.Editor.commit() was not successful");
} else {
for (String key : bundle.keySet()) {
try {
deserializeKey(key);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.save", "Error deserializing value for key: " + key + ", e = " + e);
}
}
}
}
public synchronized void clear(final List<String> keysToClear) {
SharedPreferences.Editor cacheEditor = file.edit();
for(String key : keysToClear){
cacheEditor.remove(key);
}
cacheEditor.commit();
for(String key : keysToClear){
memory.remove(key);
}
Logger.getInstance().i("Clearing keys : " + keysToClear);
}
public synchronized void clearAll() {
file.edit().clear().commit();
memory.clear();
Logger.getInstance().i("Clearing all ");
}
public synchronized Map<String, String> getStringMap(final String keyPrefix) {
reloadAll();
Map<String, String> properties = new HashMap<String, String>();
for(String key : memory.keySet()){
if(key.startsWith(keyPrefix)){
properties.put(key, (String) memory.get(key));
}
}
return properties;
}
public synchronized String getString(final String key) {
String value = memory.getString(key);
if(value == null){
try {
deserializeKey(key);
value = memory.getString(key);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.getString", "Error reading String value for key: " + key + ", e = " + e);
}
}
return value;
}
public synchronized int getInt(final String key) {
int value = memory.getInt(key, Integer.MIN_VALUE);
if(value == Integer.MIN_VALUE){
try {
deserializeKey(key);
value = memory.getInt(key, Integer.MIN_VALUE);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.getInt", "Error reading Int value for key: " + key + ", e = " + e);
}
}
return value;
}
public synchronized Long getLong(final String key) {
long value = memory.getLong(key, Long.MIN_VALUE);
if(value == Long.MIN_VALUE){
try {
deserializeKey(key);
value = memory.getLong(key, Long.MIN_VALUE);
} catch (JSONException e) {
Logger.getInstance().w("SharedPreferences.getLong", "Error reading Long value for key: " + key + ", e = " + e);
}
}
return value;
}
public synchronized Date getDate(final String key) {
long value = getLong(key);
return (value == Long.MIN_VALUE) ? null : new Date(value);
}
private void serializeKey(String key, Bundle bundle, SharedPreferences.Editor editor)
throws JSONException {
Object value = bundle.get(key);
if (value == null) {
// Cannot serialize null values.
return;
}
String supportedType = null;
JSONArray jsonArray = null;
JSONObject json = new JSONObject();
if (value instanceof Byte) {
supportedType = TYPE_BYTE;
json.put(JSON_VALUE, ((Byte)value).intValue());
} else if (value instanceof Short) {
supportedType = TYPE_SHORT;
json.put(JSON_VALUE, ((Short)value).intValue());
} else if (value instanceof Integer) {
supportedType = TYPE_INTEGER;
json.put(JSON_VALUE, ((Integer)value).intValue());
} else if (value instanceof Long) {
supportedType = TYPE_LONG;
json.put(JSON_VALUE, ((Long)value).longValue());
} else if (value instanceof Float) {
supportedType = TYPE_FLOAT;
json.put(JSON_VALUE, ((Float)value).doubleValue());
} else if (value instanceof Double) {
supportedType = TYPE_DOUBLE;
json.put(JSON_VALUE, ((Double)value).doubleValue());
} else if (value instanceof Boolean) {
supportedType = TYPE_BOOLEAN;
json.put(JSON_VALUE, ((Boolean)value).booleanValue());
} else if (value instanceof Character) {
supportedType = TYPE_CHAR;
json.put(JSON_VALUE, value.toString());
} else if (value instanceof String) {
supportedType = TYPE_STRING;
json.put(JSON_VALUE, value);
} else if (value instanceof Enum<?>) {
supportedType = TYPE_ENUM;
json.put(JSON_VALUE, value.toString());
json.put(JSON_VALUE_ENUM_TYPE, value.getClass().getName());
} else {
// Optimistically create a JSONArray. If not an array type, we can null
// it out later
jsonArray = new JSONArray();
if (value instanceof byte[]) {
supportedType = TYPE_BYTE_ARRAY;
for (byte v : (byte[])value) {
jsonArray.put((int)v);
}
} else if (value instanceof short[]) {
supportedType = TYPE_SHORT_ARRAY;
for (short v : (short[])value) {
jsonArray.put((int)v);
}
} else if (value instanceof int[]) {
supportedType = TYPE_INTEGER_ARRAY;
for (int v : (int[])value) {
jsonArray.put(v);
}
} else if (value instanceof long[]) {
supportedType = TYPE_LONG_ARRAY;
for (long v : (long[])value) {
jsonArray.put(v);
}
} else if (value instanceof float[]) {
supportedType = TYPE_FLOAT_ARRAY;
for (float v : (float[])value) {
jsonArray.put((double)v);
}
} else if (value instanceof double[]) {
supportedType = TYPE_DOUBLE_ARRAY;
for (double v : (double[])value) {
jsonArray.put(v);
}
} else if (value instanceof boolean[]) {
supportedType = TYPE_BOOLEAN_ARRAY;
for (boolean v : (boolean[])value) {
jsonArray.put(v);
}
} else if (value instanceof char[]) {
supportedType = TYPE_CHAR_ARRAY;
for (char v : (char[])value) {
jsonArray.put(String.valueOf(v));
}
} else if (value instanceof List<?>) {
supportedType = TYPE_STRING_LIST;
@SuppressWarnings("unchecked")
List<String> stringList = (List<String>)value;
for (String v : stringList) {
jsonArray.put((v == null) ? JSONObject.NULL : v);
}
} else {
// Unsupported type. Clear out the array as a precaution even though
// it is redundant with the null supportedType.
jsonArray = null;
}
}
if (supportedType != null) {
json.put(JSON_VALUE_TYPE, supportedType);
if (jsonArray != null) {
// If we have an array, it has already been converted to JSON. So use
// that instead.
json.putOpt(JSON_VALUE, jsonArray);
}
String jsonString = json.toString();
editor.putString(key, jsonString);
}
}
private void deserializeKey(String key)
throws JSONException {
String jsonString = file.getString(key, "{}");
JSONObject json = new JSONObject(jsonString);
String valueType = json.getString(JSON_VALUE_TYPE);
if (valueType.equals(TYPE_BOOLEAN)) {
memory.putBoolean(key, json.getBoolean(JSON_VALUE));
} else if (valueType.equals(TYPE_BOOLEAN_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
boolean[] array = new boolean[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = jsonArray.getBoolean(i);
}
memory.putBooleanArray(key, array);
} else if (valueType.equals(TYPE_BYTE)) {
memory.putByte(key, (byte) json.getInt(JSON_VALUE));
} else if (valueType.equals(TYPE_BYTE_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
byte[] array = new byte[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = (byte)jsonArray.getInt(i);
}
memory.putByteArray(key, array);
} else if (valueType.equals(TYPE_SHORT)) {
memory.putShort(key, (short) json.getInt(JSON_VALUE));
} else if (valueType.equals(TYPE_SHORT_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
short[] array = new short[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = (short)jsonArray.getInt(i);
}
memory.putShortArray(key, array);
} else if (valueType.equals(TYPE_INTEGER)) {
memory.putInt(key, json.getInt(JSON_VALUE));
} else if (valueType.equals(TYPE_INTEGER_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
int[] array = new int[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = jsonArray.getInt(i);
}
memory.putIntArray(key, array);
} else if (valueType.equals(TYPE_LONG)) {
memory.putLong(key, json.getLong(JSON_VALUE));
} else if (valueType.equals(TYPE_LONG_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
long[] array = new long[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = jsonArray.getLong(i);
}
memory.putLongArray(key, array);
} else if (valueType.equals(TYPE_FLOAT)) {
memory.putFloat(key, (float) json.getDouble(JSON_VALUE));
} else if (valueType.equals(TYPE_FLOAT_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
float[] array = new float[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = (float)jsonArray.getDouble(i);
}
memory.putFloatArray(key, array);
} else if (valueType.equals(TYPE_DOUBLE)) {
memory.putDouble(key, json.getDouble(JSON_VALUE));
} else if (valueType.equals(TYPE_DOUBLE_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
double[] array = new double[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
array[i] = jsonArray.getDouble(i);
}
memory.putDoubleArray(key, array);
} else if (valueType.equals(TYPE_CHAR)) {
String charString = json.getString(JSON_VALUE);
if (charString != null && charString.length() == 1) {
memory.putChar(key, charString.charAt(0));
}
} else if (valueType.equals(TYPE_CHAR_ARRAY)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
char[] array = new char[jsonArray.length()];
for (int i = 0; i < array.length; i++) {
String charString = jsonArray.getString(i);
if (charString != null && charString.length() == 1) {
array[i] = charString.charAt(0);
}
}
memory.putCharArray(key, array);
} else if (valueType.equals(TYPE_STRING)) {
memory.putString(key, json.getString(JSON_VALUE));
} else if (valueType.equals(TYPE_STRING_LIST)) {
JSONArray jsonArray = json.getJSONArray(JSON_VALUE);
int numStrings = jsonArray.length();
ArrayList<String> stringList = new ArrayList<String>(numStrings);
for (int i = 0; i < numStrings; i++) {
Object jsonStringValue = jsonArray.get(i);
stringList.add(i, jsonStringValue == JSONObject.NULL ? null : (String)jsonStringValue);
}
memory.putStringArrayList(key, stringList);
} else if (valueType.equals(TYPE_ENUM)) {
try {
String enumType = json.getString(JSON_VALUE_ENUM_TYPE);
@SuppressWarnings({ "unchecked", "rawtypes" })
Class<? extends Enum> enumClass = (Class<? extends Enum>) Class.forName(enumType);
@SuppressWarnings("unchecked")
Enum<?> enumValue = Enum.valueOf(enumClass, json.getString(JSON_VALUE));
memory.putSerializable(key, enumValue);
} catch (ClassNotFoundException e) {
Logger.getInstance().w("SharedPreferences.deserializeKey", "Error deserializing key '" + key + "' -- " + e);
} catch (IllegalArgumentException e) {
Logger.getInstance().w("SharedPreferences.deserializeKey", "Error deserializing key '" + key + "' -- " + e);
}
}
}
}