add simple file storage for objects

This commit is contained in:
jascotty2 2019-09-06 11:33:40 -05:00
parent cb4db2bb34
commit 2488e97ba6
3 changed files with 267 additions and 3 deletions

View File

@ -126,16 +126,18 @@ public class Config extends ConfigSection {
fileName = file;
}
public Config(@NotNull Plugin plugin, @NotNull String directory, @NotNull String file) {
public Config(@NotNull Plugin plugin, @Nullable String directory, @NotNull String file) {
this.plugin = plugin;
dirName = directory;
fileName = file;
}
@NotNull
public ConfigFileConfigurationAdapter getFileConfig() {
return config;
}
@NotNull
public File getFile() {
if (file == null) {
if (dirName != null) {
@ -175,6 +177,7 @@ public class Config extends ConfigSection {
* @param autosaveInterval time in seconds
* @return this class
*/
@NotNull
public Config setAutosaveInterval(int autosaveInterval) {
this.autosaveInterval = autosaveInterval;
return this;
@ -202,6 +205,7 @@ public class Config extends ConfigSection {
/**
* Default comment applied to config nodes
*/
@Nullable
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
return defaultNodeCommentFormat;
}
@ -211,7 +215,8 @@ public class Config extends ConfigSection {
*
* @return this config
*/
public Config setDefaultNodeCommentFormat(ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
@NotNull
public Config setDefaultNodeCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
return this;
}
@ -219,6 +224,7 @@ public class Config extends ConfigSection {
/**
* Default comment applied to section nodes
*/
@Nullable
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
return defaultSectionCommentFormat;
}
@ -228,7 +234,8 @@ public class Config extends ConfigSection {
*
* @return this config
*/
public Config setDefaultSectionCommentFormat(ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
@NotNull
public Config setDefaultSectionCommentFormat(@Nullable ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
return this;
}
@ -245,6 +252,7 @@ public class Config extends ConfigSection {
*
* @return this config
*/
@NotNull
public Config setRootNodeSpacing(int rootNodeSpacing) {
this.rootNodeSpacing = rootNodeSpacing;
return this;
@ -264,6 +272,7 @@ public class Config extends ConfigSection {
*
* @return this config
*/
@NotNull
public Config setCommentSpacing(int commentSpacing) {
this.commentSpacing = commentSpacing;
return this;

View File

@ -0,0 +1,35 @@
package com.songoda.core.configuration;
import org.bukkit.configuration.ConfigurationSection;
public interface DataStoreObject<T> {
/**
* @return a unique hashable instance of T to store this value under
*/
public abstract T getKey();
/**
* @return a unique identifier for saving this value with
*/
public abstract String getConfigKey();
/**
* Save this data to a ConfigurationSection
*
* @param sec
*/
public abstract void saveToSection(ConfigurationSection sec);
/**
* @return true if this data has changed from the state saved to file
*/
public boolean hasChanged();
/**
* Mark this data as needing a save or not
*
* @param isChanged
*/
public void setChanged(boolean isChanged);
}

View File

@ -0,0 +1,220 @@
package com.songoda.core.configuration;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.function.Function;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Used to easily store a set of one data value
*
* @param <K> Key value that DataObject class uses to uniquely identify values
* @param <T> DataObject class that is used to store the data
* @since 2019-09-06
* @author jascotty2
*/
public class SimpleDataStore<K, T extends DataStoreObject<K>> {
protected final Plugin plugin;
protected final String filename, dirName;
private final Function<ConfigurationSection, T> getFromSection;
protected final HashMap<K, T> data = new HashMap();
private File file;
private final Object lock = new Object();
SaveTask saveTask;
Timer autosaveTimer;
/**
* time in seconds to start a save after a change is made
*/
int autosaveInterval = 60;
public SimpleDataStore(@NotNull Plugin plugin, @NotNull String filename, Function<ConfigurationSection, T> loadFunction) {
this.plugin = plugin;
this.filename = filename;
dirName = null;
this.getFromSection = loadFunction;
}
public SimpleDataStore(@NotNull Plugin plugin, @Nullable String directory, @NotNull String filename, @NotNull Function<ConfigurationSection, T> loadFunction) {
this.plugin = plugin;
this.filename = filename;
this.dirName = directory;
this.getFromSection = loadFunction;
}
@NotNull
public File getFile() {
if (file == null) {
if (dirName != null) {
this.file = new File(plugin.getDataFolder() + dirName, filename != null ? filename : "data.yml");
} else {
this.file = new File(plugin.getDataFolder(), filename != null ? filename : "data.yml");
}
}
return file;
}
/**
* @return a directly-modifiable instance of the data mapping for this storage
*/
public Map<K, T> getData() {
return data;
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* @param key key whose mapping is to be retrieved from this storage
* @return the value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
*/
@Nullable
public T get(K key) {
return data.get(key);
}
/**
* Removes the mapping for the specified key from this storage if present.
*
* @param key key whose mapping is to be removed from this storage
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
*/
@Nullable
public T remove(@NotNull K key) {
T temp;
synchronized (lock) {
temp = data.remove(key);
}
save();
return temp;
}
/**
* Removes the mapping for the specified key from this storage if present.
*
* @param value value whose mapping is to be removed from this storage
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
*/
@Nullable
public T remove(@NotNull T value) {
if (value == null) {
return null;
}
T temp;
synchronized (lock) {
temp = data.remove(value.getKey());
}
save();
return temp;
}
/**
* Adds the specified value in this storage. If the map previously contained
* a mapping for the key, the old value is replaced.
*
* @param value value to be added
* @return the previous value associated with <tt>value.getKey()</tt>, or
* <tt>null</tt> if there was no mapping for <tt>value.getKey()</tt>.
*/
@Nullable
public T add(@NotNull T value) {
if (value == null) {
return null;
}
T temp;
synchronized (lock) {
temp = data.put(value.getKey(), value);
}
save();
return temp;
}
/**
* Load data from the associated file
*/
public void load() {
if (!getFile().exists()) {
return;
}
try {
YamlConfiguration f = new YamlConfiguration();
f.options().pathSeparator('\0');
f.load(file);
synchronized (lock) {
data.clear();
f.getValues(false).entrySet().stream()
.filter(d -> d.getValue() instanceof ConfigurationSection)
.map(Map.Entry::getValue)
.map(v -> getFromSection.apply((ConfigurationSection) v))
.forEach(v -> data.put(v.getKey(), v));
}
} catch (IOException | InvalidConfigurationException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to load data from " + file.getName(), ex);
}
}
/**
* Optionally save this storage's data to file if there have been changes made
*/
public void saveChanges() {
if (saveTask != null || data.values().stream().anyMatch(v -> v.hasChanged())) {
flushSave();
}
}
/**
* Save this file data. This saves later asynchronously.
*/
public void save() {
// save async even if no plugin or if plugin disabled
if (saveTask == null) {
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-DataStoreSave-" : "DataStoreSave-") + getFile().getName());
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
}
}
/**
* Force a new save of this storage's data
*/
public void flushSave() {
if (saveTask != null) {
//Close Threads
saveTask.cancel();
autosaveTimer.cancel();
saveTask = null;
autosaveTimer = null;
}
YamlConfiguration f = new YamlConfiguration();
synchronized (lock) {
data.values().stream().forEach(e -> e.saveToSection(f.createSection(e.getConfigKey())));
}
try {
f.save(getFile());
data.values().stream().forEach(e -> e.setChanged(false));
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to save data to " + file.getName(), ex);
}
}
class SaveTask extends TimerTask {
@Override
public void run() {
flushSave();
}
}
}