├── mcspring-build ├── mcspring-archetype │ ├── src │ │ └── main │ │ │ └── resources │ │ │ ├── archetype-resources │ │ │ ├── spigot │ │ │ │ ├── eula.txt │ │ │ │ ├── bukkit.yml │ │ │ │ ├── server.properties │ │ │ │ ├── README.md │ │ │ │ └── spigot.yml │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── ExampleCommand.java │ │ │ └── pom.xml │ │ │ └── META-INF │ │ │ └── maven │ │ │ └── archetype-metadata.xml │ └── pom.xml ├── mcspring-annotations │ ├── goddammit-maven │ │ └── META-INF │ │ │ └── services │ │ │ └── javax.annotation.processing.Processor │ ├── src │ │ └── main │ │ │ ├── java │ │ │ └── in │ │ │ │ └── kyle │ │ │ │ └── mcspring │ │ │ │ └── processor │ │ │ │ ├── annotation │ │ │ │ ├── PluginDepend.java │ │ │ │ └── SpringPlugin.java │ │ │ │ ├── util │ │ │ │ └── MainClassCreator.java │ │ │ │ └── AnnotationProcessor.java │ │ │ └── resources │ │ │ └── Main.java │ └── pom.xml ├── mcspring-plugin-manager │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── in │ │ │ │ └── kyle │ │ │ │ └── mcspring │ │ │ │ └── manager │ │ │ │ ├── Main.java │ │ │ │ ├── controller │ │ │ │ ├── PluginController.java │ │ │ │ ├── LogFileController.java │ │ │ │ ├── BukkitPluginUnloader.java │ │ │ │ └── BukkitPluginController.java │ │ │ │ └── commands │ │ │ │ ├── CommandAbout.java │ │ │ │ ├── CommandOp.java │ │ │ │ ├── CommandHeal.java │ │ │ │ ├── CommandSpeed.java │ │ │ │ ├── CommandClassLoader.java │ │ │ │ ├── CommandGamemode.java │ │ │ │ └── CommandPlugin.java │ │ └── test │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ └── manager │ │ │ ├── commands │ │ │ ├── TestCommandHeal.java │ │ │ ├── TestCommandAbout.java │ │ │ ├── TestCommandClassLoader.java │ │ │ ├── TestCommandSpeed.java │ │ │ ├── TestCommandOp.java │ │ │ ├── TestCommandGamemode.java │ │ │ └── TestCommandPlugin.java │ │ │ └── TestSpringSupport.java │ └── pom.xml ├── mcspring-plugin-layout │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ └── layout │ │ │ ├── McSpringLayoutFactory.java │ │ │ └── McSpringLayout.java │ └── pom.xml ├── pom.xml └── mcspring-starter │ └── pom.xml ├── mcspring-api ├── mcspring-subcommands │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── application.properties │ │ │ └── java │ │ │ │ └── in │ │ │ │ └── kyle │ │ │ │ └── mcspring │ │ │ │ ├── subcommands │ │ │ │ ├── TestSender.java │ │ │ │ ├── TestConsole.java │ │ │ │ ├── TestTabCompletion.java │ │ │ │ └── TestPluginCommand.java │ │ │ │ └── TestSubcommandSpringSpigotSupport.java │ │ └── main │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ ├── subcommands │ │ │ ├── PluginCommandResolver.java │ │ │ ├── tab │ │ │ │ └── TabDiscovery.java │ │ │ ├── Executors.java │ │ │ ├── PluginCommandTabCompletable.java │ │ │ ├── PluginCommand.java │ │ │ └── PluginCommandBase.java │ │ │ └── command │ │ │ └── registration │ │ │ └── TabCommandFactory.java │ └── pom.xml ├── mcspring-base │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ └── META-INF │ │ │ │ │ └── spring.factories │ │ │ └── java │ │ │ │ └── in │ │ │ │ └── kyle │ │ │ │ └── mcspring │ │ │ │ ├── command │ │ │ │ ├── registration │ │ │ │ │ ├── Resolver.java │ │ │ │ │ ├── CommandRegistration.java │ │ │ │ │ ├── CommandResolver.java │ │ │ │ │ ├── CommandController.java │ │ │ │ │ ├── BukkitCommandRegistration.java │ │ │ │ │ ├── CommandScanner.java │ │ │ │ │ └── SimpleCommandFactory.java │ │ │ │ ├── Command.java │ │ │ │ ├── injection │ │ │ │ │ ├── TypeResolver.java │ │ │ │ │ └── QualifierResolver.java │ │ │ │ └── SimpleMethodInjection.java │ │ │ │ ├── RequiresSpigot.java │ │ │ │ ├── util │ │ │ │ └── SpringScanner.java │ │ │ │ ├── event │ │ │ │ ├── EventHandlerSupport.java │ │ │ │ └── EventService.java │ │ │ │ ├── scheduler │ │ │ │ ├── SchedulerService.java │ │ │ │ └── ScheduledAnnotationSupport.java │ │ │ │ ├── SpringSpigotSupport.java │ │ │ │ └── SpringPlugin.java │ │ └── test │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ ├── event │ │ │ ├── TestEventService.java │ │ │ └── TestEventHandlerSupport.java │ │ │ └── command │ │ │ ├── registration │ │ │ ├── TestCommandScanner.java │ │ │ ├── TestSimpleCommandFactory.java │ │ │ └── TestBukkitCommandRegistration.java │ │ │ ├── injection │ │ │ ├── TestTypeResolver.java │ │ │ └── TestQualifierResolver.java │ │ │ └── TestSimpleMethodInjection.java │ └── pom.xml ├── mcspring-vault │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ └── economy │ │ │ ├── EconomyException.java │ │ │ ├── EconomyService.java │ │ │ └── VaultEconomyService.java │ └── pom.xml ├── mcspring-test │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── in │ │ │ └── kyle │ │ │ └── mcspring │ │ │ └── test │ │ │ ├── command │ │ │ ├── TestSender.java │ │ │ ├── TestCommandExecutor.java │ │ │ └── TestCommandRegistration.java │ │ │ ├── TestSpringSpigotSupport.java │ │ │ └── MCSpringTest.java │ └── pom.xml ├── mcspring-jar-loader │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── springframework │ │ │ └── boot │ │ │ └── loader │ │ │ └── mcspring │ │ │ └── McSpringLoader.java │ └── pom.xml └── pom.xml ├── .gitignore ├── mcspring-examples ├── simple-factions │ ├── src │ │ ├── main │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── example │ │ │ │ └── factions │ │ │ │ ├── api │ │ │ │ ├── FactionsApi.java │ │ │ │ └── Faction.java │ │ │ │ ├── controller │ │ │ │ └── FactionsController.java │ │ │ │ └── commands │ │ │ │ └── FactionCommand.java │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── example │ │ │ └── factions │ │ │ └── commands │ │ │ └── TestFactionsCommand.java │ └── pom.xml ├── simple-factions-addon │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── test │ │ │ └── other │ │ │ └── stats │ │ │ └── FactionsStats.java │ └── pom.xml └── pom.xml ├── .travis.yml ├── .maven.xml ├── LICENSE └── README.md /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/spigot/eula.txt: -------------------------------------------------------------------------------- 1 | eula=true 2 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | main=test 2 | debug=true 3 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/goddammit-maven/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | in.kyle.mcspring.processor.AnnotationProcessor 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | dependency-reduced-pom.xml 4 | **/target/** 5 | pom.xml.versionsBackup 6 | **/spigot/** 7 | !**/archetype-resources/spigot/** 8 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=in.kyle.mcspring.SpringSpigotSupport 2 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/src/main/java/in/kyle/mcspring/processor/annotation/PluginDepend.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.processor.annotation; 2 | 3 | public @interface PluginDepend { 4 | String[] plugins(); 5 | boolean soft() default false; 6 | } 7 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-vault/src/main/java/in/kyle/mcspring/economy/EconomyException.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.economy; 2 | 3 | public class EconomyException extends RuntimeException { 4 | 5 | EconomyException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/Resolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import java.lang.reflect.Parameter; 4 | import java.util.Optional; 5 | 6 | public interface Resolver { 7 | Optional resolve(Parameter parameter); 8 | } 9 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/src/main/java/in/kyle/mcspring/processor/annotation/SpringPlugin.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.processor.annotation; 2 | 3 | public @interface SpringPlugin { 4 | String name(); 5 | String version() default "0.0.1"; 6 | String description() default "A Spring plugin"; 7 | } 8 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/CommandRegistration.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import in.kyle.mcspring.command.Command; 6 | 7 | public interface CommandRegistration { 8 | void register(Command command, Method method, Object object); 9 | } 10 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/Main.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager; 2 | 3 | import in.kyle.mcspring.processor.annotation.SpringPlugin; 4 | 5 | // TODO: 2020-03-07 Pull this information from the pom.xml in a build plugin 6 | @SpringPlugin(name = "mcspring-plugin-manager", 7 | description = "Provides simple debugging and plugin management functions") 8 | interface Main { 9 | } 10 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/CommandResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import lombok.Value; 6 | 7 | public interface CommandResolver { 8 | 9 | Resolver makeResolver(Command command); 10 | 11 | @Value 12 | class Command { 13 | CommandSender sender; 14 | String[] args; 15 | String label; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/RequiresSpigot.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @ConditionalOnProperty("spigot.plugin") 13 | public @interface RequiresSpigot { 14 | } 15 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-layout/src/main/java/in/kyle/mcspring/layout/McSpringLayoutFactory.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.layout; 2 | 3 | import org.springframework.boot.loader.tools.Layout; 4 | import org.springframework.boot.loader.tools.LayoutFactory; 5 | 6 | import java.io.File; 7 | 8 | public class McSpringLayoutFactory implements LayoutFactory { 9 | 10 | // required by spring-boot 11 | @SuppressWarnings("unused") 12 | private String name = "mcspring"; 13 | 14 | @Override 15 | public Layout getLayout(File file) { 16 | return new McSpringLayout(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/spigot/bukkit.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | allow-end: false 3 | warn-on-overload: true 4 | permissions-file: permissions.yml 5 | update-folder: update 6 | plugin-profiling: false 7 | connection-throttle: -1 8 | query-plugins: true 9 | deprecated-verbose: default 10 | shutdown-message: Server Shut Down 11 | spawn-limits: 12 | monsters: 0 13 | animals: 0 14 | water-animals: 0 15 | ambient: 0 16 | chunk-gc: 17 | period-in-ticks: 600 18 | load-threshold: 0 19 | ticks-per: 20 | animal-spawns: 400 21 | monster-spawns: 1 22 | autosave: 6000 23 | aliases: now-in-commands.yml 24 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/controller/PluginController.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.controller; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | public interface PluginController { 11 | 12 | Optional load(Path jar); 13 | 14 | boolean unload(Plugin plugin); 15 | 16 | Map getLoadablePlugins(); 17 | 18 | Optional getPlugin(String name); 19 | 20 | Set getPlugins(); 21 | 22 | Map getAllPlugins(); 23 | } 24 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/Command.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface Command { 11 | 12 | String value(); 13 | 14 | String[] aliases() default {}; 15 | 16 | String description() default ""; 17 | 18 | String usage() default ""; 19 | 20 | String permission() default ""; 21 | 22 | String permissionMessage() default ""; 23 | } 24 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/src/main/java/in/kyle/mcspring/test/command/TestSender.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.test.command; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import lombok.Getter; 10 | 11 | public abstract class TestSender implements Player { 12 | 13 | @Getter 14 | private final List messages = new ArrayList<>(); 15 | 16 | @Override 17 | public void sendMessage(String message) { 18 | messages.add(message); 19 | } 20 | 21 | @Override 22 | public void sendMessage(String[] messages) { 23 | Arrays.stream(messages).forEach(this::sendMessage); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/src/main/java/org/example/factions/api/FactionsApi.java: -------------------------------------------------------------------------------- 1 | package org.example.factions.api; 2 | 3 | import org.bukkit.entity.Player; 4 | import in.kyle.mcspring.processor.annotation.SpringPlugin; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | @SpringPlugin(name = "factions", description = "A simple factions plugin") 10 | public interface FactionsApi { 11 | void addFaction(Faction faction); 12 | 13 | void removeFaction(String factionName); 14 | 15 | boolean isFactionMember(Player player); 16 | 17 | Optional getFaction(Player player); 18 | 19 | java.util.Set getFactions(); 20 | 21 | List getFactionNames(); 22 | } 23 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/java/in/kyle/mcspring/subcommands/TestSender.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import lombok.Getter; 10 | 11 | public abstract class TestSender implements CommandSender { 12 | 13 | @Getter 14 | private final List messages = new ArrayList<>(); 15 | 16 | @Override 17 | public void sendMessage(String message) { 18 | messages.add(message); 19 | } 20 | 21 | @Override 22 | public void sendMessage(String[] messages) { 23 | Arrays.stream(messages).forEach(this::sendMessage); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/CommandController.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandMap; 5 | import org.bukkit.plugin.Plugin; 6 | import org.springframework.stereotype.Controller; 7 | 8 | import in.kyle.mcspring.RequiresSpigot; 9 | import lombok.RequiredArgsConstructor; 10 | 11 | @Controller 12 | @RequiredArgsConstructor 13 | @RequiresSpigot 14 | class CommandController { 15 | 16 | private final CommandMap commandMap; 17 | private final Plugin plugin; 18 | 19 | public void registerCommand(Command command) { 20 | commandMap.register(command.getLabel(), plugin.getName(), command); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandHeal.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import java.util.List; 7 | 8 | import in.kyle.mcspring.test.MCSpringTest; 9 | import in.kyle.mcspring.test.command.TestCommandExecutor; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @MCSpringTest 14 | class TestCommandHeal { 15 | 16 | @Autowired 17 | TestCommandExecutor commandExecutor; 18 | 19 | @Test 20 | void testHeal() { 21 | List output = commandExecutor.run("heal"); 22 | assertThat(output).first().asString().matches("Healed [^ ]+"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/src/main/java/org/example/factions/api/Faction.java: -------------------------------------------------------------------------------- 1 | package org.example.factions.api; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | import lombok.Getter; 10 | 11 | @Getter 12 | public class Faction { 13 | 14 | private final Map members = new HashMap<>(); 15 | private String name; 16 | 17 | public Faction(String name, UUID owner) { 18 | this.name = name; 19 | this.members.put(owner, Rank.OWNER); 20 | } 21 | 22 | public boolean isMember(Player player) { 23 | return members.containsKey(player.getUniqueId()); 24 | } 25 | 26 | public enum Rank { 27 | OWNER, 28 | MEMBER 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-vault/src/main/java/in/kyle/mcspring/economy/EconomyService.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.economy; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public interface EconomyService { 8 | 9 | void deposit(OfflinePlayer player, BigDecimal amount); 10 | 11 | void withdraw(OfflinePlayer player, BigDecimal amount); 12 | 13 | void transfer(OfflinePlayer origin, OfflinePlayer destination, BigDecimal amount); 14 | 15 | boolean hasAmount(OfflinePlayer player, BigDecimal amount); 16 | 17 | void createAccount(OfflinePlayer player); 18 | 19 | String format(BigDecimal amount); 20 | 21 | BigDecimal getBalance(OfflinePlayer player); 22 | 23 | boolean hasAccount(OfflinePlayer player); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/java/in/kyle/mcspring/TestSubcommandSpringSpigotSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.ComponentScan; 6 | 7 | import java.util.Collections; 8 | 9 | import in.kyle.mcspring.command.SimpleMethodInjection; 10 | import in.kyle.mcspring.subcommands.TestConsole; 11 | 12 | @TestConfiguration 13 | //@EnableAutoConfiguration(exclude = {in.kyle.mcspring.SpringSpigotSupport.class}) 14 | @ComponentScan(basePackageClasses = TestConsole.class) 15 | public class TestSubcommandSpringSpigotSupport { 16 | 17 | @Bean 18 | SimpleMethodInjection injection() { 19 | return new SimpleMethodInjection(Collections.emptyList()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mcspring-build/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-parent 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | pom 12 | 13 | 14 | mcspring-plugin-layout 15 | mcspring-archetype 16 | mcspring-annotations 17 | mcspring-starter 18 | 19 | 20 | 4.0.0 21 | mcspring-build 22 | 23 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/TestSpringSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Server; 5 | import org.springframework.boot.SpringBootConfiguration; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | import static org.mockito.Mockito.*; 11 | 12 | @SpringBootConfiguration 13 | public class TestSpringSupport { 14 | @Bean 15 | Server server() throws Exception { 16 | Server server = mock(Server.class); 17 | 18 | // Adding a logging call on a setter is some big brain shit 19 | Field serverField = Bukkit.class.getDeclaredField("server"); 20 | serverField.setAccessible(true); 21 | serverField.set(null, server); 22 | 23 | return server; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/controller/LogFileController.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | import javax.annotation.PostConstruct; 10 | 11 | import in.kyle.mcspring.RequiresSpigot; 12 | import lombok.SneakyThrows; 13 | 14 | @Controller 15 | @RequiresSpigot 16 | class LogFileController { 17 | 18 | private final Path logsFolder = Paths.get("logs"); 19 | 20 | @PostConstruct 21 | @SneakyThrows 22 | void setup() { 23 | Files.list(logsFolder) 24 | .filter(p -> p.toString().endsWith(".log.gz")) 25 | .forEach(this::delete); 26 | } 27 | 28 | @SneakyThrows 29 | private void delete(Path f) { 30 | Files.delete(f); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/injection/TypeResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.injection; 2 | 3 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.lang.reflect.Parameter; 8 | import java.util.Optional; 9 | 10 | import in.kyle.mcspring.command.registration.Resolver; 11 | import lombok.AllArgsConstructor; 12 | 13 | @Component 14 | @AllArgsConstructor 15 | class TypeResolver implements Resolver { 16 | 17 | private final ApplicationContext context; 18 | 19 | @Override 20 | public Optional resolve(Parameter parameter) { 21 | try { 22 | return Optional.of(context.getBean(parameter.getType())); 23 | } catch (NoSuchBeanDefinitionException e) { 24 | return Optional.empty(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandAbout.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.Server; 4 | import org.springframework.boot.info.BuildProperties; 5 | import org.springframework.core.SpringVersion; 6 | import org.springframework.stereotype.Component; 7 | 8 | import in.kyle.mcspring.command.Command; 9 | 10 | @Component 11 | class CommandAbout { 12 | @Command(value = "about", 13 | description = "Provides information about current library versions being used") 14 | String about(BuildProperties properties, Server server) { 15 | return String.format( 16 | "Plugin Name: %s\nPlugin Version: %s\nSpring Version: %s\nBukkit Version: %s", 17 | properties.getName(), 18 | properties.getVersion(), 19 | SpringVersion.getVersion(), 20 | server.getBukkitVersion()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/spigot/server.properties: -------------------------------------------------------------------------------- 1 | #Minecraft server properties 2 | #Mon Jul 16 12:02:49 MDT 2018 3 | view-distance=10 4 | max-build-height=256 5 | server-ip= 6 | level-seed= 7 | allow-nether=flase 8 | enable-command-block=true 9 | server-port=25565 10 | gamemode=creative 11 | enable-rcon=false 12 | op-permission-level=4 13 | enable-query=false 14 | prevent-proxy-connections=false 15 | generator-settings=SuperFlat 16 | resource-pack= 17 | player-idle-timeout=0 18 | level-name=world 19 | motd=MCSpring Test Server 20 | force-gamemode=false 21 | hardcore=false 22 | white-list=false 23 | pvp=true 24 | spawn-npcs=false 25 | generate-structures=false 26 | spawn-animals=false 27 | snooper-enabled=true 28 | difficulty=1 29 | network-compression-threshold=256 30 | level-type=FLAT 31 | spawn-monsters=false 32 | max-players=20 33 | resource-pack-sha1= 34 | online-mode=false 35 | allow-flight=false 36 | max-world-size=29999984 37 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/spigot/README.md: -------------------------------------------------------------------------------- 1 | This is a simple Spigot installation. 2 | 3 | To start the server in Intellij: 4 | 1. Go to `Edit Run Configurations` (next to the run button in the dropdown) 5 | 2. Add a new JAR run configuration by clicking the plus button and selecting `JAR Application` 6 | 3. Set the `Path to JAR` as the downloaded spigot.jar. If you do not have a spigot.jar, run a maven install. 7 | 4. Add the VM Option `-DIReallyKnowWhatIAmDoingISwear` 8 | 5. Set the `Working Directory` as the spigot folder 9 | 10 | You will then be able to run spigot by clicking the run button in IJ. 11 | 12 | Further Notes: 13 | * You are also able to launch Spigot in debug mode and set breakpoints. 14 | * Running a maven install will automatically copy the latest plugin jar into your plugins folder. 15 | * Once you perform a new install, restart the server to get the latest version. McSpring does not 16 | yet support reloading altered jars. 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: openjdk8 3 | before_install: 4 | - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import 5 | - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust 6 | - export MAVEN_OPTS=-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn 7 | before_deploy: 8 | - mvn help:evaluate -N -Dexpression=project.version|grep -v '\[' 9 | - export project_version=$(mvn help:evaluate -N -Dexpression=project.version|grep -v '\[') 10 | deploy: 11 | provider: script 12 | skip_cleanup: true 13 | ## Build and release to maven central on tagged version 14 | script: mvn deploy --settings .maven.xml -DskipTests=true -B -U -Prelease 15 | on: 16 | tags: true 17 | branch: master 18 | name: $project_version 19 | install: 20 | mvn --settings .maven.xml clean install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V 21 | after_success: 22 | - mvn clean test jacoco:report coveralls:report 23 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandOp.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.springframework.stereotype.Component; 5 | 6 | import in.kyle.mcspring.command.Command; 7 | import in.kyle.mcspring.subcommands.PluginCommand; 8 | 9 | @Component 10 | class CommandOp { 11 | 12 | @Command(value = "op", 13 | description = "Toggle yourself or another players OP status", 14 | usage = "/op ?") 15 | void op(PluginCommand command) { 16 | command.withPlayer(s -> String.format("Player %s not found", s)); 17 | command.then(this::toggleOp); 18 | 19 | command.otherwise(this::toggleOp); 20 | } 21 | 22 | String toggleOp(CommandSender target) { 23 | target.setOp(!target.isOp()); 24 | return String.format("%s is %s op", target.getName(), target.isOp() ? "now" : "no longer"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.maven.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | ossrh 6 | ${env.SONATYPE_USERNAME} 7 | ${env.SONATYPE_PASSWORD} 8 | 9 | 10 | 11 | 12 | 13 | ossrh 14 | 15 | true 16 | 17 | 18 | gpg 19 | 20 | ${env.GPG_PASSPHRASE} 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/src/main/java/in/kyle/mcspring/test/TestSpringSpigotSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.test; 2 | 3 | import org.springframework.boot.SpringBootConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.info.BuildProperties; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.Import; 9 | 10 | import java.util.Properties; 11 | 12 | import in.kyle.mcspring.SpringPlugin; 13 | import in.kyle.mcspring.command.SimpleMethodInjection; 14 | 15 | @SpringBootConfiguration 16 | @EnableAutoConfiguration 17 | @ComponentScan(basePackageClasses = {TestSpringSpigotSupport.class, SpringPlugin.class}) 18 | @Import(SimpleMethodInjection.class) 19 | class TestSpringSpigotSupport { 20 | 21 | @Bean 22 | BuildProperties buildProperties() { 23 | return new BuildProperties(new Properties()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandHeal.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.springframework.stereotype.Component; 5 | 6 | import in.kyle.mcspring.command.Command; 7 | import in.kyle.mcspring.subcommands.PluginCommand; 8 | 9 | @Component 10 | class CommandHeal { 11 | 12 | @Command(value = "heal", 13 | description = "Heal yourself or another player", 14 | usage = "/heal ?") 15 | void heal(PluginCommand command) { 16 | command.withPlayerSender("Sender must be a player"); 17 | command.withPlayer(s -> String.format("Player %s not found", s)); 18 | command.then(this::executeHeal); 19 | command.otherwise(this::executeHeal); 20 | } 21 | 22 | String executeHeal(Player target) { 23 | target.setHealth(target.getMaxHealth()); 24 | return String.format("Healed %s", target.getName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandAbout.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import java.util.List; 7 | 8 | import in.kyle.mcspring.test.MCSpringTest; 9 | import in.kyle.mcspring.test.command.TestCommandExecutor; 10 | 11 | import static org.assertj.core.api.Assertions.*; 12 | 13 | @MCSpringTest 14 | class TestCommandAbout { 15 | 16 | @Autowired 17 | TestCommandExecutor commandExecutor; 18 | 19 | @Test 20 | void testAbout() { 21 | List output = commandExecutor.run("about"); 22 | assertThat(output).hasSize(4); 23 | assertThat(output.get(0)).matches("Plugin Name: [^ ]+"); 24 | assertThat(output.get(1)).matches("Plugin Version: [^ ]+"); 25 | assertThat(output.get(2)).matches("Spring Version: [^ ]+"); 26 | assertThat(output.get(3)).matches("Bukkit Version: [^ ]+"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/java/in/kyle/mcspring/subcommands/TestConsole.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.function.Consumer; 10 | 11 | import in.kyle.mcspring.command.SimpleMethodInjection; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Component 15 | @RequiredArgsConstructor 16 | public class TestConsole { 17 | 18 | private final SimpleMethodInjection injection; 19 | 20 | public void run(CommandSender sender, String commandString, Consumer consumer) { 21 | List parts = new ArrayList<>(Arrays.asList(commandString.split(" "))); 22 | if (parts.get(0).isEmpty()) { 23 | parts.remove(0); 24 | } 25 | PluginCommand command = new PluginCommand(injection, sender, parts, new ArrayList<>()); 26 | consumer.accept(command); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandSpeed.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.springframework.stereotype.Component; 5 | 6 | import in.kyle.mcspring.command.Command; 7 | import in.kyle.mcspring.subcommands.PluginCommand; 8 | 9 | @Component 10 | class CommandSpeed { 11 | 12 | @Command(value = "speed", 13 | description = "Set your movement and fly speed", 14 | usage = "/speed ") 15 | void speed(PluginCommand command) { 16 | command.withPlayerSender("Sender must be a player"); 17 | command.withDouble("Speed value must be an integer"); 18 | command.then(this::speedExecutor); 19 | command.otherwise("Usage: /speed "); 20 | } 21 | 22 | String speedExecutor(Player sender, double speed) { 23 | sender.setFlySpeed((float) speed); 24 | sender.setWalkSpeed((float) speed); 25 | return String.format("Speed set to %f", speed); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-jar-loader/src/main/java/org/springframework/boot/loader/mcspring/McSpringLoader.java: -------------------------------------------------------------------------------- 1 | package org.springframework.boot.loader.mcspring; 2 | 3 | import org.springframework.boot.loader.JarLauncher; 4 | import org.springframework.boot.loader.archive.Archive; 5 | 6 | import java.lang.reflect.Method; 7 | import java.net.URL; 8 | import java.net.URLClassLoader; 9 | import java.util.List; 10 | 11 | import lombok.SneakyThrows; 12 | 13 | // Packaging is so that it all blends in with the Spring loader 14 | public class McSpringLoader extends JarLauncher { 15 | 16 | @SneakyThrows 17 | public void launch(ClassLoader classLoader) { 18 | List activeArchives = getClassPathArchives(); 19 | Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); 20 | addURL.setAccessible(true); 21 | for (Archive archive : activeArchives) { 22 | addURL.invoke(classLoader, archive.getUrl()); 23 | } 24 | } 25 | 26 | @Override 27 | protected String getMainClass() { 28 | return ""; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-jar-loader/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-api 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 4.0.0 11 | 12 | mcspring-jar-loader 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | 19 | 20 | org.spigotmc 21 | spigot-api 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-loader 26 | 27 | 28 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions-addon/src/main/java/test/other/stats/FactionsStats.java: -------------------------------------------------------------------------------- 1 | package test.other.stats; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.event.player.PlayerMoveEvent; 5 | import in.kyle.mcspring.command.Command; 6 | import in.kyle.mcspring.processor.annotation.PluginDepend; 7 | import in.kyle.mcspring.processor.annotation.SpringPlugin; 8 | 9 | import org.example.factions.api.FactionsApi; 10 | import org.springframework.stereotype.Component; 11 | 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Component 15 | @RequiredArgsConstructor 16 | @PluginDepend(plugins = "factions") 17 | @SpringPlugin(name = "faction-stats", description = "Shows stats for factions") 18 | class FactionsStats { 19 | 20 | private final FactionsApi factionsApi; 21 | 22 | @Command("stats") 23 | String test() { 24 | // return "test"; 25 | return String.format("There are %d factions", factionsApi.getFactions().size()); 26 | } 27 | 28 | @EventHandler 29 | void move(PlayerMoveEvent e) { 30 | System.out.println(e.getPlayer().getName() + " moved"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kyle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/src/main/java/ExampleCommand.java: -------------------------------------------------------------------------------- 1 | package ${groupId}; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import in.kyle.mcspring.command.Command; 8 | 9 | // Remember to annotate Spring beans with @Component 10 | // If you forget this, your class will not run. 11 | @Component 12 | class ExampleCommand { 13 | // Do not extend JavaPlugin, you will regret it 14 | // To get an instance of Plugin, have Spring inject it. 15 | 16 | // Use this in-place of an onEnable method. You can make as many of these methods as you like. 17 | // for onDisable see the @PreDestroy annotation. 18 | @PostConstruct 19 | void onEnable() { 20 | 21 | } 22 | 23 | // Commands will be automatically set up. Do not create a plugin.yml 24 | // The return value of a command method will be sent to the CommandSender using Object::toString 25 | // void methods simplly will not send a message 26 | @Command("test") 27 | String test() { 28 | return "The command works!"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandClassLoader.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | 6 | import java.util.List; 7 | 8 | import in.kyle.mcspring.test.MCSpringTest; 9 | import in.kyle.mcspring.test.command.TestCommandExecutor; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @MCSpringTest 14 | class TestCommandClassLoader { 15 | 16 | @Autowired 17 | TestCommandExecutor commandExecutor; 18 | 19 | @Test 20 | void testClassLoader() { 21 | List output = commandExecutor.run("classloader " + getClass().getName()); 22 | assertThat(output).hasSize(2); 23 | assertThat(output.get(0)).isEqualTo( 24 | "ClassLoader: " + getClass().getClassLoader().toString()); 25 | assertThat(output.get(1)).isEqualTo("Domain: " + getClass().getProtectionDomain() 26 | .getCodeSource() 27 | .getLocation() 28 | .toString()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mcspring-examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-starter 7 | in.kyle.mcspring 8 | 0.0.9 9 | ../mcspring-build/mcspring-starter 10 | 11 | 12 | 13 | simple-factions 14 | simple-factions-addon 15 | 16 | 17 | 4.0.0 18 | 19 | pom 20 | mcspring-examples 21 | 22 | 23 | 24 | 25 | ${project.parent.groupId} 26 | mcspring-starter 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/src/main/java/in/kyle/mcspring/test/MCSpringTest.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.test; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.annotation.AliasFor; 7 | import org.springframework.test.annotation.DirtiesContext; 8 | 9 | import java.lang.annotation.ElementType; 10 | import java.lang.annotation.Inherited; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.RetentionPolicy; 13 | import java.lang.annotation.Target; 14 | 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Inherited 18 | @SpringBootTest(classes = TestSpringSpigotSupport.class) 19 | @Configuration 20 | @ComponentScan(basePackageClasses = TestSpringSpigotSupport.class) 21 | @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) 22 | public @interface MCSpringTest { 23 | 24 | @AliasFor(annotation = SpringBootTest.class, 25 | attribute = "classes") Class[] classes() default {TestSpringSpigotSupport.class}; 26 | } 27 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/src/main/resources/Main.java: -------------------------------------------------------------------------------- 1 | import org.springframework.boot.autoconfigure.SpringBootApplication; 2 | 3 | // @formatter::off 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.loader.mcspring.McSpringLoader; 7 | 8 | import in.kyle.mcspring.SpringPlugin; 9 | // @formatter::on 10 | 11 | public class {name} extends JavaPlugin { 12 | 13 | private McSpringLoader loader; 14 | 15 | public void onEnable() { 16 | try { 17 | new McSpringLoader().launch(getClassLoader()); 18 | SpringPlugin.setup(this, MainPluginConfig.class); 19 | } catch (Exception ignored){ 20 | getLogger().warning("MCSpring Failed to load " + getName()); 21 | // error will be logged by Spring 22 | } 23 | } 24 | 25 | public void onDisable() { 26 | SpringPlugin.teardown(this); 27 | // if (loader != null) loader.close(); 28 | loader = null; 29 | } 30 | 31 | @SpringBootApplication(scanBasePackages = {{scans}}) 32 | static class MainPluginConfig { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mcspring-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-parent 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | pom 12 | 13 | 14 | mcspring-subcommands 15 | mcspring-base 16 | mcspring-vault 17 | mcspring-jar-loader 18 | mcspring-test 19 | 20 | 21 | 4.0.0 22 | 23 | mcspring-api 24 | 25 | 26 | 27 | 28 | ${project.parent.groupId} 29 | mcspring-parent 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/injection/QualifierResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.injection; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.core.annotation.Order; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.lang.reflect.Parameter; 9 | import java.util.Optional; 10 | 11 | import in.kyle.mcspring.command.registration.Resolver; 12 | import lombok.RequiredArgsConstructor; 13 | 14 | @Order(1) 15 | @Component 16 | @RequiredArgsConstructor 17 | class QualifierResolver implements Resolver { 18 | 19 | private final ApplicationContext context; 20 | 21 | @Override 22 | public Optional resolve(Parameter parameter) { 23 | Qualifier qualifier = parameter.getAnnotation(Qualifier.class); 24 | if (qualifier != null) { 25 | Object bean = context.getBean(qualifier.value(), parameter.getType()); 26 | // spring will error out if mismatched type 27 | return Optional.of(bean); 28 | } else { 29 | return Optional.empty(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/PluginCommandResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Optional; 8 | 9 | import in.kyle.mcspring.command.registration.CommandResolver; 10 | import in.kyle.mcspring.command.registration.Resolver; 11 | import in.kyle.mcspring.command.SimpleMethodInjection; 12 | import lombok.AllArgsConstructor; 13 | 14 | @Component 15 | @AllArgsConstructor 16 | class PluginCommandResolver implements CommandResolver { 17 | 18 | private final SimpleMethodInjection injection; 19 | 20 | @Override 21 | public Resolver makeResolver(Command command) { 22 | return parameter -> parameter.getType().isAssignableFrom(PluginCommand.class) ? Optional.of( 23 | makeCommand(command)) : Optional.empty(); 24 | } 25 | 26 | private PluginCommand makeCommand(Command command) { 27 | ArrayList args = new ArrayList<>(Arrays.asList(command.getArgs())); 28 | return new PluginCommand(injection, command.getSender(), args, new ArrayList<>()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandClassLoader.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import in.kyle.mcspring.command.Command; 6 | import in.kyle.mcspring.subcommands.PluginCommand; 7 | 8 | @Component 9 | class CommandClassLoader { 10 | 11 | @Command(value = "classloader", 12 | description = "Show ClassLoader information for a specific class", 13 | usage = "/classloader ") 14 | void classLoader(PluginCommand command) { 15 | command.withString(); 16 | command.then(this::executeClassLoader); 17 | command.otherwise("Usage: /classloader "); 18 | } 19 | 20 | String executeClassLoader(String clazz) { 21 | try { 22 | Class aClass = Class.forName(clazz); 23 | String classLoader = aClass.getClassLoader().toString(); 24 | String protectionDomain = 25 | aClass.getProtectionDomain().getCodeSource().getLocation().toString(); 26 | return String.format("ClassLoader: %s\nDomain: %s", classLoader, protectionDomain); 27 | } catch (ClassNotFoundException e) { 28 | return String.format("Class %s not found", clazz); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | mcspring-examples 8 | in.kyle.mcspring 9 | 0.0.9 10 | 11 | 12 | simple-factions 13 | jar 14 | 15 | 16 | 1.15.1-R0.1-SNAPSHOT 17 | 1.8 18 | 1.8 19 | 20 | 21 | 22 | 23 | in.kyle.mcspring 24 | mcspring-subcommands 25 | 26 | 27 | in.kyle.mcspring 28 | mcspring-test 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | mcspring-starter 8 | ${project.groupId} 9 | ${project.version} 10 | 11 | 12 | \${groupId} 13 | \${artifactId} 14 | \${version} 15 | jar 16 | 17 | 18 | 1.15.1-R0.1-SNAPSHOT 19 | 20 | 21 | 22 | 23 | 24 | org.projectlombok 25 | lombok 26 | 1.18.12 27 | provided 28 | 29 | 30 | ${project.groupId} 31 | mcspring-subcommands 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-layout/src/main/java/in/kyle/mcspring/layout/McSpringLayout.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.layout; 2 | 3 | import org.springframework.boot.loader.tools.CustomLoaderLayout; 4 | import org.springframework.boot.loader.tools.Layout; 5 | import org.springframework.boot.loader.tools.LibraryScope; 6 | import org.springframework.boot.loader.tools.LoaderClassesWriter; 7 | 8 | import java.io.IOException; 9 | 10 | public class McSpringLayout implements Layout, CustomLoaderLayout { 11 | 12 | private static final String NESTED_LOADER_JAR = "META-INF/loader/mcspring-jar-loader.jar"; 13 | 14 | @Override 15 | public String getLauncherClassName() { 16 | return ""; 17 | } 18 | 19 | @Override 20 | public String getLibraryDestination(String libraryName, LibraryScope scope) { 21 | if (scope == LibraryScope.COMPILE) { 22 | return "BOOT-INF/lib/"; 23 | } else { 24 | return null; 25 | } 26 | } 27 | 28 | @Override 29 | public String getClassesLocation() { 30 | return ""; 31 | } 32 | 33 | @Override 34 | public boolean isExecutable() { 35 | return true; 36 | } 37 | 38 | @Override 39 | public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { 40 | writer.writeLoaderClasses(); 41 | writer.writeLoaderClasses(NESTED_LOADER_JAR); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandSpeed.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import in.kyle.mcspring.test.MCSpringTest; 8 | import in.kyle.mcspring.test.command.TestCommandExecutor; 9 | import in.kyle.mcspring.test.command.TestSender; 10 | 11 | import static org.assertj.core.api.Assertions.*; 12 | import static org.mockito.Mockito.*; 13 | 14 | @MCSpringTest 15 | class TestCommandSpeed { 16 | 17 | @Autowired 18 | TestCommandExecutor executor; 19 | 20 | TestSender sender; 21 | 22 | @BeforeEach 23 | void setup() { 24 | sender = spy(TestSender.class); 25 | } 26 | 27 | @Test 28 | void testSpeed() { 29 | executor.run(sender, "speed 10"); 30 | assertThat(sender.getMessages()).first().asString().matches("Speed set to [^ ]+"); 31 | verify(sender).setFlySpeed(10); 32 | verify(sender).setWalkSpeed(10); 33 | } 34 | 35 | @Test 36 | void testSpeedUsage() { 37 | executor.run(sender, "speed"); 38 | assertThat(sender.getMessages()).first().asString().startsWith("Usage: "); 39 | verify(sender, never()).setFlySpeed(anyFloat()); 40 | verify(sender, never()).setWalkSpeed(anyFloat()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-api 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 4.0.0 11 | 12 | mcspring-subcommands 13 | 14 | 15 | 16 | ${project.parent.groupId} 17 | mcspring-base 18 | 19 | 20 | org.bukkit 21 | bukkit 22 | ${spigot.version} 23 | provided 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | 29 | 30 | in.kyle.mcspring 31 | mcspring-test 32 | ${project.parent.version} 33 | test 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/BukkitCommandRegistration.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Arrays; 7 | 8 | import in.kyle.mcspring.RequiresSpigot; 9 | import in.kyle.mcspring.command.Command; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.var; 12 | 13 | @Component 14 | @RequiredArgsConstructor 15 | @RequiresSpigot 16 | class BukkitCommandRegistration implements CommandRegistration { 17 | 18 | private final CommandController controller; 19 | private final CommandFactory commandFactory; 20 | 21 | @Override 22 | public void register(Command command, Method method, Object object) { 23 | String name = command.value(); 24 | var bukkitCommand = commandFactory.makeCommand(method, object, name); 25 | bukkitCommand.setAliases(Arrays.asList(command.aliases())); 26 | bukkitCommand.setDescription(command.description()); 27 | bukkitCommand.setUsage(command.usage()); 28 | bukkitCommand.setPermission(command.permission()); 29 | bukkitCommand.setPermissionMessage(command.permissionMessage()); 30 | controller.registerCommand(bukkitCommand); 31 | } 32 | 33 | interface CommandFactory { 34 | org.bukkit.command.Command makeCommand(Method method, Object object, String name); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/event/TestEventService.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.event; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.BeanInitializationException; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | class TestEventService { 13 | 14 | EventService eventService; 15 | 16 | @BeforeEach 17 | void setup() { 18 | eventService = new EventService(null, null); 19 | } 20 | 21 | @Test 22 | void testRegisterConditions() throws NoSuchMethodException { 23 | class Test { 24 | @EventHandler 25 | void noParams() { 26 | } 27 | 28 | @EventHandler 29 | void invalidParam(int i) { 30 | } 31 | } 32 | 33 | Method noParams = Test.class.getDeclaredMethod("noParams"); 34 | assertThatExceptionOfType(BeanInitializationException.class).isThrownBy(() -> { 35 | eventService.registerEvent(noParams, null); 36 | }); 37 | 38 | Method invalidParam = Test.class.getDeclaredMethod("invalidParam", int.class); 39 | assertThatExceptionOfType(BeanInitializationException.class).isThrownBy(() -> { 40 | eventService.registerEvent(invalidParam, null); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions-addon/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | mcspring-examples 8 | in.kyle.mcspring 9 | 0.0.9 10 | 11 | 12 | simple-factions-addon 13 | jar 14 | 0.69 15 | 16 | 17 | UTF-8 18 | 1.15.1-R0.1-SNAPSHOT 19 | 1.8 20 | 1.8 21 | true 22 | 23 | 24 | 25 | 26 | in.kyle.mcspring 27 | simple-factions 28 | ${project.parent.version} 29 | provided 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | mcspring-starter 8 | in.kyle.mcspring 9 | 0.0.9 10 | ../mcspring-starter 11 | 12 | 13 | 4.0.0 14 | 15 | mcspring-plugin-manager 16 | 17 | 18 | 19 | in.kyle.mcspring 20 | mcspring-subcommands 21 | 22 | 23 | org.projectlombok 24 | lombok 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-test 29 | test 30 | 31 | 32 | in.kyle.mcspring 33 | mcspring-test 34 | ${project.parent.version} 35 | test 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/util/SpringScanner.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.util; 2 | 3 | import org.springframework.aop.support.AopUtils; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Method; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import lombok.AllArgsConstructor; 13 | 14 | @Component 15 | @AllArgsConstructor 16 | public class SpringScanner { 17 | 18 | private final ApplicationContext context; 19 | 20 | public Map scanMethods(Class annotation) { 21 | HashMap methods = new HashMap<>(); 22 | for (String beanName : context.getBeanDefinitionNames()) { 23 | Object object = context.getBean(beanName); 24 | 25 | Class clazz = getRealClass(object); 26 | 27 | for (Method method : clazz.getDeclaredMethods()) { 28 | if (method.isAnnotationPresent(annotation)) { 29 | methods.put(method, object); 30 | } 31 | } 32 | } 33 | return methods; 34 | } 35 | 36 | private Class getRealClass(Object object) { 37 | Class clazz = object.getClass(); 38 | if (AopUtils.isAopProxy(clazz)) { 39 | clazz = AopUtils.getTargetClass(object); 40 | } 41 | return clazz; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/registration/TestCommandScanner.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.SpringBootConfiguration; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.stereotype.Component; 10 | 11 | import in.kyle.mcspring.command.Command; 12 | import in.kyle.mcspring.util.SpringScanner; 13 | import lombok.var; 14 | 15 | import static org.assertj.core.api.Assertions.*; 16 | 17 | @SpringBootTest(classes = TestCommandScanner.Config.class) 18 | @Import({SpringScanner.class, CommandScanner.class, TestCommandScanner.TestCommand.class}) 19 | class TestCommandScanner { 20 | 21 | @Autowired 22 | CommandScanner scanner; 23 | 24 | @Test 25 | void test() { 26 | var commands = scanner.getRegisteredCommands(); 27 | assertThat(commands).hasSize(1); 28 | } 29 | 30 | @Component 31 | static class TestCommand { 32 | @Command("testCommand") 33 | void testCommand() { 34 | } 35 | } 36 | 37 | @SpringBootConfiguration 38 | static class Config { 39 | @Bean 40 | CommandRegistration commandRegistration() { 41 | return ((command, method, object) -> { 42 | }); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/src/main/java/org/example/factions/controller/FactionsController.java: -------------------------------------------------------------------------------- 1 | package org.example.factions.controller; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.example.factions.api.Faction; 5 | import org.example.factions.api.FactionsApi; 6 | import org.springframework.stereotype.Controller; 7 | 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | import lombok.Getter; 15 | 16 | @Controller 17 | class FactionsController implements FactionsApi { 18 | 19 | @Getter 20 | private final Set factions = new HashSet<>(); 21 | 22 | @Override 23 | public void addFaction(Faction faction) { 24 | factions.add(faction); 25 | } 26 | 27 | @Override 28 | public void removeFaction(String factionName) { 29 | factions.removeIf(f -> f.getName().equals(factionName)); 30 | } 31 | 32 | @Override 33 | public boolean isFactionMember(Player player) { 34 | return getFaction(player).map(f -> f.getMembers().containsKey(player.getUniqueId())) 35 | .orElse(false); 36 | } 37 | 38 | @Override 39 | public Optional getFaction(Player player) { 40 | return factions.stream().filter(f -> f.isMember(player)).findFirst(); 41 | } 42 | 43 | @Override 44 | public List getFactionNames() { 45 | return factions.stream().map(Faction::getName).collect(Collectors.toList()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | src/main/java 11 | 12 | **/*.java 13 | 14 | 15 | 16 | src/main/resources 17 | 18 | ** 19 | 20 | 21 | 22 | spigot 23 | 24 | bukkit.yml 25 | eula.txt 26 | server.properties 27 | spigot.yml 28 | README.md 29 | plugins/** 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/registration/TestSimpleCommandFactory.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandSender; 5 | import org.bukkit.plugin.Plugin; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | import in.kyle.mcspring.command.SimpleMethodInjection; 12 | import in.kyle.mcspring.command.registration.SimpleCommandFactory; 13 | import lombok.var; 14 | 15 | import static java.util.Collections.emptyList; 16 | import static java.util.Collections.emptySet; 17 | import static org.mockito.Mockito.*; 18 | 19 | class TestSimpleCommandFactory { 20 | 21 | SimpleCommandFactory factory; 22 | 23 | @BeforeEach 24 | void setup() { 25 | var injection = new SimpleMethodInjection(emptyList()); 26 | factory = new SimpleCommandFactory(injection, emptySet(), mock(Plugin.class)); 27 | } 28 | 29 | @Test 30 | void testExecutorInvokable() throws NoSuchMethodException { 31 | class Test { 32 | String test() { 33 | return "Hello World"; 34 | } 35 | } 36 | 37 | Method method = Test.class.getDeclaredMethod("test"); 38 | var command = factory.makeExecutor(method, new Test()); 39 | var sender = mock(CommandSender.class); 40 | 41 | command.onCommand(sender, mock(Command.class), "", new String[0]); 42 | verify(sender, times(1)).sendMessage("Hello World"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/src/main/java/in/kyle/mcspring/processor/util/MainClassCreator.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.processor.util; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Writer; 9 | import java.util.Set; 10 | import java.util.stream.Collectors; 11 | 12 | import javax.tools.FileObject; 13 | 14 | public class MainClassCreator { 15 | public static void generateMain(FileObject main, 16 | String fqn, 17 | String packageName, 18 | Set packages) throws IOException { 19 | InputStream resource = MainClassCreator.class.getResourceAsStream("/Main.java"); 20 | try (Writer writer = main.openWriter()) { 21 | String template = IOUtils.toString(new InputStreamReader(resource)); 22 | String name = fqn; 23 | if (fqn.contains(".")) { 24 | template = String.format("package %s;\n", packageName) + template; 25 | name = fqn.substring(fqn.lastIndexOf(".") + 1); 26 | } 27 | String scans = makeScanStrings(packages); 28 | writer.write(template.replace("{name}", name).replace("{scans}", scans)); 29 | } 30 | } 31 | 32 | private static String makeScanStrings(Set strings) { 33 | return strings.stream() 34 | .map(s -> String.format("\"%s\"", s)) 35 | .collect(Collectors.joining(",\n ")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/src/main/java/in/kyle/mcspring/test/command/TestCommandExecutor.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.test.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import lombok.RequiredArgsConstructor; 12 | 13 | import static org.mockito.Mockito.*; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | public class TestCommandExecutor { 18 | 19 | private final TestCommandRegistration registration; 20 | 21 | public List run(String command) { 22 | TestSender sender = spy(TestSender.class); 23 | run(sender, command); 24 | return sender.getMessages() 25 | .stream() 26 | .flatMap(s -> Arrays.stream(s.split("\n"))) 27 | .map(s -> s.replaceAll("§.", "")) 28 | .collect(Collectors.toList()); 29 | } 30 | 31 | public void run(CommandSender sender, String command) { 32 | command = command.trim(); 33 | List parts = new ArrayList<>(Arrays.asList(command.split(" "))); 34 | if (parts.get(0).isEmpty()) { 35 | parts.remove(0); 36 | } 37 | 38 | if (parts.size() != 0) { 39 | String label = parts.get(0); 40 | String[] args = parts.subList(1, parts.size()).toArray(new String[0]); 41 | registration.run(label, sender, label, args); 42 | } else { 43 | throw new RuntimeException("Empty command"); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandOp.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.Server; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import java.util.List; 9 | 10 | import in.kyle.mcspring.test.MCSpringTest; 11 | import in.kyle.mcspring.test.command.TestCommandExecutor; 12 | import in.kyle.mcspring.test.command.TestSender; 13 | 14 | import static org.assertj.core.api.Assertions.*; 15 | import static org.mockito.Mockito.*; 16 | 17 | @MCSpringTest 18 | class TestCommandOp { 19 | 20 | @Autowired 21 | TestCommandExecutor executor; 22 | 23 | @Autowired 24 | Server server; 25 | 26 | TestSender sender; 27 | 28 | @BeforeEach 29 | void setup() { 30 | sender = spy(TestSender.class); 31 | } 32 | 33 | @Test 34 | void testOpSelf() { 35 | doReturn(true).when(sender).isOp(); 36 | doReturn("wendy").when(sender).getName(); 37 | executor.run(sender, "op"); 38 | assertThat(sender.getMessages()).containsExactly("wendy is now op"); 39 | } 40 | 41 | @Test 42 | void testOpOther() { 43 | TestSender target = mock(TestSender.class); 44 | doReturn("wendy").when(target).getName(); 45 | doReturn(target).when(server).getPlayerExact("wendy"); 46 | doReturn(true).when(target).isOp(); 47 | List messages = executor.run("op " + target.getName()); 48 | assertThat(messages).containsExactly(target.getName() + " is now op"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/CommandScanner.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import in.kyle.mcspring.command.Command; 16 | import in.kyle.mcspring.util.SpringScanner; 17 | import lombok.AccessLevel; 18 | import lombok.AllArgsConstructor; 19 | import lombok.Getter; 20 | import lombok.var; 21 | 22 | @Component 23 | @AllArgsConstructor 24 | class CommandScanner implements ApplicationContextAware { 25 | 26 | private final SpringScanner scanner; 27 | private final CommandRegistration commandRegistration; 28 | 29 | @VisibleForTesting 30 | @Getter(AccessLevel.PACKAGE) 31 | private final Set registeredCommands = new HashSet<>(); 32 | 33 | @Override 34 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { 35 | Map scan = scanner.scanMethods(Command.class); 36 | for (var e : scan.entrySet()) { 37 | if (!registeredCommands.contains(e.getKey())) { 38 | Command command = e.getKey().getAnnotation(Command.class); 39 | commandRegistration.register(command, e.getKey(), e.getValue()); 40 | registeredCommands.add(e.getKey()); 41 | } 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/event/EventHandlerSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.event; 2 | 3 | import org.bukkit.event.Event; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.plugin.EventExecutor; 6 | import org.springframework.aop.support.AopUtils; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | import in.kyle.mcspring.RequiresSpigot; 15 | import in.kyle.mcspring.util.SpringScanner; 16 | import lombok.AllArgsConstructor; 17 | import lombok.SneakyThrows; 18 | 19 | @Component 20 | @AllArgsConstructor 21 | @RequiresSpigot 22 | class EventHandlerSupport implements ApplicationContextAware { 23 | 24 | private final EventService eventService; 25 | private final SpringScanner scanner; 26 | 27 | @Override 28 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { 29 | scanner.scanMethods(EventHandler.class).forEach(this::register); 30 | } 31 | 32 | private void register(Method method, Object container) { 33 | eventService.registerEvent(method, createEventExecutor(container, method)); 34 | } 35 | 36 | private EventExecutor createEventExecutor(Object listenerBean, Method method) { 37 | return (listener, event) -> triggerEvent(method, listenerBean, event); 38 | } 39 | 40 | @SneakyThrows 41 | private void triggerEvent(Method method, Object listenerBean, Event event) { 42 | AopUtils.invokeJoinpointUsingReflection(listenerBean, method, new Object[]{event}); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/event/TestEventHandlerSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.event; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.plugin.Plugin; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.core.annotation.Order; 12 | 13 | import in.kyle.mcspring.util.SpringScanner; 14 | 15 | import static org.mockito.Mockito.*; 16 | 17 | @SpringBootTest("spigot.plugin") 18 | @Import({TestEventHandlerSupport.Config.class, SpringScanner.class, EventHandlerSupport.class, 19 | TestEventHandlerSupport.TestEvent.class}) 20 | class TestEventHandlerSupport { 21 | 22 | @Autowired 23 | EventHandlerSupport support; 24 | @Autowired 25 | EventService eventService; 26 | 27 | @Test 28 | void testScanAndRegister() { 29 | // should scan all beans and register methods with @EventHandler 30 | verify(eventService, times(1)).registerEvent(any(), any()); 31 | } 32 | 33 | static class TestEvent { 34 | @EventHandler 35 | public void test() { 36 | } 37 | } 38 | 39 | @Configuration 40 | @Order(1) // Need this config to load first 41 | static class Config { 42 | @Bean 43 | EventService eventService() { 44 | return mock(EventService.class); 45 | } 46 | 47 | @Bean 48 | Plugin plugin() { 49 | return mock(Plugin.class); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/injection/TestTypeResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.injection; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.SpringBootConfiguration; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Import; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.Optional; 12 | 13 | import lombok.var; 14 | 15 | import static org.assertj.core.api.Assertions.*; 16 | 17 | @SpringBootTest(classes = TestTypeResolver.Config.class) 18 | @Import(TypeResolver.class) 19 | class TestTypeResolver { 20 | 21 | @Autowired 22 | TypeResolver resolver; 23 | 24 | @Test 25 | void testSimpleResolve() throws NoSuchMethodException { 26 | abstract class Test { 27 | abstract void test(String string); 28 | } 29 | Method method = Test.class.getDeclaredMethod("test", String.class); 30 | Optional resolve = resolver.resolve(method.getParameters()[0]); 31 | assertThat(resolve).hasValue("A string"); 32 | } 33 | 34 | @Test 35 | void testSimpleResolveDifferentType() throws NoSuchMethodException { 36 | abstract class Test { 37 | abstract void test(int value); 38 | } 39 | Method method = Test.class.getDeclaredMethod("test", int.class); 40 | var result = resolver.resolve(method.getParameters()[0]); 41 | assertThat(result).isNotPresent(); 42 | } 43 | 44 | @SpringBootConfiguration 45 | static class Config { 46 | @Bean 47 | String string() { 48 | return "A string"; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandGamemode.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.GameMode; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import in.kyle.mcspring.test.MCSpringTest; 9 | import in.kyle.mcspring.test.command.TestCommandExecutor; 10 | import in.kyle.mcspring.test.command.TestSender; 11 | 12 | import static org.assertj.core.api.Assertions.*; 13 | import static org.mockito.Mockito.*; 14 | 15 | @MCSpringTest 16 | public class TestCommandGamemode { 17 | 18 | @Autowired 19 | TestCommandExecutor executor; 20 | 21 | TestSender sender; 22 | 23 | @BeforeEach 24 | void setup() { 25 | sender = spy(TestSender.class); 26 | } 27 | 28 | @Test 29 | void testGmc() { 30 | executor.run(sender, "gmc"); 31 | assertThat(sender.getMessages()).containsExactly("Game mode set to creative"); 32 | verify(sender).setGameMode(GameMode.CREATIVE); 33 | } 34 | 35 | @Test 36 | void testGms() { 37 | executor.run(sender, "gms"); 38 | assertThat(sender.getMessages()).containsExactly("Game mode set to survival"); 39 | verify(sender).setGameMode(GameMode.SURVIVAL); 40 | } 41 | 42 | @Test 43 | void testGamemode() { 44 | executor.run(sender, "gm creative"); 45 | assertThat(sender.getMessages()).containsExactly("Game mode set to creative"); 46 | verify(sender).setGameMode(GameMode.CREATIVE); 47 | } 48 | 49 | @Test 50 | void testGamemodeNumeric() { 51 | executor.run(sender, "gm 1"); 52 | assertThat(sender.getMessages()).containsExactly("Game mode set to creative"); 53 | verify(sender).setGameMode(GameMode.CREATIVE); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/event/EventService.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.event; 2 | 3 | import org.bukkit.Server; 4 | import org.bukkit.event.Event; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.plugin.EventExecutor; 8 | import org.bukkit.plugin.Plugin; 9 | import org.springframework.beans.factory.BeanInitializationException; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | import in.kyle.mcspring.RequiresSpigot; 15 | import lombok.AllArgsConstructor; 16 | import lombok.val; 17 | 18 | @Service 19 | @AllArgsConstructor 20 | @RequiresSpigot 21 | class EventService { 22 | 23 | private final Server server; 24 | private final Plugin plugin; 25 | 26 | void registerEvent(Method method, EventExecutor executor) { 27 | val handler = method.getAnnotation(EventHandler.class); 28 | val parameters = method.getParameters(); 29 | 30 | if (parameters.length == 1 && Event.class.isAssignableFrom(parameters[0].getType())) { 31 | val eventType = (Class) parameters[0].getType(); 32 | server.getPluginManager() 33 | .registerEvent(eventType, 34 | makeListener(), 35 | handler.priority(), 36 | executor, 37 | plugin, 38 | handler.ignoreCancelled()); 39 | } else { 40 | throw new BeanInitializationException(String.format( 41 | "Cannot load @EventHandler method %s, requires 1 parameter ", 42 | method)); 43 | } 44 | } 45 | 46 | private Listener makeListener() { 47 | return new Listener() { 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandGamemode.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.GameMode; 4 | import org.bukkit.entity.Player; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import in.kyle.mcspring.command.Command; 11 | import in.kyle.mcspring.subcommands.PluginCommand; 12 | 13 | @Component 14 | class CommandGamemode { 15 | 16 | @Command(value = "gamemode", 17 | aliases = "gm", 18 | description = "Set your game mode", 19 | usage = "/gamemode ") 20 | void gamemode(PluginCommand command) { 21 | command.withPlayerSender("Only players can run this command."); 22 | Map gamemodes = new HashMap<>(); 23 | for (GameMode value : GameMode.values()) { 24 | gamemodes.put(value.name().toLowerCase(), value); 25 | gamemodes.put(String.valueOf(value.getValue()), value); 26 | } 27 | command.withMap(gamemodes, s -> String.format("%s is not a valid game mode", s)); 28 | command.then(this::gamemodeExecutor); 29 | 30 | command.otherwise("Usage: /gamemode "); 31 | } 32 | 33 | @Command(value = "gmc", description = "Set your game mode to creative") 34 | String gmc(Player sender) { 35 | return gamemodeExecutor(sender, GameMode.CREATIVE); 36 | } 37 | 38 | @Command(value = "gms", description = "Set your game mode to survival") 39 | String gms(Player sender) { 40 | return gamemodeExecutor(sender, GameMode.SURVIVAL); 41 | } 42 | 43 | String gamemodeExecutor(Player sender, GameMode gameMode) { 44 | sender.setGameMode(gameMode); 45 | return String.format("Game mode set to %s", gameMode.name().toLowerCase()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-layout/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-build 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 4.0.0 11 | 12 | mcspring-plugin-layout 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-loader-tools 18 | 19 | 20 | in.kyle.mcspring 21 | mcspring-jar-loader 22 | ${project.version} 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-dependency-plugin 30 | 31 | 32 | prepare-package 33 | 34 | copy-dependencies 35 | 36 | 37 | 38 | ${project.build.outputDirectory}/META-INF/loader 39 | 40 | mcspring-jar-loader 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/registration/TestBukkitCommandRegistration.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.Command; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | import lombok.var; 9 | 10 | import static java.util.Arrays.asList; 11 | import static org.mockito.Mockito.*; 12 | 13 | class TestBukkitCommandRegistration { 14 | 15 | @Test 16 | void testSetRequiredFields() throws NoSuchMethodException { 17 | // Tests that required command fields are copied into the command 18 | var controller = mock(CommandController.class); 19 | var command = mock(Command.class); 20 | var registration = new BukkitCommandRegistration(controller, (m, o, n) -> command); 21 | 22 | abstract class Test { 23 | @in.kyle.mcspring.command.Command(value = "command", 24 | aliases = {"a1", "a2", "a3"}, 25 | description = "description", 26 | usage = "usage", 27 | permission = "permission", 28 | permissionMessage = "permissionMessage") 29 | abstract void command(); 30 | } 31 | 32 | Method method = Test.class.getDeclaredMethod("command"); 33 | var annotation = method.getAnnotation(in.kyle.mcspring.command.Command.class); 34 | 35 | registration.register(annotation, method, null); 36 | 37 | verify(command, atLeastOnce()).setAliases(asList("a1", "a2", "a3")); 38 | verify(command, atLeastOnce()).setDescription("description"); 39 | verify(command, atLeastOnce()).setUsage("usage"); 40 | verify(command, atLeastOnce()).setPermission("permission"); 41 | verify(command, atLeastOnce()).setPermissionMessage("permissionMessage"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/scheduler/SchedulerService.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.scheduler; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.bukkit.scheduler.BukkitScheduler; 5 | import org.bukkit.scheduler.BukkitTask; 6 | import org.springframework.context.annotation.Lazy; 7 | import org.springframework.stereotype.Service; 8 | 9 | import in.kyle.mcspring.RequiresSpigot; 10 | import lombok.RequiredArgsConstructor; 11 | 12 | @Lazy 13 | @Service 14 | @RequiredArgsConstructor 15 | @RequiresSpigot 16 | public class SchedulerService { 17 | 18 | private final BukkitScheduler scheduler; 19 | private final Plugin plugin; 20 | 21 | public BukkitTask asyncTask(Runnable task) { 22 | return scheduler.runTaskAsynchronously(plugin, task); 23 | } 24 | 25 | public BukkitTask syncTask(Runnable task) { 26 | return scheduler.runTask(plugin, task); 27 | } 28 | 29 | public BukkitTask asyncDelayedTask(Runnable task, long delayTicks) { 30 | return scheduler.runTaskLaterAsynchronously(plugin, task, delayTicks); 31 | } 32 | 33 | public BukkitTask syncDelayedTask(Runnable task, long delayTicks) { 34 | return scheduler.runTaskLater(plugin, task, delayTicks); 35 | } 36 | 37 | public BukkitTask asyncRepeatingTask(Runnable task, long delayTicks, long periodTicks) { 38 | return scheduler.runTaskTimerAsynchronously(plugin, task, delayTicks, periodTicks); 39 | } 40 | 41 | public BukkitTask syncRepeatingTask(Runnable task, long delayTicks, long periodTicks) { 42 | return scheduler.runTaskTimer(plugin, task, delayTicks, periodTicks); 43 | } 44 | 45 | public void cancelTask(BukkitTask task) { 46 | scheduler.cancelTask(task.getTaskId()); 47 | } 48 | 49 | public boolean isCurrentlyRunning(BukkitTask task) { 50 | return scheduler.isCurrentlyRunning(task.getTaskId()); 51 | } 52 | 53 | public boolean isQueued(BukkitTask task) { 54 | return scheduler.isQueued(task.getTaskId()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/tab/TabDiscovery.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands.tab; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.function.Consumer; 11 | 12 | import in.kyle.mcspring.command.SimpleMethodInjection; 13 | import in.kyle.mcspring.subcommands.PluginCommand; 14 | import in.kyle.mcspring.subcommands.PluginCommandTabCompletable; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.var; 17 | 18 | @Component 19 | @RequiredArgsConstructor 20 | public class TabDiscovery { 21 | 22 | private final SimpleMethodInjection injection; 23 | 24 | public List getCompletions(CommandSender sender, 25 | String commandString, 26 | Consumer consumer) { 27 | if (!commandString.isEmpty() && !commandString.endsWith(" ")) { 28 | // TODO: 2020-02-25 Enable partial completions 29 | return Collections.emptyList(); 30 | } 31 | var parts = new ArrayList<>(Arrays.asList(commandString.split(" "))); 32 | if (parts.get(0).isEmpty()) { 33 | parts.remove(0); 34 | } 35 | var command = new PluginCommandTabCompletable(injection, sender, parts); 36 | consumer.accept(command); 37 | 38 | return getCompletions(command); 39 | } 40 | 41 | private List getCompletions(PluginCommandTabCompletable command) { 42 | if (command.hasChild()) { 43 | return getCompletions(command.getChild()); 44 | } else if (command.getState() == PluginCommand.State.MISSING_ARG || 45 | command.getState() == PluginCommand.State.CLEAN) { 46 | return command.getTabCompletionOptions(); 47 | } else { 48 | return Collections.emptyList(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-vault/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-api 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 4.0.0 11 | 12 | 13 | 1.7 14 | 15 | mcspring-vault 16 | 17 | 18 | 19 | jitpack.io 20 | https://jitpack.io 21 | 22 | 23 | 24 | 25 | 26 | ${project.parent.groupId} 27 | mcspring-base 28 | 29 | 30 | com.github.MilkBowl 31 | VaultAPI 32 | ${vault.version} 33 | provided 34 | 35 | 36 | * 37 | * 38 | 39 | 40 | 41 | 42 | org.spigotmc 43 | spigot-api 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-source-plugin 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-javadoc-plugin 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-api 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 4.0.0 11 | 12 | mcspring-test 13 | 14 | 15 | 16 | jitpack.io 17 | https://jitpack.io 18 | 19 | 20 | 21 | 22 | 23 | org.spigotmc 24 | spigot-api 25 | 26 | 27 | in.kyle.mcspring 28 | mcspring-base 29 | ${project.parent.version} 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-test 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | ${spring.version} 43 | 44 | 45 | junit 46 | junit 47 | 48 | 49 | org.junit.vintage 50 | junit-vintage-engine 51 | 52 | 53 | 54 | 55 | org.slf4j 56 | slf4j-simple 57 | 2.0.0-alpha1 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/registration/SimpleCommandFactory.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.CommandExecutor; 4 | import org.bukkit.command.PluginCommand; 5 | import org.bukkit.plugin.Plugin; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.List; 10 | import java.util.Set; 11 | import java.util.stream.Collectors; 12 | 13 | import in.kyle.mcspring.RequiresSpigot; 14 | import in.kyle.mcspring.command.SimpleMethodInjection; 15 | import lombok.RequiredArgsConstructor; 16 | import lombok.SneakyThrows; 17 | import lombok.var; 18 | 19 | @Component 20 | @RequiredArgsConstructor 21 | @RequiresSpigot 22 | class SimpleCommandFactory implements BukkitCommandRegistration.CommandFactory { 23 | 24 | private final SimpleMethodInjection injection; 25 | private final Set commandResolvers; 26 | private final Plugin plugin; 27 | 28 | @SneakyThrows 29 | public org.bukkit.command.Command makeCommand(Method method, Object object, String name) { 30 | var constructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); 31 | constructor.setAccessible(true); 32 | PluginCommand command = constructor.newInstance(name, plugin); 33 | CommandExecutor executor = makeExecutor(method, object); 34 | command.setExecutor(executor); 35 | return command; 36 | } 37 | 38 | CommandExecutor makeExecutor(Method method, Object object) { 39 | return (sender, bukkitCommand, label, args) -> { 40 | try { 41 | var command = new CommandResolver.Command(sender, args, label); 42 | List resolvers = commandResolvers.stream() 43 | .map(r -> r.makeResolver(command)) 44 | .collect(Collectors.toList()); 45 | var result = injection.invoke(method, object, resolvers, sender, args, label); 46 | if (result != null) { 47 | sender.sendMessage(result.toString()); 48 | } 49 | } catch (RuntimeException exception) { 50 | throw new RuntimeException("Could not invoke method " + method.getName(), 51 | exception); 52 | } 53 | return true; 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/injection/TestQualifierResolver.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.injection; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.BeanNotOfRequiredTypeException; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.boot.SpringBootConfiguration; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Import; 11 | 12 | import java.lang.reflect.Method; 13 | import java.util.Optional; 14 | 15 | import static org.assertj.core.api.Assertions.*; 16 | 17 | @SpringBootTest(classes = TestQualifierResolver.Config.class) 18 | @Import(QualifierResolver.class) 19 | class TestQualifierResolver { 20 | 21 | @Autowired 22 | QualifierResolver resolver; 23 | 24 | @Test 25 | void testSimpleResolve() throws NoSuchMethodException { 26 | abstract class Test { 27 | abstract void test(@Qualifier("noodle") String string); 28 | } 29 | Method method = Test.class.getDeclaredMethod("test", String.class); 30 | Optional resolve = resolver.resolve(method.getParameters()[0]); 31 | assertThat(resolve).hasValue("I like noodles"); 32 | } 33 | 34 | @Test 35 | void testSimpleResolveDifferentType() throws NoSuchMethodException { 36 | abstract class Test { 37 | abstract void test(@Qualifier("noodle") int value); 38 | } 39 | Method method = Test.class.getDeclaredMethod("test", int.class); 40 | assertThatExceptionOfType(BeanNotOfRequiredTypeException.class).isThrownBy(() -> { 41 | resolver.resolve(method.getParameters()[0]); 42 | }); 43 | } 44 | 45 | @Test 46 | void testNoQualifierIgnore() throws NoSuchMethodException { 47 | abstract class Test { 48 | abstract void test(int value); 49 | } 50 | Method method = Test.class.getDeclaredMethod("test", int.class); 51 | Optional resolve = resolver.resolve(method.getParameters()[0]); 52 | assertThat(resolve).isNotPresent(); 53 | } 54 | 55 | @SpringBootConfiguration 56 | static class Config { 57 | @Bean(name = "noodle") 58 | String noodle() { 59 | return "I like noodles"; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-vault/src/main/java/in/kyle/mcspring/economy/VaultEconomyService.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.economy; 2 | 3 | import net.milkbowl.vault.economy.Economy; 4 | import net.milkbowl.vault.economy.EconomyResponse; 5 | 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.Server; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 9 | import org.springframework.context.annotation.Lazy; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.math.BigDecimal; 13 | 14 | @Lazy 15 | @Service 16 | @ConditionalOnClass(Economy.class) 17 | class VaultEconomyService implements EconomyService { 18 | 19 | private final Economy economy; 20 | 21 | public VaultEconomyService(Server server) { 22 | economy = server.getServicesManager().getRegistration(Economy.class).getProvider(); 23 | } 24 | 25 | @Override 26 | public void deposit(OfflinePlayer player, BigDecimal amount) { 27 | assertEconomyResponse(economy.depositPlayer(player, amount.doubleValue())); 28 | } 29 | 30 | @Override 31 | public void withdraw(OfflinePlayer player, BigDecimal amount) { 32 | assertEconomyResponse(economy.withdrawPlayer(player, amount.doubleValue())); 33 | } 34 | 35 | @Override 36 | public void transfer(OfflinePlayer origin, OfflinePlayer destination, BigDecimal amount) { 37 | withdraw(origin, amount); 38 | deposit(destination, amount); 39 | } 40 | 41 | @Override 42 | public boolean hasAmount(OfflinePlayer player, BigDecimal amount) { 43 | return economy.has(player, amount.doubleValue()); 44 | } 45 | 46 | @Override 47 | public void createAccount(OfflinePlayer player) { 48 | economy.createPlayerAccount(player); 49 | } 50 | 51 | @Override 52 | public String format(BigDecimal amount) { 53 | return economy.format(amount.doubleValue()); 54 | } 55 | 56 | @Override 57 | public BigDecimal getBalance(OfflinePlayer player) { 58 | return BigDecimal.valueOf(economy.getBalance(player)); 59 | } 60 | 61 | @Override 62 | public boolean hasAccount(OfflinePlayer player) { 63 | return economy.hasAccount(player); 64 | } 65 | 66 | private void assertEconomyResponse(EconomyResponse response) { 67 | if (response.type != EconomyResponse.ResponseType.SUCCESS) { 68 | throw new EconomyException(response.errorMessage); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-api 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | 4.0.0 12 | 13 | mcspring-base 14 | 15 | 16 | 17 | spigot-repo 18 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 19 | 20 | 21 | 22 | 23 | 24 | org.spigotmc 25 | spigot-api 26 | 27 | 28 | in.kyle.mcspring 29 | mcspring-jar-loader 30 | ${project.version} 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-loader 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-aop 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-logging 47 | 48 | 49 | 50 | 51 | org.apache.logging.log4j 52 | log4j-core 53 | 2.17.0 54 | provided 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-test 63 | test 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/command/registration/TabCommandFactory.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command.registration; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.TabCompleter; 5 | import org.bukkit.plugin.Plugin; 6 | import org.springframework.context.annotation.Primary; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.Set; 11 | import java.util.function.Consumer; 12 | 13 | import in.kyle.mcspring.RequiresSpigot; 14 | import in.kyle.mcspring.command.SimpleMethodInjection; 15 | import in.kyle.mcspring.subcommands.PluginCommand; 16 | import in.kyle.mcspring.subcommands.tab.TabDiscovery; 17 | import lombok.SneakyThrows; 18 | import lombok.var; 19 | 20 | @Primary 21 | @Component 22 | @RequiresSpigot 23 | class TabCommandFactory extends SimpleCommandFactory { 24 | 25 | private final TabDiscovery tabDiscovery; 26 | 27 | public TabCommandFactory(SimpleMethodInjection injection, 28 | Set commandResolvers, 29 | Plugin plugin, 30 | TabDiscovery tabDiscovery) { 31 | super(injection, commandResolvers, plugin); 32 | this.tabDiscovery = tabDiscovery; 33 | } 34 | 35 | @Override 36 | public Command makeCommand(Method method, Object object, String name) { 37 | var command = (org.bukkit.command.PluginCommand) super.makeCommand(method, object, name); 38 | if (method.getParameterCount() == 1 && method.getParameters()[0].getType() 39 | .isAssignableFrom(PluginCommand.class)) { 40 | command.setTabCompleter(makeTabCompleter(method, object)); 41 | } 42 | return command; 43 | } 44 | 45 | private Consumer methodToConsumer(Method method, 46 | Object object) { 47 | return pluginCommand -> { 48 | method.setAccessible(true); 49 | invoke(method, object, pluginCommand); 50 | }; 51 | } 52 | 53 | @SneakyThrows 54 | private Object invoke(Method method, 55 | Object object, 56 | PluginCommand command) { 57 | return method.invoke(object, command); 58 | } 59 | 60 | private TabCompleter makeTabCompleter(Method method, Object object) { 61 | return (sender, command, s, strings) -> { 62 | var consumer = methodToConsumer(method, object); 63 | return tabDiscovery.getCompletions(sender, String.join(" ", strings), consumer); 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-build 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | 4.0.0 12 | 13 | mcspring-annotations 14 | 15 | 16 | 17 | commons-io 18 | commons-io 19 | 2.6 20 | 21 | 22 | in.kyle.mcspring 23 | mcspring-base 24 | provided 25 | 26 | 27 | commons-lang 28 | commons-lang 29 | 2.6 30 | compile 31 | 32 | 33 | org.spigotmc 34 | spigot-api 35 | compile 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | maven-resources-plugin 44 | 3.1.0 45 | 46 | 47 | copy-resources 48 | process-classes 49 | 50 | copy-resources 51 | 52 | 53 | ${project.build.outputDirectory} 54 | 55 | 56 | ${basedir}/goddammit-maven 57 | false 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-test/src/main/java/in/kyle/mcspring/test/command/TestCommandRegistration.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.test.command; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.lang.reflect.Method; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | import in.kyle.mcspring.command.Command; 16 | import in.kyle.mcspring.command.registration.CommandRegistration; 17 | import in.kyle.mcspring.command.registration.CommandResolver; 18 | import in.kyle.mcspring.command.SimpleMethodInjection; 19 | import lombok.RequiredArgsConstructor; 20 | import lombok.var; 21 | 22 | @Component 23 | @RequiredArgsConstructor 24 | class TestCommandRegistration implements CommandRegistration { 25 | 26 | private final SimpleMethodInjection injection; 27 | private final Set commandResolvers; 28 | 29 | private final Map commandExecutors = new HashMap<>(); 30 | 31 | @Override 32 | public void register(Command command, Method method, Object object) { 33 | var executor = makeExecutor(method, object); 34 | getAllNames(command).forEach(key -> commandExecutors.put(key, executor)); 35 | } 36 | 37 | private List getAllNames(Command command) { 38 | List commands = new ArrayList<>(Arrays.asList(command.aliases())); 39 | commands.add(command.value()); 40 | return commands; 41 | } 42 | 43 | private Executor makeExecutor(Method method, Object object) { 44 | return (sender, label, args) -> { 45 | var temp = new CommandResolver.Command(sender, args, label); 46 | var contextResolvers = commandResolvers.stream() 47 | .map(r -> r.makeResolver(temp)) 48 | .collect(Collectors.toList()); 49 | Object result = injection.invoke(method, object, contextResolvers, sender, args, label); 50 | if (result != null) { 51 | sender.sendMessage(result.toString()); 52 | } 53 | }; 54 | } 55 | 56 | public void run(String command, CommandSender sender, String label, String[] args) { 57 | if (commandExecutors.containsKey(command)) { 58 | commandExecutors.get(command).execute(sender, label, args); 59 | } else { 60 | throw new RuntimeException( 61 | "Command " + command + " is not registered. Make sure to @Import it"); 62 | } 63 | } 64 | 65 | interface Executor { 66 | void execute(CommandSender sender, String label, String[] args); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/Executors.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import java.io.Serializable; 4 | import java.lang.invoke.SerializedLambda; 5 | import java.lang.reflect.Method; 6 | import java.util.stream.Stream; 7 | 8 | import lombok.SneakyThrows; 9 | 10 | public interface Executors { 11 | 12 | @SneakyThrows 13 | default Method getMethod(int argCount) { 14 | Method writeReplace = this.getClass().getDeclaredMethod("writeReplace"); 15 | writeReplace.setAccessible(true); 16 | SerializedLambda sl = (SerializedLambda) writeReplace.invoke(this); 17 | String methodName = sl.getImplMethodName(); 18 | Class clazz = Class.forName(sl.getImplClass().replace("/", ".")); 19 | return Stream.of(clazz.getMethods(), clazz.getDeclaredMethods()) 20 | .flatMap(Stream::of) 21 | .filter(m -> m.getName().equals(methodName)) 22 | .filter(m -> m.getParameters().length == argCount) 23 | .findFirst() 24 | .orElseThrow(() -> new RuntimeException("Method not found")); 25 | } 26 | 27 | interface E1 extends Executors, Serializable { 28 | void handle(A a1); 29 | } 30 | 31 | interface E2 extends Executors, Serializable { 32 | void handle(A a, B b); 33 | } 34 | 35 | interface E3 extends Executors, Serializable { 36 | void handle(A a, B b, C c); 37 | } 38 | 39 | interface E4 extends Executors, Serializable { 40 | void handle(A a, B b, C c, D d); 41 | } 42 | 43 | interface E5 extends Executors, Serializable { 44 | void handle(A a, B b, C c, D d, E e); 45 | } 46 | 47 | interface E6 extends Executors, Serializable { 48 | void handle(A a, B b, C c, D d, E e, F f); 49 | } 50 | 51 | interface O0 extends Executors, Serializable { 52 | Object handle(); 53 | } 54 | 55 | interface O1 extends Executors, Serializable { 56 | Object handle(A a1); 57 | } 58 | 59 | interface O2 extends Executors, Serializable { 60 | Object handle(A a, B b); 61 | } 62 | 63 | interface O3 extends Executors, Serializable { 64 | Object handle(A a, B b, C c); 65 | } 66 | 67 | interface O4 extends Executors, Serializable { 68 | Object handle(A a, B b, C c, D d); 69 | } 70 | 71 | interface O5 extends Executors, Serializable { 72 | Object handle(A a, B b, C c, D d, E e); 73 | } 74 | 75 | interface O6 extends Executors, Serializable { 76 | void handle(A a, B b, C c, D d, E e, F f); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/test/java/in/kyle/mcspring/manager/commands/TestCommandPlugin.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.Collections; 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | 19 | import in.kyle.mcspring.manager.controller.PluginController; 20 | import in.kyle.mcspring.test.MCSpringTest; 21 | import in.kyle.mcspring.test.command.TestCommandExecutor; 22 | import lombok.val; 23 | 24 | import static org.assertj.core.api.Assertions.*; 25 | import static org.mockito.Mockito.*; 26 | 27 | @MCSpringTest 28 | class TestCommandPlugin { 29 | 30 | @Autowired 31 | TestCommandExecutor executor; 32 | 33 | @Autowired 34 | PluginController controller; 35 | 36 | @Test 37 | void testLoad() { 38 | Map loadablePlugins = new HashMap<>(); 39 | loadablePlugins.put("test-plugin", Paths.get("test-plugin.jar")); 40 | doReturn(loadablePlugins).when(controller).getLoadablePlugins(); 41 | doReturn(Optional.empty()).when(controller).load(any()); 42 | val output = executor.run("pl load test-plugin"); 43 | assertThat(output).containsExactly("Could not load test-plugin.jar see log for details"); 44 | } 45 | 46 | @Test 47 | void testUnload() { 48 | Plugin plugin = mock(Plugin.class); 49 | doReturn("test-plugin").when(plugin).getName(); 50 | doReturn(true).when(controller).unload(any()); 51 | val plugins = new HashSet<>(Collections.singletonList(plugin)); 52 | doReturn(plugins).when(controller).getPlugins(); 53 | val output = executor.run("pl unload test-plugin"); 54 | assertThat(output).containsExactly("Plugin test-plugin disabled"); 55 | } 56 | 57 | @Test 58 | void testList() { 59 | Plugin plugin = mock(Plugin.class); 60 | doReturn("test-plugin").when(plugin).getName(); 61 | val plugins = Stream.of(plugin).collect(Collectors.toMap(Plugin::getName, p -> true)); 62 | doReturn(plugins).when(controller).getAllPlugins(); 63 | 64 | val output = executor.run("pl list"); 65 | assertThat(output).containsExactly("test-plugin"); 66 | } 67 | 68 | @Configuration 69 | static class Config { 70 | @Bean 71 | PluginController pluginController() { 72 | return mock(PluginController.class); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/SpringSpigotSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Server; 5 | import org.bukkit.command.CommandMap; 6 | import org.bukkit.configuration.file.FileConfiguration; 7 | import org.bukkit.plugin.Plugin; 8 | import org.bukkit.plugin.PluginDescriptionFile; 9 | import org.bukkit.plugin.PluginLoader; 10 | import org.bukkit.plugin.PluginManager; 11 | import org.bukkit.plugin.messaging.Messenger; 12 | import org.bukkit.scheduler.BukkitScheduler; 13 | import org.bukkit.scoreboard.ScoreboardManager; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.ComponentScan; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 19 | import org.springframework.scheduling.annotation.EnableScheduling; 20 | 21 | import java.lang.reflect.Field; 22 | import java.util.logging.Logger; 23 | 24 | @Configuration 25 | @ComponentScan(basePackageClasses = SpringPlugin.class) 26 | @EnableScheduling 27 | @EnableAspectJAutoProxy 28 | @RequiresSpigot 29 | public class SpringSpigotSupport { 30 | 31 | @Bean 32 | Plugin plugin(@Value("${spigot.plugin}") String pluginName) { 33 | return Bukkit.getPluginManager().getPlugin(pluginName); 34 | } 35 | 36 | @Bean(destroyMethod = "") 37 | Server server(Plugin plugin) { 38 | return plugin.getServer(); 39 | } 40 | 41 | @Bean 42 | Logger logger(Plugin plugin) { 43 | return plugin.getLogger(); 44 | } 45 | 46 | @Bean 47 | PluginManager pluginManager(Server server) { 48 | return server.getPluginManager(); 49 | } 50 | 51 | @Bean 52 | ScoreboardManager scoreboardManager(Server server) { 53 | return server.getScoreboardManager(); 54 | } 55 | 56 | @Bean 57 | Messenger messenger(Server server) { 58 | return server.getMessenger(); 59 | } 60 | 61 | @Bean 62 | FileConfiguration configuration(Plugin plugin) { 63 | return plugin.getConfig(); 64 | } 65 | 66 | @Bean 67 | PluginDescriptionFile description(Plugin plugin) { 68 | return plugin.getDescription(); 69 | } 70 | 71 | @Bean 72 | BukkitScheduler scheduler(Server server) { 73 | return server.getScheduler(); 74 | } 75 | 76 | @Bean 77 | PluginLoader pluginLoader(Plugin plugin) { 78 | return plugin.getPluginLoader(); 79 | } 80 | 81 | @Bean 82 | CommandMap getCommandMap(Server server) throws Exception { 83 | Field bukkitCommandMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); 84 | bukkitCommandMap.setAccessible(true); 85 | return (CommandMap) bukkitCommandMap.get(server); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/commands/CommandPlugin.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.commands; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.nio.file.Path; 6 | import java.util.function.Function; 7 | import java.util.stream.Collectors; 8 | 9 | import in.kyle.mcspring.command.Command; 10 | import in.kyle.mcspring.manager.controller.PluginController; 11 | import in.kyle.mcspring.subcommands.PluginCommand; 12 | import lombok.RequiredArgsConstructor; 13 | import lombok.var; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | class CommandPlugin { 18 | 19 | private final PluginController pluginController; 20 | 21 | @Command(value = "plugin", 22 | aliases = "pl", 23 | description = "Load/unload/reload a specific plugin", 24 | usage = "/plugin ") 25 | void plugin(PluginCommand command) { 26 | command.on("load", this::load); 27 | command.on("unload", this::unload); 28 | command.on("list", this::executeListPlugins); 29 | command.otherwise("Usage: /plugin "); 30 | } 31 | 32 | private void load(PluginCommand command) { 33 | command.withMap(pluginController.getLoadablePlugins(), 34 | s -> String.format("Plugin %s not found or is already loaded", s)); 35 | command.then(this::executeLoad); 36 | command.otherwise("Usage: /plugin load "); 37 | } 38 | 39 | private void unload(PluginCommand command) { 40 | var plugins = pluginController.getPlugins() 41 | .stream() 42 | .collect(Collectors.toMap(org.bukkit.plugin.Plugin::getName, Function.identity())); 43 | command.withMap(plugins, s -> String.format("Plugin %s is not loaded", s)); 44 | command.then(this::executeDisable); 45 | command.otherwise("Usage: /plugin unload "); 46 | } 47 | 48 | private String executeListPlugins() { 49 | return pluginController.getAllPlugins() 50 | .entrySet() 51 | .stream() 52 | .map(e -> String.format("%s%s", e.getValue() ? "&1" : "&4", e.getKey())) 53 | .collect(Collectors.joining(" ")); 54 | } 55 | 56 | private String executeLoad(Path jar) { 57 | var pluginOptional = pluginController.load(jar); 58 | if (pluginOptional.isPresent()) { 59 | return String.format("Plugin %s enabled", pluginOptional.get().getName()); 60 | } else { 61 | return String.format("Could not load %s see log for details", jar); 62 | } 63 | } 64 | 65 | private String executeDisable(org.bukkit.plugin.Plugin plugin) { 66 | boolean disabled = pluginController.unload(plugin); 67 | if (disabled) { 68 | return String.format("Plugin %s disabled", plugin.getName()); 69 | } else { 70 | return String.format("Could not disable %s, see log for details", plugin.getName()); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-build 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | 4.0.0 12 | 13 | mcspring-archetype 14 | 15 | 16 | 17 | in.kyle.mcspring 18 | mcspring-plugin-manager 19 | ${project.parent.version} 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.archetype 27 | archetype-packaging 28 | 3.1.2 29 | 30 | 31 | 32 | 33 | src/main/resources 34 | true 35 | 36 | archetype-resources/pom.xml 37 | 38 | 39 | 40 | src/main/resources 41 | false 42 | 43 | archetype-resources/pom.xml 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-resources-plugin 51 | 3.1.0 52 | 53 | \ 54 | 55 | 56 | 57 | maven-dependency-plugin 58 | 59 | 60 | prepare-package 61 | 62 | copy-dependencies 63 | 64 | 65 | 66 | ${project.build.outputDirectory}/archetype-resources/spigot/plugins 67 | 68 | mcspring-plugin-manager 69 | true 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/scheduler/ScheduledAnnotationSupport.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.scheduler; 2 | 3 | import org.springframework.context.annotation.Lazy; 4 | import org.springframework.scheduling.Trigger; 5 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.concurrent.ListenableFuture; 8 | 9 | import java.util.Date; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.Future; 12 | import java.util.concurrent.ScheduledFuture; 13 | 14 | import in.kyle.mcspring.RequiresSpigot; 15 | import lombok.AllArgsConstructor; 16 | import lombok.EqualsAndHashCode; 17 | 18 | @Lazy 19 | @Component 20 | @AllArgsConstructor 21 | @RequiresSpigot 22 | class ScheduledAnnotationSupport extends ThreadPoolTaskScheduler { 23 | 24 | private final SchedulerService scheduler; 25 | 26 | @Override 27 | public ScheduledFuture schedule(Runnable task, Trigger trigger) { 28 | return super.schedule(wrapSync(task), trigger); 29 | } 30 | 31 | @Override 32 | public ScheduledFuture schedule(Runnable task, Date startTime) { 33 | return super.schedule(wrapSync(task), startTime); 34 | } 35 | 36 | @Override 37 | public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) { 38 | return super.scheduleAtFixedRate(wrapSync(task), startTime, period); 39 | } 40 | 41 | @Override 42 | public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) { 43 | return super.scheduleAtFixedRate(wrapSync(task), period); 44 | } 45 | 46 | @Override 47 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { 48 | return super.scheduleWithFixedDelay(wrapSync(task), startTime, delay); 49 | } 50 | 51 | @Override 52 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) { 53 | return super.scheduleWithFixedDelay(wrapSync(task), delay); 54 | } 55 | 56 | @Override 57 | public void execute(Runnable task) { 58 | super.execute(task); 59 | } 60 | 61 | @Override 62 | public Future submit(Runnable task) { 63 | return super.submit(task); 64 | } 65 | 66 | @Override 67 | public Future submit(Callable task) { 68 | return super.submit(task); 69 | } 70 | 71 | @Override 72 | public ListenableFuture submitListenable(Runnable task) { 73 | return super.submitListenable(task); 74 | } 75 | 76 | @Override 77 | public ListenableFuture submitListenable(Callable task) { 78 | return super.submitListenable(task); 79 | } 80 | 81 | private Runnable wrapSync(Runnable task) { 82 | return new WrappedRunnable(scheduler, task); 83 | } 84 | 85 | @AllArgsConstructor 86 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 87 | static class WrappedRunnable implements Runnable { 88 | 89 | private final SchedulerService scheduler; 90 | 91 | @EqualsAndHashCode.Include 92 | private final Runnable runnable; 93 | 94 | @Override 95 | public void run() { 96 | scheduler.syncTask(runnable); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/src/test/java/org/example/factions/commands/TestFactionsCommand.java: -------------------------------------------------------------------------------- 1 | package org.example.factions.commands; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Server; 5 | import org.example.factions.api.Faction; 6 | import org.example.factions.api.FactionsApi; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.ComponentScan; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.UUID; 15 | 16 | import in.kyle.mcspring.test.MCSpringTest; 17 | import in.kyle.mcspring.test.command.TestCommandExecutor; 18 | import in.kyle.mcspring.test.command.TestSender; 19 | import lombok.val; 20 | 21 | import static org.assertj.core.api.Assertions.*; 22 | import static org.mockito.Mockito.*; 23 | 24 | @MCSpringTest(classes = TestFactionsCommand.Config.class) 25 | class TestFactionsCommand { 26 | 27 | @Autowired 28 | TestCommandExecutor console; 29 | 30 | @Autowired 31 | Server server; 32 | 33 | @Autowired 34 | FactionsApi api; 35 | 36 | @Test 37 | void testCreate() { 38 | val output = console.run("f create test"); 39 | assertThat(output).containsExactly("Created faction: test"); 40 | assertThat(api.getFactions()).first().matches(f -> f.getName().equals("test")); 41 | } 42 | 43 | @Test 44 | void testDelete() { 45 | api.addFaction(new Faction("test-faction", UUID.randomUUID())); 46 | val output = console.run("f delete test-faction"); 47 | assertThat(output).containsExactly("Deleted faction: test-faction"); 48 | assertThat(api.getFactions()).isEmpty(); 49 | } 50 | 51 | @Test 52 | void testJoin() { 53 | api.addFaction(new Faction("test", UUID.randomUUID())); 54 | val output = console.run("f join test"); 55 | assertThat(output).containsExactly("You joined test"); 56 | assertThat(api.getFactions()).first().matches(f -> f.getMembers().size() == 2); 57 | } 58 | 59 | @Test 60 | void testInfo() { 61 | val output = console.run("f info"); 62 | assertThat(output).containsExactly("You are not in a faction"); 63 | } 64 | 65 | @Test 66 | void testList() { 67 | TestSender sender = spy(TestSender.class); 68 | doReturn("test-sender").when(sender).getName(); 69 | UUID toBeReturned = UUID.randomUUID(); 70 | doReturn(toBeReturned).when(sender).getUniqueId(); 71 | doReturn(sender).when(server).getPlayer(toBeReturned); 72 | 73 | Faction faction = new Faction("test-faction", sender.getUniqueId()); 74 | api.addFaction(faction); 75 | 76 | val output = console.run("f list"); 77 | assertThat(output).containsExactly("Factions: ", "test-faction: test-sender(owner)"); 78 | } 79 | 80 | @Configuration 81 | @ComponentScan(basePackages = {"org.example.factions"}) 82 | static class Config { 83 | @Bean 84 | Server server() throws NoSuchFieldException, IllegalAccessException { 85 | Server server = mock(Server.class); 86 | Field field = Bukkit.class.getDeclaredField("server"); 87 | field.setAccessible(true); 88 | field.set(null, server); 89 | return server; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/PluginCommandTabCompletable.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.Set; 11 | import java.util.function.Consumer; 12 | import java.util.function.Function; 13 | import java.util.function.Supplier; 14 | 15 | import in.kyle.mcspring.command.SimpleMethodInjection; 16 | import lombok.Getter; 17 | import lombok.var; 18 | 19 | public class PluginCommandTabCompletable extends PluginCommand { 20 | 21 | @Getter 22 | private final List tabCompletionOptions = new ArrayList<>(); 23 | @Getter 24 | private PluginCommandTabCompletable child; 25 | 26 | public PluginCommandTabCompletable(SimpleMethodInjection injection, 27 | CommandSender sender, 28 | List parts) { 29 | super(injection, sender, parts, Collections.emptyList()); 30 | } 31 | 32 | public State getState() { 33 | return state; 34 | } 35 | 36 | public boolean hasChild() { 37 | return child != null; 38 | } 39 | 40 | @Override 41 | public void on(String command, Consumer executor) { 42 | tabCompletionOptions.add(command); 43 | super.on(command, executor); 44 | } 45 | 46 | @Override 47 | protected void callOn(String command, Executors executors, int argSize) { 48 | tabCompletionOptions.add(command); 49 | if (hasExecutablePart()) { 50 | state = State.EXECUTED; 51 | } 52 | } 53 | 54 | @Override 55 | public void onInvalid(Function help) { 56 | } 57 | 58 | @Override 59 | public void with(Function> processor, Function error) { 60 | tabCompletionOptions.clear(); 61 | if (hasExecutablePart()) { 62 | parts.remove(0); 63 | state = State.INVALID_ARG; 64 | } else { 65 | state = State.MISSING_ARG; 66 | } 67 | } 68 | 69 | @Override 70 | public void withMap(Map options, Function invalidArg) { 71 | if (hasExecutablePart()) { 72 | Set validOptions = options.keySet(); 73 | String part = parts.remove(0).toLowerCase(); 74 | if (!validOptions.contains(part)) { 75 | state = State.INVALID_ARG; 76 | } 77 | } else { 78 | state = State.MISSING_ARG; 79 | tabCompletionOptions.addAll(options.keySet()); 80 | } 81 | } 82 | 83 | @Override 84 | public void otherwise(Supplier supplier) { 85 | } 86 | 87 | 88 | @Override 89 | public void then(Runnable r) { 90 | } 91 | 92 | @Override 93 | protected PluginCommand subCommandCopy() { 94 | var command = 95 | new PluginCommandTabCompletable(injection, sender, parts.subList(1, parts.size())); 96 | if (child == null) { 97 | child = command; 98 | return command; 99 | } else { 100 | throw new IllegalStateException("Command cannot have 2 children"); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/SpringPlugin.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring; 2 | 3 | import org.apache.logging.log4j.Level; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.core.Logger; 6 | import org.bukkit.plugin.Plugin; 7 | import org.springframework.boot.Banner; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.context.ConfigurableApplicationContext; 10 | import org.springframework.core.io.DefaultResourceLoader; 11 | import org.springframework.core.io.ResourceLoader; 12 | 13 | import java.util.LinkedHashMap; 14 | import java.util.Map; 15 | 16 | import lombok.Getter; 17 | import lombok.RequiredArgsConstructor; 18 | 19 | @RequiredArgsConstructor 20 | public class SpringPlugin { 21 | 22 | private static final LinkedHashMap SETUP_PLUGINS = new LinkedHashMap<>(); 23 | 24 | private final Plugin plugin; 25 | @Getter 26 | private ConfigurableApplicationContext context; 27 | 28 | public final void onDisable(Plugin plugin) { 29 | if (context != null) { 30 | context.close(); 31 | context = null; 32 | SETUP_PLUGINS.remove(plugin); 33 | } 34 | } 35 | 36 | private void initSpring(Class config) { 37 | SpringApplicationBuilder builder = new SpringApplicationBuilder(); 38 | if (!SETUP_PLUGINS.isEmpty()) { 39 | SpringPlugin parent = findParentCandidate(); 40 | builder.parent(parent.getContext()); 41 | } 42 | 43 | ClassLoader classLoader = plugin.getClass().getClassLoader(); 44 | Class[] sources = new Class[]{config, SpringSpigotSupport.class}; 45 | ResourceLoader loader = new DefaultResourceLoader(classLoader); 46 | 47 | runWithContextClassLoader(classLoader, () -> { 48 | context = builder.sources(sources) 49 | .resourceLoader(loader) 50 | .bannerMode(Banner.Mode.OFF) 51 | .properties("spigot.plugin=" + plugin.getName()) 52 | .logStartupInfo(true) 53 | .run(); 54 | }); 55 | } 56 | 57 | private void runWithContextClassLoader(ClassLoader classLoader, Runnable runnable) { 58 | ClassLoader temp = Thread.currentThread().getContextClassLoader(); 59 | Thread.currentThread().setContextClassLoader(classLoader); 60 | runnable.run(); 61 | Thread.currentThread().setContextClassLoader(temp); 62 | } 63 | 64 | private static void setupLogger() { 65 | if (SETUP_PLUGINS.isEmpty()) { 66 | Logger rootLogger = (Logger) LogManager.getRootLogger(); 67 | rootLogger.setLevel(Level.ALL); 68 | } 69 | } 70 | 71 | private SpringPlugin findParentCandidate() { 72 | return SETUP_PLUGINS.entrySet() 73 | .stream() 74 | .reduce((a, b) -> b) 75 | .map(Map.Entry::getValue) 76 | .orElse(null); 77 | } 78 | 79 | public static void setup(Plugin plugin, Class config) { 80 | setupLogger(); 81 | SpringPlugin springPlugin = new SpringPlugin(plugin); 82 | springPlugin.initSpring(config); 83 | SETUP_PLUGINS.put(plugin, springPlugin); 84 | } 85 | 86 | public static void teardown(Plugin plugin) { 87 | SpringPlugin springPlugin = SETUP_PLUGINS.remove(plugin); 88 | if (springPlugin != null) { 89 | springPlugin.onDisable(plugin); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/controller/BukkitPluginUnloader.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.controller; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandMap; 5 | import org.bukkit.command.PluginCommand; 6 | import org.bukkit.command.SimpleCommandMap; 7 | import org.bukkit.plugin.Plugin; 8 | import org.bukkit.plugin.PluginManager; 9 | import org.springframework.context.annotation.Lazy; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.lang.reflect.Field; 13 | import java.net.URLClassLoader; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | 18 | import javax.annotation.PostConstruct; 19 | 20 | import in.kyle.mcspring.RequiresSpigot; 21 | import lombok.RequiredArgsConstructor; 22 | import lombok.SneakyThrows; 23 | import lombok.var; 24 | 25 | @Lazy 26 | @Component 27 | @RequiredArgsConstructor 28 | @RequiresSpigot 29 | @SuppressWarnings("unchecked") 30 | class BukkitPluginUnloader { 31 | 32 | private final PluginManager pluginManager; 33 | private final CommandMap commandMap; 34 | private Map commands; 35 | private List plugins; 36 | private Map names; 37 | 38 | @PostConstruct 39 | @SneakyThrows 40 | void setup() { 41 | plugins = getDeclaredField(pluginManager, "plugins"); 42 | names = getDeclaredField(pluginManager, "lookupNames"); 43 | Field knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); 44 | knownCommands.setAccessible(true); 45 | commands = (Map) knownCommands.get(commandMap); 46 | } 47 | 48 | @SneakyThrows 49 | boolean unload(Plugin plugin) { 50 | pluginManager.disablePlugin(plugin); 51 | 52 | synchronized (pluginManager) { 53 | plugins.remove(plugin); 54 | names.remove(plugin.getName()); 55 | unregisterCommands(plugin); 56 | 57 | closeClassLoader(plugin.getClass().getClassLoader()); 58 | } 59 | System.gc(); 60 | 61 | return true; 62 | } 63 | 64 | private void unregisterCommands(Plugin plugin) { 65 | var unregister = commands.entrySet() 66 | .stream() 67 | .filter(e -> e.getValue() instanceof PluginCommand) 68 | .filter(e -> ((PluginCommand) e.getValue()).getPlugin() == plugin) 69 | .peek(e -> e.getValue().unregister(commandMap)) 70 | .collect(Collectors.toSet()); 71 | unregister.forEach(e -> commands.remove(e.getKey())); 72 | } 73 | 74 | @SneakyThrows 75 | private void closeClassLoader(ClassLoader classLoader) { 76 | if (classLoader instanceof URLClassLoader) { 77 | setDeclaredField(classLoader, "plugin", null); 78 | setDeclaredField(classLoader, "pluginInit", null); 79 | ((URLClassLoader) classLoader).close(); 80 | } 81 | } 82 | 83 | @SneakyThrows 84 | private T getDeclaredField(Object object, String fieldName) { 85 | Field field = object.getClass().getDeclaredField(fieldName); 86 | field.setAccessible(true); 87 | return (T) field.get(object); 88 | } 89 | 90 | @SneakyThrows 91 | private void setDeclaredField(Object object, String fieldName, Object value) { 92 | Field field = object.getClass().getDeclaredField(fieldName); 93 | field.setAccessible(true); 94 | field.set(object, value); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/main/java/in/kyle/mcspring/command/SimpleMethodInjection.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.ClassUtils; 7 | 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Parameter; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | import in.kyle.mcspring.command.registration.Resolver; 18 | import lombok.RequiredArgsConstructor; 19 | import lombok.SneakyThrows; 20 | import lombok.var; 21 | 22 | /** 23 | * Used to inject method parameters 24 | * Does not support annotated parameters 25 | */ 26 | @Component 27 | @RequiredArgsConstructor 28 | public class SimpleMethodInjection { 29 | 30 | private final List resolvers; 31 | 32 | @SneakyThrows 33 | public Object invoke(Method method, 34 | Object object, 35 | List resolvers, 36 | Object... contextObjects) { 37 | Object[] params = getParameters(method, resolvers, contextObjects); 38 | method.setAccessible(true); 39 | if (params.length != 0) { 40 | return method.invoke(object, params); 41 | } else { 42 | return method.invoke(object); 43 | } 44 | } 45 | 46 | @VisibleForTesting 47 | List makeResolvers(Object... contextObjects) { 48 | return Stream.of(contextObjects) 49 | .filter(Objects::nonNull) 50 | .map(o -> (Resolver) parameter -> ClassUtils.isAssignable(parameter.getType(), 51 | o.getClass()) 52 | ? Optional.of(o) 53 | : Optional.empty()) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | public Object[] getParameters(Method method, 58 | List contextResolvers, 59 | Object... contextObjects) { 60 | List methodResolvers = new ArrayList<>(); 61 | methodResolvers.addAll(contextResolvers); 62 | methodResolvers.addAll(makeResolvers(contextObjects)); 63 | return getParameters(method, methodResolvers); 64 | } 65 | 66 | public Object[] getParameters(Method method, List contextResolvers) { 67 | Parameter[] parameters = method.getParameters(); 68 | Object[] params = new Object[parameters.length]; 69 | 70 | List methodResolvers = new ArrayList<>(); 71 | methodResolvers.addAll(contextResolvers); 72 | methodResolvers.addAll(resolvers); 73 | for (int i = 0; i < parameters.length; i++) { 74 | Parameter parameter = parameters[i]; 75 | 76 | for (int j = 0; j < methodResolvers.size(); j++) { 77 | Resolver methodResolver = methodResolvers.get(j); 78 | Optional resolved = methodResolver.resolve(parameter); 79 | if (resolved.isPresent()) { 80 | params[i] = resolved.get(); 81 | var removed = methodResolvers.remove(j); 82 | methodResolvers.add(removed); 83 | break; 84 | } 85 | } 86 | 87 | if (params[i] == null) { 88 | throw new RuntimeException( 89 | "Unable to resolve parameter " + parameter.getType() + " for " + 90 | method.getName()); 91 | } 92 | } 93 | return params; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-archetype/src/main/resources/archetype-resources/spigot/spigot.yml: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for Spigot. 2 | # As you can see, there's tons to configure. Some options may impact gameplay, so use 3 | # with caution, and make sure you know what each option does before configuring. 4 | # For a reference for any variable inside this file, check out the Spigot wiki at 5 | # http://www.spigotmc.org/wiki/spigot-configuration/ 6 | # 7 | # If you need help with the configuration or have any questions related to Spigot, 8 | # join us at the IRC or drop by our forums and leave a post. 9 | # 10 | # IRC: #spigot @ irc.spi.gt ( http://www.spigotmc.org/pages/irc/ ) 11 | # Forums: http://www.spigotmc.org/ 12 | 13 | config-version: 11 14 | settings: 15 | debug: false 16 | save-user-cache-on-stop-only: false 17 | filter-creative-items: true 18 | moved-wrongly-threshold: 0.0625 19 | moved-too-quickly-multiplier: 10.0 20 | sample-count: 12 21 | attribute: 22 | maxHealth: 23 | max: 2048.0 24 | movementSpeed: 25 | max: 2048.0 26 | attackDamage: 27 | max: 2048.0 28 | timeout-time: 60000000 29 | restart-on-crash: true 30 | restart-script: ./start.sh 31 | item-dirty-ticks: 20 32 | netty-threads: 4 33 | late-bind: false 34 | player-shuffle: 0 35 | int-cache-limit: 1024 36 | user-cache-size: 1000 37 | bungeecord: false 38 | commands: 39 | tab-complete: 0 40 | silent-commandblock-console: false 41 | log: true 42 | replace-commands: 43 | - setblock 44 | - summon 45 | - testforblock 46 | - tellraw 47 | spam-exclusions: 48 | - /skill 49 | messages: 50 | whitelist: You are not whitelisted on this server! 51 | unknown-command: Unknown command. Type "/help" for help. 52 | server-full: The server is full! 53 | outdated-client: Outdated client! Please use {0} 54 | outdated-server: Outdated server! I'm still on {0} 55 | restart: Server restarting... 56 | advancements: 57 | disable-saving: false 58 | disabled: 59 | - minecraft:story/disabled 60 | stats: 61 | disable-saving: false 62 | forced-stats: {} 63 | world-settings: 64 | default: 65 | verbose: false 66 | dragon-death-sound-radius: 0 67 | item-despawn-rate: 6000 68 | merge-radius: 69 | item: 2.5 70 | exp: 3.0 71 | wither-spawn-sound-radius: 0 72 | nerf-spawner-mobs: false 73 | enable-zombie-pigmen-portal-spawns: false 74 | view-distance: 10 75 | arrow-despawn-rate: 1200 76 | hanging-tick-frequency: 100 77 | zombie-aggressive-towards-villager: true 78 | random-light-updates: false 79 | hunger: 80 | jump-walk-exhaustion: 0.05 81 | jump-sprint-exhaustion: 0.2 82 | combat-exhaustion: 0.1 83 | regen-exhaustion: 6.0 84 | swim-multiplier: 0.01 85 | sprint-multiplier: 0.1 86 | other-multiplier: 0.0 87 | entity-tracking-range: 88 | players: 48 89 | animals: 48 90 | monsters: 48 91 | misc: 32 92 | other: 64 93 | save-structure-info: true 94 | entity-activation-range: 95 | animals: 32 96 | monsters: 32 97 | misc: 16 98 | tick-inactive-villagers: true 99 | max-tick-time: 100 | tile: 60000000 101 | entity: 60000000 102 | squid-spawn-range: 103 | min: 45.0 104 | seed-village: 10387312 105 | seed-feature: 14357617 106 | seed-monument: 10387313 107 | seed-slime: 987234911 108 | ticks-per: 109 | hopper-transfer: 8 110 | hopper-check: 1 111 | hopper-amount: 1 112 | max-tnt-per-tick: 100 113 | growth: 114 | cactus-modifier: 100 115 | cane-modifier: 100 116 | melon-modifier: 100 117 | mushroom-modifier: 100 118 | pumpkin-modifier: 100 119 | sapling-modifier: 100 120 | wheat-modifier: 100 121 | netherwart-modifier: 100 122 | vine-modifier: 100 123 | cocoa-modifier: 100 124 | mob-spawn-range: 4 125 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-plugin-manager/src/main/java/in/kyle/mcspring/manager/controller/BukkitPluginController.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.manager.controller; 2 | 3 | import org.bukkit.plugin.InvalidDescriptionException; 4 | import org.bukkit.plugin.Plugin; 5 | import org.bukkit.plugin.PluginLoader; 6 | import org.bukkit.plugin.PluginManager; 7 | import org.springframework.stereotype.Controller; 8 | 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.Arrays; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Optional; 17 | import java.util.Set; 18 | import java.util.function.Function; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | import java.util.stream.Collectors; 22 | 23 | import in.kyle.mcspring.RequiresSpigot; 24 | import lombok.RequiredArgsConstructor; 25 | import lombok.SneakyThrows; 26 | import lombok.var; 27 | 28 | @Controller 29 | @RequiredArgsConstructor 30 | @RequiresSpigot 31 | class BukkitPluginController implements PluginController { 32 | 33 | private final PluginManager pluginManager; 34 | private final PluginLoader pluginLoader; 35 | private final BukkitPluginUnloader unloader; 36 | private final Logger logger; 37 | 38 | @Override 39 | public Optional load(Path jar) { 40 | try { 41 | Plugin plugin = pluginManager.loadPlugin(jar.toFile()); 42 | Optional optionalPlugin = Optional.ofNullable(plugin); 43 | optionalPlugin.ifPresent(Plugin::onLoad); 44 | return optionalPlugin; 45 | } catch (Exception e) { 46 | logger.log(Level.SEVERE, "Could not load " + jar.toAbsolutePath()); 47 | logger.log(Level.SEVERE, e, () -> ""); 48 | return Optional.empty(); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean unload(Plugin plugin) { 54 | if (pluginManager.isPluginEnabled(plugin)) { 55 | try { 56 | return unloader.unload(plugin); 57 | } catch (Exception e) { 58 | logger.log(Level.SEVERE, "Could not unload " + plugin.getName()); 59 | logger.log(Level.SEVERE, e, () -> ""); 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | @Override 66 | @SneakyThrows 67 | public Map getLoadablePlugins() { 68 | Path pluginsFolder = Paths.get("plugins"); 69 | return Files.list(pluginsFolder) 70 | .filter(p -> p.toString().endsWith(".jar")) 71 | .filter(jar -> getPluginName(jar).isPresent()) 72 | .collect(Collectors.toMap(j -> getPluginName(j).get(), Function.identity())); 73 | } 74 | 75 | @Override 76 | public Optional getPlugin(String name) { 77 | return Optional.ofNullable(pluginManager.getPlugin(name)); 78 | } 79 | 80 | @Override 81 | public Set getPlugins() { 82 | return new HashSet<>(Arrays.asList(pluginManager.getPlugins())); 83 | } 84 | 85 | @Override 86 | public Map getAllPlugins() { 87 | Map allPlugins = new HashMap<>(); 88 | getLoadablePlugins().forEach((k,v)->allPlugins.put(k, false)); 89 | getPlugins().forEach(p -> allPlugins.put(p.getName(), p.isEnabled())); 90 | return allPlugins; 91 | } 92 | 93 | private boolean isEnabled(String pluginName) { 94 | return getPlugin(pluginName).map(Plugin::isEnabled).orElse(false); 95 | } 96 | 97 | private Optional getPluginName(Path jar) { 98 | try { 99 | var description = pluginLoader.getPluginDescription(jar.toFile()); 100 | return Optional.of(description.getName()); 101 | } catch (InvalidDescriptionException e) { 102 | return Optional.empty(); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mcspring [![Build Status](https://travis-ci.org/kylepls/mcspring.svg?branch=master)](https://travis-ci.org/kylepls/mcspring) ![Maven Central](https://img.shields.io/maven-central/v/in.kyle.mcspring/mcspring-starter) [![Coverage Status](https://coveralls.io/repos/github/kylepls/mcspring/badge.svg)](https://coveralls.io/github/kylepls/mcspring) 2 | 3 | Writing Bukkit plugins is a nightmare. I often lay awake in my bed late at night unable to sleep 4 | because Bukkit made events an annotation but commands are created by implementing a class. 5 | The plugin.yml is useless and main classes that extend JavaPlugin are cluttered piles of shit. 6 | 7 | {insert your horror story/gripe here} 8 | 9 | These are solved problems. Spring Boot took care of this issue ages ago. 10 | So how about we ditch this ridiculous programming model and hop on the Spring train. 11 | 12 | ```java 13 | @Component 14 | class Test { // We don't have to extend JavaPlugin. The plugin.yml is also generated for us. 15 | 16 | @Command("test") 17 | String playerSender(Player sender, String command) { 18 | // parameters are automatically injected 19 | // injects: Player, Label, String[] args (no specific order required) 20 | // also injects any Spring beans such as Plugin (no specific order required) 21 | return command + " works!"; 22 | } 23 | 24 | @Scheduled(fixedDelay = 10000) 25 | void interval() { // runs every 10 seconds 26 | Bukkit.broadcastMessage("REMEMBER TO DONATE"); 27 | } 28 | 29 | @EventHandler 30 | void onMove(PlayerMoveEvent e) { // Events automatically registered 31 | getLogger().info(e.getPlayer().getName() + " moved"); 32 | } 33 | 34 | // sub-commands example 35 | // first we define the structure of the command (how it's parsed) 36 | // then we define the execution of the command (how it runs) 37 | // structure `/plot tp 10 20` 38 | @Command("plot") 39 | void plot(PluginCommand command) { 40 | command.on("tp", this::plotTp); // calls this method when "tp" is passed 41 | command.otherwise("Usage: plot "); // if no methods were called, fallback to this message 42 | } 43 | 44 | private void tp(PluginCommand command) { 45 | command.withInt("Parameter must be an integer"); // parse 1 integer from the command, otherwise show the message parameter 46 | command.withInt("Parameter must be an integer"); // parse 1 integer from the command, otherwise show the message parameter 47 | command.then(this::executeTp); // if everything is okay so far, run the executor 48 | command.otherwise("Usage: plot tp "); // if not enough args (or too many) were passed, show this message 49 | } 50 | 51 | // parameters are injected from the #with arguments 52 | // injects the CommandSender, Label, and String[] args 53 | // Spring beans are also injected 54 | private void executeTp(Player sender, int x, int y) { 55 | // sender corresponds to the player that sent the command, the argument position doesn't matter 56 | // x and y correspond to the 2 parameters that were parsed using the #withInt method 57 | sender.teleportToPlot(x, y); 58 | } 59 | } 60 | ``` 61 | 62 | --- 63 | 64 | ## What's in the sauce? 65 | * Main plugin class is generated automatically, you don't need to interact with it. 66 | * The `plugin.yml` is also a thing of the past. May it rest in peace. 67 | * Have two plugins? Want to share a Bean or two? Go for it. It's all taken care of. 68 | * Commands are now registered with `@Command`. Put it anywhere and forget about it. 69 | * Schedulers are defined with `@Scheduler`. Another thing to schlep away somewhere. 70 | * `@EventHandler` now registers itself. About damn time. 71 | * Like money? Vault support is in the box `in.kyle.mcspring.economy.EconomyService` 72 | * Want my hot take on sub-command handing? We've got you covered (see the wiki) 73 | 74 | ## Getting Started 75 | I went ahead and wrote a full tutorial series for you newcomers. Get started [here](https://github.com/kylepls/mcspring/wiki/Getting-Setup) 76 | 77 | If you think you're too smart for the beginner tutorial; go to the 78 | [wiki](https://github.com/kylepls/mcspring/wiki) and piece it together. 79 | 80 | If you're really really smart; check out the example plugins in the `mcspring-example` folder. 81 | 82 | --- 83 | 84 | ##### Final Notes 85 | Thanks to https://github.com/Alan-Gomes/mcspring-boot/ for the inspiration of this project! 86 | -------------------------------------------------------------------------------- /mcspring-examples/simple-factions/src/main/java/org/example/factions/commands/FactionCommand.java: -------------------------------------------------------------------------------- 1 | package org.example.factions.commands; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | import org.example.factions.api.Faction; 6 | import org.example.factions.api.FactionsApi; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.function.Function; 10 | import java.util.stream.Collectors; 11 | 12 | import in.kyle.mcspring.command.Command; 13 | import in.kyle.mcspring.subcommands.PluginCommand; 14 | import lombok.RequiredArgsConstructor; 15 | 16 | @Component 17 | @RequiredArgsConstructor 18 | class FactionCommand { 19 | 20 | private final FactionsApi factions; 21 | 22 | @Command(value = "faction", aliases = "f", description = "Faction management commands") 23 | void commandFaction(PluginCommand command) { 24 | command.withPlayerSender("Sender must be a player"); 25 | command.on("create", this::commandCreate); 26 | command.on("delete", this::commandDelete); 27 | command.on("list", this::executeFactionList); 28 | command.on("join", this::commandJoin); 29 | command.on("info", this::executeFactionInfo); 30 | command.onInvalid(s -> String.format("Invalid sub-command %s", s)); 31 | command.otherwise("Usage: /faction "); 32 | } 33 | 34 | private void commandCreate(PluginCommand command) { 35 | command.withString(); 36 | command.then(this::executeFactionCreate); 37 | command.otherwise("Usage: /faction create "); 38 | } 39 | 40 | private void commandDelete(PluginCommand command) { 41 | command.withAny(factions.getFactionNames(), 42 | s -> String.format("Faction &d%s&r not found", s)); 43 | command.then(this::executeFactionDelete); 44 | command.onInvalid(s -> String.format("Invalid sub-command: %s", s)); 45 | command.otherwise("Usage: /faction delete "); 46 | } 47 | 48 | private void commandJoin(PluginCommand command) { 49 | command.withMap(factions.getFactions() 50 | .stream() 51 | .collect(Collectors.toMap(Faction::getName, Function.identity())), 52 | s -> String.format("Faction %s not found", s)); 53 | command.then(this::executeFactionJoin); 54 | command.otherwise("Usage: /faction join "); 55 | } 56 | 57 | 58 | private String executeFactionJoin(Player sender, Faction faction) { 59 | if (!factions.isFactionMember(sender)) { 60 | faction.getMembers().put(sender.getUniqueId(), Faction.Rank.MEMBER); 61 | return String.format("You joined &1%s", faction.getName()); 62 | } else { 63 | return "You must leave your current faction before joining another one"; 64 | } 65 | } 66 | 67 | private String executeFactionInfo(Player sender) { 68 | return factions.getFaction(sender) 69 | .map(f -> "Faction: " + formatFaction(f)) 70 | .orElse("You are not in a faction"); 71 | } 72 | 73 | private String executeFactionList() { 74 | return "Factions: \n" + factions.getFactions() 75 | .stream() 76 | .map(this::formatFaction) 77 | .collect(Collectors.joining("\n")); 78 | } 79 | 80 | private String formatFaction(Faction faction) { 81 | return String.format("%s: %s", 82 | faction.getName(), 83 | faction.getMembers() 84 | .entrySet() 85 | .stream() 86 | .map(e -> Bukkit.getPlayer(e.getKey()).getName() + "(" + 87 | e.getValue().name().toLowerCase() + ")") 88 | .collect(Collectors.joining(", "))); 89 | } 90 | 91 | private String executeFactionCreate(Player sender, String name) { 92 | if (factions.isFactionMember(sender)) { 93 | return "You must first leave your current faction"; 94 | } else { 95 | factions.addFaction(new Faction(name, sender.getUniqueId())); 96 | return String.format("Created faction: &1%s", name); 97 | } 98 | } 99 | 100 | private String executeFactionDelete(Player sender, String name) { 101 | factions.removeFaction(name); 102 | return String.format("Deleted faction: &1%s", name); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-base/src/test/java/in/kyle/mcspring/command/TestSimpleMethodInjection.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.command; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Parameter; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.stream.Collectors; 11 | 12 | import in.kyle.mcspring.command.registration.Resolver; 13 | import lombok.var; 14 | 15 | import static java.util.Collections.emptyList; 16 | import static org.assertj.core.api.Assertions.*; 17 | 18 | @SuppressWarnings("unused") 19 | class TestSimpleMethodInjection { 20 | 21 | @Test 22 | void testGetParametersEmpty() throws NoSuchMethodException { 23 | abstract class Test { 24 | abstract void test(); 25 | } 26 | 27 | var injection = new SimpleMethodInjection(emptyList()); 28 | var parameters = injection.getParameters(Test.class.getDeclaredMethod("test"), emptyList()); 29 | assertThat(parameters).isEmpty(); 30 | } 31 | 32 | @Test 33 | void testGetContextObjectParameters() throws NoSuchMethodException { 34 | abstract class Test { 35 | abstract void test(String s1, String s2, String s3); 36 | } 37 | 38 | var injection = new SimpleMethodInjection(emptyList()); 39 | var testMethod = 40 | Test.class.getDeclaredMethod("test", String.class, String.class, String.class); 41 | var parameters = injection.getParameters(testMethod, emptyList(), "s1", "s2", "s3"); 42 | assertThat(parameters).containsExactly("s1", "s2", "s3"); 43 | } 44 | 45 | @Test 46 | void testGetContextObjectParametersNotFound() throws NoSuchMethodException { 47 | abstract class Test { 48 | abstract void test(int s1); 49 | } 50 | 51 | var injection = new SimpleMethodInjection(emptyList()); 52 | var testMethod = Test.class.getDeclaredMethod("test", int.class); 53 | assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { 54 | injection.getParameters(testMethod, emptyList(), "s1", "s2", "s3"); 55 | }); 56 | } 57 | 58 | @Test 59 | void testGetContextObjectFromResolver() throws NoSuchMethodException { 60 | abstract class Test { 61 | abstract void test(int s1, String s2, int s3); 62 | } 63 | 64 | var injection = new SimpleMethodInjection(emptyList()); 65 | var testMethod = Test.class.getDeclaredMethod("test", int.class, String.class, int.class); 66 | List resolvers = new ArrayList<>(); 67 | resolvers.add(parameter -> { 68 | if (int.class.isAssignableFrom(parameter.getType())) { 69 | return Optional.of(1); 70 | } else { 71 | return Optional.empty(); 72 | } 73 | }); 74 | resolvers.add(parameter -> { 75 | if (String.class.isAssignableFrom(parameter.getType())) { 76 | return Optional.of("string"); 77 | } else { 78 | return Optional.empty(); 79 | } 80 | }); 81 | var parameters = injection.getParameters(testMethod, resolvers); 82 | assertThat(parameters).containsExactly(1, "string", 1); 83 | } 84 | 85 | @Test 86 | void testMakeResolvers() throws NoSuchMethodException { 87 | // given 3 parameters, make resolvers, and then resolve them back using an int param 88 | abstract class Test { 89 | abstract void param(int i); 90 | } 91 | var injection = new SimpleMethodInjection(emptyList()); 92 | Method method = Test.class.getDeclaredMethod("param", int.class); 93 | Parameter parameter = method.getParameters()[0]; 94 | var output = injection.makeResolvers(1, 2, 3) 95 | .stream() 96 | .map(r -> r.resolve(parameter)) 97 | .map(Optional::get) 98 | .collect(Collectors.toList()); 99 | assertThat(output).containsExactly(1, 2, 3); 100 | } 101 | 102 | @Test 103 | void testMakeResolversEmpty() throws NoSuchMethodException { 104 | // a resolver should return empty if it is not assignable to a given type 105 | abstract class Test { 106 | abstract void param(int i); 107 | } 108 | var injection = new SimpleMethodInjection(emptyList()); 109 | Method method = Test.class.getDeclaredMethod("param", int.class); 110 | Parameter parameter = method.getParameters()[0]; 111 | var output = injection.makeResolvers("test") 112 | .stream() 113 | .map(r -> r.resolve(parameter)) 114 | .anyMatch(Optional::isPresent); 115 | assertThat(output).isFalse(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/PluginCommand.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import in.kyle.mcspring.command.SimpleMethodInjection; 4 | import org.bukkit.command.CommandSender; 5 | 6 | import java.util.List; 7 | 8 | public class PluginCommand extends PluginCommandBase { 9 | 10 | public PluginCommand(SimpleMethodInjection injection, 11 | CommandSender sender, 12 | List parts, 13 | List injections) { 14 | super(injection, sender, parts, injections); 15 | } 16 | 17 | public void on(String command, Executors.O1 e) { 18 | callOn(command, e, 1); 19 | } 20 | 21 | public void on(String command, Executors.O2 e) { 22 | callOn(command, e, 2); 23 | } 24 | 25 | public void on(String command, Executors.O3 e) { 26 | callOn(command, e, 3); 27 | } 28 | 29 | public void on(String command, Executors.O4 e) { 30 | callOn(command, e, 4); 31 | } 32 | 33 | public void on(String command, Executors.O5 e) { 34 | callOn(command, e, 5); 35 | } 36 | 37 | public void on(String command, Executors.O6 e) { 38 | callOn(command, e, 6); 39 | } 40 | 41 | public void on(String command, Executors.O0 executors) { 42 | callOn(command, executors, 0); 43 | } 44 | 45 | public void on(String command, Executors.E1 e) { 46 | callOn(command, e, 1); 47 | } 48 | 49 | public void on(String command, Executors.E2 e) { 50 | callOn(command, e, 2); 51 | } 52 | 53 | public void on(String command, Executors.E3 e) { 54 | callOn(command, e, 3); 55 | } 56 | 57 | public void on(String command, Executors.E4 e) { 58 | callOn(command, e, 4); 59 | } 60 | 61 | public void on(String command, Executors.E5 e) { 62 | callOn(command, e, 5); 63 | } 64 | 65 | public void on(String command, Executors.E6 e) { 66 | callOn(command, e, 6); 67 | } 68 | 69 | public void then(Executors.O0 e) { 70 | then(() -> invoke(e, 0)); 71 | } 72 | 73 | public void then(Executors.O1 e) { 74 | then(() -> invoke(e, 1)); 75 | } 76 | 77 | public void then(Executors.O2 e) { 78 | then(() -> invoke(e, 2)); 79 | } 80 | 81 | public void then(Executors.O3 e) { 82 | then(() -> invoke(e, 3)); 83 | } 84 | 85 | public void then(Executors.O4 e) { 86 | then(() -> invoke(e, 4)); 87 | } 88 | 89 | public void then(Executors.O5 e) { 90 | then(() -> invoke(e, 5)); 91 | } 92 | 93 | public void then(Executors.O6 e) { 94 | then(() -> invoke(e, 6)); 95 | } 96 | 97 | public void then(Executors.E1 e) { 98 | then(() -> invoke(e, 1)); 99 | } 100 | 101 | public void then(Executors.E2 e) { 102 | then(() -> invoke(e, 2)); 103 | } 104 | 105 | public void then(Executors.E3 e) { 106 | then(() -> invoke(e, 3)); 107 | } 108 | 109 | public void then(Executors.E4 e) { 110 | then(() -> invoke(e, 4)); 111 | } 112 | 113 | public void then(Executors.E5 e) { 114 | then(() -> invoke(e, 5)); 115 | } 116 | 117 | public void then(Executors.E6 e) { 118 | then(() -> invoke(e, 6)); 119 | } 120 | 121 | public void otherwise(Executors.O0 e) { 122 | otherwise(() -> invoke(e, 0)); 123 | } 124 | 125 | public void otherwise(Executors.O1 e) { 126 | otherwise(() -> invoke(e, 1)); 127 | } 128 | 129 | public void otherwise(Executors.O2 e) { 130 | otherwise(() -> invoke(e, 2)); 131 | } 132 | 133 | public void otherwise(Executors.O3 e) { 134 | otherwise(() -> invoke(e, 3)); 135 | } 136 | 137 | public void otherwise(Executors.O4 e) { 138 | otherwise(() -> invoke(e, 4)); 139 | } 140 | 141 | public void otherwise(Executors.O5 e) { 142 | otherwise(() -> invoke(e, 5)); 143 | } 144 | 145 | public void otherwise(Executors.O6 e) { 146 | otherwise(() -> invoke(e, 6)); 147 | } 148 | 149 | public void otherwise(Executors.E1 e) { 150 | otherwise(() -> invoke(e, 1)); 151 | } 152 | 153 | public void otherwise(Executors.E2 e) { 154 | otherwise(() -> invoke(e, 2)); 155 | } 156 | 157 | public void otherwise(Executors.E3 e) { 158 | otherwise(() -> invoke(e, 3)); 159 | } 160 | 161 | public void otherwise(Executors.E4 e) { 162 | otherwise(() -> invoke(e, 4)); 163 | } 164 | 165 | public void otherwise(Executors.E5 e) { 166 | otherwise(() -> invoke(e, 5)); 167 | } 168 | 169 | public void otherwise(Executors.E6 e) { 170 | otherwise(() -> invoke(e, 6)); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/java/in/kyle/mcspring/subcommands/TestTabCompletion.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import java.util.function.Consumer; 9 | import java.util.function.Function; 10 | 11 | import in.kyle.mcspring.subcommands.PluginCommandBase.State; 12 | import in.kyle.mcspring.subcommands.tab.TabDiscovery; 13 | import in.kyle.mcspring.test.MCSpringTest; 14 | import lombok.var; 15 | 16 | import static org.assertj.core.api.Assertions.*; 17 | import static org.mockito.Mockito.*; 18 | 19 | @MCSpringTest 20 | public class TestTabCompletion { 21 | 22 | @Autowired 23 | TabDiscovery tabDiscovery; 24 | TestSender sender; 25 | 26 | @BeforeEach 27 | void setup() { 28 | sender = spy(TestSender.class); 29 | } 30 | 31 | @Test 32 | void testTabWithDirectExecution() { 33 | class Test { 34 | 35 | void root(PluginCommand command) { 36 | command.on("test1", this::exec); 37 | command.on("test2", this::exec); 38 | command.on("test3", this::exec); 39 | } 40 | 41 | void exec(String string) { 42 | fail("Should not run"); 43 | } 44 | } 45 | Test test = new Test(); 46 | var completions = tabDiscovery.getCompletions(sender, "", test::root); 47 | assertThat(completions).containsExactly("test1", "test2", "test3"); 48 | } 49 | 50 | @Test 51 | void testNoTab() { 52 | class Test { 53 | void root(PluginCommand command) { 54 | command.then(this::exec); 55 | } 56 | 57 | void exec(CommandSender sender) { 58 | fail("Should not run"); 59 | } 60 | } 61 | 62 | Test test = new Test(); 63 | var completions = tabDiscovery.getCompletions(sender, "", test::root); 64 | 65 | assertThat(sender.getMessages()).isEmpty(); 66 | assertThat(completions).isEmpty(); 67 | } 68 | 69 | @Test 70 | void testSimpleSubs() { 71 | class Test { 72 | void root(PluginCommand command) { 73 | Consumer dontRun = cmd -> fail("should not run"); 74 | command.on("a", dontRun); 75 | command.on("b", dontRun); 76 | command.on("c", dontRun); 77 | command.then(this::exec); 78 | } 79 | 80 | void exec(CommandSender sender) { 81 | fail("Should not run"); 82 | } 83 | } 84 | 85 | Test test = new Test(); 86 | var completions = tabDiscovery.getCompletions(sender, "", test::root); 87 | 88 | assertThat(sender.getMessages()).isEmpty(); 89 | assertThat(completions).containsSequence("a", "b", "c"); 90 | } 91 | 92 | @Test 93 | void testSubMixed() { 94 | Consumer dontRun = cmd -> fail("should not run"); 95 | class Test { 96 | void root(PluginCommand command) { 97 | command.on("a", this::cmd); 98 | command.on("b", this::cmd); 99 | command.on("c", this::exec); 100 | command.on("d", this::cmd); 101 | assertThat(command.state).isEqualTo(State.EXECUTED); 102 | command.otherwise(() -> dontRun.accept(null)); 103 | } 104 | 105 | void cmd(PluginCommand command) { 106 | } 107 | 108 | void exec(CommandSender sender) { 109 | dontRun.accept(null); 110 | } 111 | } 112 | 113 | Test test = new Test(); 114 | var completions = tabDiscovery.getCompletions(sender, "a ", test::root); 115 | 116 | assertThat(sender.getMessages()).isEmpty(); 117 | assertThat(completions).isEmpty(); 118 | } 119 | 120 | @Test 121 | void testAny() { 122 | class Test { 123 | void root(PluginCommand command) { 124 | Function f = ignored -> { 125 | fail("should not run"); 126 | return ""; 127 | }; 128 | command.withAny(f, "a", "b", "c"); 129 | command.withAny(f, "d", "e", "f"); 130 | command.then(this::exec); 131 | } 132 | 133 | void exec(CommandSender sender) { 134 | fail("Should not run"); 135 | } 136 | } 137 | 138 | Test test = new Test(); 139 | var completions = tabDiscovery.getCompletions(sender, "", test::root); 140 | 141 | assertThat(sender.getMessages()).isEmpty(); 142 | assertThat(completions).containsSequence("a", "b", "c", "d", "e", "f"); 143 | 144 | } 145 | 146 | @Test 147 | void testOnTake1() { 148 | class Test { 149 | void root(PluginCommand command) { 150 | Consumer doesNothing = cmd -> { 151 | }; 152 | command.on("a", doesNothing); 153 | command.on("b", doesNothing); 154 | command.on("c", doesNothing); 155 | command.then(this::exec); 156 | } 157 | 158 | void exec(CommandSender sender) { 159 | fail("Should not run"); 160 | } 161 | } 162 | 163 | Test test = new Test(); 164 | var completions = tabDiscovery.getCompletions(sender, "", test::root); 165 | assertThat(completions).containsSequence("a", "b", "c"); 166 | 167 | completions = tabDiscovery.getCompletions(sender, "a", test::root); 168 | assertThat(completions).isEmpty(); 169 | } 170 | 171 | @Test 172 | void testInvalidStop() { 173 | class Test { 174 | void root(PluginCommand command) { 175 | Consumer doesNothing = cmd -> fail("should not run"); 176 | command.on("a", doesNothing); 177 | command.on("b", doesNothing); 178 | command.on("c", doesNothing); 179 | command.onInvalid(s -> String.format("%s is not valid", s)); 180 | command.then(this::exec); 181 | } 182 | 183 | void exec(CommandSender sender) { 184 | fail("Should not run"); 185 | } 186 | } 187 | 188 | Test test = new Test(); 189 | var completions = tabDiscovery.getCompletions(sender, "g", test::root); 190 | assertThat(completions).isEmpty(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/test/java/in/kyle/mcspring/subcommands/TestPluginCommand.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.permissions.ServerOperator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import in.kyle.mcspring.subcommands.PluginCommandBase.State; 12 | import in.kyle.mcspring.test.MCSpringTest; 13 | 14 | import static org.assertj.core.api.Assertions.*; 15 | import static org.mockito.Mockito.*; 16 | 17 | @MCSpringTest 18 | class TestPluginCommand { 19 | 20 | @Autowired 21 | TestConsole console; 22 | 23 | TestSender sender; 24 | 25 | @BeforeEach 26 | void setup() { 27 | sender = spy(TestSender.class); 28 | } 29 | 30 | @Test 31 | void testDirectExecutor() { 32 | class Test { 33 | 34 | void exec1(PluginCommand command) { 35 | command.on("test", this::handler1); 36 | command.on("ignore", this::handler1); 37 | assertThat(command.state).isEqualTo(State.EXECUTED); 38 | command.otherwise("ERROR"); 39 | } 40 | 41 | void exec2(PluginCommand command) { 42 | command.withString(); 43 | command.on("test2", this::handler2); 44 | assertThat(command.state).isEqualTo(State.EXECUTED); 45 | command.otherwise("ERROR"); 46 | } 47 | 48 | void handler1(CommandSender sender) { 49 | sender.sendMessage("Hello World"); 50 | } 51 | 52 | void handler2(CommandSender sender, String string) { 53 | sender.sendMessage(string); 54 | } 55 | } 56 | 57 | Test test = new Test(); 58 | console.run(sender, "test", test::exec1); 59 | assertThat(sender.getMessages()).containsExactly("Hello World"); 60 | 61 | sender.getMessages().clear(); 62 | console.run(sender, "hello test2", test::exec2); 63 | assertThat(sender.getMessages()).containsExactly("hello"); 64 | } 65 | 66 | @Test 67 | void testSenderArg() { 68 | class Test { 69 | void root(PluginCommand command) { 70 | command.then(this::exec); 71 | } 72 | 73 | void exec(CommandSender sender) { 74 | sender.sendMessage("Hello World"); 75 | } 76 | } 77 | Test test = new Test(); 78 | console.run(sender, "", test::root); 79 | assertThat(sender.getMessages()).containsExactly("Hello World"); 80 | } 81 | 82 | @Test 83 | void testIf() { 84 | class Test { 85 | void root(PluginCommand command) { 86 | command.ifThen(ServerOperator::isOp, this::exec); 87 | } 88 | 89 | void exec(CommandSender sender) { 90 | sender.sendMessage("Works"); 91 | } 92 | } 93 | Test test = new Test(); 94 | console.run(sender, "", test::root); 95 | assertThat(sender.getMessages()).isEmpty(); 96 | 97 | doReturn(true).when(sender).isOp(); 98 | console.run(sender, "", test::root); 99 | assertThat(sender.getMessages()).containsExactly("Works"); 100 | } 101 | 102 | @Test 103 | void testCommandSingleSentenceArg() { 104 | class Test { 105 | void root(PluginCommand command) { 106 | command.withSentence(); 107 | command.then(this::exec); 108 | } 109 | 110 | void exec(String sentence) { 111 | sender.sendMessage(sentence); 112 | } 113 | } 114 | Test test = new Test(); 115 | console.run(sender, "Hello to you world", test::root); 116 | assertThat(sender.getMessages()).containsExactly("Hello to you world"); 117 | } 118 | 119 | @Test 120 | void testCommandIntArgs() { 121 | AtomicBoolean ran = new AtomicBoolean(); 122 | class Test { 123 | void root(PluginCommand command) { 124 | command.withInt("error"); 125 | command.withInt("error"); 126 | command.withInt("error"); 127 | command.then(this::exec); 128 | } 129 | 130 | void exec(int i1, int i2, int i3) { 131 | assertThat(i1).isEqualTo(1); 132 | assertThat(i2).isEqualTo(2); 133 | assertThat(i3).isEqualTo(3); 134 | ran.set(true); 135 | } 136 | } 137 | Test test = new Test(); 138 | console.run(sender, "1 2 3", test::root); 139 | assertThat(sender.getMessages()).isEmpty(); 140 | assertThat(ran).isTrue(); 141 | } 142 | 143 | @Test 144 | void testCommandBranching() { 145 | class Test { 146 | void root(PluginCommand command) { 147 | command.on("a", this::a); 148 | } 149 | 150 | void a(PluginCommand command) { 151 | command.on("b", this::b); 152 | command.on("c", this::c); 153 | } 154 | 155 | private void b(PluginCommand command) { 156 | command.then(this::exec); 157 | } 158 | 159 | private void c(PluginCommand command) { 160 | } 161 | 162 | void exec(CommandSender sender) { 163 | sender.sendMessage("Works"); 164 | } 165 | } 166 | Test test = new Test(); 167 | console.run(sender, "a b", test::root); 168 | assertThat(sender.getMessages()).containsExactly("Works"); 169 | 170 | sender.getMessages().clear(); 171 | console.run(sender, "a c", test::root); 172 | assertThat(sender.getMessages()).isEmpty(); 173 | } 174 | 175 | @Test 176 | void testOtherwise() { 177 | class Test { 178 | void root(PluginCommand command) { 179 | command.on("a", this::a); 180 | command.otherwise("no subcommand at root"); 181 | } 182 | 183 | void a(PluginCommand command) { 184 | command.withInt("error"); 185 | command.otherwise("should run if int passed or missing arg"); 186 | } 187 | } 188 | Test test = new Test(); 189 | console.run(sender, "", test::root); 190 | assertThat(sender.getMessages()).containsExactly("no subcommand at root"); 191 | sender.getMessages().clear(); 192 | 193 | console.run(sender, "a", test::root); 194 | assertThat(sender.getMessages()).containsExactly("should run if int passed or missing arg"); 195 | sender.getMessages().clear(); 196 | 197 | console.run(sender, "a 2", test::root); 198 | assertThat(sender.getMessages()).containsExactly("should run if int passed or missing arg"); 199 | } 200 | 201 | @Test 202 | void testWithError() { 203 | class Test { 204 | void root(PluginCommand command) { 205 | command.withInt(s -> s + " is not an int"); 206 | } 207 | } 208 | Test test = new Test(); 209 | console.run(sender, "swag", test::root); 210 | assertThat(sender.getMessages()).containsExactly("swag is not an int"); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-annotations/src/main/java/in/kyle/mcspring/processor/AnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.processor; 2 | 3 | import org.apache.commons.lang.exception.ExceptionUtils; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.io.IOException; 9 | import java.io.Writer; 10 | import java.lang.annotation.Annotation; 11 | import java.util.Arrays; 12 | import java.util.Comparator; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Objects; 16 | import java.util.Set; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import javax.annotation.processing.AbstractProcessor; 21 | import javax.annotation.processing.RoundEnvironment; 22 | import javax.annotation.processing.SupportedAnnotationTypes; 23 | import javax.annotation.processing.SupportedSourceVersion; 24 | import javax.lang.model.SourceVersion; 25 | import javax.lang.model.element.Element; 26 | import javax.lang.model.element.TypeElement; 27 | import javax.tools.Diagnostic; 28 | import javax.tools.FileObject; 29 | import javax.tools.StandardLocation; 30 | 31 | import in.kyle.mcspring.processor.annotation.PluginDepend; 32 | import in.kyle.mcspring.processor.annotation.SpringPlugin; 33 | import in.kyle.mcspring.processor.util.MainClassCreator; 34 | 35 | // Adapted from https://hub.spigotmc.org/stash/projects/SPIGOT/repos/plugin-annotations/browse 36 | // /src/main/java/org/bukkit/plugin/java/annotation/PluginAnnotationProcessor.java 37 | @SupportedAnnotationTypes("*") 38 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 39 | public class AnnotationProcessor extends AbstractProcessor { 40 | 41 | private Writer yml; 42 | private String mainClass = "PluginMain"; 43 | private boolean created = false; 44 | 45 | private void setArtifactId() { 46 | processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, processingEnv.getOptions().toString()); 47 | mainClass = Objects.requireNonNull(processingEnv.getOptions().get("artifactId")) 48 | .replace("-", "") 49 | .replace(".", ""); 50 | } 51 | 52 | @Override 53 | public boolean process(Set annotations, RoundEnvironment env) { 54 | if (created) { 55 | return true; 56 | } 57 | try { 58 | setArtifactId(); 59 | this.processingEnv.getMessager() 60 | .printMessage(Diagnostic.Kind.NOTE, "Generating Plugin Data..."); 61 | process(env); 62 | created = true; 63 | } catch (Exception e) { 64 | this.processingEnv.getMessager() 65 | .printMessage(Diagnostic.Kind.ERROR, ExceptionUtils.getStackTrace(e)); 66 | return false; 67 | } 68 | return true; 69 | } 70 | 71 | private Set findPackage(RoundEnvironment env) { 72 | Set elements = env.getElementsAnnotatedWith(Component.class); 73 | elements.addAll((Set) env.getElementsAnnotatedWith(Controller.class)); 74 | elements.addAll((Set) env.getElementsAnnotatedWith(Service.class)); 75 | Set packages = new HashSet<>(); 76 | for (Element element : elements) { 77 | if (element instanceof TypeElement) { 78 | TypeElement te = (TypeElement) element; 79 | String packageName = getPackageFromFqn(te.getQualifiedName().toString()); 80 | packages.add(packageName); 81 | } 82 | } 83 | return packages; 84 | } 85 | 86 | private String getRootPackage(Set packages) { 87 | return packages.stream().min(Comparator.comparingInt(String::length)).orElse("ignore"); 88 | } 89 | 90 | private String getPackageFromFqn(String fqn) { 91 | if (fqn.contains(".")) { 92 | return fqn.substring(0, fqn.lastIndexOf(".")); 93 | } else { 94 | return fqn; 95 | } 96 | } 97 | 98 | private void process(RoundEnvironment env) throws Exception { 99 | FileObject ymlFile = processingEnv.getFiler() 100 | .createResource(StandardLocation.CLASS_OUTPUT, "", "plugin.yml"); 101 | yml = ymlFile.openWriter(); 102 | Set packages = findPackage(env); 103 | String rootPackage = "org.springframework.boot.loader"; 104 | String appendRoot = getRootPackage(packages); 105 | if (!appendRoot.isEmpty()) { 106 | rootPackage += "." + appendRoot; 107 | } 108 | mainClass = rootPackage + "." + mainClass; 109 | FileObject main = processingEnv.getFiler().createSourceFile(mainClass); 110 | MainClassCreator.generateMain(main, mainClass, rootPackage, packages); 111 | 112 | addRequired(env); 113 | addDependencies(env); 114 | yml.flush(); 115 | yml.close(); 116 | } 117 | 118 | private void addRequired(RoundEnvironment env) throws IOException { 119 | yml.write(String.format("main: %s\n", mainClass)); 120 | String name = processOne(env, 121 | SpringPlugin.class, 122 | SpringPlugin::name, 123 | "spring-plugin-default-name"); 124 | yml.write(String.format("name: %s\n", name)); 125 | String version = processOne(env, SpringPlugin.class, SpringPlugin::version, "0.0.1"); 126 | yml.write(String.format("version: %s\n", version)); 127 | String description = processOne(env, SpringPlugin.class, SpringPlugin::description, ""); 128 | yml.write(String.format("description: %s\n", description)); 129 | } 130 | 131 | private void addDependencies(RoundEnvironment env) throws IOException { 132 | Set hardDepend = new HashSet<>(); 133 | Set softDepend = new HashSet<>(); 134 | processAll(env, PluginDepend.class, depend -> { 135 | if (depend.soft()) { 136 | return softDepend.addAll(Arrays.asList(depend.plugins())); 137 | } else { 138 | return hardDepend.addAll(Arrays.asList(depend.plugins())); 139 | } 140 | }); 141 | if (!hardDepend.isEmpty()) { 142 | String dependString = String.join(", ", hardDepend); 143 | yml.write(String.format("depend: [%s]\n", dependString)); 144 | } 145 | if (!softDepend.isEmpty()) { 146 | String dependString = String.join(", ", softDepend); 147 | yml.write(String.format("softdepend: [%s]\n", dependString)); 148 | } 149 | } 150 | 151 | private static R processOne(RoundEnvironment env, 152 | Class annotation, 153 | Function function, 154 | R defaultValue) { 155 | List rs = processAll(env, annotation, function); 156 | if (!rs.isEmpty()) { 157 | return rs.get(0); 158 | } else { 159 | return defaultValue; 160 | } 161 | } 162 | 163 | private static List processAll(RoundEnvironment env, 164 | Class annotation, 165 | Function consumer) { 166 | Set elements = env.getElementsAnnotatedWith(annotation); 167 | return elements.stream() 168 | .flatMap(e -> Arrays.stream(e.getAnnotationsByType(annotation))) 169 | .map(consumer) 170 | .collect(Collectors.toList()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /mcspring-api/mcspring-subcommands/src/main/java/in/kyle/mcspring/subcommands/PluginCommandBase.java: -------------------------------------------------------------------------------- 1 | package in.kyle.mcspring.subcommands; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.*; 10 | import java.util.function.BinaryOperator; 11 | import java.util.function.Consumer; 12 | import java.util.function.Function; 13 | import java.util.function.Predicate; 14 | import java.util.function.Supplier; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.Stream; 17 | 18 | import in.kyle.mcspring.command.SimpleMethodInjection; 19 | import lombok.RequiredArgsConstructor; 20 | import lombok.SneakyThrows; 21 | 22 | @RequiredArgsConstructor 23 | public class PluginCommandBase { 24 | 25 | final SimpleMethodInjection injection; 26 | final CommandSender sender; 27 | final List parts; 28 | final List injections; 29 | State state = State.CLEAN; 30 | 31 | public void on(String command, Consumer executor) { 32 | if (hasExecutablePart()) { 33 | String part = parts.get(0); 34 | if (command.equalsIgnoreCase(part)) { 35 | executor.accept(subCommandCopy()); 36 | state = State.EXECUTED; 37 | } 38 | } 39 | } 40 | 41 | protected PluginCommand subCommandCopy() { 42 | return new PluginCommand(injection, 43 | sender, 44 | parts.subList(1, parts.size()), 45 | new ArrayList<>(injections)); 46 | } 47 | 48 | private void ifThen(Predicate ifPredicate, Executors executors, int argCount) { 49 | if (state == State.CLEAN && ifPredicate.test(sender)) { 50 | then(() -> invoke(executors, argCount)); 51 | state = State.EXECUTED; 52 | } 53 | } 54 | 55 | public void ifThen(Predicate ifPredicate, Executors.E1 e1) { 56 | ifThen(ifPredicate, e1, 1); 57 | } 58 | 59 | public void withPlayerSender(String error) { 60 | if (state == State.CLEAN && !(sender instanceof Player)) { 61 | sendMessage(error); 62 | state = State.EXECUTED; 63 | } 64 | } 65 | 66 | public void onInvalid(Function help) { 67 | if (hasExecutablePart()) { 68 | String message = help.apply(parts.get(0)); 69 | sendMessage(message); 70 | state = State.EXECUTED; 71 | } 72 | } 73 | 74 | public void otherwise(String message) { 75 | otherwise(() -> message); 76 | } 77 | 78 | public void otherwise(Supplier supplier) { 79 | if (state == State.MISSING_ARG || state == State.CLEAN) { 80 | String message = supplier.get(); 81 | sendMessage(message); 82 | state = State.EXECUTED; 83 | } 84 | } 85 | 86 | public void with(Function> processor, Function error) { 87 | if (hasExecutablePart()) { 88 | String part = parts.remove(0); 89 | Optional apply = processor.apply(part); 90 | if (apply.isPresent()) { 91 | injections.add(apply.get()); 92 | } else { 93 | String message = error.apply(part); 94 | sendMessage(message); 95 | state = State.INVALID_ARG; 96 | } 97 | } else { 98 | state = State.MISSING_ARG; 99 | } 100 | } 101 | 102 | public void withString() { 103 | with(Optional::of, null); 104 | } 105 | 106 | public void withSentence() { 107 | if (hasExecutablePart()) { 108 | String part = String.join(" ", parts); 109 | injections.add(part); 110 | parts.clear(); 111 | } 112 | } 113 | 114 | public void withOfflinePlayer(String notPlayer) { 115 | withOfflinePlayer(s -> notPlayer); 116 | } 117 | 118 | public void withOfflinePlayer(Function notPlayer) { 119 | with(s -> Optional.ofNullable(Bukkit.getOfflinePlayer(s)), notPlayer); 120 | } 121 | 122 | public void withPlayer(String notPlayer) { 123 | withPlayer(s -> notPlayer); 124 | } 125 | 126 | public void withPlayer(Function notPlayer) { 127 | with(s -> Optional.ofNullable(Bukkit.getPlayerExact(s)), notPlayer); 128 | } 129 | 130 | public void withWorld(String notWorld) { 131 | withWorld(s -> notWorld); 132 | } 133 | 134 | public void withWorld(Function notWorld) { 135 | with(s -> Bukkit.getWorlds() 136 | .stream() 137 | .filter(w -> w.getName().equalsIgnoreCase(s)) 138 | .findFirst(), notWorld); 139 | } 140 | 141 | public void withMap(Map options, Function invalidArg) { 142 | if (hasExecutablePart()) { 143 | String part = parts.remove(0).toLowerCase(); 144 | if (options.containsKey(part)) { 145 | injections.add(options.get(part)); 146 | } else { 147 | String message = invalidArg.apply(part); 148 | sendMessage(message); 149 | state = State.INVALID_ARG; 150 | } 151 | } else { 152 | state = State.MISSING_ARG; 153 | } 154 | } 155 | 156 | public void withAny(Collection options, Function invalidArg) { 157 | BinaryOperator merge = (a, b) -> { 158 | throw new RuntimeException("Duplicate option " + a); 159 | }; 160 | Map optionsMap = options.stream() 161 | .collect(Collectors.toMap(Function.identity(), 162 | Function.identity(), 163 | merge, 164 | LinkedHashMap::new)); 165 | withMap(optionsMap, invalidArg); 166 | } 167 | 168 | public void withAny(Function invalidArg, String... options) { 169 | withAny(Arrays.asList(options), invalidArg); 170 | } 171 | 172 | public void withOnlinePlayer(Function playerNotFound) { 173 | with(s -> Optional.ofNullable(Bukkit.getPlayer(s)), playerNotFound); 174 | } 175 | 176 | public void withInt(String notInteger) { 177 | withInt(a -> notInteger); 178 | } 179 | 180 | public void withInt(Function notInteger) { 181 | with(this::tryParseInt, notInteger); 182 | } 183 | 184 | public void withDouble(String notDouble) { 185 | withDouble(a -> notDouble); 186 | } 187 | 188 | public void withDouble(Function notDouble) { 189 | with(this::tryParseDouble, notDouble); 190 | } 191 | 192 | private Optional tryParseInt(String intString) { 193 | try { 194 | return Optional.of(Integer.parseInt(intString)); 195 | } catch (NumberFormatException e) { 196 | return Optional.empty(); 197 | } 198 | } 199 | 200 | private Optional tryParseDouble(String intString) { 201 | try { 202 | return Optional.of(Double.parseDouble(intString)); 203 | } catch (NumberFormatException e) { 204 | return Optional.empty(); 205 | } 206 | } 207 | 208 | protected void callOn(String command, Executors executors, int argSize) { 209 | if (hasExecutablePart()) { 210 | if (parts.get(0).equalsIgnoreCase(command)) { 211 | parts.remove(0); 212 | then(() -> invoke(executors, argSize)); 213 | } 214 | } 215 | } 216 | 217 | public void otherwise(Runnable r) { 218 | if (state == State.MISSING_ARG || state == State.CLEAN) { 219 | r.run(); 220 | state = State.EXECUTED; 221 | } 222 | } 223 | 224 | public void then(Runnable r) { 225 | if (parts.size() == 0 && state == State.CLEAN) { 226 | r.run(); 227 | state = State.EXECUTED; 228 | } 229 | } 230 | 231 | void sendMessage(String message) { 232 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); 233 | } 234 | 235 | @SneakyThrows 236 | protected void invoke(Executors executors, int argCount) { 237 | Method method = executors.getMethod(argCount); 238 | injections.add(sender); 239 | Object[] objects = injections.toArray(new Object[0]); 240 | Object[] parameters = injection.getParameters(method, Collections.emptyList(), objects); 241 | Method handleMethod = getHandleMethod(executors); 242 | handleMethod.setAccessible(true); 243 | Object output = handleMethod.invoke(executors, parameters); 244 | if (output != null) { 245 | sendMessage(output.toString()); 246 | } 247 | } 248 | 249 | boolean hasExecutablePart() { 250 | return parts.size() > 0 && state == State.CLEAN; 251 | } 252 | 253 | private Method getHandleMethod(Executors executors) { 254 | return Stream.of(executors.getClass().getDeclaredMethods()) 255 | .filter(m -> m.getName().equals("handle")) 256 | .findFirst() 257 | .orElseThrow(RuntimeException::new); 258 | } 259 | 260 | public enum State { 261 | CLEAN, 262 | MISSING_ARG, 263 | INVALID_ARG, 264 | EXECUTED 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /mcspring-build/mcspring-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mcspring-build 7 | in.kyle.mcspring 8 | 0.0.9 9 | 10 | 11 | pom 12 | 13 | 14 | ../../mcspring-examples 15 | ../mcspring-plugin-manager 16 | 17 | 4.0.0 18 | 19 | mcspring-starter 20 | 21 | 22 | 23 | 24 | in.kyle.mcspring 25 | mcspring-base 26 | 0.0.9 27 | 28 | 29 | in.kyle.mcspring 30 | mcspring-subcommands 31 | 0.0.9 32 | 33 | 34 | in.kyle.mcspring 35 | mcspring-test 36 | 0.0.9 37 | test 38 | 39 | 40 | in.kyle.mcspring 41 | mcspring-vault 42 | 0.0.9 43 | 44 | 45 | in.kyle.mcspring 46 | mcspring-annotations 47 | 0.0.9 48 | provided 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-maven-plugin 59 | ${spring.version} 60 | 61 | ignored 62 | 64 | mcspring 65 | 66 | 67 | 68 | 69 | in.kyle.mcspring 70 | mcspring-plugin-layout 71 | 72 | 0.0.9 73 | 74 | 75 | 76 | 77 | build-info 78 | 79 | repackage 80 | build-info 81 | 82 | 83 | 84 | 85 | 86 | maven-antrun-plugin 87 | 1.8 88 | 89 | 90 | package 91 | 92 | run 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | com.coderplus.maven.plugins 104 | copy-rename-maven-plugin 105 | 1.0.1 106 | 107 | 108 | init 109 | package 110 | 111 | copy 112 | 113 | 114 | 115 | ${project.build.directory}/${project.artifactId}.jar 116 | 117 | 118 | ${project.basedir}/spigot/plugins/${project.artifactId}.jar 119 | 120 | 121 | 122 | 123 | copy-plugin 124 | package 125 | 126 | copy 127 | 128 | 129 | 130 | ${project.build.directory}/${project.artifactId}.jar 131 | 132 | 133 | ${project.basedir}/spigot/plugins/update/${project.artifactId}.jar 134 | 135 | 136 | 137 | 138 | 139 | 140 | com.googlecode.maven-download-plugin 141 | download-maven-plugin 142 | 1.3.0 143 | 144 | 145 | generate-resources 146 | 147 | wget 148 | 149 | 150 | https://cdn.getbukkit.org/spigot/spigot-1.15.2.jar 151 | spigot.jar 152 | ${project.basedir}/spigot 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | true 165 | 166 | generate-yml 167 | 168 | UTF-8 169 | UTF-8 170 | 1.8 171 | 1.8 172 | 173 | 174 | 175 | 176 | org.springframework.boot 177 | spring-boot-maven-plugin 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-antrun-plugin 182 | 183 | 184 | com.coderplus.maven.plugins 185 | copy-rename-maven-plugin 186 | 187 | 188 | com.googlecode.maven-download-plugin 189 | download-maven-plugin 190 | 191 | 192 | ${project.artifactId} 193 | 194 | 195 | 196 | in.kyle.mcspring 197 | mcspring-annotations 198 | 199 | 200 | in.kyle.mcspring 201 | mcspring-base 202 | 203 | 204 | 205 | org.spigotmc 206 | spigot-api 207 | ${spigot.version} 208 | provided 209 | 210 | 211 | 212 | org.bukkit 213 | bukkit 214 | ${spigot.version} 215 | provided 216 | 217 | 218 | 219 | 220 | 221 | --------------------------------------------------------------------------------