1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-09-19 21:04:02 +00:00

Introduce StorageKey to remove unchecked cast in GuildStorage, force Java 11

This commit is contained in:
Arnold Alejo Nunag 2020-10-16 01:32:45 +08:00
parent eb50a1bab3
commit 631acaf021
Signed by: sciwhiz12
GPG Key ID: 622CF446534317E1
6 changed files with 149 additions and 44 deletions

View File

@ -30,6 +30,11 @@ archivesBaseName = 'janitor_bot'
version = getVersion()
println("Version: ${version}")
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
implementation group: 'net.dv8tion', name: 'JDA', version: jda_version
implementation group: 'com.electronwill.night-config', name: 'toml', version: nightconfig_version

View File

@ -10,6 +10,7 @@ import sciwhiz12.janitor.commands.CommandRegistry;
import sciwhiz12.janitor.config.BotConfig;
import sciwhiz12.janitor.msg.Messages;
import sciwhiz12.janitor.msg.Translations;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.utils.Util;
import java.nio.file.Path;

View File

@ -9,9 +9,10 @@ import com.google.gson.reflect.TypeToken;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.GuildStorage;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type;
import java.util.HashMap;
@ -19,10 +20,10 @@ import java.util.Map;
public class NoteStorage extends JsonStorage {
private static final Type NOTE_MAP_TYPE = new TypeToken<Map<Integer, NoteEntry>>() {}.getType();
public static final String STORAGE_KEY = "notes";
public static final StorageKey<NoteStorage> KEY = new StorageKey<>("notes", NoteStorage.class);
public static NoteStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, STORAGE_KEY, () -> new NoteStorage(storage.getBot()));
return storage.getOrCreate(guild, KEY, () -> new NoteStorage(storage.getBot()));
}
private final Gson gson;
@ -33,8 +34,8 @@ public class NoteStorage extends JsonStorage {
public NoteStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot))
.create();
.registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {
@ -58,8 +59,8 @@ public class NoteStorage extends JsonStorage {
public int getAmountOfNotes(User target) {
return (int) notes.values().stream()
.filter(entry -> entry.getTarget() == target)
.count();
.filter(entry -> entry.getTarget() == target)
.count();
}
public Map<Integer, NoteEntry> getNotes() {

View File

@ -8,9 +8,10 @@ import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import net.dv8tion.jda.api.entities.Guild;
import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.GuildStorage;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type;
import java.util.HashMap;
@ -18,10 +19,10 @@ import java.util.Map;
public class WarningStorage extends JsonStorage {
private static final Type WARNING_MAP_TYPE = new TypeToken<Map<Integer, WarningEntry>>() {}.getType();
public static final String STORAGE_KEY = "warnings";
public static final StorageKey<WarningStorage> KEY = new StorageKey<>("warnings", WarningStorage.class);
public static WarningStorage get(GuildStorage storage, Guild guild) {
return storage.getOrCreate(guild, STORAGE_KEY, () -> new WarningStorage(storage.getBot()));
return storage.getOrCreate(guild, KEY, () -> new WarningStorage(storage.getBot()));
}
private final Gson gson;
@ -32,8 +33,8 @@ public class WarningStorage extends JsonStorage {
public WarningStorage(JanitorBot bot) {
this.bot = bot;
this.gson = new GsonBuilder()
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create();
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create();
}
public JanitorBot getBot() {

View File

@ -1,10 +1,11 @@
package sciwhiz12.janitor;
package sciwhiz12.janitor.storage;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.dv8tion.jda.api.entities.Guild;
import sciwhiz12.janitor.storage.IStorage;
import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.Logging;
import java.io.IOException;
import java.io.Reader;
@ -12,18 +13,20 @@ import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Supplier;
import static java.nio.file.StandardOpenOption.*;
/**
* A storage system for guild-specific data.
*/
public class GuildStorage {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final JanitorBot bot;
private final Path mainFolder;
private final Map<Guild, Map<String, IStorage>> guildStorage = new IdentityHashMap<>();
private final Map<Long, Map<String, InnerStorage<?>>> guildStorages = new HashMap<>();
public GuildStorage(JanitorBot bot, Path mainFolder) {
Preconditions.checkArgument(Files.isDirectory(mainFolder) || Files.notExists(mainFolder));
@ -35,28 +38,31 @@ public class GuildStorage {
return bot;
}
public <T extends IStorage> T getOrCreate(Guild guild, String key, Supplier<T> defaultSupplier) {
final Map<String, IStorage> storageMap = guildStorage.computeIfAbsent(guild, g -> new HashMap<>());
//noinspection unchecked
return (T) storageMap.computeIfAbsent(key, k -> load(guild, key, defaultSupplier.get()));
public <S extends IStorage> S getOrCreate(Guild guild, StorageKey<S> key, Supplier<S> defaultSupplier) {
return getOrCreate(guild.getIdLong(), key, defaultSupplier);
}
private Path getFile(Guild guild, String key) {
final Path guildFolder = makeFolder(guild);
public <S extends IStorage> S getOrCreate(long guildID, StorageKey<S> key, Supplier<S> defaultSupplier) {
final Map<String, InnerStorage<?>> storageMappy = guildStorages.computeIfAbsent(guildID, id -> new HashMap<>());
return key.getType().cast(storageMappy.computeIfAbsent(key.getStorageID(),
k -> new InnerStorage<>(key, load(guildID, key.getStorageID(), defaultSupplier.get()))).getStorage());
}
private Path getFile(long guildID, String key) {
final Path guildFolder = Path.of(Long.toHexString(guildID));
final Path file = Path.of(key + ".json");
return mainFolder.resolve(guildFolder).resolve(file);
}
public <T extends IStorage> T load(Guild guild, String key, T storage) {
final Path file = getFile(guild, key);
public <T extends IStorage> T load(long guildID, String key, T storage) {
final Path file = getFile(guildID, key);
if (Files.notExists(file)) return storage;
Logging.JANITOR.debug("Loading storage {} for guild {}", key, guild);
Logging.JANITOR.debug("Loading storage {} for guild {}", key, guildID);
try (Reader reader = Files.newBufferedReader(file)) {
storage.read(reader);
}
catch (IOException e) {
Logging.JANITOR.error("Error while loading storage {} for guild {}", key, guild, e);
} catch (IOException e) {
Logging.JANITOR.error("Error while loading storage {} for guild {}", key, guildID, e);
}
return storage;
}
@ -69,23 +75,22 @@ public class GuildStorage {
if (!isAutosave)
Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder);
boolean anySaved = false;
for (Guild guild : guildStorage.keySet()) {
final Map<String, IStorage> storageMap = guildStorage.get(guild);
for (long guildID : guildStorages.keySet()) {
final Map<String, InnerStorage<?>> storageMap = guildStorages.get(guildID);
for (String key : storageMap.keySet()) {
final IStorage storage = storageMap.get(key);
if (storage.dirty()) {
final Path file = getFile(guild, key);
final InnerStorage<?> inner = storageMap.get(key);
if (inner.dirty()) {
final Path file = getFile(guildID, key);
try {
if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent());
if (Files.notExists(file)) Files.createFile(file);
try (Writer writer = Files
.newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) {
storage.write(writer);
.newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) {
inner.getStorage().write(writer);
anySaved = true;
}
}
catch (IOException e) {
Logging.JANITOR.error("Error while writing storage {} for guild {}", key, guild, e);
} catch (IOException e) {
Logging.JANITOR.error("Error while writing storage {} for guild {}", key, guildID, e);
}
}
}
@ -94,10 +99,9 @@ public class GuildStorage {
Logging.JANITOR.info("Saved guild storage to files under {}", mainFolder);
}
private Path makeFolder(Guild guild) {
return Path.of(Long.toHexString(guild.getIdLong()));
}
/**
* A thread that calls {@link GuildStorage#save(boolean)} between specified delays.
*/
public static class SavingThread extends Thread {
private final GuildStorage storage;
private volatile boolean running = true;
@ -117,9 +121,35 @@ public class GuildStorage {
public void run() {
while (running) {
storage.save(true);
try { Thread.sleep(storage.getBot().getConfig().AUTOSAVE_INTERVAL.get() * 1000); }
catch (InterruptedException ignored) {}
try {
Thread.sleep(storage.getBot().getConfig().AUTOSAVE_INTERVAL.get() * 1000);
} catch (InterruptedException ignored) {}
}
}
}
/**
* <strong>For internal use only.</strong>
*/
static class InnerStorage<S extends IStorage> {
private final StorageKey<S> key;
private final S storage;
InnerStorage(StorageKey<S> key, S storage) {
this.key = key;
this.storage = storage;
}
public StorageKey<S> getKey() {
return key;
}
public S getStorage() {
return storage;
}
public boolean dirty() {
return storage.dirty();
}
}
}

View File

@ -0,0 +1,67 @@
package sciwhiz12.janitor.storage;
import com.google.common.base.Preconditions;
import java.util.Objects;
/**
* A storage key, used to retrieve an instance of an {@link IStorage} from a {@link GuildStorage}.
*
* @param <S> the type of the {@link IStorage}
*/
public class StorageKey<S extends IStorage> {
private final String storageID;
private final Class<S> type;
/**
* Creates a {@link StorageKey} with the given storage ID and type.
*
* @param storageID the storage ID
* @param type the class type of the generic type
*
* @throws NullPointerException if {@code storageID} or {@code type} is {@code null}
* @throws IllegalArgumentException if {@code storageID} is empty or blank
*/
public StorageKey(String storageID, Class<S> type) {
Preconditions.checkNotNull(storageID, "Storage ID must not be null");
Preconditions.checkArgument(!storageID.isBlank(), "Storage ID must not be empty or blank");
Preconditions.checkNotNull(type, "Class type must not be null");
this.storageID = storageID;
this.type = type;
}
/**
* Returns the storage ID, used by {@link GuildStorage} to uniquely identify this storage's data.
*
* <p>This is currently used by {@code GuildStorage} as the folder name of the storage.
*
* @return the storage ID
*/
public String getStorageID() {
return storageID;
}
/**
* Returns the class of the {@link IStorage} subtype that this storage key represents, which
* is also used in the key's generics.
*
* @return the class of the generic type
*/
public Class<S> getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StorageKey<?> that = (StorageKey<?>) o;
return storageID.equals(that.storageID) &&
type.equals(that.type);
}
@Override
public int hashCode() {
return Objects.hash(storageID, type);
}
}