package org.holoeverywhere.preference; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.holoeverywhere.HoloEverywhere; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build.VERSION; import android.util.Log; public class _SharedPreferencesImpl_JSON extends _SharedPreferencesBase { private class ClearJSONManipulate implements FutureJSONManipulate { @Override public boolean onJSONManipulate(JSONObject object) { @SuppressWarnings("unchecked") Iterator<String> iterator = object.keys(); while (iterator.hasNext()) { final String key = iterator.next(); object.remove(key); notifyOnChange(key); } return true; } } private final class CouldNotCreateStorage extends RuntimeException { private static final long serialVersionUID = -8602981054023098742L; public CouldNotCreateStorage(File file, String message) { super("File \"" + file.getAbsolutePath() + "\": " + message); } } private final class EditorImpl extends _BaseEditor { private final List<FutureJSONManipulate> manipulate = new ArrayList<FutureJSONManipulate>(); private EditorImpl add(FutureJSONManipulate t) { manipulate.add(t); return this; } @Override public void apply() { JSONObject data = getData(); synchronized (data) { try { for (FutureJSONManipulate m : manipulate) { if (!m.onJSONManipulate(data)) { throw new RuntimeException(m.getClass() .getSimpleName() + ": Manipulate failed"); } } saveDataToFile(file, data); } catch (Exception e) { Log.e(TAG, "Error while save preferences data", e); } finally { manipulate.clear(); } } } @Override public Editor clear() { return add(new ClearJSONManipulate()); } @Override public boolean commit() { try { apply(); return true; } catch (Exception e) { return false; } } @Override public Editor putBoolean(String key, boolean value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putFloat(String key, float value) { return add(new PutValueJSONManipulate(key, (double) value)); } @Override public Editor putFloatSet(String key, Set<Float> value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putInt(String key, int value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putIntSet(String key, Set<Integer> value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putJSONArray(String key, JSONArray value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putJSONObject(String key, JSONObject value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putLong(String key, long value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putLongSet(String key, Set<Long> value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putString(String key, String value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor putStringSet(String key, Set<String> value) { return add(new PutValueJSONManipulate(key, value)); } @Override public Editor remove(String key) { return add(new RemoveValueJSONManipulate(key)); } } private static interface FutureJSONManipulate { public boolean onJSONManipulate(JSONObject object); } private static final class ImplReference { private JSONObject data; private Set<OnSharedPreferenceChangeListener> listeners; } private class PutValueJSONManipulate implements FutureJSONManipulate { private String key; private Object t; public PutValueJSONManipulate(String key, Object t) { this.key = key; this.t = t; } @Override public boolean onJSONManipulate(JSONObject object) { try { if (t instanceof Set) { t = new JSONArray((Set<?>) t); } object.put(key, t); notifyOnChange(key); return true; } catch (JSONException e) { return false; } } } private class RemoveValueJSONManipulate implements FutureJSONManipulate { private String key; public RemoveValueJSONManipulate(String key) { this.key = key; } @Override public boolean onJSONManipulate(JSONObject object) { if (object.has(key)) { object.remove(key); notifyOnChange(key); return true; } else { return false; } } } private static final Map<String, ImplReference> refs = new HashMap<String, ImplReference>(); private static final String TAG = _SharedPreferencesImpl_JSON.class.getSimpleName(); private String charset; private File file; private final String fileTag; @SuppressLint("NewApi") public _SharedPreferencesImpl_JSON(Context context, String name, int mode) { setCharset("utf-8"); try { File tempFile = new File(context.getApplicationInfo().dataDir + "/shared_prefs"); if (tempFile.exists()) { if (!tempFile.isDirectory()) { if (!tempFile.delete() && !tempFile.mkdirs()) { throw new CouldNotCreateStorage(tempFile, "Сann't create a storage for the preferences."); } if (VERSION.SDK_INT >= 9) { tempFile.setWritable(true); tempFile.setReadable(true); } } } else { if (!tempFile.mkdirs()) { throw new CouldNotCreateStorage(tempFile, "Сann't create a storage for the preferences."); } if (VERSION.SDK_INT >= 9) { tempFile.setWritable(true); tempFile.setReadable(true); } } tempFile = new File(tempFile, name + ".json"); if (!tempFile.exists() && !tempFile.createNewFile()) { throw new CouldNotCreateStorage(tempFile, "Сann't create a storage for the preferences."); } if (VERSION.SDK_INT >= 9) { switch (mode) { case Context.MODE_WORLD_WRITEABLE: tempFile.setWritable(true, false); tempFile.setReadable(true, false); break; case Context.MODE_WORLD_READABLE: tempFile.setWritable(true, true); tempFile.setReadable(true, false); break; case Context.MODE_PRIVATE: default: tempFile.setWritable(true, true); tempFile.setReadable(true, true); break; } } file = tempFile; fileTag = file.getAbsolutePath().intern(); if (getReference().data == null) { getReference().data = readDataFromFile(file); } } catch (IOException e) { throw new RuntimeException("IOException", e); } } @Override public synchronized boolean contains(String key) { return getData().has(key); } @Override public Editor edit() { return new EditorImpl(); } @Override public synchronized Map<String, ?> getAll() { Map<String, Object> map = new HashMap<String, Object>(getData() .length()); Iterator<?> i = getData().keys(); while (i.hasNext()) { Object o = i.next(); String key = o instanceof String ? (String) o : o.toString(); try { map.put(key, getData().get(key)); } catch (JSONException e) { } } return map; } @Override public boolean getBoolean(String key, boolean defValue) { return getData().optBoolean(key, d().getBoolean(key, defValue)); } public String getCharset() { return charset; } protected JSONObject getData() { return getReference().data; } @Override public float getFloat(String key, float defValue) { return (float) getData().optDouble(key, d().getFloat(key, defValue)); } @Override public Set<Float> getFloatSet(String key, Set<Float> defValue) { return getSet(key, defValue); } @Override public int getInt(String key, int defValue) { return getData().optInt(key, d().getInt(key, defValue)); } @Override public Set<Integer> getIntSet(String key, Set<Integer> defValue) { return getSet(key, defValue); } @Override public JSONArray getJSONArray(String key, JSONArray defValue) { JSONArray a = getData().optJSONArray(key); return a == null ? defValue : a; } @Override public JSONObject getJSONObject(String key, JSONObject defValue) { JSONObject a = getData().optJSONObject(key); return a == null ? defValue : a; } @Override public long getLong(String key, long defValue) { return getData().optLong(key, d().getLong(key, defValue)); } @Override public Set<Long> getLongSet(String key, Set<Long> defValue) { return getSet(key, defValue); } protected synchronized ImplReference getReference() { ImplReference ref = refs.get(fileTag); if (ref == null) { ref = new ImplReference(); refs.put(fileTag, ref); } return ref; } @SuppressWarnings("unchecked") private <T> Set<T> getSet(String key, Set<T> defValue) { JSONArray a = getData().optJSONArray(key); if (a == null) { try { Object o = d().get(key); if (o != null) { return new HashSet<T>(Arrays.asList((T[]) o)); } } catch (Exception e) { } return defValue; } Set<T> set = new HashSet<T>(Math.max(a.length(), 0)); for (int i = 0; i < a.length(); i++) { set.add((T) a.opt(i)); } return set; } @Override public String getString(String key, String defValue) { String defValue2 = d().getString(key); return getData().optString(key, defValue2 == null ? defValue : defValue2); } @Override public Set<String> getStringSet(String key, Set<String> defValue) { return getSet(key, defValue); } public void notifyOnChange(String key) { Set<OnSharedPreferenceChangeListener> listeners = getReference().listeners; if (listeners == null) { return; } synchronized (listeners) { for (OnSharedPreferenceChangeListener listener : listeners) { listener.onSharedPreferenceChanged(this, key); } } } protected JSONObject readDataFromFile(File file) { try { InputStream is = new FileInputStream(file); Reader reader; try { reader = new InputStreamReader(is, charset); } catch (UnsupportedEncodingException e) { if (HoloEverywhere.DEBUG) { Log.w(TAG, "Encoding unsupport: " + charset); } reader = new InputStreamReader(is); } reader = new BufferedReader(reader, 1024); StringBuilder builder = new StringBuilder(Math.max(is.available(), 0)); char[] buffer = new char[8192]; int c; while ((c = reader.read(buffer)) > 0) { builder.append(buffer, 0, c); } reader.close(); is.close(); return new JSONObject(builder.toString()); } catch (Exception e) { return new JSONObject(); } } @Override public void registerOnSharedPreferenceChangeListener( android.content.SharedPreferences.OnSharedPreferenceChangeListener listener) { throw new RuntimeException( "android.content.SharedPreferences.OnSharedPreferenceChangeListener don't supported on JSON impl"); } @Override public synchronized void registerOnSharedPreferenceChangeListener( OnSharedPreferenceChangeListener listener) { Set<OnSharedPreferenceChangeListener> listeners = getReference().listeners; if (listeners == null) { getReference().listeners = listeners = new HashSet<SharedPreferences.OnSharedPreferenceChangeListener>(); } if (!listeners.contains(listener)) { listeners.add(listener); } } public void saveDataToFile(File file, JSONObject data) { String s; if (HoloEverywhere.DEBUG) { try { s = data.toString(2); } catch (JSONException e) { Log.e(TAG, "JSONException", e); s = data.toString(); } } else { s = data.toString(); } byte[] b; try { b = s.getBytes(charset); } catch (UnsupportedEncodingException e) { b = s.getBytes(); } try { OutputStream os = new FileOutputStream(file); os.write(b); os.flush(); os.close(); } catch (IOException e) { throw new RuntimeException("IOException", e); } } public void setCharset(String charset) { if (charset == null || !Charset.isSupported(charset)) { throw new RuntimeException("Illegal charset: " + charset); } this.charset = charset; } @Override public void unregisterOnSharedPreferenceChangeListener( android.content.SharedPreferences.OnSharedPreferenceChangeListener listener) { throw new RuntimeException( "android.content.SharedPreferences.OnSharedPreferenceChangeListener don't supported on JSON impl"); } @Override public synchronized void unregisterOnSharedPreferenceChangeListener( OnSharedPreferenceChangeListener listener) { Set<OnSharedPreferenceChangeListener> listeners = getReference().listeners; if (listeners == null) { return; } if (listeners.contains(listener)) { listeners.remove(listener); } if (listeners.size() == 0) { getReference().listeners = null; } } }