/** * QucikKV * Copyright 2014-2016 Sumi Makito * Licensed under Apache License 2.0. * * @author sumimakito<[email protected]> */ package com.github.sumimakito.quickkv.database; import android.content.Context; import com.github.sumimakito.maglevio.MaglevReader; import com.github.sumimakito.maglevio.MaglevWriter; import com.github.sumimakito.quickkv.QKVConfig; import com.github.sumimakito.quickkv.QuickKV; import com.github.sumimakito.quickkv.security.AES256; import com.github.sumimakito.quickkv.util.CompressHelper; import com.github.sumimakito.quickkv.util.DataProcessor; import com.github.sumimakito.quickkv.util.KVDBProperties; import com.github.sumimakito.quickkv.util.QKVLogger; import net.minidev.json.JSONObject; import net.minidev.json.JSONValue; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class KeyValueDatabase extends QKVDB implements QKVDBImpl { private HashMap<Object, Object> dMap; public KeyValueDatabase(QuickKV quickKV, Context context) { super(quickKV, context); this.dbAlias = QKVConfig.KVDB_FILE_NAME; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public KeyValueDatabase(QuickKV quickKV, Context context, boolean enableGZip) { super(quickKV, context); this.isGZipEnabled = enableGZip; this.dbAlias = QKVConfig.KVDB_FILE_NAME; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public KeyValueDatabase(QuickKV quickKV, Context context, String dbAlias) { super(quickKV, context); this.pContext = context; this.dbAlias = dbAlias.endsWith(QKVConfig.KVDB_EXT) ? dbAlias : dbAlias + QKVConfig.KVDB_EXT; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public KeyValueDatabase(QuickKV quickKV, Context context, String dbAlias, boolean enableGZip) { super(quickKV, context); this.isGZipEnabled = enableGZip; this.pContext = context; this.dbAlias = dbAlias.endsWith(QKVConfig.KVDB_EXT) ? dbAlias : dbAlias + QKVConfig.KVDB_EXT; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public KeyValueDatabase(QuickKV quickKV, Context context, String dbAlias, String key) { super(quickKV, context); this.pKey = key; this.pContext = context; this.dbAlias = dbAlias.endsWith(QKVConfig.KVDB_EXT) ? dbAlias : dbAlias + QKVConfig.KVDB_EXT; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public KeyValueDatabase(QuickKV quickKV, Context context, String dbAlias, String key, boolean enableGZip) { super(quickKV, context); this.isGZipEnabled = enableGZip; this.pKey = key; this.pContext = context; this.dbAlias = dbAlias.endsWith(QKVConfig.KVDB_EXT) ? dbAlias : dbAlias + QKVConfig.KVDB_EXT; this.dMap = new HashMap<Object, Object>(); this.sync(false); QKVLogger.log("i", "KVDB Initialized!"); } public void setGZipEnabled(boolean enabled){ isGZipEnabled = enabled; } public boolean put(HashMap hashMap){ boolean result = true; Iterator iterator = hashMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); result=result&&put(entry.getKey(), entry.getValue()); } return result; } @Override public <K extends Object, V extends Object> boolean put(K k, V v) { if (k == null || v == null) { return false; } this.dMap.put(k, v); return true; } @Override public <K extends Object> Object get(K k) { if (k == null) { return null; } if (this.dMap.containsKey(k)) return this.dMap.get(k); else return null; } @Override public <K extends Object> boolean containsKey(K k) { if (this.dMap.containsKey(k)) return true; else return false; } @Override public <V extends Object> boolean containsValue(V v) { if (this.dMap.containsValue(v)) return true; else return false; } @Override public <K extends Object> boolean remove(K k) { if (k == null) { return false; } if (this.dMap.containsKey(k)) { this.dMap.remove(k); return true; } else return false; } @Override public <K extends Object> boolean remove(K[] k) { if (k == null || k.length == 0) { return false; } int r = 0; for (K key : k) { if (this.dMap.containsKey(key)) { this.dMap.remove(key); r++; } } if (r < k.length) return false; else return true; } @Override public void clear() { this.dMap.clear(); } @Override public int size() { return this.dMap.size(); } public List<Object> getKeys() { List<Object> list = new ArrayList<Object>(); if (this.dMap.size() > 0) { Iterator iter = dMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); list.add(key); } } return list; } public List<Object> getValues() { List<Object> list = new ArrayList<Object>(); if (this.dMap.size() > 0) { Iterator iter = dMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object value = entry.getValue(); list.add(value); } } return list; } public boolean persist() { if (this.dMap.size() > 0) { try { JSONObject treeRoot = new JSONObject(); treeRoot.put(KVDBProperties.C_PROP, new JSONObject()); JSONObject propRoot = (JSONObject) treeRoot.get(KVDBProperties.C_PROP); propRoot.put(KVDBProperties.P_PROP_STRUCT_VER, QKVConfig.STRUCT_VER_STRING); propRoot.put(KVDBProperties.P_PROP_GZIP, isGZipEnabled); propRoot.put(KVDBProperties.P_PROP_ENCRYPTION, (this.pKey != null && this.pKey.length() > 0)); treeRoot.put(KVDBProperties.P_DATA, new JSONObject()); JSONObject dataRoot = (JSONObject) treeRoot.get(KVDBProperties.P_DATA); Iterator iter = this.dMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); if (DataProcessor.Persistable.isValidDataType(key) && DataProcessor.Persistable.isValidDataType(val)) { if (this.pKey != null && this.pKey.length() > 0) dataRoot.put(AES256.encode(this.pKey, DataProcessor.Persistable.addPrefix(key)), AES256.encode(this.pKey, DataProcessor.Persistable.addPrefix(val))); else dataRoot.put(DataProcessor.Persistable.addPrefix(key), DataProcessor.Persistable.addPrefix(val)); } } if (isGZipEnabled) { String compressedData = DataProcessor.Basic.bytesToHex(CompressHelper.compress(dataRoot.toString().getBytes())); treeRoot.remove(KVDBProperties.P_DATA); treeRoot.put(KVDBProperties.P_DATA, compressedData); } String fName = dbAlias == null ? QKVConfig.KVDB_FILE_NAME : dbAlias; File fTarget = new File(pInstance.getStorageManager().getWorkspace(), fName); MaglevWriter.NIO.MappedBFR.writeBytesToFile(treeRoot.toString().getBytes(), fTarget.getAbsolutePath()); return true; } catch (Exception e) { QKVLogger.ex(e); return false; } } else return true; } public void persist(final Callback callback) { new Thread(new Runnable() { @Override public void run() { synchronized (dMap) { if (persist()) callback.onSuccess(); else callback.onFailed(); } } }).start(); } public boolean sync() { return this.sync(true); } public boolean sync(boolean merge) { try { if (!merge) { this.dMap.clear(); } File kvdbFile = new File(pInstance.getStorageManager().getWorkspace(), dbAlias == null ? QKVConfig.KVDB_FILE_NAME : dbAlias); String rawData = kvdbFile.length() < 256 * 1000 ? MaglevReader.IO.fileToString(kvdbFile.getAbsolutePath()) : MaglevReader.NIO.MappedBFR.fileToString(kvdbFile.getAbsolutePath()); if (rawData.length() > 0) { JSONObject treeRoot = (JSONObject) JSONValue.parse(rawData); JSONObject properties = (JSONObject) treeRoot.get(KVDBProperties.C_PROP); boolean gzip = (Boolean) properties.get(KVDBProperties.P_PROP_GZIP); if (gzip) { String rawDataBody = CompressHelper.decompress( DataProcessor.Basic.hexToBytes((String) treeRoot.get(KVDBProperties.P_DATA)) ); if (parseKVJS((JSONObject) JSONValue.parse(rawDataBody))) return true; else return false; } else { if (parseKVJS((JSONObject) treeRoot.get(KVDBProperties.P_DATA))) return true; else return false; } } return true; } catch (Exception e) { QKVLogger.ex(e); return false; } } public void sync(final Callback callback) { new Thread(new Runnable() { @Override public void run() { synchronized (dMap) { if (sync()) callback.onSuccess(); else callback.onFailed(); } } }).start(); } public void sync(final boolean merge, final Callback callback) { new Thread(new Runnable() { @Override public void run() { synchronized (dMap) { if (sync(merge)) callback.onSuccess(); else callback.onFailed(); } } }).start(); } public boolean enableEncryption(String key) { if (key != null && key.length() > 0) { this.pKey = key; persist(); return true; } else return false; } public void disableEncryption() { this.pKey = null; persist(); } private boolean parseKVJS(JSONObject json) { try { Iterator<String> keys = json.keySet().iterator(); while (keys.hasNext()) { String key = keys.next(); String val = json.get(key).toString(); Object k, v; if (this.pKey != null && this.pKey.length() > 0) { k = DataProcessor.Persistable.dePrefix(AES256.decode(this.pKey, key)); v = DataProcessor.Persistable.dePrefix(AES256.decode(this.pKey, val)); } else { k = DataProcessor.Persistable.dePrefix(key); v = DataProcessor.Persistable.dePrefix(val); } this.dMap.put(k, v); } return true; } catch (Exception e) { QKVLogger.ex(e); return false; } } public interface Callback { public void onSuccess(); public void onFailed(); } }