diff --git a/build.gradle b/build.gradle index eb335ef9..14e77189 100644 --- a/build.gradle +++ b/build.gradle @@ -5,12 +5,13 @@ plugins { id 'io.freefair.lombok' version libs.versions.delombok } +final def TEST_MODULE = 'testing' +final def DEMO_MODULE = 'demo' +final def VERSION_VARIABLE = 'MINECRAFT_VERSION' + group = 'it.angrybear' version = '4.0' -final def TEST_MODULE = "testing" -final def VERSION_VARIABLE = "MINECRAFT_VERSION" - allprojects { apply plugin: 'java-library' apply plugin: 'maven-publish' @@ -26,7 +27,7 @@ allprojects { def obsolete_version = libs.versions.spigot.obsolete.get() this.ext.getParentFromProject = { project -> - return new HashMap<>(project.getProperties()).get("parent") + return new HashMap<>(project.getProperties()).get('parent') } this.ext.getProjectGroupId = { @@ -37,7 +38,7 @@ allprojects { tmp = getParentFromProject(tmp) } if (groupId.size() > 0) - groupId = "." + groupId.substring(0, groupId.length() - 1) + groupId = ".${groupId.substring(0, groupId.length() - 1)}" return "${rootProject.group}${groupId}" } @@ -50,18 +51,18 @@ allprojects { repositories { mavenCentral() maven { - name = "spigotmc-repo" + name = 'spigotmc-repo' url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url = 'https://oss.sonatype.org/content/repositories/central' } maven { - name = "JitPack" - url = "https://jitpack.io" + name = 'JitPack' + url = 'https://jitpack.io' } maven { - name = "Fulminazzo repository" - url = "https://repo.fulminazzo.it/releases" + name = 'Fulminazzo repository' + url = 'https://repo.fulminazzo.it/releases' } } @@ -75,60 +76,61 @@ allprojects { def projectName = project.name - if (projectName.contains("serializer")) api(libs.yamlparser) + if (projectName.contains('serializer')) api(libs.yamlparser) - if (projectName.contains("bukkit")) { + if (projectName.contains('bukkit')) { compileOnly libs.spigot testCompileOnly libs.spigot.latest testRuntimeOnly "org.spigotmc:spigot-api:${latest_version}" - String numVersion = latest_version.substring(2, latest_version.indexOf(".", 2)) - def index = numVersion.indexOf("-") + String numVersion = latest_version.substring(2, latest_version.indexOf('.', 2)) + def index = numVersion.indexOf('-') if (index != -1) numVersion = numVersion.substring(0, index) if (Double.valueOf(numVersion) >= 13) testImplementation libs.jbukkit else testImplementation libs.jbukkit.legacy } - if (projectName.contains("legacy")) { + if (projectName.contains('legacy')) { testCompileOnly libs.spigot.legacy testRuntimeOnly "org.spigotmc:spigot-api:${legacy_version}" testImplementation libs.jbukkit.legacy } - if (projectName.contains("obsolete")) { + if (projectName.contains('obsolete')) { testCompileOnly libs.spigot.obsolete testRuntimeOnly "org.spigotmc:spigot-api:${obsolete_version}" testImplementation libs.jbukkit.legacy } - if (projectName.contains("-")) { - def name = projectName.substring(0, projectName.indexOf("-")) - def id = projectName.substring(projectName.indexOf("-") + 1) - if (!projectName.contains("base")) api(project(":${name}:${name}-base")) - if (name != "common" && id != "legacy" && id != "obsolete") + if (projectName.contains('-')) { + def name = projectName.substring(0, projectName.indexOf('-')) + def id = projectName.substring(projectName.indexOf('-') + 1) + if (!projectName.contains('base')) api(project(":${name}:${name}-base")) + if (name != 'common' && id != 'legacy' && id != 'obsolete') api(project(":common:common-${id}")) } - if (!projectName.contains("-")) - subprojects.findAll { (it.name != TEST_MODULE) } .each {api project(it.path)} - testCompileOnly libs.lombok testAnnotationProcessor libs.lombok - if (!project.name.equals(TEST_MODULE)) testImplementation project(":" + TEST_MODULE) + if (project.name != TEST_MODULE) testImplementation project(":${TEST_MODULE}") + + if (project.name == DEMO_MODULE) + rootProject.subprojects.findAll { it.name != TEST_MODULE && it.name != DEMO_MODULE } + .each { implementation project(it.path) } } - if (project.name.equals(TEST_MODULE)) return + if (project.name == TEST_MODULE) return test { def projectName = project.name if (System.getenv(VERSION_VARIABLE) == null) { - if (projectName.contains("bukkit")) environment VERSION_VARIABLE, latest_version - else if (projectName.contains("legacy")) environment VERSION_VARIABLE, legacy_version - else if (projectName.contains("obsolete")) environment VERSION_VARIABLE, obsolete_version + if (projectName.contains('bukkit')) environment VERSION_VARIABLE, latest_version + else if (projectName.contains('legacy')) environment VERSION_VARIABLE, legacy_version + else if (projectName.contains('obsolete')) environment VERSION_VARIABLE, obsolete_version } def env = System.getenv(VERSION_VARIABLE) @@ -136,14 +138,14 @@ allprojects { useJUnitPlatform() } - tasks.register("testBukkit") { + tasks.register('testBukkit') { final def minecraftVersion = getMinecraftVersion() println "Using Minecraft version ${minecraftVersion}" latest_version = minecraftVersion legacy_version = minecraftVersion obsolete_version = minecraftVersion - subprojects.findAll { it.path.contains("bukkit") }.each { dependsOn "${it.path}:test" } + subprojects.findAll { it.path.contains('bukkit') }.each {dependsOn "${it.path}:test" } } tasks.register('sourcesJar', Jar) { @@ -171,10 +173,10 @@ allprojects { repositories { maven { - url "https://repo.fulminazzo.it/releases" + url 'https://repo.fulminazzo.it/releases' credentials { - username = System.getenv("REPO_USERNAME") - password = System.getenv("REPO_PASSWORD") + username = System.getenv('REPO_USERNAME') + password = System.getenv('REPO_PASSWORD') } authentication { basic(BasicAuthentication) @@ -184,6 +186,11 @@ allprojects { } } +dependencies { + subprojects.findAll { it.name != TEST_MODULE && it.name != DEMO_MODULE } .each { api project(it.path) } + implementation project(":${DEMO_MODULE}") +} + tasks.register("aggregateJavaDoc") { allprojects.forEach { dependsOn "${it.path}:javadoc" } doLast { diff --git a/common/base/src/main/java/it/angrybear/yagl/ClassEnum.java b/common/base/src/main/java/it/angrybear/yagl/ClassEnum.java index def3551d..eed10be7 100644 --- a/common/base/src/main/java/it/angrybear/yagl/ClassEnum.java +++ b/common/base/src/main/java/it/angrybear/yagl/ClassEnum.java @@ -129,7 +129,8 @@ private void checkValues() { if (!this.values.isEmpty()) return; for (Field field : this.clazz.getDeclaredFields()) if (field.getType().equals(this.clazz)) - this.values.put(field.getName().toUpperCase(), ReflectionUtils.get(field, this.clazz)); + ReflectionUtils.get(field, this.clazz).ifPresent(o -> + this.values.put(field.getName().toUpperCase(), (T) o)); } } } diff --git a/common/base/src/main/java/it/angrybear/yagl/utils/ObjectUtils.java b/common/base/src/main/java/it/angrybear/yagl/utils/ObjectUtils.java index e1d92dd7..9e6f479c 100644 --- a/common/base/src/main/java/it/angrybear/yagl/utils/ObjectUtils.java +++ b/common/base/src/main/java/it/angrybear/yagl/utils/ObjectUtils.java @@ -66,39 +66,61 @@ public static O copy(final @NotNull T t, @NotNull Class claz clazz.getCanonicalName(), clazz.getCanonicalName())); } - Refl object = new Refl<>(clazz, new Object[0]); - for (final Field field : object.getNonStaticFields()) + final Refl object = new Refl<>(clazz, new Object[0]); + for (final Field field : object.getNonStaticFields()) { + field.setAccessible(true); try { - Object obj1 = ReflectionUtils.get(field, t); - if (obj1 instanceof Collection) { - Class tmpClass = obj1.getClass(); - // In the case of creation with Arrays.asList() - if (tmpClass.getCanonicalName().equals(Arrays.class.getCanonicalName() + ".ArrayList")) - tmpClass = ArrayList.class; - Class> finalClass = (Class>) tmpClass; - obj1 = ((Collection) obj1).stream() - .collect(Collectors.toCollection(() -> new Refl<>(finalClass, new Object[0]).getObject())); - } else if (obj1 instanceof Map) { - Map map = new HashMap<>(); - ((Map) obj1).putAll(map); - obj1 = map; - } else if (obj1 != null) - if (obj1.getClass().isArray()) { - Object[] tmp = (Object[]) obj1; - Object[] arr = (Object[]) Array.newInstance(obj1.getClass().getComponentType(), tmp.length); - System.arraycopy(tmp, 0, arr, 0, arr.length); - obj1 = arr; - } else - try { - Method copy = obj1.getClass().getDeclaredMethod("copy"); - obj1 = ReflectionUtils.setAccessible(copy).invoke(obj1); - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) {} - object.setFieldObject(field, obj1); - } catch (IllegalArgumentException ignored) {} - + ReflectionUtils.get(field, t).map(obj1 -> { + if (obj1 == null) return null; + else if (obj1 instanceof Collection) return copyCollection(obj1); + else if (obj1 instanceof Map) return copyMap(obj1); + else if (obj1.getClass().isArray()) return copyArray(obj1); + else return copyWithMethod(obj1); + }).ifPresent(obj1 -> object.setFieldObject(field, obj1)); + } catch (IllegalArgumentException e) { + if (!e.getMessage().contains("Can not set")) throw e; + } + } return object.getObject(); } + private static @NotNull Object[] copyArray(final @NotNull Object obj1) { + Object[] tmp = (Object[]) obj1; + Object[] arr = (Object[]) Array.newInstance(obj1.getClass().getComponentType(), tmp.length); + for (int i = 0; i < tmp.length; i++) arr[i] = copyWithMethod(tmp[i]); + return arr; + } + + private static @NotNull Map copyMap(final @NotNull Object obj1) { + Map map = new HashMap<>(); + ((Map) obj1).forEach((k, v) -> + map.put(copyWithMethod(k), copyWithMethod(v))); + return map; + } + + private static @NotNull Collection copyCollection(final @NotNull Object obj1) { + Class tmpClass = obj1.getClass(); + // In the case of creation with Arrays.asList() + if (tmpClass.getCanonicalName().equals(Arrays.class.getCanonicalName() + ".ArrayList")) + tmpClass = ArrayList.class; + Class> finalClass = (Class>) tmpClass; + return ((Collection) obj1).stream() + .map(ObjectUtils::copyWithMethod) + .collect(Collectors.toCollection(() -> new Refl<>(finalClass, new Object[0]).getObject())); + } + + private static @Nullable Object copyWithMethod(final @Nullable Object obj1) { + try { + if (obj1 == null) return null; + Method copy = obj1.getClass().getDeclaredMethod("copy"); + return ReflectionUtils.setAccessible(copy) + .map(m -> m.invoke(obj1)) + .orElseGet(obj1); + } catch (NoSuchMethodException e) { + return obj1; + } + } + /** * Prints the given object in a JSON format. * If the object (or an object contained in it) is "empty", diff --git a/common/base/src/test/java/it/angrybear/yagl/utils/ObjectUtilsTest.java b/common/base/src/test/java/it/angrybear/yagl/utils/ObjectUtilsTest.java index 8226c027..2fac9414 100644 --- a/common/base/src/test/java/it/angrybear/yagl/utils/ObjectUtilsTest.java +++ b/common/base/src/test/java/it/angrybear/yagl/utils/ObjectUtilsTest.java @@ -3,9 +3,7 @@ import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; +import java.util.*; import static org.junit.jupiter.api.Assertions.*; @@ -36,14 +34,32 @@ void testCopyOfData() { assertNotEquals(c1.array, c2.array); assertArrayEquals(c1.array, c2.array); assertNotEquals(c1.copiable, c2.copiable); + assertEquals(c1.map, c2.map); + // Try removal from the first map + c1.map.remove("hello"); + assertEquals("world", c2.map.get("hello")); + } + + @Test + void testCopyThrowsIllegalArgument() { + Throwable exception = assertThrowsExactly(IllegalArgumentException.class, () -> + ObjectUtils.copy(new GeneralCopyException())); + assertEquals("Everything good", exception.getMessage()); } private static class GeneralCopy { + Map map = new HashMap(){{ + put("hello", "world"); + }}; List list = Arrays.asList("hello", "world"); String[] array = new String[]{"hello", "world"}; GeneralCopiable copiable = new GeneralCopiable(); } + private static class GeneralCopyException { + GeneralCopiableException copiable = new GeneralCopiableException(); + } + private static class GeneralCopiable { public GeneralCopiable copy() { @@ -51,6 +67,13 @@ public GeneralCopiable copy() { } } + private static class GeneralCopiableException { + + public GeneralCopiable copy() { + throw new IllegalArgumentException("Everything good"); + } + } + private static class CopyIterableImpl { } private static class CopyIterable implements Iterable { diff --git a/common/serializer/src/main/java/it/angrybear/yagl/parsers/TypedParser.java b/common/serializer/src/main/java/it/angrybear/yagl/parsers/TypedParser.java index 68b15cfb..e1bffe1e 100644 --- a/common/serializer/src/main/java/it/angrybear/yagl/parsers/TypedParser.java +++ b/common/serializer/src/main/java/it/angrybear/yagl/parsers/TypedParser.java @@ -3,13 +3,15 @@ import it.angrybear.yagl.utils.ParserUtils; import it.fulminazzo.fulmicollection.interfaces.functions.TriConsumer; import it.fulminazzo.fulmicollection.objects.Refl; +import it.fulminazzo.yamlparser.configuration.ConfigurationSection; import it.fulminazzo.yamlparser.configuration.IConfiguration; import it.fulminazzo.yamlparser.parsers.CallableYAMLParser; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * This parser allows parsing an object using the {@link CallableYAMLParser} methods. - * However, it forces the saved object to store a type field, that contains the name of the class of the original object. + * However, it forces the saved object to store a type field, that contains the class name of the original object. * Upon loading, this class is retrieved (and will throw errors for invalid values) to recreate the object. * * @param the type parameter @@ -23,12 +25,7 @@ public abstract class TypedParser extends CallableYAMLParser { * @param clazz the clazz */ public TypedParser(final @NotNull Class clazz) { - super(clazz, (c) -> { - String type = c.getString(TYPE_FIELD); - if (type == null) throw new IllegalArgumentException(String.format("'%s' cannot be null", TYPE_FIELD)); - Class clz = ParserUtils.typeToClass(clazz, type); - return new Refl<>(clz, new Object[0]).getObject(); - }); + super(clazz, c -> getObjectFromType(clazz, c)); } @SuppressWarnings("unchecked") @@ -40,4 +37,23 @@ protected TriConsumer getDumper() { c.set(s + "." + TYPE_FIELD, ParserUtils.classToType(getOClass(), (Class) g.getClass())); }; } + + /** + * Tries to get the corresponding {@link #TYPE_FIELD} type from the given section, and coverts it to an instance. + * Throws an {@link IllegalArgumentException} in case no type is provided or no class is found. + * + * @param the type of the object + * @param objectClass the object class + * @param section the section + * @param parameters the parameters for the constructor + * @return the object + */ + protected static T getObjectFromType(final @NotNull Class objectClass, + final @NotNull ConfigurationSection section, + final Object @Nullable ... parameters) { + String type = section.getString(TYPE_FIELD); + if (type == null) throw new IllegalArgumentException(String.format("'%s' cannot be null", TYPE_FIELD)); + Class clazz = ParserUtils.typeToClass(objectClass, type); + return new Refl<>(clazz, parameters).getObject(); + } } diff --git a/demo/build.gradle b/demo/build.gradle new file mode 100644 index 00000000..0e2f17a9 --- /dev/null +++ b/demo/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'groovy' + id 'application' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + +dependencies { + implementation libs.groovy + + compileOnly libs.spigot.latest + testImplementation libs.spigot.latest + testImplementation libs.jbukkit +} + +shadowJar { + exclude 'META-INF/**' + + archiveFileName = "${rootProject.name}-plugin-${project.version}.jar" + mainClassName = "${project.group}.${rootProject.name.toLowerCase()}.${rootProject.name}" +} + +jar { + dependsOn processResources + dependsOn shadowJar + + archiveFileName = "${project.name}-${project.version}-original.jar" +} + +processResources { + def props = [ + version: rootProject.version, name: rootProject.name, name_lower: rootProject.name.toLowerCase(), + description: rootProject.description, author: 'Fulminazzo', + group: rootProject.group, module: rootProject.name + ] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('*.yml') { + expand props + } +} \ No newline at end of file diff --git a/demo/src/main/groovy/it/angrybear/yagl/YAGL.groovy b/demo/src/main/groovy/it/angrybear/yagl/YAGL.groovy new file mode 100644 index 00000000..54a9027f --- /dev/null +++ b/demo/src/main/groovy/it/angrybear/yagl/YAGL.groovy @@ -0,0 +1,96 @@ +package it.angrybear.yagl + +import groovy.transform.CompileStatic +import it.angrybear.yagl.commands.ShellCommand +import it.angrybear.yagl.listeners.PersistentListener +import it.fulminazzo.fulmicollection.objects.Refl +import it.fulminazzo.fulmicollection.utils.JarUtils +import it.fulminazzo.yamlparser.utils.FileUtils +import org.bukkit.Bukkit +import org.bukkit.command.Command +import org.bukkit.command.CommandMap +import org.bukkit.plugin.java.JavaPlugin +import org.jetbrains.annotations.NotNull + +@CompileStatic +class YAGL extends JavaPlugin { + private final List commands = new ArrayList<>() + + @Override + void onEnable() { + loadCommands() + Bukkit.pluginManager.registerEvents(new PersistentListener(), this) + logger.info("Loaded ${commands.size()} commands") + } + + @Override + void onDisable() { + unloadCommands() + } + + /** + * Loads all the commands from the {@link #dataFolder}/commands directory. + * If it does not exist, it is created using {@link #saveDefaultCommands(File)}. + */ + void loadCommands() { + this.commands.clear() + File commandsDir = new File(dataFolder, 'commands') + if (!commandsDir.exists()) saveDefaultCommands(commandsDir) + File[] files = commandsDir.listFiles() + if (files != null) + this.commands.addAll(files.findAll { it.name.endsWith('.groovy') }.collect { new ShellCommand(it) }) + + commandMap().ifPresent { map -> this.commands.each { map.register(name, it) } } + } + + /** + * Unloads all the commands loaded in {@link #commands}. + */ + void unloadCommands() { + commandMap().ifPresent(map -> { + Map commands = new Refl<>(map).getFieldObject('knownCommands') + if (commands == null) logger.warning('Could not find \'knownCommands\' field in CommandMap') + else commands.keySet().collect().each { key -> + Command value = commands.get(key) + if (this.commands.contains(value)) commands.remove(key, value) + } + }) + } + + /** + * Saves all the default command scripts to the given directory + * + * @param commandsDir the output directory + */ + void saveDefaultCommands(final @NotNull File commandsDir) { + final resourceDir = '/commands' + FileUtils.createFolder(commandsDir) + getClass().getResourceAsStream(resourceDir).withReader { reader -> + String fileName + while ((fileName = reader.readLine()) != null) writeResourceToFile(commandsDir, fileName, resourceDir) + } + Iterator jarEntries = JarUtils.getEntries(YAGL, '') + while (jarEntries.hasNext()) { + def entry = jarEntries.next() + if (entry.startsWith(resourceDir.substring(1)) && entry.length() > resourceDir.length()) + writeResourceToFile(commandsDir, entry.substring(resourceDir.length()), resourceDir) + } + } + + private static Optional commandMap() { + def pluginManager = Bukkit.pluginManager + // Terrible line, but necessary for JaCoCo coverage report to 100% + pluginManager == null ? Optional.empty() : Optional.ofNullable((CommandMap) new Refl<>(pluginManager) + .getFieldObject('commandMap')) + } + + private static void writeResourceToFile(final @NotNull File dir, final @NotNull String fileName, final @NotNull resourceDir) { + def file = new File(dir, fileName) + if (file.exists()) FileUtils.deleteFile(file) + FileUtils.createNewFile(file) + def input = getClass().getResourceAsStream("${resourceDir}/${fileName}") + def output = new FileOutputStream(file) + output << input + } + +} diff --git a/demo/src/main/groovy/it/angrybear/yagl/commands/ShellCommand.groovy b/demo/src/main/groovy/it/angrybear/yagl/commands/ShellCommand.groovy new file mode 100644 index 00000000..0bb880fa --- /dev/null +++ b/demo/src/main/groovy/it/angrybear/yagl/commands/ShellCommand.groovy @@ -0,0 +1,52 @@ +package it.angrybear.yagl.commands + +import groovy.transform.CompileStatic +import it.fulminazzo.yamlparser.utils.FileUtils +import org.bukkit.command.Command +import org.bukkit.command.CommandSender +import org.jetbrains.annotations.NotNull + +import java.util.regex.Pattern + +/** + * A general class used to create a command from a Groovy script. + */ +@CompileStatic +class ShellCommand extends Command { + private static final String NUMBER_FORMAT_REGEX = '(catch *\\(NumberFormatException +ignored\\) *\\{\\n)[ \\t]*(?:// auto-generated code)?(\\n *})' + private static final String INVALID_NUMBER_CODE = 'sender.sendMessage(e.message.replace(\'For input string: \', \'Invalid number \'))' + private final String shellCode + + /** + * Instantiates a new shell command + * + * @param file the file containing the script + */ + ShellCommand(final @NotNull File file) { + super(file.name.substring(0, file.name.lastIndexOf('.'))) + def code = FileUtils.readFileToString(file) + if (code == null) { + this.shellCode = '' + return + } + def matcher = Pattern.compile(NUMBER_FORMAT_REGEX).matcher(code) + while (matcher.find()) { + def replacement = "${matcher.group(1)} ${INVALID_NUMBER_CODE}${matcher.group(2)}" + code = code.replace(matcher.group(), replacement) + } + this.shellCode = "${code}\nrun(sender, label, args)" + } + + @Override + boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) { + Binding binding = new Binding(['sender': sender, 'label': label, 'args': args]) + new GroovyShell(binding).evaluate(this.shellCode) + return true + } + + @Override + List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + return new ArrayList<>() + } + +} diff --git a/demo/src/main/resources/commands/ApplyPotionEffect.groovy b/demo/src/main/resources/commands/ApplyPotionEffect.groovy new file mode 100644 index 00000000..d2da01ab --- /dev/null +++ b/demo/src/main/resources/commands/ApplyPotionEffect.groovy @@ -0,0 +1,20 @@ +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.wrappers.PotionEffect +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) { + try { + PotionEffect effect = new PotionEffect(args[0], Double.valueOf(args[1]), + Integer.valueOf(args[2]), Boolean.valueOf(args[3]), Boolean.valueOf(args[4])) + def potionEffect = WrappersAdapter.wPotionEffectToPotionEffect(effect) + sender.addPotionEffect(potionEffect) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /applypotioneffect ') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + } else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/CreateRecipe.groovy b/demo/src/main/resources/commands/CreateRecipe.groovy new file mode 100644 index 00000000..ee252520 --- /dev/null +++ b/demo/src/main/resources/commands/CreateRecipe.groovy @@ -0,0 +1,70 @@ +import it.angrybear.yagl.items.BukkitItem +import it.angrybear.yagl.items.Item +import it.angrybear.yagl.items.recipes.FurnaceRecipe +import it.angrybear.yagl.items.recipes.ShapedRecipe +import it.angrybear.yagl.items.recipes.ShapelessRecipe +import org.bukkit.entity.Player + +static shaped(sender, label, args, output, name) { + try { + def rows = Integer.valueOf(args[0]) + def columns = Integer.valueOf(args[1]) + def recipe = new ShapedRecipe(name).setShape(rows, columns) + if (args.length < 2) throw new IndexOutOfBoundsException() + for (int i = 2; i < args.length; i += 1) + recipe.setIngredient(i - 2, Item.newItem(args[i])) + BukkitItem.newRecipeItem(output).addRecipes(recipe).registerRecipes() + sender.sendMessage('Ok') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /createrecipe shaped ') + } +} + +static shapeless(sender, label, args, output, name) { + try { + if (args.length == 0) throw new IndexOutOfBoundsException() + BukkitItem.newRecipeItem(output) + .addRecipes(new ShapelessRecipe(name) + .addIngredients(Arrays.stream(args) + .map(Item::newItem) + .toArray(Item[]::new))) + .registerRecipes() + sender.sendMessage('Ok') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /createrecipe shapeless ') + } +} + +static furnace(sender, label, args, output, name) { + try { + BukkitItem.newRecipeItem(output) + .addRecipes(new FurnaceRecipe(name) + .setIngredient(Item.newItem(args[0])) + .setCookingTime(Double.valueOf(args[1])) + .setExperience(Float.valueOf(args[2]))) + .registerRecipes() + sender.sendMessage('Ok') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /createrecipe furnace ') + } +} + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + "${args[2].toLowerCase()}"(sender, label, Arrays.copyOfRange(args, 3, args.length), args[1], args[0]) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /createrecipe ') + } catch (Exception e) { + sender.sendMessage(e.message) + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/GetEnchantment.groovy b/demo/src/main/resources/commands/GetEnchantment.groovy new file mode 100644 index 00000000..40c20ae6 --- /dev/null +++ b/demo/src/main/resources/commands/GetEnchantment.groovy @@ -0,0 +1,25 @@ +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.wrappers.Enchantment +import it.fulminazzo.fulmicollection.structures.tuples.Tuple +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.meta.EnchantmentStorageMeta + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + Enchantment enchantment = new Enchantment(args[0], Integer.valueOf(args[1])) + ItemStack book = new ItemStack(Material.ENCHANTED_BOOK) + EnchantmentStorageMeta meta = book.itemMeta + Tuple tuple = WrappersAdapter.wEnchantToEnchant(enchantment) + meta.addStoredEnchant(tuple.key, tuple.value, true) + book.setItemMeta(meta) + sender.inventory.addItem(book) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /getenchantment ') + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/GiveItem.groovy b/demo/src/main/resources/commands/GiveItem.groovy new file mode 100644 index 00000000..4d4396ed --- /dev/null +++ b/demo/src/main/resources/commands/GiveItem.groovy @@ -0,0 +1,36 @@ +import it.angrybear.yagl.items.BukkitItem +import it.angrybear.yagl.items.Item +import it.angrybear.yagl.items.fields.ItemFlag +import it.angrybear.yagl.wrappers.Enchantment +import it.angrybear.yagl.wrappers.WrapperParser +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + Item item = Item.newItem(args[0]) + try { + item.setAmount(Integer.valueOf(args[1])) + .setDurability(Integer.valueOf(args[2])) + .setDisplayName(args[3]) + .setLore(args[4].split(';')) + .addEnchantments(Arrays.stream(args[5].split(';')) + .map(a -> WrapperParser.parseWrapperFromString(a, Enchantment)) + .toArray(Enchantment[]::new)) + .addItemFlags(Arrays.stream(args[6].split(';')) + .map(a -> ItemFlag.valueOf(a.toUpperCase())) + .toArray(ItemFlag[]::new)) + .setUnbreakable(Boolean.valueOf(args[7])) + .setCustomModelData(Integer.valueOf(args[8])) + } catch (IndexOutOfBoundsException ignored) { + + } + sender.inventory.addItem(item.copy(BukkitItem).create()) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /giveitem ') + sender.sendMessage('At least material is required!') + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/GivePersistentItem.groovy b/demo/src/main/resources/commands/GivePersistentItem.groovy new file mode 100644 index 00000000..516e33b7 --- /dev/null +++ b/demo/src/main/resources/commands/GivePersistentItem.groovy @@ -0,0 +1,40 @@ +import it.angrybear.yagl.items.BukkitItem +import it.angrybear.yagl.items.DeathAction +import it.angrybear.yagl.items.Mobility +import it.angrybear.yagl.items.PersistentItem +import it.angrybear.yagl.items.fields.ItemFlag +import it.angrybear.yagl.wrappers.Enchantment +import it.angrybear.yagl.wrappers.WrapperParser +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + DeathAction action = DeathAction.valueOf(args[0].toUpperCase()) + Mobility mobility = Mobility.valueOf(args[1].toUpperCase()) + PersistentItem item = PersistentItem.newItem(args[2]).setDeathAction(action).setMobility(mobility) + try { + item.setAmount(Integer.valueOf(args[3])) + .setDurability(Integer.valueOf(args[4])) + .setDisplayName(args[5]) + .setLore(args[6].split(';')) + .addEnchantments(Arrays.stream(args[7].split(';')) + .map(a -> WrapperParser.parseWrapperFromString(a, Enchantment)) + .toArray(Enchantment[]::new)) + .addItemFlags(Arrays.stream(args[8].split(';')) + .map(a -> ItemFlag.valueOf(a.toUpperCase())) + .toArray(ItemFlag[]::new)) + .setUnbreakable(Boolean.valueOf(args[9])) + .setCustomModelData(Integer.valueOf(args[10])) + } catch (IndexOutOfBoundsException ignored) { + + } + sender.inventory.addItem(item.copy(BukkitItem).create()) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /givepersistentitem ') + sender.sendMessage('At least death-action, mobility and material are required!') + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/OpenDataGUI.groovy b/demo/src/main/resources/commands/OpenDataGUI.groovy new file mode 100644 index 00000000..2ae4d64a --- /dev/null +++ b/demo/src/main/resources/commands/OpenDataGUI.groovy @@ -0,0 +1,80 @@ +import it.angrybear.yagl.GUIManager +import it.angrybear.yagl.contents.GUIContent +import it.angrybear.yagl.contents.ItemGUIContent +import it.angrybear.yagl.guis.DataGUI +import it.angrybear.yagl.guis.GUIType +import it.angrybear.yagl.guis.PageableGUI +import it.angrybear.yagl.items.BukkitItem +import it.angrybear.yagl.utils.EnumUtils +import org.bukkit.Material +import org.bukkit.entity.Player + +import java.util.function.Function + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + def data = [ + 'James', 'Mary', 'Robert', 'Patricia', 'John', 'Jennifer', 'Michael', + 'Linda', 'David', 'Elizabeth', 'William', 'Barbara', 'Richard', 'Susan', + 'Joseph', 'Jessica', 'Thomas', 'Sarah', 'Christopher', 'Karen', 'Charles', + 'Lisa', 'Daniel', 'Nancy', 'Matthew', 'Betty', 'Anthony', 'Sandra', 'Mark', + 'Margaret', 'Donald', 'Ashley', 'Steven', 'Kimberly', 'Andrew', 'Emily', 'Paul', + 'Donna', 'Joshua', 'Michelle', 'Kenneth', 'Carol', 'Kevin', 'Amanda', 'Brian', + 'Melissa', 'George', 'Deborah', 'Timothy', 'Stephanie', 'Ronald', 'Dorothy', + 'Jason', 'Rebecca', 'Edward', 'Sharon', 'Jeffrey', 'Laura', 'Ryan', 'Cynthia', + 'Jacob', 'Amy', 'Gary', 'Kathleen', 'Nicholas', 'Angela', 'Eric', 'Shirley', + 'Jonathan', 'Brenda', 'Stephen', 'Emma', 'Larry', 'Anna', 'Justin', 'Pamela', + 'Scott', 'Nicole', 'Brandon', 'Samantha', 'Benjamin', 'Katherine', 'Samuel', + 'Christine', 'Gregory', 'Helen', 'Alexander', 'Debra', 'Patrick', 'Rachel', + 'Frank', 'Carolyn', 'Raymond', 'Janet', 'Jack', 'Maria', 'Dennis', 'Catherine', + 'Jerry', 'Heather', 'Tyler', 'Diane', 'Aaron', 'Olivia', 'Jose', 'Julie', 'Adam', + 'Joyce', 'Nathan', 'Victoria', 'Henry', 'Ruth', 'Zachary', 'Virginia', 'Douglas', + 'Lauren', 'Peter', 'Kelly', 'Kyle', 'Christina', 'Noah', 'Joan', 'Ethan', 'Evelyn', + 'Jeremy', 'Judith', 'Walter', 'Andrea', 'Christian', 'Hannah', 'Keith', 'Megan', + 'Roger', 'Cheryl', 'Terry', 'Jacqueline', 'Austin', 'Martha', 'Sean', 'Madison', + 'Gerald', 'Teresa', 'Carl', 'Gloria', 'Harold', 'Sara', 'Dylan', 'Janice', 'Arthur', + 'Ann', 'Lawrence', 'Kathryn', 'Jordan', 'Abigail', 'Jesse', 'Sophia', 'Bryan', 'Frances', + 'Billy', 'Jean', 'Bruce', 'Alice', 'Gabriel', 'Judy', 'Joe', 'Isabella', 'Logan', 'Julia', + 'Alan', 'Grace', 'Juan', 'Amber', 'Albert', 'Denise', 'Willie', 'Danielle', 'Elijah', + 'Marilyn', 'Wayne', 'Beverly', 'Randy', 'Charlotte', 'Vincent', 'Natalie', 'Mason', + 'Theresa', 'Roy', 'Diana', 'Ralph', 'Brittany', 'Bobby', 'Doris', 'Russell', 'Kayla', + 'Bradley', 'Alexis', 'Philip', 'Lori', 'Eugene', 'Marie', + ] + Function converter = s -> ItemGUIContent.newInstance(Material.PLAYER_HEAD.name()) + .setDisplayName("&e${s}'s head") + .setLore("&7This head belongs to &e${s}&7.", + '&7Make sure to give it back to them', + '&7once you are done playing with it!') + PageableGUI gui + try { + gui = DataGUI.newGUI(EnumUtils.valueOf(GUIType, args[0]), converter, data) + } catch (IllegalArgumentException ignored) { + gui = DataGUI.newGUI(Integer.valueOf(args[0]), converter, data) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /opendatagui ') + return + } + def size = gui.size() + def middle = (int) Math.min(size / 2, 9 / 2) + if (size > 1) { + size -= 1 + gui.setContents(size - middle, BukkitItem.newItem(Material.OBSIDIAN).setDisplayName('&7Page: &e')) + .setPreviousPage(size - middle * 2, BukkitItem.newItem(Material.REDSTONE_BLOCK) + .setDisplayName('&7Go to page &e')) + .setNextPage(size, BukkitItem.newItem(Material.EMERALD_BLOCK) + .setDisplayName('&7Go to page &e')) + } + + gui.setTitle('Page #') + .onClickOutside((v, g) -> v.sendMessage('Please only click inside me!')) + .onOpenGUI((v, g) -> v.sendMessage(g.apply('Opening page '))) + .onCloseGUI((v, g) -> v.sendMessage('Goodbye!')) + .open(GUIManager.getViewer(sender)) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/OpenGUI.groovy b/demo/src/main/resources/commands/OpenGUI.groovy new file mode 100644 index 00000000..52845035 --- /dev/null +++ b/demo/src/main/resources/commands/OpenGUI.groovy @@ -0,0 +1,58 @@ +import it.angrybear.yagl.GUIManager +import it.angrybear.yagl.contents.ItemGUIContent +import it.angrybear.yagl.guis.GUI +import it.angrybear.yagl.guis.GUIType +import it.angrybear.yagl.items.Item +import it.angrybear.yagl.items.fields.ItemFlag +import it.angrybear.yagl.utils.EnumUtils +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + def columns = 9 + def border = Item.newItem('black_stained_glass_pane').setDisplayName(' ') + GUI gui + try { + gui = GUI.newGUI(EnumUtils.valueOf(GUIType, args[0])) + } catch (IllegalArgumentException ignored) { + gui = GUI.newResizableGUI(Integer.valueOf(args[0])) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /opengui ') + return + } + for (int i = 0; i < Math.min(gui.size(), columns); i += 1) { + gui.setContents(i, border) + gui.setContents(gui.size() - i - 1, border) + } + for (int i = 0; i < gui.size(); i += columns) { + gui.setContents(i, border) + gui.setContents(gui.size() - i - 1, border) + } + def middle = (int) (gui.size() / 2) + gui.setContents(middle, Item.newItem('gold_block').setDisplayName('This is a GUI!') + .addEnchantment('unbreaking', 1) + .addItemFlags(ItemFlag.HIDE_ENCHANTS)) + if (middle - 1 >= 0) + gui.setContents(middle - 1, Item.newItem('diamond_sword') + .setDisplayName('Pick me!') + .addEnchantment('sharpness', 2)) + .setMovable(middle - 1, true) + if (middle + 1 < gui.size()) + gui.setContents(middle + 1, ItemGUIContent.newInstance('diamond_pickaxe') + .setDisplayName('Can\'t pick me...') + .addEnchantment('efficiency', 10) + .onClickItem((v, g, c) -> v.sendMessage('You cannot pick this item!'))) + gui.setTitle(' GUI') + .onClickOutside((v, g) -> v.sendMessage('Please only click inside me!')) + .onOpenGUI((v, g) -> v.sendMessage('Opening the GUI... voila')) + .onCloseGUI((v, g) -> v.sendMessage('Goodbye!')) + .setVariable('name', 'Demo') + .open(GUIManager.getViewer(sender)) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/OpenPageableGUI.groovy b/demo/src/main/resources/commands/OpenPageableGUI.groovy new file mode 100644 index 00000000..656aec92 --- /dev/null +++ b/demo/src/main/resources/commands/OpenPageableGUI.groovy @@ -0,0 +1,43 @@ +import it.angrybear.yagl.GUIManager +import it.angrybear.yagl.guis.GUIType +import it.angrybear.yagl.guis.PageableGUI +import it.angrybear.yagl.items.BukkitItem +import it.angrybear.yagl.utils.EnumUtils +import org.bukkit.Material +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) + try { + PageableGUI gui + try { + gui = PageableGUI.newGUI(EnumUtils.valueOf(GUIType, args[0])).setPages(Integer.valueOf(args[1])) + } catch (IllegalArgumentException ignored) { + gui = PageableGUI.newGUI(Integer.valueOf(args[0])).setPages(Integer.valueOf(args[1])) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /openpageablegui ') + return + } + def size = gui.size() + def middle = (int) Math.min(size / 2, 9 / 2) + if (size > 1) { + size -= 1 + gui.setContents(size - middle, BukkitItem.newItem(Material.OBSIDIAN).setDisplayName('&7Page: &e')) + .setPreviousPage(size - middle * 2, BukkitItem.newItem(Material.REDSTONE_BLOCK) + .setDisplayName('&7Go to page &e')) + .setNextPage(size, BukkitItem.newItem(Material.EMERALD_BLOCK) + .setDisplayName('&7Go to page &e')) + } + + gui.setTitle('Page #') + .onClickOutside((v, g) -> v.sendMessage('Please only click inside me!')) + .onOpenGUI((v, g) -> v.sendMessage(g.apply('Opening page '))) + .onCloseGUI((v, g) -> v.sendMessage('Goodbye!')) + .open(GUIManager.getViewer(sender)) + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/PlayEffect.groovy b/demo/src/main/resources/commands/PlayEffect.groovy new file mode 100644 index 00000000..7a4a5b1e --- /dev/null +++ b/demo/src/main/resources/commands/PlayEffect.groovy @@ -0,0 +1,61 @@ +/** + * Spawns the specified particle at the player's eyes location. + * If additional arguments are specified, and the particle supports it, + * they are converted to the corresponding ParticleOption. + * See {@link #getOption(Object, Object, Object, Object)} to find out how. + */ +import it.angrybear.yagl.Color +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.particles.ColorParticleOption +import it.angrybear.yagl.particles.LegacyParticleType +import it.angrybear.yagl.particles.MaterialDataOption +import it.angrybear.yagl.particles.Particle +import it.angrybear.yagl.particles.PotionParticleOption +import it.angrybear.yagl.particles.PrimitiveParticleOption +import it.angrybear.yagl.wrappers.Potion +import it.fulminazzo.fulmicollection.objects.Refl +import org.bukkit.entity.Player + +static getOption(sender, particleType, optionType, args) { + if (optionType == PotionParticleOption) + new PotionParticleOption(new Potion(args[0], Integer.valueOf(args[1]), + Boolean.valueOf(args[2]), Boolean.valueOf(args[3]))) + else if (optionType == MaterialDataOption) + new MaterialDataOption(args[0]) + else if (optionType == ColorParticleOption) + new ColorParticleOption(Color.fromARGB(args[0])) + else if (particleType == LegacyParticleType.SMOKE) + new PrimitiveParticleOption<>(args[0]) + else if (particleType == LegacyParticleType.VILLAGER_PLANT_GROW) + new PrimitiveParticleOption<>(Integer.valueOf(args[0])) + else if (particleType == LegacyParticleType.ITEM_BREAK) + new PrimitiveParticleOption<>(args[0]) + else if (particleType == LegacyParticleType.COMPOSTER_FILL_ATTEMPT) + new PrimitiveParticleOption<>(Boolean.valueOf(args[0])) + else if (particleType == LegacyParticleType.BONE_MEAL_USE) + new PrimitiveParticleOption<>(Integer.valueOf(args[0])) + else if (particleType == LegacyParticleType.ELECTRIC_SPARK) + new PrimitiveParticleOption<>(args[0]) + else throw new IllegalArgumentException("Cannot get particle option of ${optionType}") +} + +def run = { sender, label, args -> + if (sender instanceof Player) { + try { + LegacyParticleType type = LegacyParticleType.valueOf(args[0]) + Class optionType = new Refl<>(type).getFieldObject('optionType') + Particle particle + if (args.length > 1 && optionType != null) { + def option = getOption(sender, type, optionType, Arrays.copyOfRange(args, 1, args.length)) + particle = type.create(option) + } else particle = type.create() + WrappersAdapter.spawnEffect(sender, particle, sender.eyeLocation) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /playeffect ') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + } else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/PlayParticle.groovy b/demo/src/main/resources/commands/PlayParticle.groovy new file mode 100644 index 00000000..5d4de4d8 --- /dev/null +++ b/demo/src/main/resources/commands/PlayParticle.groovy @@ -0,0 +1,62 @@ +/** + * Spawns the specified particle at the player's eyes location. + * If additional arguments are specified, and the particle supports it, + * they are converted to the corresponding ParticleOption. + * See {@link #getOption(Object, Object, Object, Object)} to find out how. + */ +import it.angrybear.yagl.Color +import it.angrybear.yagl.ItemAdapter +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.particles.BlockDataOption +import it.angrybear.yagl.particles.DustParticleOption +import it.angrybear.yagl.particles.DustTransitionParticleOption +import it.angrybear.yagl.particles.ItemParticleOption +import it.angrybear.yagl.particles.Particle +import it.angrybear.yagl.particles.ParticleType +import it.angrybear.yagl.particles.PrimitiveParticleOption +import it.fulminazzo.fulmicollection.objects.Refl +import org.bukkit.Location +import org.bukkit.entity.Player +import org.bukkit.inventory.EquipmentSlot + +static getOption(sender, particleType, optionType, args) { + if (optionType == DustParticleOption) + new DustParticleOption(Color.fromARGB(args[0]), Float.valueOf(args[1])) + if (optionType == DustTransitionParticleOption) + new DustTransitionParticleOption(Color.fromARGB(args[0]), Color.fromARGB(args[1]), Float.valueOf(args[2])) + else if (optionType == ItemParticleOption) + ItemAdapter.itemStackToItem(sender.inventory.getItem(EquipmentSlot.HAND)) + else if (optionType == BlockDataOption) + new BlockDataOption(args[0]) + else if (particleType == ParticleType.VIBRATION) { + Location start = sender.location + Location end = start.clone().add(0, 10, 0) + def dest = new org.bukkit.Vibration.Destination.BlockDestination(end) + new PrimitiveParticleOption<>(new org.bukkit.Vibration(start, dest, Integer.valueOf(args[0]))) + } else if (particleType == ParticleType.SCULK_CHARGE) + new PrimitiveParticleOption<>(Float.valueOf(args[0])) + else if (particleType == ParticleType.SHRIEK) + new PrimitiveParticleOption<>(Integer.valueOf(args[0])) + else throw new IllegalArgumentException("Cannot get particle option of ${optionType}") +} + +def run = { sender, label, args -> + if (sender instanceof Player) { + try { + ParticleType type = ParticleType.valueOf(args[0]) + Class optionType = new Refl<>(type).getFieldObject('optionType') + Particle particle + if (args.length > 1 && optionType != null) { + def option = getOption(sender, type, optionType, Arrays.copyOfRange(args, 1, args.length)) + particle = type.create(option) + } else particle = type.create() + WrappersAdapter.spawnParticle(sender, particle, sender.eyeLocation, 1) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /playparticle ') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + } else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/PlayYAGLCustomSound.groovy b/demo/src/main/resources/commands/PlayYAGLCustomSound.groovy new file mode 100644 index 00000000..2b81a2ee --- /dev/null +++ b/demo/src/main/resources/commands/PlayYAGLCustomSound.groovy @@ -0,0 +1,18 @@ +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.wrappers.Sound +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) { + try { + Sound sound = new Sound(args[0], Float.valueOf(args[1]), Float.valueOf(args[2]), args[3]) + WrappersAdapter.playCustomSound(sender, sound) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /playsound ') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + } else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/commands/PlayYAGLSound.groovy b/demo/src/main/resources/commands/PlayYAGLSound.groovy new file mode 100644 index 00000000..1c855482 --- /dev/null +++ b/demo/src/main/resources/commands/PlayYAGLSound.groovy @@ -0,0 +1,18 @@ +import it.angrybear.yagl.WrappersAdapter +import it.angrybear.yagl.wrappers.Sound +import org.bukkit.entity.Player + +def run = { sender, label, args -> + if (sender instanceof Player) { + try { + Sound sound = new Sound(args[0], Float.valueOf(args[1]), Float.valueOf(args[2]), args[3]) + WrappersAdapter.playSound(sender, sound) + } catch (IndexOutOfBoundsException ignored) { + sender.sendMessage('Usage: /playsound ') + } catch (NumberFormatException ignored) { + // auto-generated code + } catch (Exception e) { + sender.sendMessage(e.message) + } + } else sender.sendMessage('Console cannot execute this command!') +} diff --git a/demo/src/main/resources/plugin.yml b/demo/src/main/resources/plugin.yml new file mode 100644 index 00000000..4b3349e3 --- /dev/null +++ b/demo/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +name: "${name}" +description: "${description}" +version: "${version}" +authors: ["${author}"] +main: "${group}.${name_lower}.${name}" +api-version: 1.13 diff --git a/demo/src/test/java/it/angrybear/yagl/YAGLTest.java b/demo/src/test/java/it/angrybear/yagl/YAGLTest.java new file mode 100644 index 00000000..30bc9b06 --- /dev/null +++ b/demo/src/test/java/it/angrybear/yagl/YAGLTest.java @@ -0,0 +1,193 @@ +package it.angrybear.yagl; + +import it.angrybear.yagl.commands.ShellCommand; +import it.fulminazzo.fulmicollection.objects.Refl; +import it.fulminazzo.fulmicollection.utils.JarUtils; +import it.fulminazzo.jbukkit.BukkitUtils; +import it.fulminazzo.yamlparser.utils.FileUtils; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.SimplePluginManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.logging.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class YAGLTest { + private YAGL plugin; + private Handler handler; + private ByteArrayOutputStream output; + + @BeforeEach + void setUp() throws IOException { + BukkitUtils.setupServer(); + + this.output = new ByteArrayOutputStream(); + this.handler = new StreamHandler(this.output, new SimpleFormatter()); + this.handler.setLevel(Level.ALL); + Logger logger = Logger.getAnonymousLogger(); + logger.addHandler(handler); + + this.plugin = mock(YAGL.class); + File dataDir = new File("build/resources/test/plugin"); + if (dataDir.exists()) FileUtils.deleteFolder(dataDir); + when(this.plugin.getDataFolder()).thenReturn(dataDir); + new Refl<>(this.plugin).setFieldObject("commands", new LinkedList<>()); + when(this.plugin.getName()).thenReturn("YAGL-Plugin"); + doCallRealMethod().when(this.plugin).loadCommands(); + doCallRealMethod().when(this.plugin).unloadCommands(); + doCallRealMethod().when(this.plugin).saveDefaultCommands(any()); + when(this.plugin.getLogger()).thenReturn(logger); + } + + @Test + void testOnEnableShouldCallLoadCommands() { + Server server = mock(Server.class); + when(server.getPluginManager()).thenReturn(mock(PluginManager.class)); + new Refl<>(Bukkit.class).setFieldObject("server", server); + YAGL plugin = mock(YAGL.class); + when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); + new Refl<>(plugin).setFieldObject("commands", new ArrayList<>()); + doCallRealMethod().when(plugin).onEnable(); + plugin.onEnable(); + verify(plugin, atLeastOnce()).loadCommands(); + } + + @Test + void testOnDisableShouldCallUnloadCommands() { + YAGL plugin = mock(YAGL.class); + doCallRealMethod().when(plugin).onDisable(); + plugin.onDisable(); + verify(plugin, atLeastOnce()).unloadCommands(); + } + + @Test + void testLoadCommands() { + this.plugin.loadCommands(); + List commands = new Refl<>(this.plugin).getFieldObject("commands"); + assertNotNull(commands); + assertEquals(1, commands.size(), "Expected one command"); + ShellCommand command = commands.get(0); + assertEquals("mock", command.getName()); + } + + @Test + void testLoadCommandsFromAlreadyPresentDirectory() throws IOException { + FileUtils.createFolder(new File(this.plugin.getDataFolder(), "commands")); + this.plugin.loadCommands(); + List commands = new Refl<>(this.plugin).getFieldObject("commands"); + assertNotNull(commands); + assertEquals(0, commands.size(), "Expected zero commands"); + } + + @Test + void testSaveDefaultCommands() throws IOException { + final String fileName = "commands/mock.groovy"; + File expected = new File(this.plugin.getDataFolder(), fileName); + String expectedContent = FileUtils.readFileToString(new File("build/resources/test/" + fileName)); + + this.plugin.saveDefaultCommands(expected.getParentFile()); + + assertTrue(expected.exists(), String.format("Expected file '%s' to exist", expected.getAbsolutePath())); + String actualContent = FileUtils.readFileToString(expected); + + assertEquals(expectedContent, actualContent, "Content did not match expected"); + } + + @Test + void testLoadAndRegisterCommands() { + setupPluginManager(); + CommandMap commandMap = new Refl<>(Bukkit.getPluginManager()).getFieldObject("commandMap"); + assertNotNull(commandMap); + assertNull(commandMap.getCommand("mock"), "Not expected 'mock' command but some was found"); + this.plugin.loadCommands(); + assertNotNull(commandMap.getCommand("mock"), "Expected 'mock' command but none was found"); + } + + @Test + void testUnloadAndUnregisterCommands() { + setupPluginManager(); + CommandMap commandMap = new Refl<>(Bukkit.getPluginManager()).getFieldObject("commandMap"); + assertNotNull(commandMap); + + ShellCommand shellCommand = new ShellCommand(new File(this.plugin.getDataFolder(), "commands/mock.groovy")); + Map knownCommands = new Refl<>(commandMap).getFieldObject("knownCommands"); + assertNotNull(knownCommands); + knownCommands.put("mock", shellCommand); + + List commands = new Refl<>(this.plugin).getFieldObject("commands"); + assertNotNull(commands); + commands.add(shellCommand); + + assertNotNull(commandMap.getCommand("mock"), "Expected 'mock' command but none was found"); + this.plugin.unloadCommands(); + assertNull(commandMap.getCommand("mock"), "Not expected 'mock' command but some was found"); + } + + @Test + void testInvalidCommandsDirectory() throws IOException { + File file = new File(this.plugin.getDataFolder(), "commands"); + if (file.isFile()) FileUtils.deleteFile(file); + if (file.isDirectory()) FileUtils.deleteFolder(file); + FileUtils.createNewFile(file); + assertDoesNotThrow(() -> this.plugin.loadCommands()); + file.delete(); + } + + @Test + void testNullKnownCommands() { + final String field = "knownCommands"; + + setupPluginManager(); + new Refl<>(Bukkit.getPluginManager()).getFieldRefl("commandMap").setFieldObject(field, null); + + assertDoesNotThrow(() -> this.plugin.unloadCommands()); + + String printOutput = getOutput(); + assertTrue(printOutput.contains(field), String.format("'%s' did not contain '%s'", printOutput, field)); + } + + @Test + void testNullPluginManager() { + assertDoesNotThrow(() -> this.plugin.loadCommands()); + } + + @Test + void simulateJarEntries() { + setupPluginManager(); + List entries = Arrays.asList("ignored", "also-ignored", "commands", "commands/", "commands/mock.groovy"); + try (MockedStatic ignored = mockStatic(JarUtils.class)) { + when(JarUtils.getEntries((Class) any(), anyString())).thenAnswer(a -> entries.iterator()); + + this.plugin.loadCommands(); + + List commands = new Refl<>(this.plugin).getFieldObject("commands"); + assertNotNull(commands); + assertEquals(1, commands.size(), "Commands size did not match expected"); + } + } + + private void setupPluginManager() { + Server server = Bukkit.getServer(); + SimpleCommandMap commandMap = new SimpleCommandMap(server); + SimplePluginManager pluginManager = new SimplePluginManager(server, commandMap); + when(server.getPluginManager()).thenReturn(pluginManager); + } + + private String getOutput() { + this.handler.flush(); + return this.output.toString(); + } +} \ No newline at end of file diff --git a/demo/src/test/java/it/angrybear/yagl/commands/ShellCommandTest.java b/demo/src/test/java/it/angrybear/yagl/commands/ShellCommandTest.java new file mode 100644 index 00000000..65ef6f29 --- /dev/null +++ b/demo/src/test/java/it/angrybear/yagl/commands/ShellCommandTest.java @@ -0,0 +1,60 @@ +package it.angrybear.yagl.commands; + +import it.fulminazzo.fulmicollection.objects.Refl; +import org.bukkit.command.CommandSender; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.*; + +class ShellCommandTest { + + @Test + void testExecute() { + // Prepare file + File file = new File("build/resources/test/shell-command.groovy"); + // Prepare sender + AtomicReference message = new AtomicReference<>(); + CommandSender sender = mock(CommandSender.class); + doAnswer(a -> { + message.set(a.getArgument(0)); + return null; + }).when(sender).sendMessage(anyString()); + + ShellCommand shellCommand = new ShellCommand(file); + shellCommand.execute(sender, "command", new String[0]); + + assertEquals("Hello world", message.get()); + } + + @Test + void testNumberFormatExceptionReplacement() { + String expected = "def run = { sender, label, args ->\n" + + " try {\n" + + " sender.sendMessage(\"Your number is ${Integer.valueOf(args[0])}\")\n" + + " } catch (NumberFormatException ignored) {\n" + + " sender.sendMessage(e.message.replace('For input string: ', 'Invalid number '))\n" + + " }\n" + + "}\n" + + "run(sender, label, args)"; + File file = new File("build/resources/test/number-format-exception-command.groovy"); + + ShellCommand shellCommand = new ShellCommand(file); + String actual = new Refl<>(shellCommand).getFieldObject("shellCode"); + + assertEquals(expected, actual); + } + + @Test + void testTabCompleteShouldNotBeNull() throws IOException { + File file = File.createTempFile("build/resources/test", "file.groovy"); + assertNotNull(new ShellCommand(file).tabComplete(mock(CommandSender.class), + "command", new String[0])); + } + +} \ No newline at end of file diff --git a/demo/src/test/resources/commands/mock.groovy b/demo/src/test/resources/commands/mock.groovy new file mode 100644 index 00000000..f9cab74f --- /dev/null +++ b/demo/src/test/resources/commands/mock.groovy @@ -0,0 +1 @@ +def str = 'content' \ No newline at end of file diff --git a/demo/src/test/resources/number-format-exception-command.groovy b/demo/src/test/resources/number-format-exception-command.groovy new file mode 100644 index 00000000..9d1fa92a --- /dev/null +++ b/demo/src/test/resources/number-format-exception-command.groovy @@ -0,0 +1,7 @@ +def run = { sender, label, args -> + try { + sender.sendMessage("Your number is ${Integer.valueOf(args[0])}") + } catch (NumberFormatException ignored) { + // auto-generated code + } +} \ No newline at end of file diff --git a/demo/src/test/resources/shell-command.groovy b/demo/src/test/resources/shell-command.groovy new file mode 100644 index 00000000..aa6ca73a --- /dev/null +++ b/demo/src/test/resources/shell-command.groovy @@ -0,0 +1,3 @@ +def run = { sender, label, args -> + sender.sendMessage("Hello world") +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 58990277..cafc6391 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,9 @@ [versions] delombok = "8.4" +groovy = "4.0.16" -fulmicollection = "1.5.2" -yamlparser = "1.6.2" +fulmicollection = "1.6" +yamlparser = "1.6.3" spigot = "1.14-R0.1-SNAPSHOT" spigot_obsolete = "1.8.8-R0.1-SNAPSHOT" @@ -17,6 +18,7 @@ mockito = "4.11.0" jbukkit = "2.1" [libraries] +groovy = { module = "org.apache.groovy:groovy", version.ref = "groovy" } fulmicollection = { module = "it.fulminazzo:FulmiCollection", version.ref = "fulmicollection" } yamlparser = { module = "it.fulminazzo:YAMLParser", version.ref = "yamlparser" } diff --git a/gui/base/src/main/java/it/angrybear/yagl/Metadatable.java b/gui/base/src/main/java/it/angrybear/yagl/Metadatable.java index 84de5885..db5f48c6 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/Metadatable.java +++ b/gui/base/src/main/java/it/angrybear/yagl/Metadatable.java @@ -123,15 +123,17 @@ default boolean hasVariable(final @NotNull String name) { @SuppressWarnings("unchecked") default T apply(final T object) { if (object == null) return null; + else if (object.getClass().isEnum()) return object; else if (object instanceof String) return (T) apply((String) object); else if (object instanceof Collection) return (T) apply((Collection) object); else if (object instanceof Map) return (T) apply((Map) object); else if (!ReflectionUtils.isPrimitiveOrWrapper(object.getClass())) { final Refl refl = new Refl<>(object); - for (Field field : refl.getNonStaticFields()) { - Object o = refl.getFieldObject(field); - refl.setFieldObject(field, apply(o)); - } + for (Field field : refl.getNonStaticFields()) + ReflectionUtils.setAccessible(field) + .map(f -> f.get(object)) + .filter(o -> !o.toString().contains("Lambda")) + .ifPresent(o -> field.set(object, apply(o))); } return object; } diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/DataGUI.java b/gui/base/src/main/java/it/angrybear/yagl/guis/DataGUI.java new file mode 100644 index 00000000..80babc32 --- /dev/null +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/DataGUI.java @@ -0,0 +1,869 @@ +package it.angrybear.yagl.guis; + +import it.angrybear.yagl.Metadatable; +import it.angrybear.yagl.actions.BiGUIAction; +import it.angrybear.yagl.actions.GUIAction; +import it.angrybear.yagl.contents.GUIContent; +import it.angrybear.yagl.contents.ItemGUIContent; +import it.angrybear.yagl.exceptions.NotImplemented; +import it.angrybear.yagl.items.Item; +import it.angrybear.yagl.viewers.Viewer; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * An implementation of {@link PageableGUI} that allows data to be automatically displayed in a multi-paged format. + * + * @param the type of the data + */ +@SuppressWarnings("unchecked") +public class DataGUI extends PageableGUI { + private static final String ERROR_MESSAGE = "Pages are dynamically calculated when opening this GUI. They cannot be singly edited"; + + @Getter + private final @NotNull List data; + private final @NotNull Function dataConverter; + + private DataGUI() { + this.data = new LinkedList<>(); + this.dataConverter = t -> { + throw new NotImplemented(); + }; + } + + private DataGUI(final int size) { + this(size, t -> { + throw new NotImplemented(); + }); + } + + /** + * Instantiates a new Data gui. + * + * @param size the size + * @param dataConverter the data converter + */ + DataGUI(final int size, final @NotNull Function dataConverter) { + super(size); + this.data = new LinkedList<>(); + this.dataConverter = dataConverter; + } + + /** + * Instantiates a new Data gui. + * + * @param type the type + * @param dataConverter the data converter + */ + DataGUI(final @NotNull GUIType type, final @NotNull Function dataConverter) { + super(type); + this.data = new LinkedList<>(); + this.dataConverter = dataConverter; + } + + /** + * Adds the given data. + * + * @param data the data + * @return this gui + */ + public @NotNull DataGUI addData(final T @NotNull ... data) { + return addData(Arrays.asList(data)); + } + + /** + * Adds the given data. + * + * @param data the data + * @return this gui + */ + public @NotNull DataGUI addData(final @NotNull Collection data) { + this.data.addAll(data); + return this; + } + + /** + * Clears the current data and sets the given data. + * + * @param data the data + * @return the data + */ + public @NotNull DataGUI setData(final T @NotNull ... data) { + return setData(Arrays.asList(data)); + } + + /** + * Clears the current data and sets the given data. + * + * @param data the data + * @return the data + */ + public @NotNull DataGUI setData(final @NotNull Collection data) { + return clearData().addData(data); + } + + /** + * Removes the data equal to any of the given data. + * + * @param data the data + * @return this gui + */ + public @NotNull DataGUI removeData(final T @NotNull ... data) { + return removeData(Arrays.asList(data)); + } + + /** + * Removes the data equal to any of the given data. + * + * @param data the data + * @return this gui + */ + public @NotNull DataGUI removeData(final @NotNull Collection data) { + return removeData(t -> data.stream().anyMatch(c -> Objects.equals(c, t))); + } + + /** + * Removes the data that match the given {@link Predicate} function. + * + * @param function the function + * @return this gui + */ + public @NotNull DataGUI removeData(final @NotNull Predicate function) { + this.data.removeIf(function); + return this; + } + + /** + * Removes all the data. + * + * @return this gui + */ + public @NotNull DataGUI clearData() { + this.data.clear(); + return this; + } + + @Override + public void open(@NotNull Viewer viewer, int page) { + fillContents(prepareOpenGUI(this.templateGUI, page), page).open(viewer); + } + + private @NotNull GUI fillContents(final @NotNull GUI gui, final int page) { + int emptySlots = emptySlots().size(); + int min = emptySlots * page; + if (min >= this.data.size()) + throw new IllegalArgumentException(String.format("No such page '%s'", page)); + if (page > 0) min++; + int size = Math.min(gui.emptySlots().size() + min, this.data.size()); + for (int i = min; i < size; i++) { + T data = this.data.get(i); + GUIContent content = this.dataConverter.apply(data); + gui.addContent(content); + } + return gui; + } + + /** + * Gets the first page empty slots. + * + * @return the slots + */ + protected int getFirstPageEmptySlots() { + int slots = getPageEmptySlots(); + if (this.previousPage.isPresent()) slots++; + return slots; + } + + /** + * Gets the last page empty slots. + * + * @return the slots + */ + protected int getLastPageEmptySlots() { + int slots = getPageEmptySlots(); + if (this.nextPage.isPresent()) slots++; + return slots; + } + + /** + * Gets a general page empty slots. + * + * @return the slots + */ + protected int getPageEmptySlots() { + final Set slots = emptySlots(); + this.previousPage.ifPresent((i, p) -> slots.remove(i)); + this.nextPage.ifPresent((i, p) -> slots.remove(i)); + return slots.size(); + } + + /** + * Gets the number of pages based on the amount of data provided. + * + * @return the pages + */ + @Override + public int pages() { + int dataSize = this.data.size(); + if (dataSize == 0) throw new IllegalArgumentException("Cannot set empty data"); + final int emptySlots = getPageEmptySlots(); + final int firstPageSlots = getFirstPageEmptySlots(); + final int lastPageSlots = getLastPageEmptySlots(); + int pages = 1; + // First page + dataSize -= firstPageSlots; + if (dataSize < 1) return pages; + if (emptySlots < 1) + throw new IllegalStateException(String.format("Not enough empty slots available to set %s items of data", dataSize)); + // Last page + pages++; + dataSize -= lastPageSlots; + if (dataSize < 1) return pages; + // Other pages + while (dataSize > 0) { + pages++; + dataSize -= emptySlots; + } + return pages; + } + + /** + * Gets the {@link GUI} page from the given index. + * The index starts from 0. + * + * @param page the page + * @deprecated In {@link DataGUI}s pages are not pre-defined, but rather calculated upon opening. + * @return the corresponding {@link GUI} page + */ + @Override + @Deprecated + public @NotNull GUI getPage(int page) { + throw new IllegalStateException(ERROR_MESSAGE); + } + + /** + * Sets pages. + * + * @param pages the pages + * @deprecated In {@link DataGUI}s pages are not pre-defined, but rather calculated upon opening. + * @return this gui + */ + @Override + @Deprecated + public @NotNull DataGUI setPages(int pages) { + throw new IllegalStateException(ERROR_MESSAGE); + } + + @Override + public @NotNull DataGUI setPreviousPage(int slot, @NotNull Item previousPage) { + return (DataGUI) super.setPreviousPage(slot, previousPage); + } + + @Override + public @NotNull DataGUI setPreviousPage(int slot, @NotNull GUIContent previousPage) { + return (DataGUI) super.setPreviousPage(slot, previousPage); + } + + @Override + public @NotNull DataGUI unsetPreviousPage() { + return (DataGUI) super.unsetPreviousPage(); + } + + @Override + public @NotNull DataGUI setNextPage(int slot, @NotNull Item nextPage) { + return (DataGUI) super.setNextPage(slot, nextPage); + } + + @Override + public @NotNull DataGUI setNextPage(int slot, @NotNull GUIContent nextPage) { + return (DataGUI) super.setNextPage(slot, nextPage); + } + + @Override + public @NotNull DataGUI unsetNextPage() { + return (DataGUI) super.unsetNextPage(); + } + + @Override + public @NotNull DataGUI setTitle(@Nullable String title) { + return (DataGUI) super.setTitle(title); + } + + @Override + public @NotNull DataGUI setMovable(int slot, boolean movable) { + return (DataGUI) super.setMovable(slot, movable); + } + + @Override + public @NotNull DataGUI addContent(GUIContent @NotNull ... contents) { + return (DataGUI) super.addContent(contents); + } + + @Override + public @NotNull DataGUI setContents(int slot, GUIContent @NotNull ... contents) { + return (DataGUI) super.setContents(slot, contents); + } + + @Override + public @NotNull DataGUI setContents(int slot, @NotNull Collection contents) { + return (DataGUI) super.setContents(slot, contents); + } + + @Override + public @NotNull DataGUI unsetContent(int slot) { + return (DataGUI) super.unsetContent(slot); + } + + @Override + public @NotNull DataGUI onClickOutside(@NotNull GUIAction action) { + return (DataGUI) super.onClickOutside(action); + } + + @Override + public @NotNull DataGUI onOpenGUI(@NotNull GUIAction action) { + return (DataGUI) super.onOpenGUI(action); + } + + @Override + public @NotNull DataGUI onCloseGUI(@NotNull GUIAction action) { + return (DataGUI) super.onCloseGUI(action); + } + + @Override + public @NotNull DataGUI onChangeGUI(@NotNull BiGUIAction action) { + return (DataGUI) super.onChangeGUI(action); + } + + @Override + public @NotNull DataGUI setAllMovable() { + return (DataGUI) super.setAllMovable(); + } + + @Override + public @NotNull DataGUI setAllUnmovable() { + return (DataGUI) super.setAllUnmovable(); + } + + @Override + public @NotNull DataGUI addContent(Item @NotNull ... contents) { + return (DataGUI) super.addContent(contents); + } + + @Override + public @NotNull DataGUI addContent(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.addContent(contents); + } + + @Override + public @NotNull DataGUI setContents(int slot, Item @NotNull ... contents) { + return (DataGUI) super.setContents(slot, contents); + } + + @Override + public @NotNull DataGUI setContents(int slot, ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setContents(slot, contents); + } + + @Override + public @NotNull DataGUI setAllSides(Item @NotNull ... contents) { + return (DataGUI) super.setAllSides(contents); + } + + @Override + public @NotNull DataGUI setAllSides(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setAllSides(contents); + } + + @Override + public @NotNull DataGUI setAllSides(GUIContent @NotNull ... contents) { + return (DataGUI) super.setAllSides(contents); + } + + @Override + public @NotNull DataGUI setAllSides(@NotNull Collection contents) { + return (DataGUI) super.setAllSides(contents); + } + + @Override + public @NotNull DataGUI unsetAllSides() { + return (DataGUI) super.unsetAllSides(); + } + + @Override + public @NotNull DataGUI setTopAndBottomSides(Item @NotNull ... contents) { + return (DataGUI) super.setTopAndBottomSides(contents); + } + + @Override + public @NotNull DataGUI setTopAndBottomSides(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setTopAndBottomSides(contents); + } + + @Override + public @NotNull DataGUI setTopAndBottomSides(GUIContent @NotNull ... contents) { + return (DataGUI) super.setTopAndBottomSides(contents); + } + + @Override + public @NotNull DataGUI setTopAndBottomSides(@NotNull Collection contents) { + return (DataGUI) super.setTopAndBottomSides(contents); + } + + @Override + public @NotNull DataGUI unsetTopAndBottomSides() { + return (DataGUI) super.unsetTopAndBottomSides(); + } + + @Override + public @NotNull DataGUI setLeftAndRightSides(Item @NotNull ... contents) { + return (DataGUI) super.setLeftAndRightSides(contents); + } + + @Override + public @NotNull DataGUI setLeftAndRightSides(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setLeftAndRightSides(contents); + } + + @Override + public @NotNull DataGUI setLeftAndRightSides(GUIContent @NotNull ... contents) { + return (DataGUI) super.setLeftAndRightSides(contents); + } + + @Override + public @NotNull DataGUI setLeftAndRightSides(@NotNull Collection contents) { + return (DataGUI) super.setLeftAndRightSides(contents); + } + + @Override + public @NotNull DataGUI unsetLeftAndRightSides() { + return (DataGUI) super.unsetLeftAndRightSides(); + } + + @Override + public @NotNull DataGUI setTopSide(Item @NotNull ... contents) { + return (DataGUI) super.setTopSide(contents); + } + + @Override + public @NotNull DataGUI setTopSide(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setTopSide(contents); + } + + @Override + public @NotNull DataGUI setTopSide(GUIContent @NotNull ... contents) { + return (DataGUI) super.setTopSide(contents); + } + + @Override + public @NotNull DataGUI setTopSide(@NotNull Collection contents) { + return (DataGUI) super.setTopSide(contents); + } + + @Override + public @NotNull DataGUI unsetTopSide() { + return (DataGUI) super.unsetTopSide(); + } + + @Override + public @NotNull DataGUI setLeftSide(Item @NotNull ... contents) { + return (DataGUI) super.setLeftSide(contents); + } + + @Override + public @NotNull DataGUI setLeftSide(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setLeftSide(contents); + } + + @Override + public @NotNull DataGUI setLeftSide(GUIContent @NotNull ... contents) { + return (DataGUI) super.setLeftSide(contents); + } + + @Override + public @NotNull DataGUI setLeftSide(@NotNull Collection contents) { + return (DataGUI) super.setLeftSide(contents); + } + + @Override + public @NotNull DataGUI unsetLeftSide() { + return (DataGUI) super.unsetLeftSide(); + } + + @Override + public @NotNull DataGUI setBottomSide(Item @NotNull ... contents) { + return (DataGUI) super.setBottomSide(contents); + } + + @Override + public @NotNull DataGUI setBottomSide(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setBottomSide(contents); + } + + @Override + public @NotNull DataGUI setBottomSide(GUIContent @NotNull ... contents) { + return (DataGUI) super.setBottomSide(contents); + } + + @Override + public @NotNull DataGUI setBottomSide(@NotNull Collection contents) { + return (DataGUI) super.setBottomSide(contents); + } + + @Override + public @NotNull DataGUI unsetBottomSide() { + return (DataGUI) super.unsetBottomSide(); + } + + @Override + public @NotNull DataGUI setRightSide(Item @NotNull ... contents) { + return (DataGUI) super.setRightSide(contents); + } + + @Override + public @NotNull DataGUI setRightSide(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setRightSide(contents); + } + + @Override + public @NotNull DataGUI setRightSide(GUIContent @NotNull ... contents) { + return (DataGUI) super.setRightSide(contents); + } + + @Override + public @NotNull DataGUI setRightSide(@NotNull Collection contents) { + return (DataGUI) super.setRightSide(contents); + } + + @Override + public @NotNull DataGUI unsetRightSide() { + return (DataGUI) super.unsetRightSide(); + } + + @Override + public @NotNull DataGUI setNorthWest(Item @NotNull ... contents) { + return (DataGUI) super.setNorthWest(contents); + } + + @Override + public @NotNull DataGUI setNorthWest(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setNorthWest(contents); + } + + @Override + public @NotNull DataGUI setNorthWest(GUIContent @NotNull ... contents) { + return (DataGUI) super.setNorthWest(contents); + } + + @Override + public @NotNull DataGUI unsetNorthWest() { + return (DataGUI) super.unsetNorthWest(); + } + + @Override + public @NotNull DataGUI setNorth(Item @NotNull ... contents) { + return (DataGUI) super.setNorth(contents); + } + + @Override + public @NotNull DataGUI setNorth(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setNorth(contents); + } + + @Override + public @NotNull DataGUI setNorth(GUIContent @NotNull ... contents) { + return (DataGUI) super.setNorth(contents); + } + + @Override + public @NotNull DataGUI unsetNorth() { + return (DataGUI) super.unsetNorth(); + } + + @Override + public @NotNull DataGUI setNorthEast(Item @NotNull ... contents) { + return (DataGUI) super.setNorthEast(contents); + } + + @Override + public @NotNull DataGUI setNorthEast(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setNorthEast(contents); + } + + @Override + public @NotNull DataGUI setNorthEast(GUIContent @NotNull ... contents) { + return (DataGUI) super.setNorthEast(contents); + } + + @Override + public @NotNull DataGUI unsetNorthEast() { + return (DataGUI) super.unsetNorthEast(); + } + + @Override + public @NotNull DataGUI setMiddleWest(Item @NotNull ... contents) { + return (DataGUI) super.setMiddleWest(contents); + } + + @Override + public @NotNull DataGUI setMiddleWest(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddleWest(contents); + } + + @Override + public @NotNull DataGUI setMiddleWest(GUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddleWest(contents); + } + + @Override + public @NotNull DataGUI unsetMiddleWest() { + return (DataGUI) super.unsetMiddleWest(); + } + + @Override + public @NotNull DataGUI setMiddle(Item @NotNull ... contents) { + return (DataGUI) super.setMiddle(contents); + } + + @Override + public @NotNull DataGUI setMiddle(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddle(contents); + } + + @Override + public @NotNull DataGUI setMiddle(GUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddle(contents); + } + + @Override + public @NotNull DataGUI unsetMiddle() { + return (DataGUI) super.unsetMiddle(); + } + + @Override + public @NotNull DataGUI setMiddleEast(Item @NotNull ... contents) { + return (DataGUI) super.setMiddleEast(contents); + } + + @Override + public @NotNull DataGUI setMiddleEast(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddleEast(contents); + } + + @Override + public @NotNull DataGUI setMiddleEast(GUIContent @NotNull ... contents) { + return (DataGUI) super.setMiddleEast(contents); + } + + @Override + public @NotNull DataGUI unsetMiddleEast() { + return (DataGUI) super.unsetMiddleEast(); + } + + @Override + public @NotNull DataGUI setSouthWest(Item @NotNull ... contents) { + return (DataGUI) super.setSouthWest(contents); + } + + @Override + public @NotNull DataGUI setSouthWest(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setSouthWest(contents); + } + + @Override + public @NotNull DataGUI setSouthWest(GUIContent @NotNull ... contents) { + return (DataGUI) super.setSouthWest(contents); + } + + @Override + public @NotNull DataGUI unsetSouthWest() { + return (DataGUI) super.unsetSouthWest(); + } + + @Override + public @NotNull DataGUI setSouth(Item @NotNull ... contents) { + return (DataGUI) super.setSouth(contents); + } + + @Override + public @NotNull DataGUI setSouth(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setSouth(contents); + } + + @Override + public @NotNull DataGUI setSouth(GUIContent @NotNull ... contents) { + return (DataGUI) super.setSouth(contents); + } + + @Override + public @NotNull DataGUI unsetSouth() { + return (DataGUI) super.unsetSouth(); + } + + @Override + public @NotNull DataGUI setSouthEast(Item @NotNull ... contents) { + return (DataGUI) super.setSouthEast(contents); + } + + @Override + public @NotNull DataGUI setSouthEast(ItemGUIContent @NotNull ... contents) { + return (DataGUI) super.setSouthEast(contents); + } + + @Override + public @NotNull DataGUI setSouthEast(GUIContent @NotNull ... contents) { + return (DataGUI) super.setSouthEast(contents); + } + + @Override + public @NotNull DataGUI unsetSouthEast() { + return (DataGUI) super.unsetSouthEast(); + } + + @Override + public @NotNull DataGUI clear() { + return (DataGUI) super.clear(); + } + + @Override + public @NotNull DataGUI onClickOutside(@NotNull String command) { + return (DataGUI) super.onClickOutside(command); + } + + @Override + public @NotNull DataGUI onOpenGUI(@NotNull String command) { + return (DataGUI) super.onOpenGUI(command); + } + + @Override + public @NotNull DataGUI onCloseGUI(@NotNull String command) { + return (DataGUI) super.onCloseGUI(command); + } + + @Override + public @NotNull DataGUI onChangeGUI(@NotNull String command) { + return (DataGUI) super.onChangeGUI(command); + } + + @Override + public @NotNull DataGUI setVariable(@NotNull String name, @NotNull String value) { + return (DataGUI) super.setVariable(name, value); + } + + @Override + public @NotNull DataGUI unsetVariable(@NotNull String name) { + return (DataGUI) super.unsetVariable(name); + } + + @Override + public @NotNull DataGUI copyAll(@NotNull GUI other, boolean replace) { + return (DataGUI) super.copyAll(other, replace); + } + + @Override + public @NotNull DataGUI copyFrom(@NotNull GUI other, boolean replace) { + return (DataGUI) super.copyFrom(other, replace); + } + + @Override + public DataGUI copy() { + return (DataGUI) super.copy(); + } + + @Override + public @NotNull DataGUI copyAll(@NotNull Metadatable other, boolean replace) { + return (DataGUI) super.copyAll(other, replace); + } + + @Override + public @NotNull DataGUI copyFrom(@NotNull Metadatable other, boolean replace) { + return (DataGUI) super.copyFrom(other, replace); + } + + /** + * Creates a new {@link DataGUI} with the given size and converter. + * + * @param the type of the data + * @param size the size + * @param dataConverter the data converter + * @return the data gui + */ + public static @NotNull DataGUI newGUI(final int size, final @NotNull Function dataConverter) { + return new DataGUI<>(size, dataConverter); + } + + /** + * Creates a new {@link DataGUI} with the given size, converter and data. + * + * @param the type of the data + * @param size the size + * @param dataConverter the data converter + * @param data the data + * @return the data gui + */ + @SafeVarargs + public static @NotNull DataGUI newGUI(final int size, final @NotNull Function dataConverter, + final T @NotNull ... data) { + return new DataGUI<>(size, dataConverter).setData(data); + } + + /** + * Creates a new {@link DataGUI} with the given size, converter and data. + * + * @param the type of the data + * @param size the size + * @param dataConverter the data converter + * @param data the data + * @return the data gui + */ + public static @NotNull DataGUI newGUI(final int size, final @NotNull Function dataConverter, + final @NotNull Collection data) { + return new DataGUI<>(size, dataConverter).setData(data); + } + + /** + * Creates a new {@link DataGUI} with the given type and converter. + * + * @param the type of the data + * @param type the type + * @param dataConverter the data converter + * @return the data gui + */ + public static @NotNull DataGUI newGUI(final @NotNull GUIType type, final @NotNull Function dataConverter) { + return new DataGUI<>(type, dataConverter); + } + + /** + * Creates a new {@link DataGUI} with the given type, converter and data. + * + * @param the type of the data + * @param type the type + * @param dataConverter the data converter + * @param data the data + * @return the data gui + */ + @SafeVarargs + public static @NotNull DataGUI newGUI(final @NotNull GUIType type, final @NotNull Function dataConverter, + final T @NotNull ... data) { + return new DataGUI<>(type, dataConverter).setData(data); + } + + /** + * Creates a new {@link DataGUI} with the given type, converter and data. + * + * @param the type of the data + * @param type the type + * @param dataConverter the data converter + * @param data the data + * @return the data gui + */ + public static @NotNull DataGUI newGUI(final @NotNull GUIType type, final @NotNull Function dataConverter, + final @NotNull Collection data) { + return new DataGUI<>(type, dataConverter).setData(data); + } + +} diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/GUI.java b/gui/base/src/main/java/it/angrybear/yagl/guis/GUI.java index aa98a5e8..1236b835 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/guis/GUI.java +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/GUI.java @@ -1163,6 +1163,18 @@ default int southEast() { */ int columns(); + /** + * Counts the empty slots of the current GUI. + * + * @return the slots + */ + default @NotNull Set emptySlots() { + Set slots = new HashSet<>(); + for (int i = 0; i < size(); i++) + if (getContents(i).isEmpty()) slots.add(i); + return slots; + } + /** * Removes all the contents in this GUI. * @@ -1361,7 +1373,7 @@ default GUI copy() { * @param size the size * @return the gui */ - static GUI newGUI(final int size) { + static @NotNull GUI newGUI(final int size) { return new DefaultGUI(size); } @@ -1374,7 +1386,7 @@ static GUI newGUI(final int size) { * @param size the size * @return the resizable gui */ - static ResizableGUI newResizableGUI(final int size) { + static @NotNull ResizableGUI newResizableGUI(final int size) { return new ResizableGUI(size); } @@ -1384,7 +1396,7 @@ static ResizableGUI newResizableGUI(final int size) { * @param type the type * @return the gui */ - static GUI newGUI(final GUIType type) { + static @NotNull GUI newGUI(final @NotNull GUIType type) { return new TypeGUI(type); } } diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/GUIImpl.java b/gui/base/src/main/java/it/angrybear/yagl/guis/GUIImpl.java index 6bad1dee..10247b74 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/guis/GUIImpl.java +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/GUIImpl.java @@ -20,9 +20,9 @@ abstract class GUIImpl extends FieldEquable implements GUI { protected static final int MAX_SIZE = 54; @Getter - protected String title; + protected @Nullable String title; protected List contents; - protected final Set movableSlots; + protected final @NotNull Set movableSlots; protected final Map variables = new HashMap<>(); protected GUIAction clickOutsideAction; @@ -202,7 +202,7 @@ protected int addSingle(final @NotNull GUIContent content, int index) { * @param copyFrom if not null, copy the contents of this list in the resulting one * @return the list */ - protected List createContents(int size, final List copyFrom) { + protected @NotNull List createContents(int size, final @Nullable List copyFrom) { List contents = new LinkedList<>(); for (int i = 0; i < size; i++) contents.add(null); if (copyFrom != null) @@ -215,7 +215,7 @@ protected List createContents(int size, final List copyFrom) * A type to keep track of multiple {@link GUIContent} for one slot. */ public static class Contents { - private final GUIContent[] contents; + private final GUIContent @NotNull [] contents; /** * Instantiates a new Contents. @@ -231,10 +231,21 @@ protected Contents(final GUIContent @NotNull ... contents) { * * @return the contents */ - public List getContents() { + public @NotNull List getContents() { return Arrays.asList(this.contents); } + /** + * Copies the current object to a new one. + * + * @return the copy + */ + public @NotNull Contents copy() { + return new GUIImpl.Contents(Arrays.stream(this.contents) + .map(c -> c == null ? null : c.copy()) + .toArray(GUIContent[]::new)); + } + @Override public boolean equals(Object o) { if (o instanceof Contents) { @@ -263,7 +274,7 @@ public int hashCode() { } @Override - public String toString() { + public @NotNull String toString() { return Arrays.toString(this.contents); } } diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/PageableGUI.java b/gui/base/src/main/java/it/angrybear/yagl/guis/PageableGUI.java index a8ac2caf..a6b697fb 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/guis/PageableGUI.java +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/PageableGUI.java @@ -8,7 +8,7 @@ import it.angrybear.yagl.items.Item; import it.angrybear.yagl.viewers.Viewer; import it.fulminazzo.fulmicollection.objects.FieldEquable; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,22 +18,35 @@ * An implementation of {@link GUI} that allows multiple GUI pages to be added. */ public class PageableGUI extends FieldEquable implements Iterable, Metadatable, GUI { - private final GUI templateGUI; + protected final @NotNull GUI templateGUI; private final List pages = new LinkedList<>(); private final Map variables = new HashMap<>(); - private final Tuple previousPage = new Tuple<>(); - private final Tuple nextPage = new Tuple<>(); + protected final Tuple previousPage = new Tuple<>(); + protected final Tuple nextPage = new Tuple<>(); - private PageableGUI() { - this.templateGUI = null; + /** + * Instantiates a new Pageable gui. + */ + PageableGUI() { + this.templateGUI = new DefaultGUI(); } - private PageableGUI(final int size) { + /** + * Instantiates a new Pageable gui. + * + * @param size the size + */ + PageableGUI(final int size) { this.templateGUI = GUI.newGUI(size); } - private PageableGUI(final @NotNull GUIType type) { + /** + * Instantiates a new Pageable gui. + * + * @param type the type + */ + PageableGUI(final @NotNull GUIType type) { this.templateGUI = GUI.newGUI(type); } @@ -53,7 +66,7 @@ public int pages() { * @param page the page * @return the corresponding {@link GUI} page */ - public GUI getPage(final int page) { + public @NotNull GUI getPage(final int page) { try { return this.pages.get(page); } catch (IndexOutOfBoundsException e) { @@ -67,7 +80,7 @@ public GUI getPage(final int page) { * @param pages the pages * @return this gui */ - public PageableGUI setPages(final int pages) { + public @NotNull PageableGUI setPages(final int pages) { if (pages < 0) throw new IllegalArgumentException(String.format("Invalid pages '%s'", pages)); int s; while ((s = this.pages.size()) - pages > 0) this.pages.remove(s - 1); @@ -83,7 +96,7 @@ public PageableGUI setPages(final int pages) { * @param previousPage the previous page * @return the previous page */ - public PageableGUI setPreviousPage(final int slot, final @NotNull Item previousPage) { + public @NotNull PageableGUI setPreviousPage(final int slot, final @NotNull Item previousPage) { return setPreviousPage(slot, (GUIContent) ItemGUIContent.newInstance(previousPage)); } @@ -95,7 +108,7 @@ public PageableGUI setPreviousPage(final int slot, final @NotNull Item previousP * @param previousPage the previous page * @return the previous page */ - public PageableGUI setPreviousPage(final int slot, final @NotNull GUIContent previousPage) { + public @NotNull PageableGUI setPreviousPage(final int slot, final @NotNull GUIContent previousPage) { this.previousPage.set(checkSlot(slot), previousPage); return this; } @@ -105,7 +118,7 @@ public PageableGUI setPreviousPage(final int slot, final @NotNull GUIContent pre * * @return the pageable gui */ - public PageableGUI unsetPreviousPage() { + public @NotNull PageableGUI unsetPreviousPage() { this.previousPage.set(null, null); return this; } @@ -118,7 +131,7 @@ public PageableGUI unsetPreviousPage() { * @param nextPage the next page * @return the next page */ - public PageableGUI setNextPage(final int slot, final @NotNull Item nextPage) { + public @NotNull PageableGUI setNextPage(final int slot, final @NotNull Item nextPage) { return setNextPage(slot, (GUIContent) ItemGUIContent.newInstance(nextPage)); } @@ -130,7 +143,7 @@ public PageableGUI setNextPage(final int slot, final @NotNull Item nextPage) { * @param nextPage the next page * @return the next page */ - public PageableGUI setNextPage(final int slot, final @NotNull GUIContent nextPage) { + public @NotNull PageableGUI setNextPage(final int slot, final @NotNull GUIContent nextPage) { this.nextPage.set(checkSlot(slot), nextPage); return this; } @@ -140,7 +153,7 @@ public PageableGUI setNextPage(final int slot, final @NotNull GUIContent nextPag * * @return the pageable gui */ - public PageableGUI unsetNextPage() { + public @NotNull PageableGUI unsetNextPage() { this.nextPage.set(null, null); return this; } @@ -176,16 +189,42 @@ public void open(final @NotNull Viewer viewer) { * @param page the page */ public void open(final @NotNull Viewer viewer, final int page) { - GUI gui = getPage(page).copy().copyFrom(this, false) + prepareOpenGUI(getPage(page), page).open(viewer); + } + + /** + * Prepares the {@link GUI} at the given page for {@link #open(Viewer, int)}. + * + * @param gui the gui + * @param page the page + * @return the gui + */ + protected @NotNull GUI prepareOpenGUI(final @NotNull GUI gui, final int page) { + GUI newGUI = gui.copy().copyFrom(this, false) .setVariable("page", String.valueOf(page + 1)) .setVariable("previous-page", String.valueOf(page)) .setVariable("next-page", String.valueOf(page + 2)) .setVariable("pages", String.valueOf(pages())); if (page > 0) this.previousPage.ifPresent((s, p) -> - gui.setContents(s, p.copy().onClickItem((v, g, i) -> open(v, page - 1)))); + newGUI.setContents(s, p.copy().onClickItem((v, g, i) -> open(v, page - 1)))); if (page + 1 < pages()) this.nextPage.ifPresent((s, p) -> - gui.setContents(s, p.copy().onClickItem((v, g, i) -> open(v, page + 1)))); - gui.open(viewer); + newGUI.setContents(s, p.copy().onClickItem((v, g, i) -> open(v, page + 1)))); + return newGUI; + } + + /** + * Counts the empty slots of the current GUI. + * If {@link #setPreviousPage(int, GUIContent)} or {@link #setNextPage(int, GUIContent)} + * were used, their slots are removed from the final count. + * + * @return the slots + */ + @Override + public @NotNull Set emptySlots() { + final Set slots = GUI.super.emptySlots(); + this.previousPage.ifPresent((i, p) -> slots.remove(i)); + this.nextPage.ifPresent((i, p) -> slots.remove(i)); + return slots; } @Override @@ -438,11 +477,6 @@ public int columns() { return (PageableGUI) GUI.super.unsetTopSide(); } - @Override - public @NotNull Set topSlots() { - return GUI.super.topSlots(); - } - @Override public @NotNull PageableGUI setLeftSide(Item @NotNull ... contents) { return (PageableGUI) GUI.super.setLeftSide(contents); @@ -468,11 +502,6 @@ public int columns() { return (PageableGUI) GUI.super.unsetLeftSide(); } - @Override - public @NotNull Set leftSlots() { - return GUI.super.leftSlots(); - } - @Override public @NotNull PageableGUI setBottomSide(Item @NotNull ... contents) { return (PageableGUI) GUI.super.setBottomSide(contents); @@ -498,11 +527,6 @@ public int columns() { return (PageableGUI) GUI.super.unsetBottomSide(); } - @Override - public @NotNull Set bottomSlots() { - return GUI.super.bottomSlots(); - } - @Override public @NotNull PageableGUI setRightSide(Item @NotNull ... contents) { return (PageableGUI) GUI.super.setRightSide(contents); @@ -528,11 +552,6 @@ public int columns() { return (PageableGUI) GUI.super.unsetRightSide(); } - @Override - public @NotNull Set rightSlots() { - return GUI.super.rightSlots(); - } - @Override public @NotNull PageableGUI setNorthWest(Item @NotNull ... contents) { return (PageableGUI) GUI.super.setNorthWest(contents); @@ -774,7 +793,7 @@ public PageableGUI copy() { * @param size the size * @return the pageable gui */ - public static PageableGUI newGUI(final int size) { + public static @NotNull PageableGUI newGUI(final int size) { return new PageableGUI(size); } @@ -784,7 +803,7 @@ public static PageableGUI newGUI(final int size) { * @param type the type * @return the pageable gui */ - public static PageableGUI newGUI(final @NotNull GUIType type) { + public static @NotNull PageableGUI newGUI(final @NotNull GUIType type) { return new PageableGUI(type); } diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/ResizableGUI.java b/gui/base/src/main/java/it/angrybear/yagl/guis/ResizableGUI.java index 30736538..1b92006b 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/guis/ResizableGUI.java +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/ResizableGUI.java @@ -13,7 +13,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.Set; /** * Represents a "chest" GUI that can be resized. @@ -188,11 +187,6 @@ public void resize(int size) { return (ResizableGUI) super.unsetTopSide(); } - @Override - public @NotNull Set topSlots() { - return super.topSlots(); - } - @Override public @NotNull ResizableGUI setLeftSide(Item @NotNull ... contents) { return (ResizableGUI) super.setLeftSide(contents); @@ -218,11 +212,6 @@ public void resize(int size) { return (ResizableGUI) super.unsetLeftSide(); } - @Override - public @NotNull Set leftSlots() { - return super.leftSlots(); - } - @Override public @NotNull ResizableGUI setBottomSide(Item @NotNull ... contents) { return (ResizableGUI) super.setBottomSide(contents); @@ -248,11 +237,6 @@ public void resize(int size) { return (ResizableGUI) super.unsetBottomSide(); } - @Override - public @NotNull Set bottomSlots() { - return super.bottomSlots(); - } - @Override public @NotNull ResizableGUI setRightSide(Item @NotNull ... contents) { return (ResizableGUI) super.setRightSide(contents); @@ -278,11 +262,6 @@ public void resize(int size) { return (ResizableGUI) super.unsetRightSide(); } - @Override - public @NotNull Set rightSlots() { - return super.rightSlots(); - } - @Override public @NotNull ResizableGUI setNorthWest(Item @NotNull ... contents) { return (ResizableGUI) super.setNorthWest(contents); diff --git a/gui/base/src/main/java/it/angrybear/yagl/guis/TypeGUI.java b/gui/base/src/main/java/it/angrybear/yagl/guis/TypeGUI.java index 1111211b..1183e685 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/guis/TypeGUI.java +++ b/gui/base/src/main/java/it/angrybear/yagl/guis/TypeGUI.java @@ -21,13 +21,13 @@ public class TypeGUI extends GUIImpl { private static final int LOOM_MIDDLE_LINE = 0; private static final int LOOM_SOUTH = 2; - private final GUIType inventoryType; + private final @NotNull GUIType inventoryType; /** * Internal constructor, used for serializing purposes. */ private TypeGUI() { - this.inventoryType = null; + this.inventoryType = GUIType.CHEST; } /** diff --git a/gui/base/src/main/java/it/angrybear/yagl/viewers/Viewer.java b/gui/base/src/main/java/it/angrybear/yagl/viewers/Viewer.java index af91ebb0..e3974ab6 100644 --- a/gui/base/src/main/java/it/angrybear/yagl/viewers/Viewer.java +++ b/gui/base/src/main/java/it/angrybear/yagl/viewers/Viewer.java @@ -44,6 +44,13 @@ public boolean hasOpenGUI() { */ public abstract void playSound(final @NotNull Sound sound); + /** + * Sends the given message to the player. + * + * @param message the message + */ + public abstract void sendMessage(final @NotNull String message); + /** * Execute command. * diff --git a/gui/base/src/test/java/it/angrybear/yagl/contents/ItemGUIContentTest.java b/gui/base/src/test/java/it/angrybear/yagl/contents/ItemGUIContentTest.java index 8232f18c..2ab4e5ac 100644 --- a/gui/base/src/test/java/it/angrybear/yagl/contents/ItemGUIContentTest.java +++ b/gui/base/src/test/java/it/angrybear/yagl/contents/ItemGUIContentTest.java @@ -53,8 +53,8 @@ void testItemMethods() throws InvocationTargetException, IllegalAccessException if (method.getName().equals("isSimilar")) params = new Object[]{expected, new ItemField[0]}; else params = Arrays.stream(method.getParameterTypes()).map(TestUtils::mockParameter).toArray(Object[]::new); - Object obj1 = ReflectionUtils.setAccessible(method).invoke(actual, params); - Object obj2 = ReflectionUtils.setAccessible(method).invoke(expected, params); + Object obj1 = ReflectionUtils.setAccessibleOrThrow(method).invoke(actual, params); + Object obj2 = ReflectionUtils.setAccessibleOrThrow(method).invoke(expected, params); assertEquals(obj2, obj1); } } diff --git a/gui/base/src/test/java/it/angrybear/yagl/guis/DataGUITest.java b/gui/base/src/test/java/it/angrybear/yagl/guis/DataGUITest.java new file mode 100644 index 00000000..e3d3bb7d --- /dev/null +++ b/gui/base/src/test/java/it/angrybear/yagl/guis/DataGUITest.java @@ -0,0 +1,254 @@ +package it.angrybear.yagl.guis; + +import it.angrybear.yagl.TestUtils; +import it.angrybear.yagl.actions.GUIItemAction; +import it.angrybear.yagl.contents.GUIContent; +import it.angrybear.yagl.contents.ItemGUIContent; +import it.angrybear.yagl.exceptions.NotImplemented; +import it.angrybear.yagl.items.Item; +import it.fulminazzo.fulmicollection.objects.Refl; +import it.fulminazzo.fulmicollection.utils.ReflectionUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.MockedStatic; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; + +class DataGUITest { + + private static Object[][] pagesTest() { + return new Object[][]{ + new Object[]{0, 27, 1, false, false}, + new Object[]{0, 54, 2, false, false}, + new Object[]{9, 54, 3, false, false}, + new Object[]{26, 54, 54, false, false}, + new Object[]{14, 198, 16, false, false}, + new Object[]{0, 27, 2, false, true}, + new Object[]{0, 27, 1, true, false}, + new Object[]{0, 54, 3, true, true}, + }; + } + + @ParameterizedTest + @MethodSource("pagesTest") + void testPagesMethod(int contents, int data, int expected, boolean prev, boolean next) { + DataGUI dataGUI = setupGUI(contents, data, prev, next, ItemGUIContent.newInstance()); + int pages = dataGUI.pages(); + assertEquals(expected, pages); + } + + @ParameterizedTest + @MethodSource("pagesTest") + void testFillContentsMethod(int contents, int data, int pages, boolean prev, boolean next) { + GUIContent convertedContent = ItemGUIContent.newInstance("grass"); + DataGUI dataGUI = setupGUI(contents, data, prev, next, convertedContent); + Refl guiRefl = new Refl<>(dataGUI); + + for (int p = 0; p < pages; p++) { + GUI gui = guiRefl.getFieldObject("templateGUI"); + assertNotNull(gui); + gui = guiRefl.invokeMethod("fillContents", gui.copy(), p); + assertNotNull(gui); + + for (int c = 0; c < dataGUI.size(); c++) { + if (gui.emptySlots().contains(c)) continue; + @NotNull List cs = gui.getContents(c); + assertFalse(cs.isEmpty(), String.format("Expected not empty at slot %s for page %s", c, p)); + GUIContent content = cs.get(0); + String message = String.format("Invalid material at slot %s for page %s", c, p); + if (c < contents) assertNotEquals(convertedContent, content, message); + else assertEquals(convertedContent, content, message); + } + } + } + + private static @NotNull DataGUI setupGUI(int contents, int data, boolean prev, boolean next, GUIContent convertedContent) { + DataGUI dataGUI = DataGUI.newGUI(27, s -> convertedContent); + if (prev) dataGUI.setPreviousPage(0, Item.newItem()); + if (next) dataGUI.setNextPage(2, Item.newItem()); + for (int i = 0; i < data; i++) dataGUI.addData(i); + @NotNull Item stone = Item.newItem("stone"); + for (int i = 0; i < contents; i++) dataGUI.addContent(stone); + return dataGUI; + } + + private static Object[] removeDataParameters() { + return new Object[]{ + "hello,world", + Arrays.asList("hello", "world"), + (Predicate) s -> s.equals("hello") || s.equals("world") + }; + } + + @ParameterizedTest + @MethodSource("removeDataParameters") + void testRemoveData(Object object) { + if (object instanceof String) + object = Arrays.stream(object.toString().split(",")).toArray(Object[]::new); + DataGUI gui = DataGUI.newGUI(9, s -> null, "hello", "world"); + new Refl<>(gui).invokeMethod("removeData", object); + @NotNull List data = gui.getData(); + assertTrue(data.isEmpty(), String.format("Expected empty but was '%s'", data)); + } + + @Test + void testNoData() { + DataGUI gui = DataGUI.newGUI(9, s -> null); + int page = 3; + assertThrowsExactly(IllegalArgumentException.class, () -> gui.open(null, page)); + } + + @Test + void testInvalidPage() { + DataGUI gui = DataGUI.newGUI(9, s -> null).setData(new String[]{"Hello"}); + int page = 3; + Throwable throwable = assertThrowsExactly(IllegalArgumentException.class, () -> + gui.open(null, page)); + String message = throwable.getMessage(); + assertTrue(message.contains(String.valueOf(page)), + String.format("Message should contain '%s' but was '%s'", page, message)); + } + + @Test + void testNoEmptySlots() { + int size = 9; + Item[] contents = new Item[size]; + Arrays.fill(contents, Item.newItem("stone")); + DataGUI gui = DataGUI.newGUI(size, s -> null, "Hello").addContent(contents); + assertThrowsExactly(IllegalStateException.class, gui::pages); + } + + @ParameterizedTest + @ValueSource(strings = {"getPage", "setPages"}) + void testPagesRelatedMethodShouldThrowException(String method) { + Throwable throwable = assertThrowsExactly(IllegalStateException.class, () -> + new Refl<>(DataGUI.newGUI(GUIType.ANVIL, s -> null)).invokeMethod(method, 1)); + assertEquals(new Refl<>(DataGUI.class).getFieldObject("ERROR_MESSAGE"), throwable.getMessage()); + } + + private static Constructor[] privateConstructors() { + return Arrays.stream(DataGUI.class.getDeclaredConstructors()) + .filter(c -> Modifier.isPrivate(c.getModifiers())) + .toArray(Constructor[]::new); + } + + @ParameterizedTest + @MethodSource("privateConstructors") + void testPrivateConstructorsConverter(Constructor constructor) throws InvocationTargetException, InstantiationException, IllegalAccessException { + Object[] parameters = Arrays.stream(constructor.getParameterTypes()) + .map(TestUtils::mockParameter) + .map(o -> o instanceof Number ? 9 : o) + .toArray(Object[]::new); + Object object = ReflectionUtils.setAccessibleOrThrow(constructor).newInstance(parameters); + assertThrowsExactly(NotImplemented.class, () -> new Refl<>(object) + .getFieldRefl("dataConverter") + .invokeMethod("apply", (Object) null)); + } + + @Test + void testOpenPage() { + int[][] slots = new int[][]{ + new int[]{0, 1, 2, 3, 5, 6, 7}, + new int[]{1, 2, 3, 5, 6, 7}, + new int[]{1, 2, 3, 5, 6, 7, 8} + }; + + Double[] data = new Double[] { + 0.0, 0.1, 0.2, 0.3, 0.5, 0.6, 0.7, + 1.1, 1.2, 1.3, 1.5, 1.6, 1.7, + 2.1, 2.2, 2.3, 2.5, 2.6, 2.7, 2.8, + }; + Function cc = d -> ItemGUIContent.newInstance() + .setDisplayName("Data: " + d) + .setAmount((int) (d * 10)); + PageableGUI gui = DataGUI.newGUI(9, cc, data) + .setPreviousPage(0, Item.newItem("redstone_block") + .setDisplayName("&7Go to page &e")) + .setNextPage(8, Item.newItem("emerald_block") + .setDisplayName("&7Go to page &e")) + .setContents(4, Item.newItem("obsidian").setDisplayName("&7Page: &e")); + + final PageableGUITest.MockViewer viewer = new PageableGUITest.MockViewer(UUID.randomUUID(), "Steve"); + try (MockedStatic clazz = mockStatic(ReflectionUtils.class, CALLS_REAL_METHODS)) { + clazz.when(() -> ReflectionUtils.getClass("it.angrybear.yagl.GUIAdapter")) + .thenReturn(PageableGUITest.MockGUIAdapter.class); + + for (int i = 0; i < gui.pages(); i++) { + gui.open(viewer, i); + GUI expected = new Refl<>(gui).getFieldObject("templateGUI"); + assertNotNull(expected); + expected = PageableGUITest.generateExpected(expected.copy(), i); + + int[] tmpSlots = slots[i]; + for (int s = 0; s < tmpSlots.length; s++) { + int ind = s; + for (int k = i; k > 0; k--) ind += slots[k - 1].length; + double d = data[ind]; + expected.setContents(tmpSlots[s], cc.apply(d)); + } + + GUI actual = viewer.openedGUI; + actual.getContents(0).forEach(e -> e.onClickItem((GUIItemAction) null)); + actual.getContents(8).forEach(e -> e.onClickItem((GUIItemAction) null)); + + assertEquals(expected, actual, String.format("Invalid page %s", i)); + } + } + } + + @Test + void testReturnTypes() { + TestUtils.testReturnType(DataGUI.newGUI(9, c -> null), PageableGUI.class, m -> { + for (String s : Arrays.asList("copy", "setPages", "getPage")) + if (s.equals(m.getName())) return true; + return false; + }); + } + + @Test + void testCopy() { + DataGUI expected = DataGUI.newGUI(9, c -> null, "Hello", "world"); + DataGUI actual = expected.copy(); + assertEquals(expected, actual); + } + + private static Object[][] constructorParameters() { + return new Object[][]{ + new Object[]{27, null, null}, + new Object[]{27, null, new Object[]{"Hello", "World"}}, + new Object[]{27, null, Arrays.asList("Hello", "World")}, + new Object[]{GUIType.CHEST, null, null}, + new Object[]{GUIType.CHEST, null, new Object[]{"Hello", "World"}}, + new Object[]{GUIType.CHEST, null, Arrays.asList("Hello", "World")}, + }; + } + + @ParameterizedTest + @MethodSource("constructorParameters") + void testConstructors(Object obj1, Object obj2, Object obj3) { + @NotNull DataGUI expected = obj1 instanceof GUIType ? + new DataGUI<>(GUIType.CHEST, null) : + new DataGUI<>(27, null); + expected.setData("Hello", "World"); + DataGUI actual; + if (obj3 == null) { + actual = new Refl<>(DataGUI.class).invokeMethod("newGUI", obj1, obj2); + actual.setData("Hello", "World"); + } else actual = new Refl<>(DataGUI.class).invokeMethod("newGUI", obj1, obj2, obj3); + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/gui/base/src/test/java/it/angrybear/yagl/guis/GUIImplTest.java b/gui/base/src/test/java/it/angrybear/yagl/guis/GUIImplTest.java index 48b03c48..dbffae5c 100644 --- a/gui/base/src/test/java/it/angrybear/yagl/guis/GUIImplTest.java +++ b/gui/base/src/test/java/it/angrybear/yagl/guis/GUIImplTest.java @@ -7,6 +7,7 @@ import it.angrybear.yagl.items.Item; import it.angrybear.yagl.viewers.Viewer; import it.angrybear.yagl.wrappers.Sound; +import it.fulminazzo.fulmicollection.objects.Refl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; @@ -224,6 +225,20 @@ void testEqualsDifferentObjects() { assertNotEquals(c1, ItemGUIContent.newInstance("grass")); } + @Test + void testCopy() { + GUIImpl.Contents c1 = new GUIImpl.Contents( + ItemGUIContent.newInstance("stone"), + ItemGUIContent.newInstance("grass"), + null + ); + GUIImpl.Contents c2 = c1.copy(); + assertEquals(c1.getContents(), c2.getContents()); + Object cs1 = new Refl<>(c1).getFieldRefl("contents"); + Object cs2 = new Refl<>(c2).getFieldRefl("contents"); + assertNotEquals(cs1.hashCode(), cs2.hashCode()); + } + } public static class MockGUI extends GUIImpl { diff --git a/gui/base/src/test/java/it/angrybear/yagl/guis/GUITest.java b/gui/base/src/test/java/it/angrybear/yagl/guis/GUITest.java index 206ceabc..e7929177 100644 --- a/gui/base/src/test/java/it/angrybear/yagl/guis/GUITest.java +++ b/gui/base/src/test/java/it/angrybear/yagl/guis/GUITest.java @@ -5,14 +5,12 @@ import it.angrybear.yagl.items.Item; import it.fulminazzo.fulmicollection.objects.Refl; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Predicate; import static org.junit.jupiter.api.Assertions.*; @@ -268,4 +266,15 @@ void testSetCornersMethods(String methodName) { assertEquals(guiContent, gui.getContents(slot).get(0)); } + @Test + void testEmptySlots() { + GUI gui = GUI.newGUI(54); + gui.setContents(1, Item.newItem("Stone")); + + Set slots = gui.emptySlots(); + for (int i = 0; i < gui.size(); i++) + if (i == 1) assertFalse(slots.contains(i), String.format("Slots should not contain '%s'", i)); + else assertTrue(slots.contains(i), String.format("Slots should contain '%s'", i)); + } + } \ No newline at end of file diff --git a/gui/base/src/test/java/it/angrybear/yagl/guis/PageableGUITest.java b/gui/base/src/test/java/it/angrybear/yagl/guis/PageableGUITest.java index a1312e2c..23bc2adb 100644 --- a/gui/base/src/test/java/it/angrybear/yagl/guis/PageableGUITest.java +++ b/gui/base/src/test/java/it/angrybear/yagl/guis/PageableGUITest.java @@ -9,7 +9,7 @@ import it.angrybear.yagl.viewers.Viewer; import it.angrybear.yagl.wrappers.Sound; import it.fulminazzo.fulmicollection.objects.Refl; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.fulmicollection.utils.ReflectionUtils; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -63,7 +63,8 @@ void testOpenPage() { final MockViewer viewer = new MockViewer(UUID.randomUUID(), "Steve"); try (MockedStatic clazz = mockStatic(ReflectionUtils.class, CALLS_REAL_METHODS)) { - clazz.when(() -> ReflectionUtils.getClass("it.angrybear.yagl.GUIAdapter")).thenReturn(MockGUIAdapter.class); + clazz.when(() -> ReflectionUtils.getClass("it.angrybear.yagl.GUIAdapter")) + .thenReturn(MockGUIAdapter.class); for (int i = 0; i < gui.pages(); i++) { gui.open(viewer, i); @@ -87,7 +88,7 @@ void testOpenPage() { } } - private GUI generateExpected(GUI gui, int index) { + static GUI generateExpected(GUI gui, int index) { GUI g = GUI.newGUI(9) .setContents(1, gui.getContents(1)) .setContents(4, Item.newItem("obsidian").setDisplayName("&7Page: &e")) @@ -136,7 +137,7 @@ void testPageableGUIMethods() throws InvocationTargetException, IllegalAccessExc Metadatable.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); continue; } catch (NoSuchMethodException ignored) {} - method = ReflectionUtils.setAccessible(method); + method = ReflectionUtils.setAccessibleOrThrow(method); Object[] params = Arrays.stream(method.getParameterTypes()) .map(TestUtils::mockParameter) .map(o -> o instanceof Integer ? 9 : o) @@ -232,7 +233,7 @@ private GUI setupGUI(GUI gui) { .onChangeGUI("command"); } - private static class MockViewer extends Viewer { + static class MockViewer extends Viewer { GUI openedGUI; protected MockViewer(UUID uniqueId, String name) { @@ -241,12 +242,17 @@ protected MockViewer(UUID uniqueId, String name) { @Override public void playSound(@NotNull Sound sound) { + // + } + @Override + public void sendMessage(@NotNull String message) { + // } @Override public void executeCommand(@NotNull String command) { - + // } @Override @@ -255,7 +261,7 @@ public boolean hasPermission(@NotNull String permission) { } } - private static class MockGUIAdapter { + static class MockGUIAdapter { public static void openGUI(GUI gui, MockViewer viewer) { viewer.openedGUI = gui; diff --git a/gui/bukkit/src/main/java/it/angrybear/yagl/GUIAdapter.java b/gui/bukkit/src/main/java/it/angrybear/yagl/GUIAdapter.java index 8cfca022..6aeea1df 100644 --- a/gui/bukkit/src/main/java/it/angrybear/yagl/GUIAdapter.java +++ b/gui/bukkit/src/main/java/it/angrybear/yagl/GUIAdapter.java @@ -73,12 +73,9 @@ public static void openGUI(final @NotNull GUI gui, @NotNull GUIManager.getOpenGUIViewer(uuid).ifPresent((v, g) -> { reflViewer.setFieldObject("previousGUI", g).setFieldObject("openGUI", null); g.changeGUIAction().ifPresent(a -> a.execute(v, g, gui)); - player.closeInventory(); }); - // Set new GUI - reflViewer.setFieldObject("openGUI", gui); // Open inventory - Inventory inventory = guiToInventory(gui); + Inventory inventory = guiToInventory(gui.apply(gui)); for (int i = 0; i < gui.size(); i++) { GUIContent content = gui.getContent(viewer, i); if (content != null) { @@ -90,6 +87,28 @@ public static void openGUI(final @NotNull GUI gui, @NotNull } } player.openInventory(inventory); + // Set new GUI + reflViewer.setFieldObject("openGUI", gui); + // Execute action if present + gui.openGUIAction().ifPresent(a -> a.execute(reflViewer.getObject(), gui)); + } + + /** + * Closes the currently open {@link GUI} for the specified {@link Viewer}, if present. + * + * @param viewer the viewer + */ + public static void closeGUI(final @NotNull Viewer viewer) { + final UUID uuid = viewer.getUniqueId(); + final Player player = Bukkit.getPlayer(uuid); + if (player == null) throw new PlayerOfflineException(viewer.getName()); + final Refl reflViewer = new Refl<>(viewer); + // Save previous GUI, if present + GUIManager.getOpenGUIViewer(uuid).ifPresent((v, g) -> { + reflViewer.setFieldObject("previousGUI", g).setFieldObject("openGUI", null); + g.closeGUIAction().ifPresent(a -> a.execute(v, g)); + player.closeInventory(); + }); } /** diff --git a/gui/bukkit/src/main/java/it/angrybear/yagl/GUIManager.java b/gui/bukkit/src/main/java/it/angrybear/yagl/GUIManager.java index 73c3957b..6012ad84 100644 --- a/gui/bukkit/src/main/java/it/angrybear/yagl/GUIManager.java +++ b/gui/bukkit/src/main/java/it/angrybear/yagl/GUIManager.java @@ -4,17 +4,15 @@ import it.angrybear.yagl.guis.GUI; import it.angrybear.yagl.viewers.Viewer; import it.fulminazzo.fulmicollection.objects.Refl; -import it.fulminazzo.fulmicollection.structures.BiOptional; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.fulmicollection.utils.ReflectionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -43,12 +41,6 @@ public GUIManager() { this.viewers = new ArrayList<>(); } - @EventHandler - void on(final @NotNull InventoryOpenEvent event) { - getOpenGUIViewer(event.getPlayer()).ifPresent((v, g) -> - g.openGUIAction().ifPresent(a -> a.execute(v, g))); - } - @EventHandler void on(final @NotNull InventoryClickEvent event) { getOpenGUIViewer(event.getWhoClicked()).ifPresent((v, g) -> { @@ -69,9 +61,7 @@ void on(final @NotNull InventoryDragEvent event) { @EventHandler void on(final @NotNull InventoryCloseEvent event) { - Player player = (Player) event.getPlayer(); - getOpenGUIViewer(player).ifPresent((v, g) -> - g.closeGUIAction().ifPresent(a -> a.execute(v, g))); + GUIAdapter.closeGUI(getViewer(event.getPlayer())); } @EventHandler @@ -90,27 +80,27 @@ void on(final @NotNull PluginDisableEvent event) { } /** - * Gets a {@link BiOptional} with the corresponding {@link Viewer} and open {@link GUI} if present. + * Gets a {@link Tuple} with the corresponding {@link Viewer} and open {@link GUI} if present. * * @param player the player - * @return the BiOptional + * @return the Tuple */ - public static @NotNull BiOptional getOpenGUIViewer(final @NotNull HumanEntity player) { + public static @NotNull Tuple getOpenGUIViewer(final @NotNull HumanEntity player) { Viewer viewer = getViewer(player); - if (viewer.hasOpenGUI()) return BiOptional.of(viewer, viewer.getOpenGUI()); - else return BiOptional.empty(); + if (viewer.hasOpenGUI()) return new Tuple<>(viewer, viewer.getOpenGUI()); + else return new Tuple<>(); } /** - * Gets a {@link BiOptional} with the corresponding {@link Viewer} and open {@link GUI} if present. + * Gets a {@link Tuple} with the corresponding {@link Viewer} and open {@link GUI} if present. * * @param uuid the uuid - * @return the BiOptional + * @return the Tuple */ - public static @NotNull BiOptional getOpenGUIViewer(final @NotNull UUID uuid) { + public static @NotNull Tuple getOpenGUIViewer(final @NotNull UUID uuid) { Viewer viewer = getViewer(uuid); - if (viewer != null && viewer.hasOpenGUI()) return BiOptional.of(viewer, viewer.getOpenGUI()); - else return BiOptional.empty(); + if (viewer != null && viewer.hasOpenGUI()) return new Tuple<>(viewer, viewer.getOpenGUI()); + else return new Tuple<>(); } /** diff --git a/gui/bukkit/src/main/java/it/angrybear/yagl/viewers/BukkitViewer.java b/gui/bukkit/src/main/java/it/angrybear/yagl/viewers/BukkitViewer.java index 8ad5290e..3e2eae9e 100644 --- a/gui/bukkit/src/main/java/it/angrybear/yagl/viewers/BukkitViewer.java +++ b/gui/bukkit/src/main/java/it/angrybear/yagl/viewers/BukkitViewer.java @@ -32,6 +32,12 @@ public void playSound(@NotNull Sound sound) { WrappersAdapter.playCustomSound(player, sound); } + @Override + public void sendMessage(@NotNull String message) { + Player player = getPlayer().orElseThrow(() -> new PlayerOfflineException(this.name)); + player.sendMessage(message); + } + @Override public void executeCommand(final @NotNull String command) { Player player = getPlayer().orElseThrow(() -> new PlayerOfflineException(this.name)); diff --git a/gui/bukkit/src/test/java/it/angrybear/yagl/GUIAdapterTest.java b/gui/bukkit/src/test/java/it/angrybear/yagl/GUIAdapterTest.java index 19da8003..40ee374f 100644 --- a/gui/bukkit/src/test/java/it/angrybear/yagl/GUIAdapterTest.java +++ b/gui/bukkit/src/test/java/it/angrybear/yagl/GUIAdapterTest.java @@ -1,5 +1,6 @@ package it.angrybear.yagl; +import it.angrybear.yagl.actions.GUIAction; import it.angrybear.yagl.contents.GUIContent; import it.angrybear.yagl.contents.ItemGUIContent; import it.angrybear.yagl.guis.GUI; @@ -25,6 +26,7 @@ import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -130,8 +132,28 @@ void testOpenGUIForOfflinePlayer() { assertThrowsExactly(PlayerOfflineException.class, () -> openGUI(GUI.newGUI(9))); } + @Test + void testCloseGUIForOfflinePlayer() { + BukkitUtils.removePlayer(this.player); + assertThrowsExactly(PlayerOfflineException.class, this::closeGUI); + } + + @Test + void testOpenGUIAction() { + GUI gui = GUI.newGUI(9); + AtomicBoolean executed = new AtomicBoolean(false); + GUIAction openAction = (g, v) -> executed.set(true); + gui.onOpenGUI(openAction); + openGUI(gui); + assertTrue(executed.get(), "openAction was not executed"); + } + private void openGUI(GUI gui) { GUITestUtils.mockPlugin(p -> gui.open(GUIManager.getViewer(this.player))); } + private void closeGUI() { + GUITestUtils.mockPlugin(p -> GUIAdapter.closeGUI(GUIManager.getViewer(this.player))); + } + } \ No newline at end of file diff --git a/gui/bukkit/src/test/java/it/angrybear/yagl/GUIManagerTest.java b/gui/bukkit/src/test/java/it/angrybear/yagl/GUIManagerTest.java index 2d37fef1..1aee6cde 100644 --- a/gui/bukkit/src/test/java/it/angrybear/yagl/GUIManagerTest.java +++ b/gui/bukkit/src/test/java/it/angrybear/yagl/GUIManagerTest.java @@ -31,6 +31,14 @@ class GUIManagerTest { + @Test + void testGetOpenGUIViewerPlayer() { + BukkitUtils.setupServer(); + Player player = BukkitUtils.addPlayer(UUID.randomUUID(), "Alex"); + GUITestUtils.mockPlugin(p -> + assertFalse(GUIManager.getOpenGUIViewer(player).isPresent(), "Should not be present")); + } + @Test void testGetOpenGUIViewerUUID() { BukkitUtils.setupServer(); @@ -175,18 +183,6 @@ void testClickOutside() { assertTrue(expected.get(), "Outside action was not invoked"); } - @Test - void testOpenEvent() { - AtomicBoolean expected = new AtomicBoolean(false); - this.expected.onOpenGUI((v, g) -> expected.set(true)); - - InventoryView view = getView(); - - this.guiManager.on(new InventoryOpenEvent(view)); - - assertTrue(expected.get(), "OpenGUI action was not invoked"); - } - @Test void testDragEvent() { InventoryView view = getView(); diff --git a/gui/bukkit/src/test/java/it/angrybear/yagl/viewers/BukkitViewerTest.java b/gui/bukkit/src/test/java/it/angrybear/yagl/viewers/BukkitViewerTest.java index 2a28f8b0..ecd2f86e 100644 --- a/gui/bukkit/src/test/java/it/angrybear/yagl/viewers/BukkitViewerTest.java +++ b/gui/bukkit/src/test/java/it/angrybear/yagl/viewers/BukkitViewerTest.java @@ -9,7 +9,7 @@ import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; class BukkitViewerTest { private Player player; @@ -19,6 +19,7 @@ class BukkitViewerTest { void setUp() { BukkitUtils.setupServer(); this.player = BukkitUtils.addPlayer(UUID.randomUUID(), "Alex"); + when(this.player.isOnline()).thenReturn(true); this.viewer = BukkitViewer.newViewer(this.player); } @@ -28,6 +29,19 @@ void testNewViewer() { assertEquals(this.player.getName(), this.viewer.getName()); } + @Test + void testSendMessage() { + String expected = "Hello world"; + this.viewer.sendMessage(expected); + verify(this.player, atLeastOnce()).sendMessage(expected); + } + + @Test + void testSendMessageOffline() { + BukkitUtils.removePlayer(this.player); + assertThrowsExactly(PlayerOfflineException.class, () -> this.viewer.sendMessage("Message")); + } + @Test void testPlaySoundOffline() { BukkitUtils.removePlayer(this.player); diff --git a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/GUIParser.java b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/GUIParser.java index 6640aeeb..0a055daf 100644 --- a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/GUIParser.java +++ b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/GUIParser.java @@ -9,6 +9,7 @@ import it.fulminazzo.yamlparser.configuration.FileConfiguration; import it.fulminazzo.yamlparser.configuration.IConfiguration; import it.fulminazzo.yamlparser.parsers.YAMLParser; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -31,7 +32,7 @@ protected BiFunctionException getLoader() { ConfigurationSection section = c.getConfigurationSection(s); if (section == null) return null; GUI tmp = new Refl<>(this).getFieldRefl("function").invokeMethod("apply", section); - YAMLParser parser = FileConfiguration.getParser(tmp.getClass()); + YAMLParser parser = getSpecificGUIParser(tmp); if (!parser.equals(this)) return parser.load(c, s); GUI g = super.getLoader().apply(c, s); Integer size = c.getInteger(s + ".size"); @@ -46,13 +47,12 @@ protected BiFunctionException getLoader() { }; } - @SuppressWarnings("unchecked") @Override protected TriConsumer getDumper() { return (c, s, g) -> { c.set(s, null); if (g == null) return; - YAMLParser parser = (YAMLParser) FileConfiguration.getParser(g.getClass()); + YAMLParser parser = getSpecificGUIParser(g); if (!this.equals(parser)) parser.dump(c, s, g); else { super.getDumper().accept(c, s, g); @@ -63,4 +63,13 @@ protected TriConsumer getDumper() { }; } + @SuppressWarnings("unchecked") + private YAMLParser getSpecificGUIParser(final @NotNull GUI gui) { + FileConfiguration.removeParsers(this); + YAMLParser parser = FileConfiguration.getParser(gui.getClass()); + FileConfiguration.addParsers(this); + if (parser == null || !getOClass().isAssignableFrom(parser.getOClass())) return this; + return (YAMLParser) parser; + } + } diff --git a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/PageableGUIParser.java b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/PageableGUIParser.java index d0c852a2..73b29f97 100644 --- a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/PageableGUIParser.java +++ b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/PageableGUIParser.java @@ -7,10 +7,9 @@ import it.fulminazzo.fulmicollection.interfaces.functions.BiFunctionException; import it.fulminazzo.fulmicollection.interfaces.functions.TriConsumer; import it.fulminazzo.fulmicollection.objects.Refl; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.yamlparser.configuration.ConfigurationSection; import it.fulminazzo.yamlparser.configuration.IConfiguration; -import it.fulminazzo.yamlparser.parsers.YAMLParser; import org.jetbrains.annotations.NotNull; import java.util.LinkedList; @@ -22,7 +21,7 @@ * A parser to serialize {@link PageableGUI}. */ @SuppressWarnings("DataFlowIssue") -public class PageableGUIParser extends YAMLParser { +public class PageableGUIParser extends TypedParser { private static final String[] IGNORE_FIELDS = new String[]{"type", "size"}; /** @@ -41,7 +40,10 @@ protected BiFunctionException getLoader() { if (size == null) throw new IllegalArgumentException("'size' cannot be null"); Integer pages = section.getInteger("pages"); if (pages == null) throw new IllegalArgumentException("'pages' cannot be null"); - final PageableGUI gui = PageableGUI.newGUI(size).setPages(pages); + final PageableGUI gui = (PageableGUI) getObjectFromType(GUI.class, section, size); + try { + gui.setPages(pages); + } catch (IllegalStateException ignored) {} final Refl refl = new Refl<>(gui); final String guiType = section.getString("gui-type"); @@ -108,7 +110,7 @@ protected TriConsumer getDumper() { c.set(s, refl.getFieldObject("templateGUI")); ConfigurationSection section = c.getConfigurationSection(s); section.set("gui-type", section.getString("type")); - section.set("type", ParserUtils.classToType(GUI.class, getOClass())); + section.set("type", ParserUtils.classToType(GUI.class, p.getClass())); section.set("size", p.size()); section.set("pages", p.pages()); diff --git a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/SerializableFunctionParser.java b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/SerializableFunctionParser.java index 3e25f37e..78ec3172 100644 --- a/gui/serializer/src/main/java/it/angrybear/yagl/parsers/SerializableFunctionParser.java +++ b/gui/serializer/src/main/java/it/angrybear/yagl/parsers/SerializableFunctionParser.java @@ -34,7 +34,7 @@ protected BiFunctionException getLoader() { return (c, s) -> { ConfigurationSection section = c.getConfigurationSection(s); if (section == null) return null; - // Get type + // Get the type String type = section.getString("type"); if (type == null) throw new IllegalArgumentException("'type' cannot be null"); Class clazz = typeToClass(getOClass(), type); @@ -61,9 +61,9 @@ protected TriConsumer getDumper() { } /** - * Type to class class. + * Converts the given type to the corresponding class. * - * @param the type parameter + * @param the type of the class * @param mainClass the main class * @param type the type * @return the class diff --git a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/DataGUIParserTest.java b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/DataGUIParserTest.java new file mode 100644 index 00000000..63d7736d --- /dev/null +++ b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/DataGUIParserTest.java @@ -0,0 +1,63 @@ +package it.angrybear.yagl.parsers; + +import it.angrybear.yagl.contents.GUIContent; +import it.angrybear.yagl.guis.DataGUI; +import it.angrybear.yagl.guis.GUI; +import it.angrybear.yagl.items.Item; +import it.angrybear.yagl.items.fields.ItemFlag; +import it.fulminazzo.fulmicollection.objects.Refl; +import it.fulminazzo.yamlparser.configuration.FileConfiguration; +import it.fulminazzo.yamlparser.utils.FileUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.*; + +class DataGUIParserTest { + + @Test + void testSaveAndLoadDataGUI() throws IOException { + GUIYAGLParser.addAllParsers(); + Function function = s -> null; + DataGUI expected = DataGUI.newGUI(9, function, "Hello", "world") + .setPreviousPage(1, Item.newItem("paper") + .setDisplayName("&ePrevious Page") + .addEnchantment("unbreaking", 3) + .addItemFlags(ItemFlag.HIDE_UNBREAKABLE)) + .setNextPage(1, Item.newItem("paper") + .setDisplayName("&eNext Page") + .addEnchantment("unbreaking", 3) + .addItemFlags(ItemFlag.HIDE_UNBREAKABLE)) + .setVariable("var1", "hello") + .setVariable("var2", "world"); + GUIParserTest.setupContents(expected); + + File file = new File("build/resources/test/data-gui.yml"); + if (!file.exists()) FileUtils.createNewFile(file); + FileConfiguration configuration = new FileConfiguration(file); + final String path = expected.getClass().getSimpleName().toLowerCase(); + configuration.set(path, expected); + configuration.save(); + + configuration = new FileConfiguration(file); + GUI actual = configuration.get(path, GUI.class); + assertNotNull(actual); + assertInstanceOf(DataGUI.class, actual); + + Refl reflGUI = new Refl<>(actual); + DataGUI dataGUI = (DataGUI) reflGUI + .setFieldObject("dataConverter", function) + .getObject(); + @NotNull List data = dataGUI.getData(); + assertTrue(data.isEmpty(), String.format("Expected empty but was %s", data)); + + reflGUI.setFieldObject("data", expected.getData()); + assertEquals(expected, actual); + } + +} \ No newline at end of file diff --git a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/GUIParserTest.java b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/GUIParserTest.java index ace3f010..6e214156 100644 --- a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/GUIParserTest.java +++ b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/GUIParserTest.java @@ -88,8 +88,8 @@ void testSaveAndLoadGUI(GUI expected) throws IOException { for (final Field field : new Refl<>(expected).getNonStaticFields()) if (!field.getName().equals("contents")) { - Object obj1 = ReflectionUtils.get(field, expected); - Object obj2 = ReflectionUtils.get(field, actual); + Object obj1 = ReflectionUtils.getOrThrow(field, expected); + Object obj2 = ReflectionUtils.getOrThrow(field, actual); assertEquals(obj1, obj2); } diff --git a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/MockGUIParser.java b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/MockGUIParser.java index 87bdcaea..9a26c176 100644 --- a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/MockGUIParser.java +++ b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/MockGUIParser.java @@ -37,7 +37,7 @@ public static class MockGUI implements GUI { @Override public void open(@NotNull Viewer viewer) { - + // } @Override diff --git a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/PageableGUIParserTest.java b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/PageableGUIParserTest.java index 1f124ee8..6aeedddb 100644 --- a/gui/serializer/src/test/java/it/angrybear/yagl/parsers/PageableGUIParserTest.java +++ b/gui/serializer/src/test/java/it/angrybear/yagl/parsers/PageableGUIParserTest.java @@ -89,6 +89,7 @@ void testSizeNotSpecified() { @Test void testPagesNotSpecified() { IConfiguration configuration = getConfiguration(c -> { + c.set("type", "PAGEABLE"); c.set("size", 3); }); Throwable throwable = assertThrowsExactly(IllegalArgumentException.class, () -> @@ -99,6 +100,7 @@ void testPagesNotSpecified() { @Test void testGUITypeNotSpecified() { IConfiguration configuration = getConfiguration(c -> { + c.set("type", "PAGEABLE"); c.set("size", 9); c.set("pages", 3); }); @@ -110,6 +112,7 @@ void testGUITypeNotSpecified() { @Test void testInvalidTemplateGUI() { IConfiguration configuration = getConfiguration(c -> { + c.set("type", "PAGEABLE"); c.set("size", 9); c.set("pages", 3); c.set("gui-type", "DEFAULT"); @@ -153,6 +156,7 @@ void testVariableMaps(Map variables, Object expected) throws Exc doAnswer(a -> variables == null ? null : new HashMap<>(variables)) .when(section).get("variables", Map.class); configuration.toMap().put("gui", section); + section.set("type", "PAGEABLE"); section.set("size", 9); section.set("pages", 3); section.set("gui-type", "DEFAULT"); diff --git a/item/base/src/main/java/it/angrybear/yagl/items/ItemImpl.java b/item/base/src/main/java/it/angrybear/yagl/items/ItemImpl.java index a50832f4..b5d29fef 100644 --- a/item/base/src/main/java/it/angrybear/yagl/items/ItemImpl.java +++ b/item/base/src/main/java/it/angrybear/yagl/items/ItemImpl.java @@ -105,8 +105,8 @@ public boolean isSimilar(final @Nullable Item item, final ItemField @NotNull ... .noneMatch(f2 -> f.getName().equalsIgnoreCase(f2.name() .replace("_", "")))) .allMatch(f -> { - Object obj1 = ReflectionUtils.get(f, this); - Object obj2 = ReflectionUtils.get(f, item); + Object obj1 = ReflectionUtils.getOrThrow(f, this); + Object obj2 = ReflectionUtils.getOrThrow(f, item); return Objects.equals(obj1, obj2); }); } diff --git a/item/base/src/test/java/it/angrybear/yagl/items/ItemTest.java b/item/base/src/test/java/it/angrybear/yagl/items/ItemTest.java index b083c009..80a66a1e 100644 --- a/item/base/src/test/java/it/angrybear/yagl/items/ItemTest.java +++ b/item/base/src/test/java/it/angrybear/yagl/items/ItemTest.java @@ -79,7 +79,7 @@ void testCopyItemFromAbstractWithNoImpl(Class clazz) { assertTrue(message.contains(implName), "Exception message did not contain abstract implementation class name"); // Check that the returned exception is not from ReflectionUtils - String reflMessage = ReflectionUtils.get(ReflectionUtils.getField(ReflectionUtils.class, "CLASS_NOT_FOUND"), ReflectionUtils.class); + String reflMessage = ReflectionUtils.getOrThrow(ReflectionUtils.getField(ReflectionUtils.class, "CLASS_NOT_FOUND"), ReflectionUtils.class); assertNotEquals(reflMessage.replace("%class%", implName), message, "Exception message should not be the same as the one returned by ReflectionUtils"); } diff --git a/item/bukkit/src/main/java/it/angrybear/yagl/ItemAdapter.java b/item/bukkit/src/main/java/it/angrybear/yagl/ItemAdapter.java index f6e8f44d..a8948a2c 100644 --- a/item/bukkit/src/main/java/it/angrybear/yagl/ItemAdapter.java +++ b/item/bukkit/src/main/java/it/angrybear/yagl/ItemAdapter.java @@ -9,7 +9,7 @@ import it.angrybear.yagl.items.recipes.ShapelessRecipe; import it.angrybear.yagl.utils.EnumUtils; import it.fulminazzo.fulmicollection.objects.Refl; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.bukkit.Bukkit; @@ -56,8 +56,7 @@ public final class ItemAdapter { invokeNoSuchMethod(() -> item.setUnbreakable(meta.isUnbreakable()), () -> item.setUnbreakable(meta.spigot().isUnbreakable())); invokeNoSuchMethod(() -> { - int modelData = meta.getCustomModelData(); - if (modelData > 0) item.setCustomModelData(modelData); + if (meta.hasCustomModelData()) item.setCustomModelData(meta.getCustomModelData()); }, null); } return item; diff --git a/item/bukkit/src/main/java/it/angrybear/yagl/items/PersistentItem.java b/item/bukkit/src/main/java/it/angrybear/yagl/items/PersistentItem.java index 92edf8b4..43037e05 100644 --- a/item/bukkit/src/main/java/it/angrybear/yagl/items/PersistentItem.java +++ b/item/bukkit/src/main/java/it/angrybear/yagl/items/PersistentItem.java @@ -100,7 +100,7 @@ public void interact(final @NotNull Player player, final @NotNull ItemStack item /** * Set the action executed on interacting. - * A player interacts with an item when they right-click with it in game. + * A player interacts with an item when they right-click with it in the game. * * @param action the action * @return this persistent item diff --git a/item/bukkit/src/main/java/it/angrybear/yagl/listeners/PersistentListener.java b/item/bukkit/src/main/java/it/angrybear/yagl/listeners/PersistentListener.java index b68a9988..a58d1589 100644 --- a/item/bukkit/src/main/java/it/angrybear/yagl/listeners/PersistentListener.java +++ b/item/bukkit/src/main/java/it/angrybear/yagl/listeners/PersistentListener.java @@ -12,6 +12,7 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.player.PlayerDropItemEvent; @@ -79,7 +80,7 @@ protected void on(@NotNull PlayerDeathEvent event) { for (int i = 0; i < contents.length; i++) { int finalI = i; final ItemStack item = contents[i]; - // Save every PersistentItem with the MAINTAIN action, remove if they have DISAPPEAR. + // Save every PersistentItem with the MAINTAIN action, remove if they have the DISAPPEAR one. findPersistentItem(p -> { DeathAction deathAction = p.getDeathAction(); if (deathAction == null) return; @@ -129,7 +130,7 @@ protected void on(@NotNull InventoryClickEvent event) { ItemStack itemStack = event.getCurrentItem(); Consumer ifPresent = clickConsumer(event, player); - // Check the current item and the cursor; + // Check the current item and the cursor. if (!clickPersistentItem(player, type, ifPresent, itemStack, event.getCursor()) && type.equals(ClickType.NUMBER_KEY)) { // Check if a number has been used from the keyboard to move the item. itemStack = player.getInventory().getItem(event.getHotbarButton()); @@ -174,7 +175,8 @@ protected void on(@NotNull InventoryDragEvent event) { return e -> { Inventory open = player.getOpenInventory().getTopInventory(); int rawSlot = event.getRawSlot(); - if (e.getMobility() != Mobility.INTERNAL || rawSlot < open.getSize()) cancelled(event).accept(e); + if (e.getMobility() != Mobility.INTERNAL || (rawSlot < open.getSize() || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY))) + cancelled(event).accept(e); }; } diff --git a/item/bukkit/src/test/java/it/angrybear/yagl/ItemAdapterTest.java b/item/bukkit/src/test/java/it/angrybear/yagl/ItemAdapterTest.java index b1e47489..e6ddd475 100644 --- a/item/bukkit/src/test/java/it/angrybear/yagl/ItemAdapterTest.java +++ b/item/bukkit/src/test/java/it/angrybear/yagl/ItemAdapterTest.java @@ -156,7 +156,7 @@ void testNullRecipe() throws InvocationTargetException, IllegalAccessException { @NotNull List methods = adapter.getMethods(m -> m.getName().equals(methodName)); assertEquals(4, methods.size(), String.format("Could not find all '%s' methods", methodName)); for (Method method : methods) { - Object result = ReflectionUtils.setAccessible(method).invoke(ItemAdapter.class, (Object) null); + Object result = ReflectionUtils.setAccessibleOrThrow(method).invoke(ItemAdapter.class, (Object) null); assertNull(result); } } diff --git a/item/bukkit/src/test/java/it/angrybear/yagl/listeners/PersistentListenerTest.java b/item/bukkit/src/test/java/it/angrybear/yagl/listeners/PersistentListenerTest.java index 17707599..2c6b767f 100644 --- a/item/bukkit/src/test/java/it/angrybear/yagl/listeners/PersistentListenerTest.java +++ b/item/bukkit/src/test/java/it/angrybear/yagl/listeners/PersistentListenerTest.java @@ -260,7 +260,12 @@ private static InventoryView getInventoryView(Player player, Inventory inventory when(view.getPlayer()).thenReturn(player); when(view.getItem(any(int.class))).thenCallRealMethod(); when(view.getCursor()).thenCallRealMethod(); - when(view.convertSlot(any(int.class))).thenCallRealMethod(); + when(view.convertSlot(any(int.class))).thenAnswer(a -> { + int slot = a.getArgument(0); + int size = inventory.getSize(); + if (slot >= size) slot -= size; + return slot; + }); when(view.getInventory(any(int.class))).thenAnswer(a -> { int slot = a.getArgument(0); if (slot < inventory.getSize()) return inventory; @@ -290,13 +295,14 @@ private InventoryClickEvent[] inventoryClickEvents() { new InventoryClickEvent(view, InventoryType.SlotType.CONTAINER, 0, ClickType.LEFT, InventoryAction.CLONE_STACK), new InventoryClickEvent(view, InventoryType.SlotType.CONTAINER, 2, ClickType.LEFT, InventoryAction.CLONE_STACK), new InventoryClickEvent(view, InventoryType.SlotType.CONTAINER, 3, ClickType.NUMBER_KEY, InventoryAction.CLONE_STACK, 0), + new InventoryClickEvent(view, InventoryType.SlotType.CONTAINER, 9, ClickType.SHIFT_LEFT, InventoryAction.MOVE_TO_OTHER_INVENTORY), }; } @ParameterizedTest @MethodSource("inventoryClickEvents") void simulateInventoryClick(InventoryClickEvent event) { - if (event.getRawSlot() == 2) cursor = maintain.create(); + if (event.getRawSlot() == 2) cursor = maintain.setMobility(Mobility.INTERNAL).create(); assertFalse(clicked, "Clicked should be initialized as false"); assertFalse(event.isCancelled(), "Event should not be cancelled"); diff --git a/item/serializer/src/test/java/it/angrybear/yagl/parsers/RecipeParserTest.java b/item/serializer/src/test/java/it/angrybear/yagl/parsers/RecipeParserTest.java index 8cb54834..57d056f1 100644 --- a/item/serializer/src/test/java/it/angrybear/yagl/parsers/RecipeParserTest.java +++ b/item/serializer/src/test/java/it/angrybear/yagl/parsers/RecipeParserTest.java @@ -69,8 +69,8 @@ void testRecipe(Recipe recipe) throws IOException { Recipe recipe2 = configuration.get(path, Recipe.class); for (final Field field : recipe.getClass().getDeclaredFields()) { - Object obj1 = ReflectionUtils.get(field, recipe); - Object obj2 = ReflectionUtils.get(field, recipe2); + Object obj1 = ReflectionUtils.getOrThrow(field, recipe); + Object obj2 = ReflectionUtils.getOrThrow(field, recipe2); assertEquals(obj1, obj2); } } diff --git a/settings.gradle b/settings.gradle index c26efb45..4da565f9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -43,6 +43,7 @@ include 'gui:gui-serializer' project(':gui:gui-serializer').projectDir = file('gui/serializer') include 'testing' +include 'demo' gradleEnterprise { if (System.getenv("CI") != null) { @@ -52,4 +53,5 @@ gradleEnterprise { termsOfServiceAgree = 'yes' } } -} \ No newline at end of file +} + diff --git a/testing/src/main/java/it/angrybear/yagl/TestUtils.java b/testing/src/main/java/it/angrybear/yagl/TestUtils.java index 45d9157f..917c5a52 100644 --- a/testing/src/main/java/it/angrybear/yagl/TestUtils.java +++ b/testing/src/main/java/it/angrybear/yagl/TestUtils.java @@ -85,11 +85,11 @@ public static void testMultipleMethods(final @NotNull Object executor, final @No try { // Execute target method final Object[] parameters = initializeParameters(targetMethod.getParameterTypes(), staticObjects); - ReflectionUtils.setAccessible(targetMethod).invoke(executor, parameters); + ReflectionUtils.setAccessibleOrThrow(targetMethod).invoke(executor, parameters); // Verify execution with mock Method method = target.getClass().getDeclaredMethod(invokedMethod, invokedMethodParamTypes); - ReflectionUtils.setAccessible(method).invoke(verify(target), + ReflectionUtils.setAccessibleOrThrow(method).invoke(verify(target), Arrays.stream(captors).map(ArgumentCaptor::capture).toArray(Object[]::new)); return captors; @@ -169,7 +169,7 @@ public static void testReturnType(final @NotNull T object, final @NotNull Cl methodString, objectClassName); Object[] mockParameters = Arrays.stream(parameters).map(TestUtils::mockParameter).toArray(Object[]::new); - Object o = ReflectionUtils.setAccessible(method).invoke(object, mockParameters); + Object o = ReflectionUtils.setAccessibleOrThrow(method).invoke(object, mockParameters); if (method.getName().equals("copy")) assertInstanceOf(objectClass, o, String.format("Returned object from %s call should have been %s but was %s", diff --git a/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustParticleOption.java b/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustParticleOption.java index 23235d3a..d9fdb357 100644 --- a/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustParticleOption.java +++ b/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustParticleOption.java @@ -1,7 +1,7 @@ package it.angrybear.yagl.particles; import it.angrybear.yagl.Color; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import org.jetbrains.annotations.NotNull; /** diff --git a/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustTransitionParticleOption.java b/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustTransitionParticleOption.java index e6fd7369..1bbdc781 100644 --- a/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustTransitionParticleOption.java +++ b/wrappers/base/src/main/java/it/angrybear/yagl/particles/DustTransitionParticleOption.java @@ -1,7 +1,7 @@ package it.angrybear.yagl.particles; import it.angrybear.yagl.Color; -import it.fulminazzo.fulmicollection.structures.Triple; +import it.fulminazzo.fulmicollection.structures.tuples.Triple; import org.jetbrains.annotations.NotNull; /** diff --git a/wrappers/base/src/main/java/it/angrybear/yagl/particles/MaterialDataOption.java b/wrappers/base/src/main/java/it/angrybear/yagl/particles/MaterialDataOption.java index eef94544..9855e4f9 100644 --- a/wrappers/base/src/main/java/it/angrybear/yagl/particles/MaterialDataOption.java +++ b/wrappers/base/src/main/java/it/angrybear/yagl/particles/MaterialDataOption.java @@ -1,6 +1,6 @@ package it.angrybear.yagl.particles; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import org.jetbrains.annotations.NotNull; /** diff --git a/wrappers/bukkit/src/main/java/it/angrybear/yagl/WrappersAdapter.java b/wrappers/bukkit/src/main/java/it/angrybear/yagl/WrappersAdapter.java index a441615c..fad9c42b 100644 --- a/wrappers/bukkit/src/main/java/it/angrybear/yagl/WrappersAdapter.java +++ b/wrappers/bukkit/src/main/java/it/angrybear/yagl/WrappersAdapter.java @@ -10,8 +10,8 @@ import it.angrybear.yagl.wrappers.Sound; import it.fulminazzo.fulmicollection.objects.Refl; import it.fulminazzo.fulmicollection.structures.CacheMap; -import it.fulminazzo.fulmicollection.structures.Triple; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Triple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.fulmicollection.utils.ReflectionUtils; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -393,7 +393,7 @@ else if (dataType.getCanonicalName().equals("org.bukkit.block.data.BlockData")) String nbt = blockDataOption.getNBT().trim(); return nbt.isEmpty() ? material.createBlockData() : material.createBlockData(String.format("[%s]", nbt)); } else { - // Try creation from data type + // Try creation from the data type final Object finalOption; Constructor constructor = dataType.getDeclaredConstructors()[0]; int size = constructor.getParameterCount(); @@ -425,9 +425,9 @@ else if (dataType.getCanonicalName().equals("org.bukkit.block.data.BlockData")) public static @Nullable ItemStack itemToItemStack(final @Nullable AbstractItem item) { final Class itemUtils; try { - itemUtils = ReflectionUtils.getClass("it.angrybear.yagl.utils.ItemUtils"); + itemUtils = ReflectionUtils.getClass("it.angrybear.yagl.ItemAdapter"); } catch (IllegalArgumentException e) { - throw new IllegalStateException("Could not find ItemUtils class. This function requires the 'item:bukkit' module to be added"); + throw new IllegalStateException("Could not find ItemAdapter class. This function requires the 'item:bukkit' module to be added"); } return new Refl<>(itemUtils).invokeMethod("itemToItemStack", item); } diff --git a/wrappers/bukkit/src/test/java/it/angrybear/yagl/utils/ItemUtils.java b/wrappers/bukkit/src/test/java/it/angrybear/yagl/ItemAdapter.java similarity index 85% rename from wrappers/bukkit/src/test/java/it/angrybear/yagl/utils/ItemUtils.java rename to wrappers/bukkit/src/test/java/it/angrybear/yagl/ItemAdapter.java index 5d1f739a..b25cba7f 100644 --- a/wrappers/bukkit/src/test/java/it/angrybear/yagl/utils/ItemUtils.java +++ b/wrappers/bukkit/src/test/java/it/angrybear/yagl/ItemAdapter.java @@ -1,4 +1,4 @@ -package it.angrybear.yagl.utils; +package it.angrybear.yagl; import it.angrybear.yagl.items.AbstractItem; import lombok.AccessLevel; @@ -8,7 +8,7 @@ @SuppressWarnings("deprecation") @NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ItemUtils { +public final class ItemAdapter { public static ItemStack itemToItemStack(AbstractItem item) { return new ItemStack(Material.STONE, 7); diff --git a/wrappers/bukkit/src/test/java/it/angrybear/yagl/WrappersAdapterTest.java b/wrappers/bukkit/src/test/java/it/angrybear/yagl/WrappersAdapterTest.java index 295b9558..d0a36133 100644 --- a/wrappers/bukkit/src/test/java/it/angrybear/yagl/WrappersAdapterTest.java +++ b/wrappers/bukkit/src/test/java/it/angrybear/yagl/WrappersAdapterTest.java @@ -7,7 +7,7 @@ import it.angrybear.yagl.wrappers.PotionEffect; import it.angrybear.yagl.wrappers.Sound; import it.fulminazzo.fulmicollection.objects.Refl; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.fulmicollection.utils.ReflectionUtils; import it.fulminazzo.jbukkit.BukkitUtils; import it.fulminazzo.jbukkit.annotations.After1_; @@ -287,7 +287,7 @@ private static org.bukkit.potion.PotionEffect[] getPotionEffects() { List potionEffects = new ArrayList<>(); for (Field field : PotionEffectType.class.getDeclaredFields()) if (field.getType().equals(PotionEffectType.class)) { - PotionEffectType type = ReflectionUtils.get(field, PotionEffectType.class); + PotionEffectType type = ReflectionUtils.getOrThrow(field, PotionEffectType.class); potionEffects.add(new MockPotionEffect(type.getId(), field.getName())); } // Register potion effects @@ -310,7 +310,7 @@ private static org.bukkit.enchantments.Enchantment[] getEnchantments() { List enchantments = new ArrayList<>(); for (Field field : org.bukkit.enchantments.Enchantment.class.getDeclaredFields()) if (field.getType().equals(org.bukkit.enchantments.Enchantment.class)) { - org.bukkit.enchantments.Enchantment enchant = ReflectionUtils.get(field, org.bukkit.enchantments.Enchantment.class); + org.bukkit.enchantments.Enchantment enchant = ReflectionUtils.getOrThrow(field, org.bukkit.enchantments.Enchantment.class); enchantments.add(new MockEnchantment(enchant.getKey())); } // Register enchantments diff --git a/wrappers/serializer/src/main/java/it/angrybear/yagl/particles/MaterialDataOptionParser.java b/wrappers/serializer/src/main/java/it/angrybear/yagl/particles/MaterialDataOptionParser.java index 27da299e..2f149f0c 100644 --- a/wrappers/serializer/src/main/java/it/angrybear/yagl/particles/MaterialDataOptionParser.java +++ b/wrappers/serializer/src/main/java/it/angrybear/yagl/particles/MaterialDataOptionParser.java @@ -2,7 +2,7 @@ import it.fulminazzo.fulmicollection.interfaces.functions.BiFunctionException; import it.fulminazzo.fulmicollection.interfaces.functions.TriConsumer; -import it.fulminazzo.fulmicollection.structures.Tuple; +import it.fulminazzo.fulmicollection.structures.tuples.Tuple; import it.fulminazzo.yamlparser.configuration.IConfiguration; import it.fulminazzo.yamlparser.parsers.YAMLParser; diff --git a/wrappers/serializer/src/main/java/it/angrybear/yagl/wrappers/WrapperParser.java b/wrappers/serializer/src/main/java/it/angrybear/yagl/wrappers/WrapperParser.java index f39c47d9..8c2f59e2 100644 --- a/wrappers/serializer/src/main/java/it/angrybear/yagl/wrappers/WrapperParser.java +++ b/wrappers/serializer/src/main/java/it/angrybear/yagl/wrappers/WrapperParser.java @@ -38,24 +38,35 @@ protected BiFunctionException getLoader() { return (c, s) -> { String raw = c.getString(s); if (raw == null || raw.trim().isEmpty()) return null; - else { - String[] rawData = raw.split(":"); - Constructor constructor = findConstructorFromRaw(rawData); - Object[] parameters = initializeParameters(rawData, constructor); - return new Refl<>(getOClass(), parameters).getObject(); - } + else return parseWrapperFromString(raw, getOClass()); }; } - private @NotNull Constructor findConstructorFromRaw(final String @NotNull [] rawData) throws NoSuchMethodException { - Constructor constructor = (Constructor) Arrays.stream(getOClass().getConstructors()) + /** + * Converts the given string to an instance of the given wrapper class by using the most appropriate constructor. + * + * @param the type of the wrapper + * @param raw the string to convert from + * @param clazz the class of the wrapper + * @return the wrapper + * @throws NoSuchMethodException an exception thrown in case the constructor cannot be found + */ + public static @NotNull W parseWrapperFromString(final @NotNull String raw, final @NotNull Class clazz) throws NoSuchMethodException { + String[] rawData = raw.split(":"); + Constructor constructor = findConstructorFromRaw(rawData, clazz); + Object[] parameters = initializeParameters(rawData, constructor); + return new Refl<>(clazz, parameters).getObject(); + } + + private static @NotNull Constructor findConstructorFromRaw(final String @NotNull [] rawData, final @NotNull Class clazz) throws NoSuchMethodException { + Constructor constructor = (Constructor) Arrays.stream(clazz.getConstructors()) .filter(t -> t.getParameterCount() <= rawData.length) .min(Comparator.comparing(t -> -t.getParameterCount())).orElse(null); if (constructor == null) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < rawData.length; i++) builder.append("?, "); throw new NoSuchMethodException(String.format("Could not find method %s(%s)", - getOClass().getSimpleName(), builder.substring(0, Math.max(0, builder.length() - 2)))); + clazz.getSimpleName(), builder.substring(0, Math.max(0, builder.length() - 2)))); } return constructor; } diff --git a/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleOptionParserTest.java b/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleOptionParserTest.java index a890eaf2..b19348eb 100644 --- a/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleOptionParserTest.java +++ b/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleOptionParserTest.java @@ -59,8 +59,8 @@ void testOptions(ParticleOption expected) throws IOException { Field[] fields = expected.getClass().getDeclaredFields(); for (Field field : fields) { - Object obj1 = ReflectionUtils.get(field, expected); - Object obj2 = ReflectionUtils.get(field, actual); + Object obj1 = ReflectionUtils.getOrThrow(field, expected); + Object obj2 = ReflectionUtils.getOrThrow(field, actual); assertEquals(obj1.getClass(), obj2.getClass()); assertEquals(obj1, obj2); } diff --git a/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleParserTest.java b/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleParserTest.java index f4cd942f..31d5c0a6 100644 --- a/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleParserTest.java +++ b/wrappers/serializer/src/test/java/it/angrybear/yagl/particles/ParticleParserTest.java @@ -79,8 +79,8 @@ void testTypes(AParticleType type) throws IOException { Field[] fields = expected.getClass().getDeclaredFields(); for (Field field : fields) { - Object obj1 = ReflectionUtils.get(field, expected); - Object obj2 = ReflectionUtils.get(field, actual); + Object obj1 = ReflectionUtils.getOrThrow(field, expected); + Object obj2 = ReflectionUtils.getOrThrow(field, actual); if (obj1 == null) assertNull(obj2); else { assertEquals(obj1.getClass(), obj2.getClass());