1
0
mirror of https://github.com/sciwhiz12/Janitor.git synced 2024-09-20 00:34: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() version = getVersion()
println("Version: ${version}") println("Version: ${version}")
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies { dependencies {
implementation group: 'net.dv8tion', name: 'JDA', version: jda_version implementation group: 'net.dv8tion', name: 'JDA', version: jda_version
implementation group: 'com.electronwill.night-config', name: 'toml', version: nightconfig_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.config.BotConfig;
import sciwhiz12.janitor.msg.Messages; import sciwhiz12.janitor.msg.Messages;
import sciwhiz12.janitor.msg.Translations; import sciwhiz12.janitor.msg.Translations;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.utils.Util; import sciwhiz12.janitor.utils.Util;
import java.nio.file.Path; 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.Guild;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.GuildStorage;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage; import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
@ -19,10 +20,10 @@ import java.util.Map;
public class NoteStorage extends JsonStorage { public class NoteStorage extends JsonStorage {
private static final Type NOTE_MAP_TYPE = new TypeToken<Map<Integer, NoteEntry>>() {}.getType(); 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) { 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; private final Gson gson;
@ -33,8 +34,8 @@ public class NoteStorage extends JsonStorage {
public NoteStorage(JanitorBot bot) { public NoteStorage(JanitorBot bot) {
this.bot = bot; this.bot = bot;
this.gson = new GsonBuilder() this.gson = new GsonBuilder()
.registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot)) .registerTypeAdapter(NoteEntry.class, new NoteEntry.Serializer(bot))
.create(); .create();
} }
public JanitorBot getBot() { public JanitorBot getBot() {
@ -58,8 +59,8 @@ public class NoteStorage extends JsonStorage {
public int getAmountOfNotes(User target) { public int getAmountOfNotes(User target) {
return (int) notes.values().stream() return (int) notes.values().stream()
.filter(entry -> entry.getTarget() == target) .filter(entry -> entry.getTarget() == target)
.count(); .count();
} }
public Map<Integer, NoteEntry> getNotes() { public Map<Integer, NoteEntry> getNotes() {

View File

@ -8,9 +8,10 @@ import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import sciwhiz12.janitor.GuildStorage;
import sciwhiz12.janitor.JanitorBot; import sciwhiz12.janitor.JanitorBot;
import sciwhiz12.janitor.storage.GuildStorage;
import sciwhiz12.janitor.storage.JsonStorage; import sciwhiz12.janitor.storage.JsonStorage;
import sciwhiz12.janitor.storage.StorageKey;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
@ -18,10 +19,10 @@ import java.util.Map;
public class WarningStorage extends JsonStorage { public class WarningStorage extends JsonStorage {
private static final Type WARNING_MAP_TYPE = new TypeToken<Map<Integer, WarningEntry>>() {}.getType(); 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) { 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; private final Gson gson;
@ -32,8 +33,8 @@ public class WarningStorage extends JsonStorage {
public WarningStorage(JanitorBot bot) { public WarningStorage(JanitorBot bot) {
this.bot = bot; this.bot = bot;
this.gson = new GsonBuilder() this.gson = new GsonBuilder()
.registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot)) .registerTypeAdapter(WarningEntry.class, new WarningEntry.Serializer(bot))
.create(); .create();
} }
public JanitorBot getBot() { 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.common.base.Preconditions;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import net.dv8tion.jda.api.entities.Guild; 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.IOException;
import java.io.Reader; import java.io.Reader;
@ -12,18 +13,20 @@ import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import static java.nio.file.StandardOpenOption.*; import static java.nio.file.StandardOpenOption.*;
/**
* A storage system for guild-specific data.
*/
public class GuildStorage { public class GuildStorage {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
private final JanitorBot bot; private final JanitorBot bot;
private final Path mainFolder; 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) { public GuildStorage(JanitorBot bot, Path mainFolder) {
Preconditions.checkArgument(Files.isDirectory(mainFolder) || Files.notExists(mainFolder)); Preconditions.checkArgument(Files.isDirectory(mainFolder) || Files.notExists(mainFolder));
@ -35,28 +38,31 @@ public class GuildStorage {
return bot; return bot;
} }
public <T extends IStorage> T getOrCreate(Guild guild, String key, Supplier<T> defaultSupplier) { public <S extends IStorage> S getOrCreate(Guild guild, StorageKey<S> key, Supplier<S> defaultSupplier) {
final Map<String, IStorage> storageMap = guildStorage.computeIfAbsent(guild, g -> new HashMap<>()); return getOrCreate(guild.getIdLong(), key, defaultSupplier);
//noinspection unchecked
return (T) storageMap.computeIfAbsent(key, k -> load(guild, key, defaultSupplier.get()));
} }
private Path getFile(Guild guild, String key) { public <S extends IStorage> S getOrCreate(long guildID, StorageKey<S> key, Supplier<S> defaultSupplier) {
final Path guildFolder = makeFolder(guild); 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"); final Path file = Path.of(key + ".json");
return mainFolder.resolve(guildFolder).resolve(file); return mainFolder.resolve(guildFolder).resolve(file);
} }
public <T extends IStorage> T load(Guild guild, String key, T storage) { public <T extends IStorage> T load(long guildID, String key, T storage) {
final Path file = getFile(guild, key); final Path file = getFile(guildID, key);
if (Files.notExists(file)) return storage; 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)) { try (Reader reader = Files.newBufferedReader(file)) {
storage.read(reader); storage.read(reader);
} } catch (IOException e) {
catch (IOException e) { Logging.JANITOR.error("Error while loading storage {} for guild {}", key, guildID, e);
Logging.JANITOR.error("Error while loading storage {} for guild {}", key, guild, e);
} }
return storage; return storage;
} }
@ -69,23 +75,22 @@ public class GuildStorage {
if (!isAutosave) if (!isAutosave)
Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder); Logging.JANITOR.debug("Saving guild storage to files under {}...", mainFolder);
boolean anySaved = false; boolean anySaved = false;
for (Guild guild : guildStorage.keySet()) { for (long guildID : guildStorages.keySet()) {
final Map<String, IStorage> storageMap = guildStorage.get(guild); final Map<String, InnerStorage<?>> storageMap = guildStorages.get(guildID);
for (String key : storageMap.keySet()) { for (String key : storageMap.keySet()) {
final IStorage storage = storageMap.get(key); final InnerStorage<?> inner = storageMap.get(key);
if (storage.dirty()) { if (inner.dirty()) {
final Path file = getFile(guild, key); final Path file = getFile(guildID, key);
try { try {
if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent()); if (Files.notExists(file.getParent())) Files.createDirectories(file.getParent());
if (Files.notExists(file)) Files.createFile(file); if (Files.notExists(file)) Files.createFile(file);
try (Writer writer = Files try (Writer writer = Files
.newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) { .newBufferedWriter(file, CREATE, WRITE, TRUNCATE_EXISTING)) {
storage.write(writer); inner.getStorage().write(writer);
anySaved = true; anySaved = true;
} }
} } catch (IOException e) {
catch (IOException e) { Logging.JANITOR.error("Error while writing storage {} for guild {}", key, guildID, e);
Logging.JANITOR.error("Error while writing storage {} for guild {}", key, guild, e);
} }
} }
} }
@ -94,10 +99,9 @@ public class GuildStorage {
Logging.JANITOR.info("Saved guild storage to files under {}", mainFolder); 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 { public static class SavingThread extends Thread {
private final GuildStorage storage; private final GuildStorage storage;
private volatile boolean running = true; private volatile boolean running = true;
@ -117,9 +121,35 @@ public class GuildStorage {
public void run() { public void run() {
while (running) { while (running) {
storage.save(true); storage.save(true);
try { Thread.sleep(storage.getBot().getConfig().AUTOSAVE_INTERVAL.get() * 1000); } try {
catch (InterruptedException ignored) {} 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);
}
}