Skip to content

Commit 6ca721a

Browse files
committed
Fix NPE in ParseKeyValueCache due to cache dir not exist
1 parent d1e2907 commit 6ca721a

File tree

2 files changed

+77
-28
lines changed

2 files changed

+77
-28
lines changed

Parse/src/main/java/com/parse/ParseKeyValueCache.java

+42-28
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,21 @@
6464
}
6565

6666
private static File getKeyValueCacheDir() {
67+
if (directory == null || !directory.exists()) {
68+
directory.mkdir();
69+
}
6770
return directory;
6871
}
6972

7073
/**
7174
* How many files are in the key-value cache.
7275
*/
7376
/* package */ static int size() {
74-
return getKeyValueCacheDir().listFiles().length;
77+
File[] files = getKeyValueCacheDir().listFiles();
78+
if (files == null) {
79+
return 0;
80+
}
81+
return files.length;
7582
}
7683

7784
private static File getKeyValueCacheFile(String key) {
@@ -96,7 +103,7 @@ private static long getKeyValueCacheAge(File cacheFile) {
96103
}
97104
}
98105

99-
/* package */ private static File createKeyValueCacheFile(String key) {
106+
private static File createKeyValueCacheFile(String key) {
100107
String filename = String.valueOf(new Date().getTime()) + '.' + key;
101108
return new File(getKeyValueCacheDir(), filename);
102109
}
@@ -127,9 +134,7 @@ private static long getKeyValueCacheAge(File cacheFile) {
127134
}
128135
File f = createKeyValueCacheFile(key);
129136
try {
130-
FileOutputStream out = new FileOutputStream(f);
131-
out.write(value.getBytes("UTF-8"));
132-
out.close();
137+
ParseFileUtils.writeByteArrayToFile(f, value.getBytes("UTF-8"));
133138
} catch (UnsupportedEncodingException e) {
134139
// do nothing
135140
} catch (IOException e) {
@@ -138,38 +143,47 @@ private static long getKeyValueCacheAge(File cacheFile) {
138143

139144
// Check if we should kick out old cache entries
140145
File[] files = getKeyValueCacheDir().listFiles();
146+
// We still need this check since dir.mkdir() may fail
147+
if (files == null || files.length == 0) {
148+
return;
149+
}
150+
141151
int numFiles = files.length;
142152
int numBytes = 0;
143153
for (File file : files) {
144154
numBytes += file.length();
145155
}
146-
if (numFiles > maxKeyValueCacheFiles || numBytes > maxKeyValueCacheBytes) {
147-
// We need to kick out some cache entries.
148-
// Sort oldest-first. We touch on read so mtime is really LRU.
149-
// Sometimes (i.e. tests) the time of lastModified isn't granular enough,
150-
// so we resort
151-
// to sorting by the file name which is always prepended with time in ms
152-
Arrays.sort(files, new Comparator<File>() {
153-
@Override
154-
public int compare(File f1, File f2) {
155-
int dateCompare = Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
156-
if (dateCompare != 0) {
157-
return dateCompare;
158-
} else {
159-
return f1.getName().compareTo(f2.getName());
160-
}
161-
}
162-
});
163156

164-
for (File file : files) {
165-
numFiles--;
166-
numBytes -= file.length();
167-
file.delete();
157+
// If we do not need to clear the cache, simply return
158+
if (numFiles <= maxKeyValueCacheFiles && numBytes <= maxKeyValueCacheBytes) {
159+
return;
160+
}
168161

169-
if (numFiles <= maxKeyValueCacheFiles && numBytes <= maxKeyValueCacheBytes) {
170-
break;
162+
// We need to kick out some cache entries.
163+
// Sort oldest-first. We touch on read so mtime is really LRU.
164+
// Sometimes (i.e. tests) the time of lastModified isn't granular enough,
165+
// so we resort
166+
// to sorting by the file name which is always prepended with time in ms
167+
Arrays.sort(files, new Comparator<File>() {
168+
@Override
169+
public int compare(File f1, File f2) {
170+
int dateCompare = Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
171+
if (dateCompare != 0) {
172+
return dateCompare;
173+
} else {
174+
return f1.getName().compareTo(f2.getName());
171175
}
172176
}
177+
});
178+
179+
for (File file : files) {
180+
numFiles--;
181+
numBytes -= file.length();
182+
file.delete();
183+
184+
if (numFiles <= maxKeyValueCacheFiles && numBytes <= maxKeyValueCacheBytes) {
185+
break;
186+
}
173187
}
174188
}
175189
}

Parse/src/test/java/com/parse/ParseKeyValueCacheTest.java

+35
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,18 @@
1414
import org.junit.Test;
1515
import org.junit.rules.TemporaryFolder;
1616

17+
import java.io.File;
1718
import java.util.ArrayList;
1819
import java.util.List;
1920
import java.util.concurrent.Callable;
2021

2122
import bolts.Task;
2223

24+
import static org.junit.Assert.assertArrayEquals;
25+
import static org.junit.Assert.assertEquals;
26+
import static org.junit.Assert.assertFalse;
27+
import static org.junit.Assert.assertTrue;
28+
2329
public class ParseKeyValueCacheTest {
2430

2531
@Rule
@@ -59,4 +65,33 @@ public Void call() throws Exception {
5965
}
6066
ParseTaskUtils.wait(Task.whenAll(tasks));
6167
}
68+
69+
@Test
70+
public void testSaveToKeyValueCacheWithoutCacheDir() throws Exception {
71+
// Delete the cache folder(Simulate users clear the app cache)
72+
File keyValueCacheDir = new File(temporaryFolder.getRoot(), "ParseKeyValueCache");
73+
assertTrue(keyValueCacheDir.exists());
74+
keyValueCacheDir.delete();
75+
assertFalse(keyValueCacheDir.exists());
76+
77+
// Save a key value pair
78+
ParseKeyValueCache.saveToKeyValueCache("key", "value");
79+
80+
// Verify cache file is correct
81+
assertEquals(1, keyValueCacheDir.listFiles().length);
82+
assertArrayEquals(
83+
"value".getBytes(), ParseFileUtils.readFileToByteArray(keyValueCacheDir.listFiles()[0]));
84+
}
85+
86+
@Test
87+
public void testGetSizeWithoutCacheDir() throws Exception {
88+
// Delete the cache folder(Simulate users clear the app cache)
89+
File keyValueCacheDir = new File(temporaryFolder.getRoot(), "ParseKeyValueCache");
90+
assertTrue(keyValueCacheDir.exists());
91+
keyValueCacheDir.delete();
92+
assertFalse(keyValueCacheDir.exists());
93+
94+
// Verify size is zero
95+
assertEquals(0, ParseKeyValueCache.size());
96+
}
6297
}

0 commit comments

Comments
 (0)