From 38a9eb306689994fd035fbca5887de13cc7c0286 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Tue, 26 Mar 2024 22:06:30 -0600 Subject: [PATCH 1/9] Add database support --- build.gradle | 39 +- gradle.properties | 5 + .../skriptparser/file/FileSection.java | 15 + .../registration/SkriptRegistration.java | 21 +- .../syst3ms/skriptparser/types/Type.java | 33 +- .../skriptparser/types/TypeManager.java | 6 +- .../types/changers/TypeSerializer.java | 25 ++ .../variables/SerializedVariable.java | 58 +++ .../variables/VariableStorage.java | 372 ++++++++++++++++++ .../skriptparser/variables/Variables.java | 230 +++++++++-- .../skriptparser/file/package-info.java | 4 - .../syst3ms/skriptparser/package-info.java | 4 - .../parsing/SyntaxParserTest.java | 5 +- .../skriptparser/parsing/package-info.java | 4 - .../skriptparser/variables/RamStorage.java | 55 +++ .../skriptparser/variables/VariablesTest.java | 56 +++ 16 files changed, 859 insertions(+), 73 deletions(-) create mode 100644 gradle.properties create mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java delete mode 100644 src/test/java/io/github/syst3ms/skriptparser/file/package-info.java delete mode 100644 src/test/java/io/github/syst3ms/skriptparser/package-info.java delete mode 100644 src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java diff --git a/build.gradle b/build.gradle index 0619f6fc..ea5ff4b4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,16 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.apache.tools.ant.filters.ReplaceTokens + plugins { - id "java" - id "application" + id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'application' + id 'java' } -mainClassName = "io.github.syst3ms.skriptparser.Parser" +compileTestJava.options.encoding = 'UTF-8' +compileJava.options.encoding = 'UTF-8' + +mainClassName = 'io.github.syst3ms.skriptparser.Parser' sourceCompatibility = 1.11 @@ -16,18 +23,26 @@ test { } dependencies { - implementation "org.jetbrains:annotations:15.0" - implementation group: "com.google.code.findbugs", name: "jsr305", version: "3.0.2" - testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.4.1" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.4.1" - testImplementation "junit:junit:4.12" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.1" + implementation (group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2') + implementation (group: 'org.jetbrains', name: 'annotations', version: '15.0') + shadow (group: 'com.google.code.gson', name: 'gson', version: '2.10.1') + + testRuntimeOnly (group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '5.4.1') + testRuntimeOnly (group: 'org.junit.jupiter', name:'junit-jupiter-engine', version: '5.4.1') + + testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.1') + testImplementation (group: 'com.google.code.gson', name: 'gson', version: '2.10.1') + testImplementation (group: 'junit', name: 'junit', version: '4.12') } jar { manifest { attributes("Main-Class": mainClassName, - "Implementation-Title": "skript-parser", - "Implementation-Version": "alpha") + "Implementation-Title": "skript-parser", + "Implementation-Version": project.property("version")) } -} \ No newline at end of file +} + +processResources { + filter ReplaceTokens, tokens: ["version": project.property("version")] +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..87def18a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# Increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +version=alpha diff --git a/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java b/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java index 61c2142b..826598e6 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java +++ b/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java @@ -1,12 +1,16 @@ package io.github.syst3ms.skriptparser.file; +import io.github.syst3ms.skriptparser.lang.entries.OptionLoader; + import java.util.List; +import java.util.Optional; /** * A class describing a section of a script inside a file (e.g a line ending with a colon and containing all the lines that * were indented after it. "all the lines" doesn't exclude sections. */ public class FileSection extends FileElement { + private final List elements; private int length = -1; @@ -37,6 +41,16 @@ public int length() { return length; } + public Optional get(String line) { + return elements.stream() + .filter(element -> { + String content = element.getLineContent(); + content = content.substring(0, content.lastIndexOf(OptionLoader.OPTION_SPLIT_PATTERN.trim())); + return content.equalsIgnoreCase(line); + }) + .findFirst(); + } + @Override public boolean equals(Object obj) { return super.equals(obj) && elements.equals(((FileSection) obj).elements); @@ -46,4 +60,5 @@ public boolean equals(Object obj) { public String toString() { return super.toString() + ":"; } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index 12f17bf6..4ae5eb25 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -35,6 +35,7 @@ import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.types.conversions.ConverterInfo; import io.github.syst3ms.skriptparser.types.conversions.Converters; import io.github.syst3ms.skriptparser.util.CollectionUtils; @@ -606,14 +607,21 @@ public interface Registrar { * @param the represented class */ public class TypeRegistrar implements Registrar { - private final Class c; + + private Function toStringFunction = o -> Objects.toString(o, TypeManager.NULL_REPRESENTATION); private final String baseName; private final String pattern; - private Function toStringFunction = o -> Objects.toString(o, TypeManager.NULL_REPRESENTATION); + private final Class c; + @Nullable private Function literalParser; + @Nullable private Changer defaultChanger; + + @Nullable + private TypeSerializer serializer; + @Nullable private Arithmetic arithmetic; @@ -623,6 +631,15 @@ public TypeRegistrar(Class c, String baseName, String pattern) { this.pattern = pattern; } + /** + * @param serializer add a type serializer that allows the type to be saved to databases. + * @return the registrar + */ + public TypeRegistrar serializer(TypeSerializer serializer) { + this.serializer = serializer; + return this; + } + /** * @param literalParser a function interpreting a string as an instance of the type * @return the registrar diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java index 5cfc651f..b01838fb 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -2,6 +2,7 @@ import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.util.StringUtils; import org.jetbrains.annotations.Nullable; @@ -15,17 +16,24 @@ * @see PatternType */ public class Type { - private final Class typeClass; - private final String baseName; - private final String[] pluralForms; - private final Function toStringFunction; + @Nullable private final Function literalParser; + @Nullable private final Changer defaultChanger; + + @Nullable + private final TypeSerializer serializer; + @Nullable private final Arithmetic arithmetic; + private final Function toStringFunction; + private final String[] pluralForms; + private final Class typeClass; + private final String baseName; + /** * Constructs a new Type. * @@ -96,6 +104,16 @@ public Type(Class typeClass, this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null); } + public Type(Class typeClass, + String baseName, + String pattern, + @Nullable Function literalParser, + Function toStringFunction, + @Nullable Changer defaultChanger, + @Nullable Arithmetic arithmetic) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, null); + } + @SuppressWarnings("unchecked") public Type(Class typeClass, String baseName, @@ -103,7 +121,7 @@ public Type(Class typeClass, @Nullable Function literalParser, Function toStringFunction, @Nullable Changer defaultChanger, - @Nullable Arithmetic arithmetic) { + @Nullable Arithmetic arithmetic, @Nullable TypeSerializer serializer) { this.typeClass = typeClass; this.baseName = baseName; this.literalParser = literalParser; @@ -111,6 +129,7 @@ public Type(Class typeClass, this.pluralForms = StringUtils.getForms(pattern.strip()); this.defaultChanger = defaultChanger; this.arithmetic = arithmetic; + this.serializer = serializer; } public Class getTypeClass() { @@ -129,6 +148,10 @@ public Function getToStringFunction() { return toStringFunction; } + public Optional> getSerializer() { + return Optional.ofNullable(serializer); + } + public Optional> getLiteralParser() { return Optional.ofNullable(literalParser); } diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java index d5bf10e0..9c6ac564 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java @@ -12,10 +12,12 @@ */ @SuppressWarnings("unchecked") public class TypeManager { + /** * The string equivalent of null */ public static final String NULL_REPRESENTATION = ""; + /** * The string equivalent of an empty array */ @@ -51,7 +53,6 @@ public static Optional> getByName(String name) { return Optional.empty(); } - /** * Gets a {@link Type} from its associated {@link Class}. * @param c the Class to get the Type from @@ -121,4 +122,5 @@ public static void register(SkriptRegistration reg) { classToType.put(type.getTypeClass(), type); } } -} \ No newline at end of file + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java new file mode 100644 index 00000000..be46858b --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java @@ -0,0 +1,25 @@ +package io.github.syst3ms.skriptparser.types.changers; + +import com.google.gson.JsonElement; + +/** + * An interface for serializing type objects. + */ +public interface TypeSerializer { + + /** + * Serialize a value to a GSON json element. + * + * @param value the value to serialize + * @return the classes of the objects that the implementing object can be changed to + */ + JsonElement serialize(T value); + + /** + * Deserialize a GSON json element to object. + * + * @param element the GSON json element. + */ + T deserialize(JsonElement element); + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java new file mode 100644 index 00000000..9b5753af --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java @@ -0,0 +1,58 @@ +package io.github.syst3ms.skriptparser.variables; + +import org.jetbrains.annotations.Nullable; + +public class SerializedVariable { + + /** + * The name of the variable. + */ + public final String name; + + /** + * The serialized value of the variable. + *

+ * A value of {@code null} indicates the variable will be deleted. + */ + @Nullable + public final Value value; + + /** + * Creates a new serialized variable with the given name and value. + * + * @param name the given name. + * @param value the given value, or {@code null} to indicate a deletion. + */ + public SerializedVariable(String name, @Nullable Value value) { + this.name = name; + this.value = value; + } + + /** + * A serialized value of a variable. + */ + public static final class Value { + + /** + * The type of this value. + */ + public final String type; + + /** + * The serialized value data. + */ + public final byte[] data; + + /** + * Creates a new serialized value. + * @param type the value type. + * @param data the serialized value data. + */ + public Value(String type, byte[] data) { + this.type = type; + this.data = data; + } + + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java new file mode 100644 index 00000000..e778d2de --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -0,0 +1,372 @@ +package io.github.syst3ms.skriptparser.variables; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; +import io.github.syst3ms.skriptparser.variables.SerializedVariable.Value; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * A variable storage is holds the means and methods of storing variables. + *

+ * This is usually some sort of database, and could be as simply as a text file. + * + * Must contain a constructor of just SkriptLogger. + */ +public abstract class VariableStorage implements Closeable { + + /** + * Whether this variable storage has been {@link #close() closed}. + */ + protected volatile boolean closed; + + /** + * The name of the database used in the configurations. + */ + protected final String[] names; + + /** + * The pattern of the variable name this storage accepts. + * {@code null} for '{@code .*}' or '{@code .*}'. + */ + @Nullable + private Pattern variableNamePattern; + + private final SkriptLogger logger; + protected final Gson gson; + private File file; + + protected final LinkedBlockingQueue changesQueue = new LinkedBlockingQueue<>(1000); + + /** + * Creates a new variable storage with the given name. + * Gson will be handled. + * + * @param logger the logger to print logs to. + * @param name the name. + */ + protected VariableStorage(SkriptLogger logger, @NotNull String... names) { + this(logger, new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .serializeNulls() + .setLenient() + .create(), + names); + } + + /** + * Creates a new variable storage with the given names. + * + * @param logger the logger to print logs to. + * @param gson the gson that controls the serialization of the json elements. + * @param name the name. + */ + protected VariableStorage(SkriptLogger logger, @NotNull Gson gson, @NotNull String... names) { + this.logger = logger; + this.names = names; + this.gson = gson; + // TODO allow this runnable to be interupted. + CompletableFuture.runAsync(() -> { + while (!closed) { + try { + SerializedVariable variable = changesQueue.take(); + SerializedVariable.Value value = variable.value; + + if (value != null) { + save(variable.name, value.type, value.data); + } else { + save(variable.name, null, null); + } + } catch (InterruptedException ignored) {} + } + }); + } + + /** + * Gets the string value at the given key of the given section node. + * + * @param section the file section. + * @param key the key node. + * @return the value, or {@code null} if the value was invalid, + * or not found. + */ + @Nullable + protected String getConfigurationValue(FileSection section, String key) { + return getConfigurationValue(section, key, String.class); + } + + /** + * Gets the value at the given key of the given section node, + * parsed with the given class type. + * + * @param section the file section. + * @param key the key node. + * @param type the class type. + * @return the parsed value, or {@code null} if the value was invalid, + * or not found. + * @param the class type generic. + */ + @Nullable + @SuppressWarnings("unchecked") + protected T getConfigurationValue(FileSection section, String key, Class classType) { + Optional value = section.get(key); + if (!value.isPresent()) { + logger.error("The configuration is missing the entry for '" + key + "' for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + if (classType.equals(String.class)) + return (T) value.get().getLineContent(); + + Optional> type = TypeManager.getByClassExact(classType); + if (!type.isPresent()) { + logger.error("The class type '" + classType.getName() + "' is not registered. Cannot parse node '" + key + "' for database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + + Optional> parser = type.get().getLiteralParser(); + if (!parser.isPresent()) { + logger.error("Type " + type.get().withIndefiniteArticle(true) + " cannot be parsed as a literal.", ErrorType.SEMANTIC_ERROR); + return null; + } + + T parsedValue = parser.get().apply(value.get().getLineContent()); + if (parsedValue == null) { + logger.error("The entry for '" + key + "' in the database '" + names[0] + "' must be " + + type.get().withIndefiniteArticle(true), ErrorType.SEMANTIC_ERROR); + return null; + } + return parsedValue; + } + + /** + * Loads the configuration for this variable storage + * from the given section node. + * + * @param sectionNode the section node. + * @return whether the loading succeeded. + */ + public final boolean loadConfiguration(FileSection section) { + String pattern = getConfigurationValue(section, "pattern"); + if (pattern == null) + return false; + + try { + // Set variable name pattern, see field javadoc for explanation of null value + variableNamePattern = pattern.equals(".*") || pattern.equals(".+") ? null : Pattern.compile(pattern); + } catch (PatternSyntaxException e) { + logger.error("Invalid pattern '" + pattern + "': " + e.getLocalizedMessage(), ErrorType.SEMANTIC_ERROR); + return false; + } + + if (requiresFile()) { + String fileName = getConfigurationValue(section, "file"); + if (fileName == null) + return false; + + this.file = getFile(fileName).getAbsoluteFile(); + + if (file.exists() && !file.isFile()) { + logger.error("The database file '" + file.getName() + "' does not exist or is a directory.", ErrorType.SEMANTIC_ERROR); + return false; + } + + try { + file.createNewFile(); + } catch (IOException e) { + logger.error("Cannot create the database file '" + file.getName() + "': " + e.getLocalizedMessage(), ErrorType.SEMANTIC_ERROR); + return false; + } + + // Check for read & write permissions to the file + if (!file.canWrite()) { + logger.error("Cannot write to the database file '" + file.getName() + "'!", ErrorType.SEMANTIC_ERROR); + return false; + } + + if (!file.canRead()) { + logger.error("Cannot read from the database file '" + file.getName() + "'!", ErrorType.SEMANTIC_ERROR); + return false; + } + } + + // Get the subclass to load it's part of the configuration section. + return load(section); + } + + /** + * Loads configurations and variables. + * + * @return Whether the database could be loaded successfully, + * i.e. whether the configuration is correct and all variables could be loaded. + */ + protected abstract boolean load(FileSection section); + + protected void load(String name, SerializedVariable variable) { + load(name, variable.value.type, variable.value.data); + } + + /** + * Loads a variable into Skript ram. Call this inside {@link #load(FileSection)} + * + * @param name the name of the variable. + * @param type the type of the variable. + * @param value the serialized value of the variable. + */ + protected void load(String name, @NotNull String type, @NotNull byte[] value) { + if (value == null || type == null) + throw new IllegalArgumentException("value and/or typeName cannot be null"); + Variables.queueVariableChange(name, deserialize(type, value)); + } + + /** + * Called after all storages have been loaded, and variables + * have been redistributed if settings have changed. + * This should commit the first transaction. + */ + protected abstract void allLoaded(); + + /** + * Checks if this storage requires a file for storing data, like SQLite. + * + * @return if this storage needs a file. + */ + protected abstract boolean requiresFile(); + + /** + * Gets the file needed for this variable storage from the given file name. + *

+ * Will only be called if {@link #requiresFile()} is {@code true}. + * + * @param fileName the given file name. + * @return the {@link File} object. + */ + @Nullable + protected abstract File getFile(String fileName); + + /** + * Checks if this variable storage accepts the given variable name. + * + * @param var the variable name. + * @return if this storage accepts the variable name. + * + * @see #variableNamePattern + */ + boolean accept(@Nullable String variableName) { + if (variableName == null) + return false; + return variableNamePattern == null || variableNamePattern.matcher(variableName).matches(); + } + + /** + * Creates a {@link SerializedVariable} from the given variable name and value. + * Can be overriden to add custom encryption in your implemented VariableStorage. + * Call super. + * + * @param name the variable name. + * @param value the variable value. + * @return the serialized variable. + */ + @SuppressWarnings("unchecked") + public SerializedVariable serialize(String name, @NotNull T value) { + if (value == null) + throw new IllegalArgumentException("value cannot be null"); + Type type = (Type) TypeManager.getByClassExact(value.getClass()).orElse(null); + if (type == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type registered."); + TypeSerializer serializer = type.getSerializer().orElse(null); + if (serializer == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type serializer."); + JsonElement element = serializer.serialize(value); + return new SerializedVariable(name, new Value(type.getBaseName(), gson.toJson(element).getBytes())); + } + + /** + * Used by {@link #load(String, String, byte[]). + * You don't need to use this method, but if you need to read the Object, this method allows for deserialization. + * + * @param typeName The name of the type. + * @param value The value that represents a object. + * @return The Object after deserialization, not present if not possible to deserialize due to missing serializer on Type. + */ + protected Optional deserialize(@NotNull String typeName, @NotNull byte[] value) { + if (value == null || typeName == null) + throw new IllegalArgumentException("value and/or typeName cannot be null"); + Type type = TypeManager.getByExactName(typeName).orElse(null); + if (type == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type registered."); + TypeSerializer serializer = type.getSerializer().orElse(null); + if (serializer == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type serializer."); + String json = new String(value); + JsonReader reader = gson.newJsonReader(new StringReader(json)); + return Optional.ofNullable(serializer.deserialize(JsonParser.parseReader(reader))); + } + + private long lastError = Long.MIN_VALUE; + private long ERROR_INTERVAL = 10; + + /** + * Saves the given serialized variable. + * + * @param variable the serialized variable. + */ + final void save(SerializedVariable variable) { + if (!changesQueue.offer(variable)) { + if (lastError < System.currentTimeMillis() - ERROR_INTERVAL * 1000) { + // Inform console about overload of variable changes + System.out.println("Skript cannot save any variables to the database '" + names[0] + "'. " + + "The thread will hang to avoid losing variable."); + + lastError = System.currentTimeMillis(); + } + while (true) { + try { + changesQueue.put(variable); + break; + } catch (InterruptedException ignored) {} + } + } + } + + protected void clearChangesQueue() { + changesQueue.clear(); + } + + /** + * Saves a variable. + *

+ * {@code type} and {@code value} are both {@code null} + * if this call is to delete the variable. + * + * @param name the name of the variable. + * @param type the type of the variable. + * @param value the serialized value of the variable. + * @return Whether the variable was saved. + */ + protected abstract boolean save(String name, @Nullable String type, @Nullable byte[] value); + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index 1055750e..b12cf17d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -1,5 +1,22 @@ package io.github.syst3ms.skriptparser.variables; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +import org.jetbrains.annotations.Nullable; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.lang.Variable; @@ -7,45 +24,117 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParserState; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Pattern; +import io.github.syst3ms.skriptparser.util.MultiMap; /** - * A class handling operations on variables + * A class handling operations on variables. */ public class Variables { - public static final String LIST_SEPARATOR = "::"; - public static final String LOCAL_VARIABLE_TOKEN = "_"; - public static final Pattern REGEX_PATTERN = Pattern.compile("\\{([^{}]|%\\{|}%)+}"); + + static final MultiMap, String> AVAILABLE_STORAGES = new MultiMap<>(); + static final List STORAGES = new ArrayList<>(); + + private static final Map localVariables = new HashMap<>(); // Make trigger-specific private static final VariableMap variableMap = new VariableMap(); - // Yes, I know it should be trigger-specific, but I haven't got to that part yet, ok ? TODO make the change - private static final Map localVariables = new HashMap<>(); + private static final ReentrantLock LOCK = new ReentrantLock(); + + public static final Pattern REGEX_PATTERN = Pattern.compile("\\{([^{}]|%\\{|}%)+}"); + public static final String LOCAL_VARIABLE_TOKEN = "_"; + public static final String LIST_SEPARATOR = "::"; + + static { + LOCK.unlock(); + } + + public static boolean hasStorages() { + return !STORAGES.isEmpty(); + } - public static Optional> parseVariable(String s, Class types, ParserState parserState, SkriptLogger logger) { - s = s.strip(); - if (REGEX_PATTERN.matcher(s).matches()) { - s = s.substring(1, s.length() - 1); + /** + * Register a VariableStorage class. + * + * @param storage The class of the VariableStorage implementation. + * @param names The names used to reference this storage. + * @param Generic representing class that extends VariableStorage. + * @return if the storage was registered, false if it's already registered. + */ + public static boolean registerStorage(Class storage, String... names) { + if (AVAILABLE_STORAGES.containsKey(storage)) + return false; + for (String name : names) { + if (AVAILABLE_STORAGES.getAllValues().contains(name.toLowerCase(Locale.ENGLISH))) + return false; + } + for (String name : names) + AVAILABLE_STORAGES.putOne(storage, name.toLowerCase(Locale.ENGLISH)); + return true; + } + + /** + * Loads a section configuration containing all the database info. + * Parent section node name must be 'databases:' + * + * @param logger the SkriptLogger to print errors to. + * @param section the FileSection to load for the configurations. + * @throws IllegalArgumentException throws when the section is not valid. + */ + public static void load(SkriptLogger logger, FileSection section) throws IllegalArgumentException{ + for (FileElement databaseElement : section.getElements()) { + if (!(databaseElement instanceof FileSection)) { + logger.error("The file node 'databases." + databaseElement.getLineContent() + "' was not a section.", ErrorType.STRUCTURE_ERROR); + continue; + } + String databaseName = databaseElement.getLineContent(); + if (databaseName.isBlank()) { + logger.error("A database name was incorrect, cannot be an empty string. (line " + databaseElement.getLine() + ")", ErrorType.SEMANTIC_ERROR); + continue; + } + FileSection databaseSection = (FileSection) databaseElement; + Class storageClass = AVAILABLE_STORAGES.entrySet().stream() + .filter(entry -> entry.getValue().contains(databaseName.toLowerCase(Locale.ENGLISH))) + .map(entry -> entry.getKey()) + .findFirst() + .orElse(null); + if (storageClass == null) { + logger.error("There is no database registered with the name '" + databaseName + "'", ErrorType.SEMANTIC_ERROR); + continue; + } + + try { + Constructor constructor = storageClass.getConstructor(SkriptLogger.class); + VariableStorage storage = constructor.newInstance(logger); + if (storage.loadConfiguration(databaseSection)) + STORAGES.add(storage); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + logger.error("VariableStorage class '" + storageClass.getName() + "' does not implement the correct constructors.", ErrorType.SEMANTIC_ERROR); + logger.debug("Exception: " + e.getLocalizedMessage()); + } + } + STORAGES.forEach(VariableStorage::allLoaded); + processChangeQueue(); + } + + public static Optional> parseVariable(String input, Class types, ParserState parserState, SkriptLogger logger) { + input = input.strip(); + if (REGEX_PATTERN.matcher(input).matches()) { + input = input.substring(1, input.length() - 1); } else { return Optional.empty(); } - if (!isValidVariableName(s, true, logger)) { + if (!isValidVariableName(input, true, logger)) { return Optional.empty(); } var vs = VariableString.newInstance( - s.startsWith(LOCAL_VARIABLE_TOKEN) ? s.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : s, + input.startsWith(LOCAL_VARIABLE_TOKEN) ? input.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : input, parserState, logger ); - var finalS = s; + var finalS = input; return vs.map(v -> new Variable<>(v, finalS.startsWith(LOCAL_VARIABLE_TOKEN), finalS.endsWith(LIST_SEPARATOR + "*"), types)); } /** - * Checks whether a string is a valid variable name. This is used to verify variable names as well as command and function arguments. + * Checks whether a string is a valid variable name. * * @param name The name to test * @param printErrors Whether to print errors when they are encountered @@ -53,8 +142,7 @@ public static Optional> parseVariable(String s, Clas * @return true if the name is valid, false otherwise. */ public static boolean isValidVariableName(String name, boolean printErrors, SkriptLogger logger) { - name = name.startsWith(LOCAL_VARIABLE_TOKEN) ? name.substring(LOCAL_VARIABLE_TOKEN.length()).strip() - : name.strip(); + name = name.startsWith(LOCAL_VARIABLE_TOKEN) ? name.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : name.strip(); if (name.startsWith(LIST_SEPARATOR) || name.endsWith(LIST_SEPARATOR)) { if (printErrors) { logger.error("A variable name cannot start nor end with the list separator " + LIST_SEPARATOR, ErrorType.MALFORMED_INPUT); @@ -75,13 +163,13 @@ public static boolean isValidVariableName(String name, boolean printErrors, Skri } /** - * Returns the internal value of the requested variable. - *

- * Do not modify the returned value! - * - * @param name the name of the variable - * @return an Object for a normal Variable or a Map for a list variable, or null if the variable is not set. - */ + * Returns the internal value of the requested variable. + *

+ * Do not modify the returned value! + * + * @param name the name of the variable + * @return an Object for a normal Variable or a Map for a list variable, or null if the variable is not set. + */ public static Optional getVariable(String name, TriggerContext e, boolean local) { if (local) { var map = localVariables.get(e); @@ -94,11 +182,11 @@ public static Optional getVariable(String name, TriggerContext e, boolea } /** - * Sets a variable. - * - * @param name The variable's name. Can be a "list variable::*" (value must be null in this case) - * @param value The variable's value. Use null to delete the variable. - */ + * Sets a variable. + * + * @param name The variable's name. Can be a "list variable::*" (value must be null in this case) + * @param value The variable's value. Use null to delete the variable. + */ public static void setVariable(String name, @Nullable Object value, @Nullable TriggerContext e, boolean local) { if (local) { assert e != null : name; @@ -108,13 +196,83 @@ public static void setVariable(String name, @Nullable Object value, @Nullable Tr map.setVariable(name, value); } else { variableMap.setVariable(name, value); + if (!hasStorages()) + return; + queueVariableChange(name, value); + try { + if (LOCK.tryLock()) + processChangeQueue(); + } finally { + LOCK.unlock(); + } } } /** - * Clears all variables. + * Changes to variables that have not yet been performed. */ - public static void clearVariables() { + private static final Queue VARIABLE_CHANGE_QUEUE = new ConcurrentLinkedQueue<>(); + + static void queueVariableChange(String name, @Nullable Object value) { + if (!hasStorages()) + return; + VARIABLE_CHANGE_QUEUE.add(new VariableChange(name, value)); + } + + final void clearVariables() { variableMap.clearVariables(); } + + /** + * Processes all entries in variable change queue. + *

+ * Note that caller must acquire write lock before calling this, + * then release it. + */ + private static void processChangeQueue() { + while (true) { + VariableChange change = VARIABLE_CHANGE_QUEUE.poll(); + if (change == null) + break; + + variableMap.setVariable(change.name, change.value); + STORAGES.stream() + .filter(storage -> storage.accept(change.name)) + .forEach(storage -> { + SerializedVariable serialized = storage.serialize(change.name, change.value); + storage.save(serialized); + }); + } + } + + /** + * Represents a variable that is to be changed. + * Key-value pair. Key being the variable name. + */ + private static class VariableChange { + + /** + * The variable name of the changed variable. + */ + public final String name; + + /** + * The value of the variable change. + */ + @Nullable + public final Object value; + + /** + * Creates a new {@link VariableChange} with the given name and value. + * + * @param name the variable name. + * @param value the new variable value. + */ + public VariableChange(String name, @Nullable Object value) { + this.name = name; + this.value = value; + } + + } + } diff --git a/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java deleted file mode 100644 index ba71ee5d..00000000 --- a/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@ParametersAreNonnullByDefault -package io.github.syst3ms.skriptparser.file; - -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/package-info.java deleted file mode 100644 index 488c809b..00000000 --- a/src/test/java/io/github/syst3ms/skriptparser/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@ParametersAreNonnullByDefault -package io.github.syst3ms.skriptparser; - -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java index 37cd7e6c..5c9627e0 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java @@ -4,7 +4,7 @@ import io.github.syst3ms.skriptparser.log.LogEntry; import io.github.syst3ms.skriptparser.log.LogType; import io.github.syst3ms.skriptparser.registration.SkriptAddon; -import io.github.syst3ms.skriptparser.variables.Variables; + import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; @@ -71,9 +71,6 @@ public Iterator syntaxTest() { var allErrors = new ArrayList<>(errorsFound); Set duplicateErrors = new HashSet<>(); allErrors.removeIf(val -> !duplicateErrors.add(val.getMessage())); - - // Reset variables - Variables.clearVariables(); errorsFound.clear(); MultipleFailureException.assertEmpty(allErrors); diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java deleted file mode 100644 index 38a574aa..00000000 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@ParametersAreNonnullByDefault -package io.github.syst3ms.skriptparser.parsing; - -import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java new file mode 100644 index 00000000..117eb8fb --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java @@ -0,0 +1,55 @@ +package io.github.syst3ms.skriptparser.variables; + +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.log.SkriptLogger; + +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class RamStorage extends VariableStorage { + + public static final Map VARIABLES = new HashMap<>(); + + public RamStorage(SkriptLogger logger) { + super(logger, "ram"); + } + + @Override + public void close() throws IOException { } + + @Override + protected boolean load(FileSection section) { + assert section.getElements().get(0).getLineContent().equals("test node: true"); + return true; + } + + @Override + protected void allLoaded() {} + + @Override + protected boolean requiresFile() { + return false; + } + + @Override + @Nullable + protected File getFile(String fileName) { + return null; + } + + @Override + protected boolean save(String name, @Nullable String type, @Nullable byte[] value) { + if (type == null || value == null) { + VARIABLES.remove(name); + return true; + } + SerializedVariable.Value v = new SerializedVariable.Value(type, value); + VARIABLES.put(name, new SerializedVariable(name, v)); + return true; + } + +} diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java new file mode 100644 index 00000000..dd7e7b3b --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java @@ -0,0 +1,56 @@ +package io.github.syst3ms.skriptparser.variables; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileParser; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.log.SkriptLogger; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class VariablesTest { + + private static final List LINES = Arrays.asList( + "databases:", + "\tram:", + "\t\ttest node: true", + "\t\tpattern: .*" + ); + + static { + Variables.registerStorage(RamStorage.class, "ram"); + } + + @Before + public void setupVariables() { + Variables.registerStorage(RamStorage.class, "ram"); + SkriptLogger logger = new SkriptLogger(true); + List elements = FileParser.parseFileLines("database-test", LINES, 0, 1, logger); + assert elements.size() > 0; + FileElement element = elements.get(0); + assert element instanceof FileSection; + Variables.load(logger, (FileSection) element); + logger.finalizeLogs(); + logger.close(); + assert Variables.AVAILABLE_STORAGES.size() > 0; + assert Variables.STORAGES.size() > 0; + Variables.setVariable("test", "Hello World!", null, false); + } + + @Test + public void testVariables() { + assert RamStorage.VARIABLES.containsKey("test"); + Optional object = Variables.getVariable("test", null, false); + assert object.isPresent(); + assert object.get().equals("Hello World!"); + Variables.setVariable("test", "Hello New World!", null, false); + Optional newObject = Variables.getVariable("test", null, false); + assert newObject.isPresent(); + assert newObject.get().equals("Hello New World!"); + } + +} From 75887c8a5588d4f2e0e23c20f87501ab3550dc69 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Tue, 26 Mar 2024 22:51:05 -0600 Subject: [PATCH 2/9] Finish serializers for default types --- .../registration/DefaultRegistration.java | 59 ++++++++++++++++++- .../registration/SkriptRegistration.java | 13 +++- .../types/changers/TypeSerializer.java | 9 ++- .../variables/VariableStorage.java | 15 +++-- .../skriptparser/variables/Variables.java | 10 +--- .../skriptparser/variables/RamStorage.java | 1 + .../skriptparser/variables/VariablesTest.java | 5 +- 7 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 8ef77755..77415c73 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -1,9 +1,14 @@ package io.github.syst3ms.skriptparser.registration; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.types.comparisons.Comparator; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; @@ -55,6 +60,19 @@ public static void register() { return null; } }) + .serializer(new TypeSerializer() { + @Override + public JsonElement serialize(Gson gson, Number value) { + JsonObject json = new JsonObject(); + json.addProperty("number", value); + return json; + } + + @Override + public Number deserialize(Gson gson, JsonElement element) { + return element.getAsJsonObject().get("number").getAsNumber(); + } + }) .toStringFunction(o -> { if (o instanceof BigDecimal) { BigDecimal bd = (BigDecimal) o; @@ -118,6 +136,19 @@ public Class getRelativeType() { s = s.replaceAll("_", ""); return s.matches(INTEGER_PATTERN) ? new BigInteger(s) : null; }) + .serializer(new TypeSerializer() { + @Override + public JsonElement serialize(Gson gson, BigInteger value) { + JsonObject json = new JsonObject(); + json.addProperty("number", value); + return json; + } + + @Override + public BigInteger deserialize(Gson gson, JsonElement element) { + return BigInteger.valueOf(element.getAsJsonObject().get("number").getAsNumber().longValue()); + } + }) .arithmetic(new Arithmetic() { @Override public BigInteger difference(BigInteger first, BigInteger second) { @@ -144,7 +175,20 @@ public Class getRelativeType() { registration.addType( String.class, "string", - "string@s" + "string@s", + new TypeSerializer() { + @Override + public JsonElement serialize(Gson gson, String value) { + JsonObject json = new JsonObject(); + json.addProperty("string", value); + return json; + } + + @Override + public String deserialize(Gson gson, JsonElement element) { + return element.getAsJsonObject().get("string").getAsString(); + } + } ); registration.newType(Boolean.class, "boolean", "boolean@s") @@ -157,6 +201,19 @@ public Class getRelativeType() { return null; } }) + .serializer(new TypeSerializer() { + @Override + public JsonElement serialize(Gson gson, Boolean value) { + JsonObject json = new JsonObject(); + json.addProperty("boolean", value); + return json; + } + + @Override + public Boolean deserialize(Gson gson, JsonElement element) { + return element.getAsJsonObject().get("boolean").getAsBoolean(); + } + }) .toStringFunction(String::valueOf) .register(); diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index 4ae5eb25..46857430 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -526,6 +526,17 @@ public void addType(Class c, String name, String pattern) { newType(c, name, pattern).register(); } + /** + * Registers a {@link Type} + * @param c the class the Type represents + * @param pattern the Type's pattern + * @param serializer the Type's serializer + * @param the represented class + */ + public void addType(Class c, String name, String pattern, TypeSerializer serializer) { + newType(c, name, pattern).serializer(serializer).register(); + } + /** * Registers a converter * @param from the class it converts from @@ -682,7 +693,7 @@ public TypeRegistrar arithmetic(Arithmetic arithmetic) { @Override public void register() { newTypes = true; - types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic)); + types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, serializer)); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java index be46858b..947954fc 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.types.changers; +import com.google.gson.Gson; import com.google.gson.JsonElement; /** @@ -9,17 +10,21 @@ public interface TypeSerializer { /** * Serialize a value to a GSON json element. + * The Gson object is preserved from the VariableStorage. * + * @param gson Gson context. * @param value the value to serialize * @return the classes of the objects that the implementing object can be changed to */ - JsonElement serialize(T value); + JsonElement serialize(Gson gson, T value); /** * Deserialize a GSON json element to object. + * The Gson object is preserved from the VariableStorage. * + * @param gson Gson context. * @param element the GSON json element. */ - T deserialize(JsonElement element); + T deserialize(Gson gson, JsonElement element); } diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java index e778d2de..ebb20172 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -9,6 +9,7 @@ import io.github.syst3ms.skriptparser.file.FileElement; import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.lang.entries.OptionLoader; import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.types.Type; @@ -138,8 +139,14 @@ protected T getConfigurationValue(FileSection section, String key, Class logger.error("The configuration is missing the entry for '" + key + "' for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); return null; } + String[] split = value.get().getLineContent().split(OptionLoader.OPTION_SPLIT_PATTERN); + if (split.length < 2) { + logger.error("The configuration entry '" + key + "' is not a option entry (key: value) for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + String content = split[1]; if (classType.equals(String.class)) - return (T) value.get().getLineContent(); + return (T) content; Optional> type = TypeManager.getByClassExact(classType); if (!type.isPresent()) { @@ -153,7 +160,7 @@ protected T getConfigurationValue(FileSection section, String key, Class return null; } - T parsedValue = parser.get().apply(value.get().getLineContent()); + T parsedValue = parser.get().apply(content); if (parsedValue == null) { logger.error("The entry for '" + key + "' in the database '" + names[0] + "' must be " + type.get().withIndefiniteArticle(true), ErrorType.SEMANTIC_ERROR); @@ -300,7 +307,7 @@ public SerializedVariable serialize(String name, @NotNull T value) { TypeSerializer serializer = type.getSerializer().orElse(null); if (serializer == null) throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type serializer."); - JsonElement element = serializer.serialize(value); + JsonElement element = serializer.serialize(gson, value); return new SerializedVariable(name, new Value(type.getBaseName(), gson.toJson(element).getBytes())); } @@ -323,7 +330,7 @@ protected Optional deserialize(@NotNull String typeName, @NotNull byte[] valu throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type serializer."); String json = new String(value); JsonReader reader = gson.newJsonReader(new StringReader(json)); - return Optional.ofNullable(serializer.deserialize(JsonParser.parseReader(reader))); + return Optional.ofNullable(serializer.deserialize(gson, JsonParser.parseReader(reader))); } private long lastError = Long.MIN_VALUE; diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index b12cf17d..fddc5080 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -42,10 +42,6 @@ public class Variables { public static final String LOCAL_VARIABLE_TOKEN = "_"; public static final String LIST_SEPARATOR = "::"; - static { - LOCK.unlock(); - } - public static boolean hasStorages() { return !STORAGES.isEmpty(); } @@ -78,7 +74,7 @@ public static boolean registerStorage(Class stora * @param section the FileSection to load for the configurations. * @throws IllegalArgumentException throws when the section is not valid. */ - public static void load(SkriptLogger logger, FileSection section) throws IllegalArgumentException{ + public static void load(SkriptLogger logger, FileSection section) throws IllegalArgumentException { for (FileElement databaseElement : section.getElements()) { if (!(databaseElement instanceof FileSection)) { logger.error("The file node 'databases." + databaseElement.getLineContent() + "' was not a section.", ErrorType.STRUCTURE_ERROR); @@ -219,10 +215,6 @@ static void queueVariableChange(String name, @Nullable Object value) { VARIABLE_CHANGE_QUEUE.add(new VariableChange(name, value)); } - final void clearVariables() { - variableMap.clearVariables(); - } - /** * Processes all entries in variable change queue. *

diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java index 117eb8fb..cd844199 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java @@ -24,6 +24,7 @@ public void close() throws IOException { } @Override protected boolean load(FileSection section) { assert section.getElements().get(0).getLineContent().equals("test node: true"); + assert getConfigurationValue(section, "test node", Boolean.class) == true; return true; } diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java index dd7e7b3b..35829225 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java @@ -42,8 +42,9 @@ public void setupVariables() { } @Test - public void testVariables() { - assert RamStorage.VARIABLES.containsKey("test"); + public void testVariables() throws InterruptedException { + Thread.sleep(1); + assert RamStorage.VARIABLES.containsKey("test") : Arrays.toString(RamStorage.VARIABLES.keySet().toArray(String[]::new)); Optional object = Variables.getVariable("test", null, false); assert object.isPresent(); assert object.get().equals("Hello World!"); From 6fd6615be04e6e9df736f32cb2fe96c53ae79e2c Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Tue, 26 Mar 2024 22:57:04 -0600 Subject: [PATCH 3/9] Make test more strict --- .../skriptparser/variables/RamStorage.java | 26 +++++++++++++++++++ .../skriptparser/variables/VariablesTest.java | 8 +++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java index cd844199..ff0fa92f 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java @@ -1,21 +1,32 @@ package io.github.syst3ms.skriptparser.variables; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + import io.github.syst3ms.skriptparser.file.FileSection; import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.io.StringReader; import java.util.HashMap; import java.util.Map; +import java.util.Optional; public class RamStorage extends VariableStorage { public static final Map VARIABLES = new HashMap<>(); + public static RamStorage SELF; public RamStorage(SkriptLogger logger) { super(logger, "ram"); + SELF = this; } @Override @@ -42,6 +53,21 @@ protected File getFile(String fileName) { return null; } + @Override + public Optional deserialize(@NotNull String typeName, @NotNull byte[] value) { + if (value == null || typeName == null) + throw new IllegalArgumentException("value and/or typeName cannot be null"); + Type type = TypeManager.getByExactName(typeName).orElse(null); + if (type == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type registered."); + TypeSerializer serializer = type.getSerializer().orElse(null); + if (serializer == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type serializer."); + String json = new String(value); + JsonReader reader = gson.newJsonReader(new StringReader(json)); + return Optional.ofNullable(serializer.deserialize(gson, JsonParser.parseReader(reader))); + } + @Override protected boolean save(String name, @Nullable String type, @Nullable byte[] value) { if (type == null || value == null) { diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java index 35829225..9ef0d16b 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java @@ -44,14 +44,20 @@ public void setupVariables() { @Test public void testVariables() throws InterruptedException { Thread.sleep(1); - assert RamStorage.VARIABLES.containsKey("test") : Arrays.toString(RamStorage.VARIABLES.keySet().toArray(String[]::new)); + assert RamStorage.VARIABLES.containsKey("test"); Optional object = Variables.getVariable("test", null, false); assert object.isPresent(); assert object.get().equals("Hello World!"); Variables.setVariable("test", "Hello New World!", null, false); + Thread.sleep(1); Optional newObject = Variables.getVariable("test", null, false); assert newObject.isPresent(); assert newObject.get().equals("Hello New World!"); + assert RamStorage.VARIABLES.containsKey("test"); + SerializedVariable variable = RamStorage.VARIABLES.get("test"); + Optional value = RamStorage.SELF.deserialize(variable.value.type, variable.value.data); + assert value.isPresent(); + assert value.get().equals("Hello New World!") : value.get(); } } From dce3aa9f420cc96a24af88897866bc84d835f41c Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Tue, 26 Mar 2024 23:12:03 -0600 Subject: [PATCH 4/9] re-add packet-info --- .../io/github/syst3ms/skriptparser/file/package-info.java | 4 ++++ .../java/io/github/syst3ms/skriptparser/log/package-info.java | 4 ++++ .../java/io/github/syst3ms/skriptparser/package-info.java | 4 ++++ .../io/github/syst3ms/skriptparser/parsing/package-info.java | 4 ++++ .../github/syst3ms/skriptparser/variables/package-info.java | 4 ++++ 5 files changed, 20 insertions(+) create mode 100644 src/test/java/io/github/syst3ms/skriptparser/file/package-info.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/log/package-info.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/package-info.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java create mode 100644 src/test/java/io/github/syst3ms/skriptparser/variables/package-info.java diff --git a/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java new file mode 100644 index 00000000..ba71ee5d --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/file/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser.file; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/log/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/log/package-info.java new file mode 100644 index 00000000..b59b8618 --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/log/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser.log; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/package-info.java new file mode 100644 index 00000000..488c809b --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java new file mode 100644 index 00000000..38a574aa --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser.parsing; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/package-info.java b/src/test/java/io/github/syst3ms/skriptparser/variables/package-info.java new file mode 100644 index 00000000..47415246 --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser.variables; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file From 6d43723cec3faa6378fb7c4c9027f8f4c6cddc7c Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Wed, 27 Mar 2024 13:19:34 -0600 Subject: [PATCH 5/9] Re-add the clear variables but package access only --- .../io/github/syst3ms/skriptparser/variables/Variables.java | 4 ++++ .../syst3ms/skriptparser/variables/VariablesTest.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index fddc5080..7bb3a5c7 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -237,6 +237,10 @@ private static void processChangeQueue() { } } + static final void clearVariables() { + variableMap.clearVariables(); + } + /** * Represents a variable that is to be changed. * Key-value pair. Key being the variable name. diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java index 9ef0d16b..0e99ceda 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java @@ -7,6 +7,7 @@ import org.junit.Before; import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; import java.util.Arrays; import java.util.List; @@ -25,6 +26,11 @@ public class VariablesTest { Variables.registerStorage(RamStorage.class, "ram"); } + @BeforeAll + public void clearVariables() { + Variables.clearVariables(); + } + @Before public void setupVariables() { Variables.registerStorage(RamStorage.class, "ram"); From 43275d015a8beb5ead2b0d260fc654e0aecc0ef0 Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Wed, 27 Mar 2024 13:22:19 -0600 Subject: [PATCH 6/9] Re-add the clear variables but package access only --- .../github/syst3ms/skriptparser/variables/VariablesTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java index 0e99ceda..62306bd3 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java @@ -7,7 +7,7 @@ import org.junit.Before; import org.junit.Test; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import java.util.Arrays; import java.util.List; @@ -26,7 +26,7 @@ public class VariablesTest { Variables.registerStorage(RamStorage.class, "ram"); } - @BeforeAll + @BeforeEach public void clearVariables() { Variables.clearVariables(); } From 8569bca7155eea0774a9e7c6884798b4387b098c Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Wed, 27 Mar 2024 13:25:51 -0600 Subject: [PATCH 7/9] Re-add the clear variables but package access only --- .../syst3ms/skriptparser/parsing/SyntaxParserTest.java | 3 +++ .../variables/{VariablesTest.java => DatabaseTest.java} | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) rename src/test/java/io/github/syst3ms/skriptparser/variables/{VariablesTest.java => DatabaseTest.java} (95%) diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java index 5c9627e0..2ca35cb8 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java @@ -4,6 +4,7 @@ import io.github.syst3ms.skriptparser.log.LogEntry; import io.github.syst3ms.skriptparser.log.LogType; import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import io.github.syst3ms.skriptparser.variables.DatabaseTest; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; @@ -71,6 +72,8 @@ public Iterator syntaxTest() { var allErrors = new ArrayList<>(errorsFound); Set duplicateErrors = new HashSet<>(); allErrors.removeIf(val -> !duplicateErrors.add(val.getMessage())); + + DatabaseTest.clearAllVariables(); errorsFound.clear(); MultipleFailureException.assertEmpty(allErrors); diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java similarity index 95% rename from src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java rename to src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java index 62306bd3..fe04af18 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java @@ -13,7 +13,7 @@ import java.util.List; import java.util.Optional; -public class VariablesTest { +public class DatabaseTest { private static final List LINES = Arrays.asList( "databases:", @@ -31,6 +31,10 @@ public void clearVariables() { Variables.clearVariables(); } + public static void clearAllVariables() { + Variables.clearVariables(); + } + @Before public void setupVariables() { Variables.registerStorage(RamStorage.class, "ram"); From c95ee21716aab84e536de92c87e6e9941df657cf Mon Sep 17 00:00:00 2001 From: TheLimeGlass Date: Wed, 27 Mar 2024 13:54:33 -0600 Subject: [PATCH 8/9] Some fixes for deleting --- .../variables/VariableStorage.java | 18 +++++++++++------- .../skriptparser/variables/Variables.java | 2 +- .../skriptparser/variables/DatabaseTest.java | 4 ++++ .../skriptparser/variables/RamStorage.java | 3 +++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java index ebb20172..3029cc00 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -195,7 +195,6 @@ public final boolean loadConfiguration(FileSection section) { return false; this.file = getFile(fileName).getAbsoluteFile(); - if (file.exists() && !file.isFile()) { logger.error("The database file '" + file.getName() + "' does not exist or is a directory.", ErrorType.SEMANTIC_ERROR); return false; @@ -225,15 +224,20 @@ public final boolean loadConfiguration(FileSection section) { } /** - * Loads configurations and variables. + * Loads configurations and should start loading variables too. * * @return Whether the database could be loaded successfully, * i.e. whether the configuration is correct and all variables could be loaded. */ protected abstract boolean load(FileSection section); - protected void load(String name, SerializedVariable variable) { - load(name, variable.value.type, variable.value.data); + protected void loadVariable(String name, SerializedVariable variable) { + Value value = variable.value; + if (value == null) { + Variables.queueVariableChange(name, null); + return; + } + loadVariable(name, value.type, value.data); } /** @@ -243,7 +247,7 @@ protected void load(String name, SerializedVariable variable) { * @param type the type of the variable. * @param value the serialized value of the variable. */ - protected void load(String name, @NotNull String type, @NotNull byte[] value) { + protected void loadVariable(String name, @NotNull String type, @NotNull byte[] value) { if (value == null || type == null) throw new IllegalArgumentException("value and/or typeName cannot be null"); Variables.queueVariableChange(name, deserialize(type, value)); @@ -298,9 +302,9 @@ boolean accept(@Nullable String variableName) { * @return the serialized variable. */ @SuppressWarnings("unchecked") - public SerializedVariable serialize(String name, @NotNull T value) { + public SerializedVariable serialize(String name, @Nullable T value) { if (value == null) - throw new IllegalArgumentException("value cannot be null"); + return new SerializedVariable(name, null); Type type = (Type) TypeManager.getByClassExact(value.getClass()).orElse(null); if (type == null) throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type registered."); diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index 7bb3a5c7..29e81072 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -232,7 +232,7 @@ private static void processChangeQueue() { .filter(storage -> storage.accept(change.name)) .forEach(storage -> { SerializedVariable serialized = storage.serialize(change.name, change.value); - storage.save(serialized); + storage.save(serialized); }); } } diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java index fe04af18..23f78604 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java @@ -68,6 +68,10 @@ public void testVariables() throws InterruptedException { Optional value = RamStorage.SELF.deserialize(variable.value.type, variable.value.data); assert value.isPresent(); assert value.get().equals("Hello New World!") : value.get(); + Variables.setVariable("test", null, null, false); + Thread.sleep(1); + assert !Variables.getVariable("test", null, false).isPresent(); + assert !RamStorage.VARIABLES.containsKey("test"); } } diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java index ff0fa92f..c769cc81 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/RamStorage.java @@ -19,6 +19,9 @@ import java.util.Map; import java.util.Optional; +/** + * A VariableStorage that only saves into ram memory. + */ public class RamStorage extends VariableStorage { public static final Map VARIABLES = new HashMap<>(); From b198d08400b47765194c1d7408ceddf77488c98c Mon Sep 17 00:00:00 2001 From: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com> Date: Sat, 30 Mar 2024 19:51:02 -0600 Subject: [PATCH 9/9] Update DatabaseTest.java --- .../github/syst3ms/skriptparser/variables/DatabaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java index 23f78604..154f365c 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/variables/DatabaseTest.java @@ -53,7 +53,7 @@ public void setupVariables() { @Test public void testVariables() throws InterruptedException { - Thread.sleep(1); + Thread.sleep(100); assert RamStorage.VARIABLES.containsKey("test"); Optional object = Variables.getVariable("test", null, false); assert object.isPresent(); @@ -69,7 +69,7 @@ public void testVariables() throws InterruptedException { assert value.isPresent(); assert value.get().equals("Hello New World!") : value.get(); Variables.setVariable("test", null, null, false); - Thread.sleep(1); + Thread.sleep(100); assert !Variables.getVariable("test", null, false).isPresent(); assert !RamStorage.VARIABLES.containsKey("test"); }