Skip to content

Commit e58dd1d

Browse files
committed
Add thread-safe locking for load/save operations for json velocity files
1 parent 90d48de commit e58dd1d

File tree

1 file changed

+228
-88
lines changed

1 file changed

+228
-88
lines changed
Lines changed: 228 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
package com.bencodez.simpleapi.file.velocity;
22

3-
import java.io.File;
43
import java.io.IOException;
4+
import java.nio.file.AtomicMoveNotSupportedException;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.nio.file.StandardCopyOption;
58
import java.util.ArrayList;
69
import java.util.List;
10+
import java.util.Map;
11+
import java.util.concurrent.locks.ReentrantReadWriteLock;
12+
import java.util.logging.Level;
13+
import java.util.logging.Logger;
714

815
import com.google.common.reflect.TypeToken;
916

@@ -14,91 +21,224 @@
1421
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
1522

1623
public class VelocityJSONFile {
17-
@Getter
18-
@Setter
19-
private ConfigurationNode conf;
20-
@Getter
21-
private File file;
22-
private GsonConfigurationLoader loader;
23-
24-
public VelocityJSONFile(File file) {
25-
26-
this.file = file;
27-
if (!file.exists()) {
28-
try {
29-
file.getParentFile().mkdirs();
30-
file.createNewFile();
31-
} catch (IOException e) {
32-
// TODO Auto-generated catch block
33-
e.printStackTrace();
34-
}
35-
}
36-
loader = GsonConfigurationLoader.builder().setPath(file.toPath()).build();
37-
38-
try {
39-
conf = loader.load();
40-
} catch (IOException e) {
41-
e.printStackTrace();
42-
}
43-
44-
}
45-
46-
public boolean getBoolean(ConfigurationNode node, boolean def) {
47-
return node.getBoolean(def);
48-
}
49-
50-
public ConfigurationNode getData() {
51-
return conf;
52-
}
53-
54-
public int getInt(ConfigurationNode node, int def) {
55-
return node.getInt(def);
56-
}
57-
58-
public ArrayList<String> getKeys(ConfigurationNode node) {
59-
ArrayList<String> keys = new ArrayList<>();
60-
for (ConfigurationNode key : node.getChildrenMap().values()) {
61-
keys.add(key.getKey().toString());
62-
}
63-
return keys;
64-
}
65-
66-
public long getLong(ConfigurationNode node, long def) {
67-
return node.getLong(def);
68-
}
69-
70-
public ConfigurationNode getNode(Object... path) {
71-
return getData().getNode(path);
72-
}
73-
74-
public String getString(ConfigurationNode node, String def) {
75-
return node.getString(def);
76-
}
77-
78-
public List<String> getStringList(ConfigurationNode node, ArrayList<String> def) {
79-
try {
80-
return node.getList(TypeToken.of(String.class), def);
81-
} catch (ObjectMappingException e) {
82-
e.printStackTrace();
83-
return def;
84-
}
85-
}
86-
87-
public void reload() {
88-
loader = GsonConfigurationLoader.builder().setPath(file.toPath()).build();
89-
90-
try {
91-
conf = loader.load();
92-
} catch (IOException e) {
93-
e.printStackTrace();
94-
}
95-
}
96-
97-
public void save() {
98-
try {
99-
loader.save(conf);
100-
} catch (IOException e) {
101-
e.printStackTrace();
102-
}
103-
}
24+
private static final Logger LOG = Logger.getLogger(VelocityJSONFile.class.getName());
25+
26+
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
27+
28+
@Getter @Setter
29+
private ConfigurationNode conf;
30+
31+
@Getter
32+
private final Path path;
33+
34+
private GsonConfigurationLoader loader;
35+
36+
public VelocityJSONFile(Path path) {
37+
this.path = path;
38+
ensureFileExists(path);
39+
buildLoader();
40+
loadInternal(true);
41+
}
42+
43+
public VelocityJSONFile(java.io.File file) {
44+
this(file.toPath());
45+
}
46+
47+
/* ===================== Load / Save ===================== */
48+
49+
/** Reloads from disk (rebuilds the loader in case the path changed or options are updated). */
50+
public void reload() {
51+
lock.writeLock().lock();
52+
try {
53+
buildLoader();
54+
loadInternal(true);
55+
} finally {
56+
lock.writeLock().unlock();
57+
}
58+
}
59+
60+
/** Saves to disk, using a temp file + atomic move where supported. */
61+
public void save() {
62+
lock.readLock().lock();
63+
try {
64+
Path tmp = path.resolveSibling(path.getFileName() + ".tmp");
65+
try {
66+
GsonConfigurationLoader tmpLoader = GsonConfigurationLoader.builder().setPath(tmp).build();
67+
tmpLoader.save(conf);
68+
69+
try {
70+
Files.move(tmp, path, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
71+
} catch (AtomicMoveNotSupportedException e) {
72+
Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING);
73+
}
74+
} finally {
75+
try { Files.deleteIfExists(tmp); } catch (IOException ignored) {}
76+
}
77+
} catch (IOException e) {
78+
LOG.log(Level.SEVERE, "Failed to save JSON config: " + path, e);
79+
} finally {
80+
lock.readLock().unlock();
81+
}
82+
}
83+
84+
/* ===================== Getters / Helpers ===================== */
85+
86+
public ConfigurationNode getData() {
87+
lock.readLock().lock();
88+
try {
89+
return conf;
90+
} finally {
91+
lock.readLock().unlock();
92+
}
93+
}
94+
95+
public ConfigurationNode getNode(Object... path) {
96+
lock.readLock().lock();
97+
try {
98+
return conf.getNode(path);
99+
} finally {
100+
lock.readLock().unlock();
101+
}
102+
}
103+
104+
public boolean contains(Object... path) {
105+
lock.readLock().lock();
106+
try {
107+
return !conf.getNode(path).isVirtual();
108+
} finally {
109+
lock.readLock().unlock();
110+
}
111+
}
112+
113+
public boolean getBoolean(ConfigurationNode node, boolean def) {
114+
lock.readLock().lock();
115+
try {
116+
return node.getBoolean(def);
117+
} finally {
118+
lock.readLock().unlock();
119+
}
120+
}
121+
122+
public int getInt(ConfigurationNode node, int def) {
123+
lock.readLock().lock();
124+
try {
125+
return node.getInt(def);
126+
} finally {
127+
lock.readLock().unlock();
128+
}
129+
}
130+
131+
public long getLong(ConfigurationNode node, long def) {
132+
lock.readLock().lock();
133+
try {
134+
return node.getLong(def);
135+
} finally {
136+
lock.readLock().unlock();
137+
}
138+
}
139+
140+
public String getString(ConfigurationNode node, String def) {
141+
lock.readLock().lock();
142+
try {
143+
return node.getString(def);
144+
} finally {
145+
lock.readLock().unlock();
146+
}
147+
}
148+
149+
public ArrayList<String> getKeys(ConfigurationNode node) {
150+
lock.readLock().lock();
151+
try {
152+
ArrayList<String> keys = new ArrayList<>();
153+
for (Map.Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()) {
154+
keys.add(String.valueOf(e.getKey()));
155+
}
156+
return keys;
157+
} finally {
158+
lock.readLock().unlock();
159+
}
160+
}
161+
162+
public List<String> getStringList(ConfigurationNode node, List<String> def) {
163+
lock.readLock().lock();
164+
try {
165+
return node.getList(TypeToken.of(String.class), def);
166+
} catch (ObjectMappingException e) {
167+
LOG.log(Level.WARNING, "Failed to read string list at " + safeNodePath(node) + ", using default.", e);
168+
return def;
169+
} finally {
170+
lock.readLock().unlock();
171+
}
172+
}
173+
174+
/* ===================== Mutators ===================== */
175+
176+
public void set(Object[] path, Object value) {
177+
lock.writeLock().lock();
178+
try {
179+
conf.getNode(path).setValue(value);
180+
} finally {
181+
lock.writeLock().unlock();
182+
}
183+
}
184+
185+
public void remove(Object... path) {
186+
lock.writeLock().lock();
187+
try {
188+
conf.getNode(path).setValue(null);
189+
} finally {
190+
lock.writeLock().unlock();
191+
}
192+
}
193+
194+
/* ===================== Internals ===================== */
195+
196+
private void buildLoader() {
197+
this.loader = GsonConfigurationLoader.builder()
198+
.setPath(this.path)
199+
.build();
200+
}
201+
202+
private void loadInternal(boolean logErrors) {
203+
lock.writeLock().lock();
204+
try {
205+
conf = loader.load();
206+
if (conf == null) {
207+
conf = loader.createEmptyNode();
208+
}
209+
} catch (IOException e) {
210+
if (logErrors) {
211+
LOG.log(Level.SEVERE, "Failed to load JSON config: " + path, e);
212+
}
213+
if (conf == null) {
214+
conf = loader.createEmptyNode();
215+
}
216+
} finally {
217+
lock.writeLock().unlock();
218+
}
219+
}
220+
221+
private void ensureFileExists(Path p) {
222+
try {
223+
if (p.getParent() != null) {
224+
Files.createDirectories(p.getParent());
225+
}
226+
if (!Files.exists(p)) {
227+
Files.createFile(p);
228+
buildLoader();
229+
conf = loader.createEmptyNode();
230+
save();
231+
}
232+
} catch (IOException e) {
233+
LOG.log(Level.SEVERE, "Failed to ensure config file exists: " + p, e);
234+
}
235+
}
236+
237+
private String safeNodePath(ConfigurationNode node) {
238+
try {
239+
return node.getPath().toString();
240+
} catch (Throwable t) {
241+
return "<unknown>";
242+
}
243+
}
104244
}

0 commit comments

Comments
 (0)