co.easimart.LocalIdManager Maven / Gradle / Ivy
package co.easimart;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Manages a set of local ids and possible mappings to global Easimart objectIds. This class is
* thread-safe.
*/
/** package */ class LocalIdManager {
/**
* Internal class representing all the information we know about a local id.
*/
private static class MapEntry {
String objectId;
int retainCount;
}
// Path to the local id storage on disk.
private final File diskPath;
// Random generator for inventing new ids.
private final Random random;
/**
* Creates a new LocalIdManager with default options.
*/
/* package for tests */ LocalIdManager(File root) {
diskPath = new File(root, "LocalId");
random = new Random();
}
/**
* Returns true if localId has the right basic format for a local id.
*/
private boolean isLocalId(String localId) {
if (!localId.startsWith("local_")) {
return false;
}
for (int i = 6; i < localId.length(); ++i) {
char c = localId.charAt(i);
if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f')) {
return false;
}
}
return true;
}
/**
* Grabs one entry in the local id map off the disk.
*/
private synchronized MapEntry getMapEntry(String localId) {
if (!isLocalId(localId)) {
throw new IllegalStateException("Tried to get invalid local id: \"" + localId + "\".");
}
try {
JSONObject json = EasimartFileUtils.readFileToJSONObject(new File(diskPath, localId));
MapEntry entry = new MapEntry();
entry.retainCount = json.optInt("retainCount", 0);
entry.objectId = json.optString("objectId", null);
return entry;
} catch (IOException | JSONException e) {
return new MapEntry();
}
}
/**
* Writes one entry to the local id map on disk.
*/
private synchronized void putMapEntry(String localId, MapEntry entry) {
if (!isLocalId(localId)) {
throw new IllegalStateException("Tried to get invalid local id: \"" + localId + "\".");
}
JSONObject json = new JSONObject();
try {
json.put("retainCount", entry.retainCount);
if (entry.objectId != null) {
json.put("objectId", entry.objectId);
}
} catch (JSONException je) {
throw new IllegalStateException("Error creating local id map entry.", je);
}
File file = new File(diskPath, localId);
if (!diskPath.exists()) {
diskPath.mkdirs();
}
try {
EasimartFileUtils.writeJSONObjectToFile(file, json);
} catch (IOException e) {
//TODO (grantland): We should do something if this fails...
}
}
/**
* Removes an entry from the local id map on disk.
*/
private synchronized void removeMapEntry(String localId) {
if (!isLocalId(localId)) {
throw new IllegalStateException("Tried to get invalid local id: \"" + localId + "\".");
}
File file = new File(diskPath, localId);
EasimartFileUtils.deleteQuietly(file);
}
/**
* Creates a new local id.
*/
synchronized String createLocalId() {
long localIdNumber = random.nextLong();
String localId = "local_" + Long.toHexString(localIdNumber);
if (!isLocalId(localId)) {
throw new IllegalStateException("Generated an invalid local id: \"" + localId + "\". "
+ "This should never happen. Contact us at https://parse.com/help");
}
return localId;
}
/**
* Increments the retain count of a local id on disk.
*/
synchronized void retainLocalIdOnDisk(String localId) {
MapEntry entry = getMapEntry(localId);
entry.retainCount++;
putMapEntry(localId, entry);
}
/**
* Decrements the retain count of a local id on disk. If the retain count hits zero, the id is
* forgotten forever.
*/
synchronized void releaseLocalIdOnDisk(String localId) {
MapEntry entry = getMapEntry(localId);
entry.retainCount--;
if (entry.retainCount > 0) {
putMapEntry(localId, entry);
} else {
removeMapEntry(localId);
}
}
/**
* Returns the objectId associated with a given local id. Returns null if no objectId is yet known
* for the local id.
*/
synchronized String getObjectId(String localId) {
MapEntry entry = getMapEntry(localId);
return entry.objectId;
}
/**
* Sets the objectId associated with a given local id.
*/
synchronized void setObjectId(String localId, String objectId) {
MapEntry entry = getMapEntry(localId);
if (entry.retainCount > 0) {
if (entry.objectId != null) {
throw new IllegalStateException(
"Tried to set an objectId for a localId that already has one.");
}
entry.objectId = objectId;
putMapEntry(localId, entry);
}
}
/**
* Clears all local ids from the map. Returns true is the cache was already empty.
*/
synchronized boolean clear() throws IOException {
String[] files = diskPath.list();
if (files == null) {
return false;
}
if (files.length == 0) {
return false;
}
for (String fileName : files) {
File file = new File(diskPath, fileName);
if (!file.delete()) {
throw new IOException("Unable to delete file " + fileName + " in localId cache.");
}
}
return true;
}
}