├── .gitignore ├── LICENSE ├── README.MD ├── build.gradle ├── bukkit ├── build.gradle └── src │ └── main │ └── java │ └── me │ └── saiintbrisson │ └── bukkit │ └── command │ ├── BukkitFrame.java │ ├── command │ ├── BukkitChildCommand.java │ ├── BukkitCommand.java │ └── BukkitContext.java │ ├── executor │ ├── BukkitCommandExecutor.java │ ├── BukkitCompleterExecutor.java │ └── BukkitSchedulerExecutor.java │ └── target │ └── BukkitTargetValidator.java ├── bungee ├── build.gradle └── src │ └── main │ └── java │ └── me │ └── saiintbrisson │ └── bungee │ └── command │ ├── BungeeFrame.java │ ├── command │ ├── BungeeChildCommand.java │ ├── BungeeCommand.java │ └── BungeeContext.java │ ├── executor │ ├── BungeeCommandExecutor.java │ └── BungeeCompleterExecutor.java │ └── target │ └── BungeeTargetValidator.java ├── docs ├── Creating-your-first-Minecraft-command.md ├── Home.md ├── Introduction-to-Minecraft-commands.md └── Setting-up-the-environment.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── shared ├── build.gradle └── src ├── main └── java │ └── me │ └── saiintbrisson │ └── minecraft │ └── command │ ├── CommandFrame.java │ ├── CommandInfoIterator.java │ ├── annotation │ ├── Command.java │ ├── Completer.java │ ├── IgnoreQuote.java │ └── Optional.java │ ├── argument │ ├── AdapterMap.java │ ├── Argument.java │ ├── TypeAdapter.java │ └── eval │ │ ├── ArgumentEvaluator.java │ │ └── MethodEvaluator.java │ ├── command │ ├── CommandHolder.java │ ├── CommandInfo.java │ └── Context.java │ ├── exception │ ├── CommandException.java │ └── NoSuchConverterException.java │ ├── executor │ ├── CommandExecutor.java │ └── CompleterExecutor.java │ ├── message │ ├── MessageHolder.java │ └── MessageType.java │ ├── target │ ├── CommandTarget.java │ └── TargetValidator.java │ └── util │ ├── ArrayUtil.java │ └── StringUtil.java └── test └── java ├── ArgumentParsingTest.java └── TestContext.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | I'm not maintaining this project anymore, PRs are welcome. 2 | 3 | # CommandFramework 4 | 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5223a66192eb4874a4fb12728957d77a)](https://app.codacy.com/manual/SaiintBrisson/command-framework?utm_source=github.com&utm_medium=referral&utm_content=SaiintBrisson/command-framework&utm_campaign=Badge_Grade_Dashboard) 6 | 7 | Documentation available at [GitHub Wiki](https://github.com/SaiintBrisson/command-framework/wiki) 8 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | afterEvaluate { 3 | apply plugin: 'java' 4 | apply plugin: 'maven' 5 | 6 | group 'me.saiintbrisson' 7 | version '1.3.1-SNAPSHOT' 8 | 9 | sourceCompatibility = 1.8 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'org.jetbrains:annotations:21.0.1' 17 | 18 | def lombok = 'org.projectlombok:lombok:1.18.20' 19 | compileOnly lombok 20 | annotationProcessor lombok 21 | } 22 | } 23 | 24 | tasks.withType(JavaCompile) { 25 | options.encoding = 'UTF-8' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bukkit/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'java-library' 4 | 5 | id 'maven' 6 | } 7 | 8 | group 'me.saiintbrisson' 9 | version '1.2-SNAPSHOT' 10 | 11 | repositories { 12 | maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 13 | maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } 14 | } 15 | 16 | dependencies { 17 | compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT' 18 | 19 | implementation project(':shared') 20 | api project(':shared') 21 | } 22 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/BukkitFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command; 18 | 19 | import lombok.AccessLevel; 20 | import lombok.Getter; 21 | import lombok.NonNull; 22 | import lombok.Setter; 23 | import me.saiintbrisson.bukkit.command.command.BukkitCommand; 24 | import me.saiintbrisson.bukkit.command.executor.BukkitCommandExecutor; 25 | import me.saiintbrisson.bukkit.command.executor.BukkitCompleterExecutor; 26 | import me.saiintbrisson.minecraft.command.CommandFrame; 27 | import me.saiintbrisson.minecraft.command.annotation.Command; 28 | import me.saiintbrisson.minecraft.command.annotation.Completer; 29 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 30 | import me.saiintbrisson.minecraft.command.argument.eval.MethodEvaluator; 31 | import me.saiintbrisson.minecraft.command.command.CommandInfo; 32 | import me.saiintbrisson.minecraft.command.exception.CommandException; 33 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 34 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 35 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 36 | import org.bukkit.Bukkit; 37 | import org.bukkit.OfflinePlayer; 38 | import org.bukkit.Server; 39 | import org.bukkit.command.CommandMap; 40 | import org.bukkit.command.CommandSender; 41 | import org.bukkit.entity.Player; 42 | import org.bukkit.plugin.Plugin; 43 | import org.jetbrains.annotations.NotNull; 44 | 45 | import java.lang.reflect.InvocationTargetException; 46 | import java.lang.reflect.Method; 47 | import java.util.Arrays; 48 | import java.util.HashMap; 49 | import java.util.Map; 50 | import java.util.concurrent.Executor; 51 | import java.util.function.Consumer; 52 | 53 | /** 54 | * The BukkitFrame is the core of the framework, 55 | * it registers the commands, adapters {@link AdapterMap} 56 | * and message holders {@link MessageHolder} 57 | * 58 | * @author Luiz Carlos Mourão 59 | */ 60 | @Getter 61 | public final class BukkitFrame implements CommandFrame { 62 | 63 | private final Plugin plugin; 64 | private final AdapterMap adapterMap; 65 | private final MessageHolder messageHolder; 66 | 67 | private final Map commandMap; 68 | private final MethodEvaluator methodEvaluator; 69 | 70 | @Getter(AccessLevel.PRIVATE) 71 | private final CommandMap bukkitCommandMap; 72 | 73 | @Setter 74 | private Executor executor; 75 | 76 | /** 77 | * Creates a new BukkitFrame with the AdapterMap provided. 78 | * 79 | * @param plugin Plugin 80 | * @param adapterMap AdapterMap 81 | */ 82 | public BukkitFrame(@NonNull @NotNull Plugin plugin, @NonNull @NotNull AdapterMap adapterMap) { 83 | this.plugin = plugin; 84 | 85 | this.adapterMap = adapterMap; 86 | this.messageHolder = new MessageHolder(); 87 | 88 | this.commandMap = new HashMap<>(); 89 | this.methodEvaluator = new MethodEvaluator(adapterMap); 90 | 91 | try { 92 | final Server server = Bukkit.getServer(); 93 | final Method mapMethod = server.getClass().getMethod("getCommandMap"); 94 | 95 | this.bukkitCommandMap = (CommandMap) mapMethod.invoke(server); 96 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) { 97 | throw new CommandException(exception); 98 | } 99 | } 100 | 101 | /** 102 | * Creates a new BukkitFrame with the default AdapterMap.

If the registerDefault is true, 103 | * it registers the default adapters for Bukkit. 104 | * 105 | * @param plugin Plugin 106 | * @param registerDefault Boolean 107 | */ 108 | public BukkitFrame(@NonNull @NotNull Plugin plugin, boolean registerDefault) { 109 | this(plugin, new AdapterMap(registerDefault)); 110 | 111 | if (registerDefault) { 112 | registerAdapter(Player.class, Bukkit::getPlayer); 113 | registerAdapter(OfflinePlayer.class, Bukkit::getOfflinePlayer); 114 | } 115 | } 116 | 117 | /** 118 | * Creates a new BukkitFrame with the default AdapterMap 119 | * and default Bukkit adapters. 120 | * 121 | * @param plugin Plugin 122 | */ 123 | public BukkitFrame(Plugin plugin) { 124 | this(plugin, true); 125 | } 126 | 127 | /** 128 | * Get a command by their name in the CommandMap 129 | * 130 | * @param name String 131 | * @return BukkitCommand 132 | */ 133 | @Override 134 | public BukkitCommand getCommand(String name) { 135 | return getOrRegisterCommand(name, null); 136 | } 137 | 138 | private BukkitCommand getOrRegisterCommand(String name, Consumer whenComplete) { 139 | String[] segments = name.split("\\."); 140 | String root = segments[0]; 141 | 142 | BukkitCommand command = commandMap.get(root); 143 | if (command == null) { 144 | command = new BukkitCommand(this, root, 0); 145 | commandMap.put(root, command); 146 | 147 | if (segments.length > 1) { 148 | bukkitCommandMap.register(plugin.getName(), command); 149 | } 150 | } 151 | 152 | command = command.createRecursive(name); 153 | if (whenComplete != null) { 154 | whenComplete.accept(command); 155 | } 156 | 157 | if (command.getPosition() == 0) { 158 | bukkitCommandMap.register(plugin.getName(), command); 159 | } 160 | 161 | return command; 162 | } 163 | 164 | /** 165 | * Registers multiple command objects into the CommandMap. 166 | * 167 | * @param objects Object... 168 | */ 169 | @Override 170 | public void registerCommands(Object... objects) { 171 | for (Object object : objects) { 172 | for (Method method : object.getClass().getDeclaredMethods()) { 173 | final Command command = method.getAnnotation(Command.class); 174 | if (command != null) { 175 | registerCommand(new CommandInfo(command), new BukkitCommandExecutor(this, method, object)); 176 | continue; 177 | } 178 | 179 | Completer completer = method.getAnnotation(Completer.class); 180 | if (completer != null) { 181 | registerCompleter(completer.name(), new BukkitCompleterExecutor(method, object)); 182 | } 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * Registers a command into the CommandMap 189 | * 190 | * @param commandInfo CommandInfo 191 | * @param commandExecutor CommandExecutor 192 | */ 193 | @Override 194 | public void registerCommand(CommandInfo commandInfo, CommandExecutor commandExecutor) { 195 | getOrRegisterCommand(commandInfo.getName(), command -> { 196 | command.initCommand(commandInfo, commandExecutor); 197 | }); 198 | } 199 | 200 | @Override 201 | public void registerCompleter(String name, CompleterExecutor completerExecutor) { 202 | getOrRegisterCommand(name, command -> { 203 | command.initCompleter(completerExecutor); 204 | }); 205 | } 206 | 207 | /** 208 | * Unregisters a command from the CommandMap by 209 | * the Command name. 210 | * 211 | *

Returns a boolean that depends if the 212 | * operation was successful

213 | * 214 | * @param name String | Command name 215 | * @return boolean 216 | */ 217 | @Override 218 | public boolean unregisterCommand(String name) { 219 | final BukkitCommand command = commandMap.remove(name); 220 | return command != null && command.unregister(bukkitCommandMap); 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/command/BukkitChildCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.command; 18 | 19 | import me.saiintbrisson.bukkit.command.BukkitFrame; 20 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 21 | 22 | import java.util.Optional; 23 | 24 | /** 25 | * The BukkitChildCommand is an inherited command from 26 | * the parent command {@link BukkitCommand} 27 | * 28 | *

As a example, /help is a {@link BukkitCommand} but in /help list, 29 | * the list argument is a Child command. The /ħelp continues to be a parent command.

30 | * 31 | * @author Luiz Carlos Mourão 32 | */ 33 | public class BukkitChildCommand extends BukkitCommand { 34 | 35 | private final BukkitCommand parentCommand; 36 | 37 | /** 38 | * Creates a new Child command with the name and parent command provided. 39 | * @param frame BukkitFrame 40 | * @param name String 41 | * @param parentCommand BukkitCommand 42 | */ 43 | public BukkitChildCommand(BukkitFrame frame, String name, BukkitCommand parentCommand) { 44 | super(frame, name, parentCommand.getPosition() + 1); 45 | this.parentCommand = parentCommand; 46 | } 47 | 48 | @Override 49 | public String getFancyName() { 50 | return parentCommand.getFancyName() + " " + getName(); 51 | } 52 | 53 | public Optional> getParentCommand() { 54 | return Optional.of(parentCommand); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/command/BukkitCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.command; 18 | 19 | import lombok.Getter; 20 | import lombok.NonNull; 21 | import me.saiintbrisson.bukkit.command.BukkitFrame; 22 | import me.saiintbrisson.bukkit.command.executor.BukkitCommandExecutor; 23 | import me.saiintbrisson.bukkit.command.target.BukkitTargetValidator; 24 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 25 | import me.saiintbrisson.minecraft.command.command.CommandInfo; 26 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 27 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 28 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 29 | import me.saiintbrisson.minecraft.command.message.MessageType; 30 | import org.apache.commons.lang.StringUtils; 31 | import org.bukkit.command.Command; 32 | import org.bukkit.command.CommandException; 33 | import org.bukkit.command.CommandSender; 34 | import org.jetbrains.annotations.NotNull; 35 | import org.jetbrains.annotations.Nullable; 36 | 37 | import java.util.ArrayList; 38 | import java.util.Arrays; 39 | import java.util.Collections; 40 | import java.util.List; 41 | 42 | /** 43 | * The BukkitCommand is the main implementation of the 44 | * AbstractCommand, it contains the main information about 45 | * that command {@link CommandInfo} position and Child commands {@link BukkitChildCommand} 46 | * 47 | * @author Luiz Carlos Mourão 48 | */ 49 | @Getter 50 | public class BukkitCommand extends Command implements CommandHolder { 51 | 52 | private final BukkitFrame frame; 53 | private final MessageHolder messageHolder; 54 | 55 | private CommandInfo commandInfo; 56 | 57 | private final int position; 58 | 59 | private CommandExecutor commandExecutor; 60 | private CompleterExecutor completerExecutor; 61 | 62 | private final List childCommandList; 63 | 64 | /** 65 | * Creates a new BukkitCommand with the name provided. 66 | * @param frame BukkitFrame 67 | * @param name String 68 | * @param position Integer 69 | */ 70 | public BukkitCommand(@NonNull @NotNull BukkitFrame frame, @NonNull @NotNull String name, int position) { 71 | super(name); 72 | 73 | this.frame = frame; 74 | this.position = position; 75 | 76 | this.messageHolder = frame.getMessageHolder(); 77 | this.childCommandList = new ArrayList<>(); 78 | } 79 | 80 | /** 81 | * Initializes the command when the server is started. 82 | *

If you try to register the same commands multiple times, it throws 83 | * a CommandException

84 | * @param commandInfo CommandInfo 85 | * @param commandExecutor CommandExecutor 86 | */ 87 | public final void initCommand(CommandInfo commandInfo, CommandExecutor commandExecutor) { 88 | if (this.commandInfo != null) { 89 | throw new CommandException("Command already initialized"); 90 | } 91 | 92 | this.commandInfo = commandInfo; 93 | this.commandExecutor = commandExecutor; 94 | 95 | setAliases(Arrays.asList(commandInfo.getAliases())); 96 | 97 | if (StringUtils.isNotEmpty(commandInfo.getPermission())) { 98 | setPermission(commandInfo.getPermission()); 99 | } 100 | 101 | final String usage = commandInfo.getUsage(); 102 | if (StringUtils.isNotEmpty(usage)) { 103 | setUsage(usage); 104 | } else if (commandExecutor instanceof BukkitCommandExecutor) { 105 | setUsage(((BukkitCommandExecutor) commandExecutor).getEvaluator().buildUsage(getFancyName())); 106 | } 107 | 108 | if ((StringUtils.isNotEmpty(commandInfo.getDescription()))) { 109 | setDescription(commandInfo.getDescription()); 110 | } 111 | 112 | if (commandExecutor instanceof BukkitCommandExecutor) { 113 | ((BukkitCommandExecutor) commandExecutor).setCommand(this); 114 | } 115 | } 116 | 117 | public final void initCompleter(CompleterExecutor completerExecutor) { 118 | if (this.completerExecutor != null) { 119 | throw new IllegalStateException("Completer already initialized"); 120 | } 121 | 122 | this.completerExecutor = completerExecutor; 123 | } 124 | 125 | /** 126 | * Get the Child command from this by the name, if it's not register it 127 | * will return null. 128 | * @param name String 129 | * 130 | * @return BukkitChildCommand 131 | */ 132 | @Override @Nullable 133 | public BukkitChildCommand getChildCommand(String name) { 134 | for (BukkitChildCommand childCommand : childCommandList) { 135 | if (childCommand.equals(name)) return childCommand; 136 | } 137 | 138 | return null; 139 | } 140 | 141 | @Override 142 | public String getFancyName() { 143 | return getName(); 144 | } 145 | 146 | /** 147 | * Executes the command with the provided label and arguments. 148 | *

If returns false, it wasn't able to execute

149 | * @param sender CommandSender 150 | * @param commandLabel String 151 | * @param args String[] 152 | * 153 | * @return boolean 154 | */ 155 | @Override 156 | public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { 157 | if (!frame.getPlugin().isEnabled()) { 158 | return false; 159 | } 160 | 161 | if (!testPermissionSilent(sender)) { 162 | sender.sendMessage( 163 | messageHolder.getReplacing(MessageType.NO_PERMISSION, getPermission()) 164 | ); 165 | return false; 166 | } 167 | 168 | if (commandInfo != null && !BukkitTargetValidator.INSTANCE.validate(commandInfo.getTarget(), sender)) { 169 | sender.sendMessage(frame.getMessageHolder().getReplacing( 170 | MessageType.INCORRECT_TARGET, 171 | commandInfo.getTarget().name() 172 | )); 173 | return false; 174 | } 175 | 176 | if (args.length > 0) { 177 | BukkitChildCommand command = getChildCommand(args[0]); 178 | 179 | if (command != null) { 180 | final String label = commandLabel + " " + args[0]; 181 | return command.execute(sender, label, Arrays.copyOfRange(args, 1, args.length)); 182 | } 183 | } 184 | 185 | if (commandExecutor == null) { 186 | return false; 187 | } 188 | 189 | final BukkitContext context = new BukkitContext( 190 | commandLabel, 191 | sender, 192 | BukkitTargetValidator.INSTANCE.fromSender(sender), 193 | args, 194 | frame, 195 | this 196 | ); 197 | 198 | if (commandInfo.isAsync() && frame.getExecutor() != null) { 199 | frame.getExecutor().execute(() -> commandExecutor.execute(context)); 200 | return false; 201 | } 202 | 203 | return commandExecutor.execute(context); 204 | } 205 | 206 | @Override 207 | public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, 208 | @NotNull String[] args) throws IllegalArgumentException { 209 | if (!testPermissionSilent(sender)) { 210 | return Collections.emptyList(); 211 | } 212 | 213 | if (completerExecutor != null) { 214 | return completerExecutor.execute(new BukkitContext( 215 | alias, 216 | sender, 217 | BukkitTargetValidator.INSTANCE.fromSender(sender), 218 | args, 219 | frame, 220 | this 221 | )); 222 | } 223 | 224 | if (childCommandList.size() != 0 && args.length != 0) { 225 | List matchedChildCommands = new ArrayList<>(); 226 | 227 | for (BukkitChildCommand command : childCommandList) { 228 | if (StringUtils.startsWithIgnoreCase(command.getName(), args[args.length - 1]) 229 | && command.testPermissionSilent(sender)) { 230 | matchedChildCommands.add(command.getName()); 231 | } 232 | } 233 | 234 | if (matchedChildCommands.size() != 0) { 235 | matchedChildCommands.sort(String.CASE_INSENSITIVE_ORDER); 236 | return matchedChildCommands; 237 | } 238 | } 239 | 240 | return super.tabComplete(sender, alias, args); 241 | } 242 | 243 | public BukkitCommand createRecursive(String name) { 244 | int position = getPosition() + StringUtils.countMatches(name, "."); 245 | if (position == getPosition()) { 246 | return this; 247 | } 248 | 249 | String subName = name.substring(Math.max(name.indexOf('.') + 1, 0)); 250 | 251 | int index = subName.indexOf('.'); 252 | String nextSubCommand = subName; 253 | if (index != -1) { 254 | nextSubCommand = subName.substring(0, index); 255 | } 256 | 257 | BukkitChildCommand childCommand = getChildCommand(nextSubCommand); 258 | 259 | if (childCommand == null) { 260 | childCommand = new BukkitChildCommand(frame, nextSubCommand, this); 261 | getChildCommandList().add(childCommand); 262 | } 263 | 264 | return childCommand.createRecursive(subName); 265 | } 266 | 267 | @Override 268 | public List getAliasesList() { 269 | return getAliases(); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/command/BukkitContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.command; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Getter; 21 | import me.saiintbrisson.bukkit.command.target.BukkitTargetValidator; 22 | import me.saiintbrisson.minecraft.command.CommandFrame; 23 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 24 | import me.saiintbrisson.minecraft.command.command.Context; 25 | import me.saiintbrisson.minecraft.command.exception.CommandException; 26 | import me.saiintbrisson.minecraft.command.message.MessageType; 27 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 28 | import org.bukkit.command.CommandSender; 29 | 30 | /** 31 | * @author Luiz Carlos Mourão 32 | */ 33 | @Getter 34 | @AllArgsConstructor 35 | public class BukkitContext implements Context { 36 | 37 | private final String label; 38 | private final CommandSender sender; 39 | private final CommandTarget target; 40 | private final String[] args; 41 | 42 | private final CommandFrame commandFrame; 43 | private final CommandHolder commandHolder; 44 | 45 | /** 46 | * Sends a message to the CommandSender 47 | * @param message the message to be sent 48 | */ 49 | @Override 50 | public void sendMessage(String message) { 51 | sender.sendMessage(message); 52 | } 53 | 54 | /** 55 | * Sends a array of messages to the CommandSender 56 | * @param messages the messages to be sent 57 | */ 58 | @Override 59 | public void sendMessage(String[] messages) { 60 | sender.sendMessage(messages); 61 | } 62 | 63 | /** 64 | * Tests a permission into the CommandSender. 65 | *

Returns true if it was successful

66 | * @param permission the permission to be tested 67 | * @param silent whether a exception should be thrown 68 | * 69 | * @return boolenn 70 | * @throws CommandException Throws if the player doesn't have permission and the test isn't silent. 71 | */ 72 | @Override 73 | public boolean testPermission(String permission, boolean silent) throws CommandException { 74 | if (sender.hasPermission(permission)) { 75 | return true; 76 | } 77 | 78 | if (!silent) { 79 | throw new CommandException(MessageType.NO_PERMISSION, permission); 80 | } 81 | 82 | return false; 83 | } 84 | 85 | /** 86 | * Validates if the target is a {@link CommandTarget} 87 | *

Returns true if the test was successful

88 | * @param target the target to be tested 89 | * @param silent whether a exception should be thrown 90 | * 91 | * @return boolean 92 | * @throws CommandException Throws if the validation was wrong and the test isn't silent. 93 | */ 94 | @Override 95 | public boolean testTarget(CommandTarget target, boolean silent) throws CommandException { 96 | if (BukkitTargetValidator.INSTANCE.validate(target, sender)) { 97 | return true; 98 | } 99 | 100 | if (!silent) { 101 | throw new CommandException(MessageType.INCORRECT_USAGE, target.name()); 102 | } 103 | 104 | return false; 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/executor/BukkitCommandExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.executor; 18 | 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | import me.saiintbrisson.bukkit.command.BukkitFrame; 22 | import me.saiintbrisson.bukkit.command.command.BukkitCommand; 23 | import me.saiintbrisson.minecraft.command.argument.eval.ArgumentEvaluator; 24 | import me.saiintbrisson.minecraft.command.command.Context; 25 | import me.saiintbrisson.minecraft.command.exception.CommandException; 26 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 27 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 28 | import me.saiintbrisson.minecraft.command.message.MessageType; 29 | import org.bukkit.command.CommandSender; 30 | 31 | import java.lang.reflect.InvocationTargetException; 32 | import java.lang.reflect.Method; 33 | 34 | /** 35 | * The BukkitCommandExecutor is the main executor of each 36 | * method that is listed as a Command, it invokes the method 37 | * and executes everything inside. 38 | * 39 | * @author Luiz Carlos Mourão 40 | */ 41 | public class BukkitCommandExecutor implements CommandExecutor { 42 | 43 | private final Method method; 44 | private final Object holder; 45 | 46 | @Getter 47 | private final ArgumentEvaluator evaluator; 48 | 49 | private final MessageHolder messageHolder; 50 | 51 | @Setter 52 | private BukkitCommand command; 53 | 54 | /** 55 | * Creates a new BukkitCommandExecutor with the provided 56 | * Command method to execute and Command holder 57 | * @param frame BukkitFrame 58 | * @param method Method 59 | * @param holder Object 60 | */ 61 | public BukkitCommandExecutor(BukkitFrame frame, Method method, Object holder) { 62 | final Class returnType = method.getReturnType(); 63 | 64 | if (!returnType.equals(Void.TYPE) 65 | && !returnType.equals(Boolean.TYPE)) { 66 | throw new CommandException("Illegal return type, '" + method.getName()); 67 | } 68 | 69 | this.method = method; 70 | this.holder = holder; 71 | 72 | this.evaluator = new ArgumentEvaluator<>(frame.getMethodEvaluator().evaluateMethod(method)); 73 | this.messageHolder = frame.getMessageHolder(); 74 | } 75 | 76 | /** 77 | * Executes the command with the provided context 78 | *

Returns false if the execution wasn't successful

79 | * @param context Context 80 | * 81 | * @return boolean 82 | */ 83 | @Override 84 | public boolean execute(Context context) { 85 | final Object result = invokeCommand(context); 86 | 87 | if (result != null && result.getClass().equals(Boolean.TYPE)) { 88 | return ((boolean) result); 89 | } 90 | 91 | return false; 92 | } 93 | 94 | /** 95 | * Invokes the command method and returns the 96 | * result of dispatching that method. 97 | * @param context Context 98 | * 99 | * @return Object 100 | */ 101 | public Object invokeCommand(Context context) { 102 | try { 103 | if (evaluator.getArgumentList().size() == 0) { 104 | return method.invoke(holder); 105 | } 106 | 107 | final Object[] parameters; 108 | try { 109 | parameters = evaluator.parseArguments(context); 110 | } catch (Exception e) { 111 | throw new InvocationTargetException(new CommandException(MessageType.INCORRECT_USAGE, null)); 112 | } 113 | 114 | return method.invoke(holder, parameters); 115 | } catch (InvocationTargetException targetException) { 116 | final Throwable throwable = targetException.getTargetException(); 117 | 118 | if (!(throwable instanceof CommandException)) { 119 | targetException.printStackTrace(); 120 | context.sendMessage("§cAn internal error occurred, please contact the staff team."); 121 | 122 | return false; 123 | } 124 | 125 | final CommandException exception = (CommandException) throwable; 126 | final MessageType messageType = exception.getMessageType(); 127 | 128 | String message = throwable.getMessage(); 129 | 130 | if (messageType != null) { 131 | if (message == null) { 132 | message = messageType.getDefault(command); 133 | } 134 | 135 | context.sendMessage(messageHolder.getReplacing(messageType, message)); 136 | return true; 137 | } 138 | 139 | targetException.printStackTrace(); 140 | 141 | if (targetException.getMessage() != null) { 142 | context.sendMessage(messageHolder.getReplacing(MessageType.ERROR, targetException.getMessage())); 143 | } 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | context.sendMessage("§cAn internal error occurred, please contact the staff team."); 147 | } 148 | 149 | return false; 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/executor/BukkitCompleterExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.executor; 18 | 19 | import me.saiintbrisson.minecraft.command.command.Context; 20 | import me.saiintbrisson.minecraft.command.exception.CommandException; 21 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 22 | import org.bukkit.command.CommandSender; 23 | 24 | import java.lang.reflect.Method; 25 | import java.util.List; 26 | 27 | /** 28 | * @author Luiz Carlos Mourão 29 | */ 30 | public class BukkitCompleterExecutor implements CompleterExecutor { 31 | 32 | private final Method method; 33 | private final Object holder; 34 | 35 | public BukkitCompleterExecutor(Method method, Object holder) { 36 | final Class returnType = method.getReturnType(); 37 | final Class[] parameters = method.getParameterTypes(); 38 | 39 | if (!List.class.isAssignableFrom(returnType)) { 40 | throw new CommandException("Illegal return type, '" + method.getName()); 41 | } 42 | 43 | if (parameters.length > 1 || (parameters.length == 1 && !Context.class.isAssignableFrom(parameters[0]))) { 44 | throw new CommandException("Illegal parameter type, '" + method.getName()); 45 | } 46 | 47 | this.method = method; 48 | this.holder = holder; 49 | } 50 | 51 | @Override @SuppressWarnings("unchecked") 52 | public List execute(Context context) { 53 | final Class[] types = method.getParameterTypes(); 54 | try { 55 | if (types.length == 0) { 56 | return (List) method.invoke(holder); 57 | } 58 | 59 | if (types.length == 1 && types[0] == Context.class) { 60 | return (List) method.invoke(holder, context); 61 | } 62 | 63 | return null; 64 | } catch (Exception exception) { 65 | return null; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/executor/BukkitSchedulerExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.executor; 18 | 19 | import org.bukkit.plugin.Plugin; 20 | import org.bukkit.scheduler.BukkitScheduler; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | import java.util.concurrent.Executor; 24 | 25 | /** 26 | * @author Luiz Carlos Mourão 27 | */ 28 | public class BukkitSchedulerExecutor implements Executor { 29 | 30 | private final Plugin plugin; 31 | private final BukkitScheduler scheduler; 32 | 33 | public BukkitSchedulerExecutor(Plugin plugin) { 34 | this.plugin = plugin; 35 | this.scheduler = plugin.getServer().getScheduler(); 36 | } 37 | 38 | @Override 39 | public void execute(@NotNull Runnable command) { 40 | scheduler.runTaskAsynchronously(plugin, command); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /bukkit/src/main/java/me/saiintbrisson/bukkit/command/target/BukkitTargetValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bukkit.command.target; 18 | 19 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 20 | import me.saiintbrisson.minecraft.command.target.TargetValidator; 21 | import org.bukkit.command.ConsoleCommandSender; 22 | import org.bukkit.entity.Player; 23 | 24 | /** 25 | * The BukkitTargetValidator validates if the Target 26 | * is a correct and usable {@link CommandTarget} 27 | * 28 | * @author Luiz Carlos Mourão 29 | */ 30 | public final class BukkitTargetValidator implements TargetValidator { 31 | 32 | public static final BukkitTargetValidator INSTANCE = new BukkitTargetValidator(); 33 | 34 | /** 35 | * Tries to validate the Command target and Sender object. 36 | *

Returns false if it wasn't validated

37 | * @param target CommandTarget 38 | * @param object Object 39 | * 40 | * @return Boolean 41 | */ 42 | @Override 43 | public boolean validate(CommandTarget target, Object object) { 44 | if (target == CommandTarget.ALL) { 45 | return true; 46 | } 47 | 48 | if (target == CommandTarget.PLAYER && object instanceof Player) { 49 | return true; 50 | } 51 | 52 | return target == CommandTarget.CONSOLE && object instanceof ConsoleCommandSender; 53 | } 54 | 55 | /** 56 | * Returns the CommandTarget by the Sender object 57 | *

Example: The Player object returns a {@link CommandTarget} of PLAYER

58 | * @param object Object 59 | * 60 | * @return CommandTarget 61 | */ 62 | @Override 63 | public CommandTarget fromSender(Object object) { 64 | if (object instanceof Player) { 65 | return CommandTarget.PLAYER; 66 | } 67 | 68 | if (object instanceof ConsoleCommandSender) { 69 | return CommandTarget.CONSOLE; 70 | } 71 | 72 | return CommandTarget.ALL; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /bungee/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'java-library' 4 | 5 | id 'maven' 6 | } 7 | 8 | repositories { 9 | maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } 10 | } 11 | 12 | dependencies { 13 | compileOnly 'net.md-5:bungeecord-api:1.16-R0.4' 14 | 15 | implementation project(':shared') 16 | api project(':shared') 17 | } 18 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/BungeeFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command; 18 | 19 | import lombok.Getter; 20 | import me.saiintbrisson.bungee.command.command.BungeeCommand; 21 | import me.saiintbrisson.bungee.command.executor.BungeeCommandExecutor; 22 | import me.saiintbrisson.bungee.command.executor.BungeeCompleterExecutor; 23 | import me.saiintbrisson.minecraft.command.CommandFrame; 24 | import me.saiintbrisson.minecraft.command.annotation.Command; 25 | import me.saiintbrisson.minecraft.command.annotation.Completer; 26 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 27 | import me.saiintbrisson.minecraft.command.argument.eval.MethodEvaluator; 28 | import me.saiintbrisson.minecraft.command.command.CommandInfo; 29 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 30 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 31 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 32 | import net.md_5.bungee.api.CommandSender; 33 | import net.md_5.bungee.api.ProxyServer; 34 | import net.md_5.bungee.api.connection.ProxiedPlayer; 35 | import net.md_5.bungee.api.plugin.Plugin; 36 | import org.jetbrains.annotations.Nullable; 37 | 38 | import java.lang.reflect.Method; 39 | import java.util.HashMap; 40 | import java.util.Map; 41 | import java.util.concurrent.Executor; 42 | 43 | /** 44 | * @author Henry Fábio 45 | * Github: https://github.com/HenryFabio 46 | */ 47 | @Getter 48 | public class BungeeFrame implements CommandFrame { 49 | private final Plugin plugin; 50 | private final AdapterMap adapterMap; 51 | private final MessageHolder messageHolder; 52 | 53 | private final Map commandMap; 54 | private final MethodEvaluator methodEvaluator; 55 | 56 | public BungeeFrame(Plugin plugin, AdapterMap adapterMap) { 57 | this.plugin = plugin; 58 | 59 | this.adapterMap = adapterMap; 60 | this.messageHolder = new MessageHolder(); 61 | 62 | this.commandMap = new HashMap<>(); 63 | this.methodEvaluator = new MethodEvaluator(adapterMap); 64 | } 65 | 66 | public BungeeFrame(Plugin plugin, boolean registerDefault) { 67 | this(plugin, new AdapterMap(registerDefault)); 68 | 69 | if (registerDefault) { 70 | registerAdapter(ProxiedPlayer.class, ProxyServer.getInstance()::getPlayer); 71 | } 72 | } 73 | 74 | public BungeeFrame(Plugin plugin) { 75 | this(plugin, true); 76 | } 77 | 78 | @Override 79 | public @Nullable Executor getExecutor() { 80 | return null; 81 | } 82 | 83 | @Override 84 | public BungeeCommand getCommand(String name) { 85 | int index = name.indexOf('.'); 86 | String recursiveCommand = (index != -1 ? name.substring(0, index) : name).toLowerCase(); 87 | 88 | BungeeCommand command = commandMap.get(recursiveCommand); 89 | if (command == null) { 90 | command = new BungeeCommand(this, recursiveCommand, 0); 91 | commandMap.put(recursiveCommand, command); 92 | } 93 | 94 | return index != -1 ? command.createRecursive(name) : command; 95 | } 96 | 97 | @Override 98 | public void registerCommands(Object... objects) { 99 | for (Object object : objects) { 100 | for (Method method : object.getClass().getDeclaredMethods()) { 101 | Command command = method.getAnnotation(Command.class); 102 | if (command != null) { 103 | registerCommand(new CommandInfo(command), new BungeeCommandExecutor(this, method, object)); 104 | continue; 105 | } 106 | 107 | Completer completer = method.getAnnotation(Completer.class); 108 | if (completer != null) { 109 | registerCompleter(completer.name(), new BungeeCompleterExecutor(method, object)); 110 | } 111 | } 112 | } 113 | } 114 | 115 | @Override 116 | public void registerCommand(CommandInfo commandInfo, CommandExecutor commandExecutor) { 117 | BungeeCommand recursive = getCommand(commandInfo.getName()); 118 | if (recursive == null) { 119 | return; 120 | } 121 | 122 | recursive.initCommand(commandInfo, commandExecutor); 123 | 124 | if (recursive.getPosition() == 0) { 125 | ProxyServer.getInstance().getPluginManager().registerCommand( 126 | plugin, 127 | recursive 128 | ); 129 | } 130 | } 131 | 132 | @Override 133 | public void registerCompleter(String name, CompleterExecutor completerExecutor) { 134 | BungeeCommand recursive = getCommand(name); 135 | if(recursive == null) { 136 | return; 137 | } 138 | 139 | recursive.initCompleter(completerExecutor); 140 | } 141 | 142 | @Override 143 | public boolean unregisterCommand(String name) { 144 | final BungeeCommand command = commandMap.remove(name); 145 | if (command == null) return false; 146 | 147 | ProxyServer.getInstance().getPluginManager().unregisterCommand(command); 148 | return true; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/command/BungeeChildCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.command; 18 | 19 | import me.saiintbrisson.bungee.command.BungeeFrame; 20 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 21 | 22 | import java.util.Optional; 23 | 24 | /** 25 | * @author Henry Fábio 26 | * Github: https://github.com/HenryFabio 27 | */ 28 | public class BungeeChildCommand extends BungeeCommand { 29 | private final BungeeCommand parentCommand; 30 | 31 | public BungeeChildCommand(BungeeFrame frame, String name, BungeeCommand parentCommand) { 32 | super(frame, name, parentCommand.getPosition() + 1); 33 | this.parentCommand = parentCommand; 34 | } 35 | 36 | @Override 37 | public String getFancyName() { 38 | return parentCommand.getFancyName() + " " + getName(); 39 | } 40 | 41 | public Optional> getParentCommand() { 42 | return Optional.of(parentCommand); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/command/BungeeCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.command; 18 | 19 | import com.google.common.collect.Lists; 20 | import lombok.Getter; 21 | import lombok.Setter; 22 | import me.saiintbrisson.bungee.command.BungeeFrame; 23 | import me.saiintbrisson.bungee.command.executor.BungeeCommandExecutor; 24 | import me.saiintbrisson.bungee.command.target.BungeeTargetValidator; 25 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 26 | import me.saiintbrisson.minecraft.command.command.CommandInfo; 27 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 28 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 29 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 30 | import me.saiintbrisson.minecraft.command.message.MessageType; 31 | import me.saiintbrisson.minecraft.command.util.ArrayUtil; 32 | import me.saiintbrisson.minecraft.command.util.StringUtil; 33 | import net.md_5.bungee.api.CommandSender; 34 | import net.md_5.bungee.api.chat.TextComponent; 35 | import net.md_5.bungee.api.plugin.Command; 36 | import net.md_5.bungee.api.plugin.TabExecutor; 37 | import org.jetbrains.annotations.NotNull; 38 | 39 | import java.util.*; 40 | 41 | /** 42 | * @author Henry Fábio 43 | * Github: https://github.com/HenryFabio 44 | */ 45 | @Getter 46 | public class BungeeCommand extends Command implements CommandHolder, TabExecutor { 47 | private final BungeeFrame frame; 48 | private final MessageHolder messageHolder; 49 | 50 | private CommandInfo commandInfo; 51 | 52 | private final int position; 53 | 54 | private CommandExecutor commandExecutor; 55 | private CompleterExecutor completerExecutor; 56 | 57 | private final List childCommandList = new LinkedList<>(); 58 | 59 | private String permission; 60 | private String[] aliases; 61 | 62 | @Setter 63 | private String usage; 64 | 65 | private final String description = "Not provided"; 66 | 67 | public BungeeCommand(BungeeFrame frame, String name, int position) { 68 | super(name); 69 | this.frame = frame; 70 | this.position = position; 71 | this.messageHolder = frame.getMessageHolder(); 72 | } 73 | 74 | public final void initCommand(CommandInfo commandInfo, CommandExecutor commandExecutor) { 75 | if (this.commandInfo != null) { 76 | throw new IllegalStateException("Command already initialized"); 77 | } 78 | 79 | this.commandInfo = commandInfo; 80 | this.commandExecutor = commandExecutor; 81 | 82 | this.aliases = commandInfo.getAliases(); 83 | 84 | String permission = commandInfo.getPermission(); 85 | if (!StringUtil.isEmpty(permission)) { 86 | this.permission = permission; 87 | } 88 | 89 | final String usage = commandInfo.getUsage(); 90 | if (!StringUtil.isEmpty(usage)) { 91 | setUsage(usage); 92 | } else if (commandExecutor instanceof BungeeCommandExecutor) { 93 | setUsage(((BungeeCommandExecutor) commandExecutor).getEvaluator().buildUsage(getFancyName())); 94 | } 95 | 96 | if (commandExecutor instanceof BungeeCommandExecutor) { 97 | ((BungeeCommandExecutor) commandExecutor).setCommand(this); 98 | } 99 | 100 | } 101 | 102 | public final void initCompleter(CompleterExecutor completerExecutor) { 103 | if (this.completerExecutor != null) { 104 | throw new IllegalStateException("Completer already initialized"); 105 | } 106 | 107 | this.completerExecutor = completerExecutor; 108 | } 109 | 110 | @Override 111 | public void execute(CommandSender sender, String[] args) { 112 | if (!testPermissionSilent(sender)) { 113 | sender.sendMessage(new TextComponent( 114 | messageHolder.getReplacing(MessageType.NO_PERMISSION, getPermission()) 115 | )); 116 | return; 117 | } 118 | 119 | if (!BungeeTargetValidator.INSTANCE.validate(commandInfo.getTarget(), sender)) { 120 | sender.sendMessage(new TextComponent(frame.getMessageHolder().getReplacing( 121 | MessageType.INCORRECT_TARGET, 122 | commandInfo.getTarget().name() 123 | ))); 124 | return; 125 | } 126 | 127 | if (args.length > 0) { 128 | BungeeChildCommand command = getChildCommand(args[0]); 129 | if (command != null) { 130 | command.execute(sender, ArrayUtil.copyOfRange(args, 1, args.length)); 131 | return; 132 | } 133 | } 134 | 135 | if (commandExecutor == null) { 136 | return; 137 | } 138 | 139 | commandExecutor.execute(new BungeeContext( 140 | sender, 141 | BungeeTargetValidator.INSTANCE.fromSender(sender), 142 | args, 143 | frame, 144 | this 145 | )); 146 | } 147 | 148 | @Override 149 | public Iterable onTabComplete(CommandSender sender, String[] args) { 150 | if (!testPermissionSilent(sender)) { 151 | return Collections.emptyList(); 152 | } 153 | 154 | if (completerExecutor != null) { 155 | return completerExecutor.execute(new BungeeContext( 156 | sender, 157 | BungeeTargetValidator.INSTANCE.fromSender(sender), 158 | args, 159 | frame, 160 | this 161 | )); 162 | } 163 | 164 | if (childCommandList.size() != 0 && args.length != 0) { 165 | List matchedChildCommands = new ArrayList<>(); 166 | 167 | for (BungeeChildCommand command : childCommandList) { 168 | if (StringUtil.startsWithIgnoreCase(command.getName(), args[args.length - 1]) 169 | && command.testPermissionSilent(sender)) { 170 | matchedChildCommands.add(command.getName()); 171 | } 172 | } 173 | 174 | if (matchedChildCommands.size() != 0) { 175 | matchedChildCommands.sort(String.CASE_INSENSITIVE_ORDER); 176 | return matchedChildCommands; 177 | } 178 | } 179 | 180 | return Lists.newArrayList(); 181 | } 182 | 183 | @Override 184 | public BungeeChildCommand getChildCommand(String name) { 185 | for (BungeeChildCommand childCommand : childCommandList) { 186 | if (childCommand.equals(name)) return childCommand; 187 | } 188 | 189 | return null; 190 | } 191 | 192 | public BungeeCommand createRecursive(String name) { 193 | int position = getPosition() + StringUtil.countMatches(name, "."); 194 | if (position == getPosition()) { 195 | return this; 196 | } 197 | 198 | String recursive = name.substring(name.indexOf('.') + 1); 199 | 200 | int index = recursive.indexOf('.'); 201 | String childCommandName = index != -1 ? recursive.substring(0, index) : recursive; 202 | 203 | BungeeChildCommand childCommand = getChildCommand(childCommandName); 204 | if (childCommand == null) { 205 | childCommand = new BungeeChildCommand(frame, childCommandName, this); 206 | getChildCommandList().add(childCommand); 207 | } 208 | 209 | return childCommand.createRecursive(recursive); 210 | } 211 | 212 | @Override 213 | public List getAliasesList() { 214 | String[] aliases = this.getAliases(); 215 | return aliases == null ? Collections.emptyList() : Arrays.asList(aliases); 216 | } 217 | 218 | @Override 219 | public String getFancyName() { 220 | return getName(); 221 | } 222 | 223 | protected boolean testPermissionSilent(@NotNull CommandSender target) { 224 | String permission = getPermission(); 225 | if ((permission == null) || (permission.length() == 0)) { 226 | return true; 227 | } 228 | 229 | for (String p : permission.split(";")) { 230 | if (target.hasPermission(p)) { 231 | return true; 232 | } 233 | } 234 | 235 | return false; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/command/BungeeContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.command; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Getter; 21 | import me.saiintbrisson.bungee.command.target.BungeeTargetValidator; 22 | import me.saiintbrisson.minecraft.command.CommandFrame; 23 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 24 | import me.saiintbrisson.minecraft.command.command.Context; 25 | import me.saiintbrisson.minecraft.command.exception.CommandException; 26 | import me.saiintbrisson.minecraft.command.message.MessageType; 27 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 28 | import net.md_5.bungee.api.CommandSender; 29 | import net.md_5.bungee.api.chat.TextComponent; 30 | 31 | /** 32 | * @author Henry Fábio 33 | * Github: https://github.com/HenryFabio 34 | */ 35 | @Getter 36 | @AllArgsConstructor 37 | public class BungeeContext implements Context { 38 | private final CommandSender sender; 39 | private final CommandTarget target; 40 | private final String[] args; 41 | 42 | private final CommandFrame commandFrame; 43 | private final CommandHolder commandHolder; 44 | 45 | @Override 46 | public void sendMessage(String message) { 47 | sender.sendMessage(new TextComponent(message)); 48 | } 49 | 50 | @Override 51 | public void sendMessage(String[] messages) { 52 | for (String message : messages) { 53 | sendMessage(message); 54 | } 55 | } 56 | 57 | @Override 58 | public boolean testPermission(String permission, boolean silent) throws CommandException { 59 | if (sender.hasPermission(permission)) { 60 | return true; 61 | } 62 | 63 | if (!silent) { 64 | throw new CommandException(MessageType.NO_PERMISSION, permission); 65 | } 66 | 67 | return false; 68 | } 69 | 70 | @Override 71 | public boolean testTarget(CommandTarget target, boolean silent) throws CommandException { 72 | if (BungeeTargetValidator.INSTANCE.validate(target, sender)) { 73 | return true; 74 | } 75 | 76 | if (!silent) { 77 | throw new CommandException(MessageType.INCORRECT_USAGE, target.name()); 78 | } 79 | 80 | return false; 81 | } 82 | 83 | @Override 84 | public String getLabel() { 85 | throw new UnsupportedOperationException(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/executor/BungeeCommandExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.executor; 18 | 19 | import lombok.Getter; 20 | import lombok.Setter; 21 | import me.saiintbrisson.bungee.command.BungeeFrame; 22 | import me.saiintbrisson.bungee.command.command.BungeeCommand; 23 | import me.saiintbrisson.minecraft.command.argument.eval.ArgumentEvaluator; 24 | import me.saiintbrisson.minecraft.command.command.Context; 25 | import me.saiintbrisson.minecraft.command.exception.CommandException; 26 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 27 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 28 | import me.saiintbrisson.minecraft.command.message.MessageType; 29 | import net.md_5.bungee.api.CommandSender; 30 | 31 | import java.lang.reflect.InvocationTargetException; 32 | import java.lang.reflect.Method; 33 | 34 | /** 35 | * @author Henry Fábio 36 | * Github: https://github.com/HenryFabio 37 | */ 38 | public final class BungeeCommandExecutor implements CommandExecutor { 39 | private final Method method; 40 | private final Object holder; 41 | 42 | @Getter 43 | private final ArgumentEvaluator evaluator; 44 | 45 | private final MessageHolder messageHolder; 46 | 47 | @Setter 48 | private BungeeCommand command; 49 | 50 | public BungeeCommandExecutor(BungeeFrame frame, Method method, Object holder) { 51 | final Class returnType = method.getReturnType(); 52 | 53 | if (!returnType.equals(Void.TYPE) 54 | && !returnType.equals(Boolean.TYPE)) { 55 | throw new CommandException("Illegal return type, '" + method.getName()); 56 | } 57 | 58 | this.method = method; 59 | this.holder = holder; 60 | 61 | this.evaluator = new ArgumentEvaluator<>(frame.getMethodEvaluator().evaluateMethod(method)); 62 | this.messageHolder = frame.getMessageHolder(); 63 | } 64 | 65 | @Override 66 | public boolean execute(Context context) { 67 | final Object result = invokeCommand(context); 68 | 69 | if (result != null && result.getClass().equals(Boolean.TYPE)) { 70 | return ((boolean) result); 71 | } 72 | 73 | return false; 74 | } 75 | 76 | public Object invokeCommand(Context context) { 77 | try { 78 | if (evaluator.getArgumentList().size() == 0) { 79 | return method.invoke(holder); 80 | } 81 | 82 | final Object[] parameters; 83 | 84 | try { 85 | parameters = evaluator.parseArguments(context); 86 | } catch (Exception e) { 87 | throw new InvocationTargetException(new CommandException(MessageType.INCORRECT_USAGE, null)); 88 | } 89 | 90 | return method.invoke(holder, parameters); 91 | } catch (InvocationTargetException e) { 92 | final Throwable throwable = e.getTargetException(); 93 | 94 | if (!(throwable instanceof CommandException)) { 95 | e.printStackTrace(); 96 | context.sendMessage("§cAn internal error occurred, please contact the staff team."); 97 | 98 | return false; 99 | } 100 | 101 | final CommandException exception = (CommandException) throwable; 102 | final MessageType messageType = exception.getMessageType(); 103 | 104 | String message = throwable.getMessage(); 105 | 106 | if (messageType != null) { 107 | if (message == null) { 108 | message = messageType.getDefault(command); 109 | } 110 | 111 | context.sendMessage(messageHolder.getReplacing(messageType, message)); 112 | } else { 113 | e.printStackTrace(); 114 | 115 | if (e.getMessage() != null) { 116 | context.sendMessage(messageHolder.getReplacing(MessageType.ERROR, e.getMessage())); 117 | } 118 | } 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | context.sendMessage("§cAn internal error occurred, please contact the staff team."); 122 | } 123 | 124 | return false; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/executor/BungeeCompleterExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.executor; 18 | 19 | import me.saiintbrisson.minecraft.command.command.Context; 20 | import me.saiintbrisson.minecraft.command.exception.CommandException; 21 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 22 | import net.md_5.bungee.api.CommandSender; 23 | 24 | import java.lang.reflect.Method; 25 | import java.util.List; 26 | 27 | /** 28 | * @author Henry Fábio 29 | * Github: https://github.com/HenryFabio 30 | */ 31 | public class BungeeCompleterExecutor implements CompleterExecutor { 32 | private final Method method; 33 | private final Object holder; 34 | 35 | public BungeeCompleterExecutor(Method method, Object holder) { 36 | final Class returnType = method.getReturnType(); 37 | final Class[] parameters = method.getParameterTypes(); 38 | 39 | if (!List.class.isAssignableFrom(returnType)) { 40 | throw new CommandException("Illegal return type, '" + method.getName()); 41 | } 42 | 43 | if (parameters.length > 1 || (parameters.length == 1 && !Context.class.isAssignableFrom(parameters[0]))) { 44 | throw new CommandException("Illegal parameter type, '" + method.getName()); 45 | } 46 | 47 | this.method = method; 48 | this.holder = holder; 49 | } 50 | 51 | @Override 52 | public List execute(Context context) { 53 | Class[] types = method.getParameterTypes(); 54 | 55 | try { 56 | if (types.length == 0) { 57 | return (List) method.invoke(holder); 58 | } else if (types.length == 1 && types[0] == Context.class) { 59 | return (List) method.invoke(holder, context); 60 | } else { 61 | return null; 62 | } 63 | } catch (Exception e) { 64 | return null; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bungee/src/main/java/me/saiintbrisson/bungee/command/target/BungeeTargetValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.bungee.command.target; 18 | 19 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 20 | import me.saiintbrisson.minecraft.command.target.TargetValidator; 21 | import net.md_5.bungee.api.CommandSender; 22 | import net.md_5.bungee.api.connection.ProxiedPlayer; 23 | 24 | /** 25 | * @author Henry Fábio 26 | * Github: https://github.com/HenryFabio 27 | */ 28 | public class BungeeTargetValidator implements TargetValidator { 29 | public static final BungeeTargetValidator INSTANCE = new BungeeTargetValidator(); 30 | 31 | @Override 32 | public boolean validate(CommandTarget target, Object object) { 33 | if (target == CommandTarget.CONSOLE) { 34 | return !(object instanceof ProxiedPlayer); 35 | } 36 | 37 | if (target == CommandTarget.PLAYER) { 38 | return object instanceof ProxiedPlayer; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | @Override 45 | public CommandTarget fromSender(Object object) { 46 | if (object instanceof ProxiedPlayer) { 47 | return CommandTarget.PLAYER; 48 | } 49 | 50 | if (object instanceof CommandSender) { 51 | return CommandTarget.CONSOLE; 52 | } 53 | 54 | return CommandTarget.ALL; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/Creating-your-first-Minecraft-command.md: -------------------------------------------------------------------------------- 1 | There are two ways to create a command: by methods annotated with a [Command][Command] or by registering a [CommandInfo][CommandInfo] with a [CommandExecutor][CommandExecutor] using `CommandFrame#registerCommand(CommandInfo, CommandExecutor)`. 2 | 3 | ##### Subcommands 4 | 5 | Subcommands are defined with dots between the command name, e.g: `@Command(name = "parent.child")`. They inherit both target and permissions from their parents, if a parent requires the sender to be a player or have a specific permission, the subcommand will require it too. 6 | 7 | ### Via the Command annotation 8 | 9 | Methods annotated with `@Command` will be automatically registered. Parameters defined in the method will be used as arguments and are automatically converted into the parameter type, they are order dependent. Parameters are required by default, use the [Optional][Optional] annotation to make it not required, you can set default values too. 10 | 11 | You can create multiple commands per class. 12 | 13 | > Creating a simple command 14 | ```java 15 | public class Command { 16 | 17 | @Command(name = "command", aliases = {"nice"}, target = CommandTarget.CONSOLE) 18 | public void handleCommand(Context context, 19 | Player target, 20 | @Optional(def = {"Hello,", "World!"}) String[] message) { 21 | 22 | target.sendMessage("Console sent you: %s", String.join(" ", message)); 23 | } 24 | 25 | @Command(name = "command.child") 26 | public void handleCommandChild(Context context) { 27 | // ... 28 | } 29 | 30 | } 31 | ``` 32 | 33 | The command above can only be ran by the console (`target = CommandTarget.CONSOLE`), the message parameter is optional (`@Optional(def = {"Hello,", "World!"}) String[] message`), so you can run it with: `/command SaiintBrisson` or `/command SaiintBrisson hey friend, how are you?`. Optionals without a default value will be null. 34 | 35 | > Registering your command class 36 | ```java 37 | BukkitFrame frame = new BukkitFrame(plugin); 38 | 39 | frame.registerCommands(new Command()); 40 | ``` 41 | 42 | ### Via `CommandFrame#registerCommand` method 43 | 44 | This approach **does not** support arguments, you can still use them via [Context][Context] however. 45 | 46 | [CommandInfo][CommandInfo] has the same fields as [Command][Command] and [CommandExecutor][CommandExecutor] is a functional interface that receives `Context`. 47 | 48 | > Creating a simple command 49 | ```java 50 | BukkitFrame frame = new BukkitFrame(plugin); 51 | 52 | frame.registerCommand(CommandInfo.builder() 53 | .name("command") 54 | .aliases(new String[]{ 55 | "nice" 56 | }) 57 | .build(), context -> { 58 | context.sendMessage("Hey!"); 59 | return false; 60 | }); 61 | ``` 62 | 63 | 64 | 65 | [Command]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Command.java 66 | [CommandInfo]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/command/CommandInfo.java 67 | [CommandExecutor]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/executor/CommandExecutor.java 68 | 69 | [Context]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/command/Context.java 70 | 71 | [Optional]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Optional.java -------------------------------------------------------------------------------- /docs/Home.md: -------------------------------------------------------------------------------- 1 | # Saiint's command-framework 2 | 3 | You're visiting the official command-framework documentation page! 4 | 5 | This framework was originally developed to help Bukkit developers but currently supports BungeeCord too. The project is constantly receiving updates and new features. 6 | 7 | *** 8 | 9 | Information tree: 10 | 11 | * [Home](https://github.com/SaiintBrisson/command-framework/wiki) - this page 12 | * [Setting up the environment](https://github.com/SaiintBrisson/command-framework/wiki/Setting-up-the-environment) 13 | * * [Bukkit](https://github.com/SaiintBrisson/command-framework/wiki/Setting-up-the-environment#bukkit) 14 | * * [BungeeCord](https://github.com/SaiintBrisson/command-framework/wiki/Setting-up-the-environment#bungeecord) 15 | * * [Shared](https://github.com/SaiintBrisson/command-framework/wiki/Setting-up-the-environment#shared) 16 | * Introduction to commands 17 | * * [within Minecraft](https://github.com/SaiintBrisson/command-framework/wiki/Introduction-to-Minecraft-commands) 18 | * Creating your first command 19 | * * [within Minecraft](https://github.com/SaiintBrisson/command-framework/wiki/Creating-your-first-Minecraft-command) -------------------------------------------------------------------------------- /docs/Introduction-to-Minecraft-commands.md: -------------------------------------------------------------------------------- 1 | The three important classes introduced by this framework are: [CommandFrame][CommandFrame], [Command][Command], and [Context][Context]. 2 | 3 | ### CommandFrame 4 | 5 | This class has a different implementation for each platform supported, we will work with [BukkitFrame][BukkitFrame] for now. 6 | 7 | CommandFrame is our main class, it's used to **register your commands, completers, and adapters**. There are many other functionalities such as setting the executor to be used in async commands and creating your own [custom messages][Custom-messages]. 8 | 9 | > Creating a CommandFrame: 10 | ```java 11 | import me.saiintbrisson.bukkit.command.BukkitFrame; 12 | 13 | public class YourPlugin extends JavaPlugin { 14 | 15 | public void onEnable() { 16 | BukkitFrame frame = new BukkitFrame(plugin); // Creates a BukkitFrame with the default adapters 17 | 18 | BukkitFrame frame = new BukkitFrame(plugin, false); // Creates a BukkitFrame without the default adapters 19 | 20 | BukkitFrame frame = new BukkitFrame(plugin, adapterMap); // Creates a BukkitFrame with the adapters registered in your AdapterMap 21 | } 22 | 23 | } 24 | ``` 25 | 26 | ### Adapters 27 | 28 | Adapters are used to convert the received argument string into the type defined in your method. 29 | 30 | The framework has support for all primitive types (and their wrappers) built-in, but this is not enough in most cases, the CommandFrame class provides a way to register custom adapters or overwrite existing ones. You can create default adapter maps with [AdapterMap][AdapterMap]. 31 | 32 | > Registering a custom adapter 33 | ```java 34 | BukkitFrame frame = new BukkitFrame(plugin); 35 | 36 | frame.registerAdapter(Integer.TYPE, argument -> Integer.parseInt(argument) * 2); 37 | frame.registerAdapter(YourObject.class, YourObject::new); 38 | ``` 39 | 40 | ### The Command Annotation 41 | 42 | The [Command][Command] annotation is how the framework detects and registers your command's methods. It stores the command's information, such as name, aliases, target and other metadata. 43 | 44 | > Annotation overhaul 45 | ```java 46 | @Command( 47 | name = "supercommand", // Name is the only required field 48 | aliases = {"hipercommand"}, // Aliases are "synonyms" to your name 49 | 50 | description = "This command is awesome!", // Description is usually a short message specifying what the command does 51 | usage = "supercommand ", // If omitted, the framework will create an usage message from the method definition 52 | permission = "super.command", // Defines the permission required to run the command, this is tested automatically 53 | 54 | target = CommandTarget.PLAYER, // Who can execute the command, defaults to ALL 55 | async = true // Whether this command should be ran using the defined executor 56 | ) 57 | ``` 58 | 59 | Async commands require an executor in the CommandFrame. Bukkit has a native executor implementation called [BukkitSchedulerExecution][BukkitSchedulerExecution]. 60 | 61 | > Setting an executor 62 | ```java 63 | BukkitFrame frame = new BukkitFrame(plugin); 64 | 65 | frame.setExecutor(new BukkitSchedulerExecutor(plugin)); 66 | ``` 67 | 68 | ### Context 69 | 70 | [Context][Context] is the information created during execution, it stores the sender, label and arguments. It contains a lot of useful commands. `S` is the generic type of the sender. 71 | 72 | > General purpose use 73 | ```java 74 | @Command(name = "command", target = CommandTarget.PLAYER) 75 | public void handleCommand(Context context) { 76 | Player sender = context.getSender(); 77 | 78 | sender.setFlying(!sender.isFlying()); 79 | context.sendMessage("Flying mode set to %s", sender.isFlying()); 80 | } 81 | ``` 82 | 83 | 84 | 85 | [CommandFrame]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/CommandFrame.java 86 | [Command]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Command.java 87 | [Context]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/command/Context.java 88 | 89 | [BukkitFrame]: https://github.com/SaiintBrisson/command-framework/blob/master/bukkit/src/main/java/me/saiintbrisson/bukkit/command/BukkitFrame.java 90 | [Custom-messages]: https://github.com/SaiintBrisson/command-framework/wiki/Custom-messages 91 | 92 | [AdapterMap]: https://github.com/SaiintBrisson/command-framework/blob/master/shared/src/main/java/me/saiintbrisson/minecraft/command/argument/AdapterMap.java 93 | 94 | [BukkitSchedulerExecution]: https://github.com/SaiintBrisson/command-framework/blob/master/bukkit/src/main/java/me/saiintbrisson/bukkit/command/executor/BukkitSchedulerExecutor.java -------------------------------------------------------------------------------- /docs/Setting-up-the-environment.md: -------------------------------------------------------------------------------- 1 | # Using command-framework as a dependency 2 | 3 | [![](https://jitpack.io/v/SaiintBrisson/command-framework.svg)](https://jitpack.io/#SaiintBrisson/command-framework) 4 | 5 | This project is hosted by [JitPack](https://jitpack.io/#SaiintBrisson/command-framework), build artifacts can be found on their site. 6 | 7 | ## Adding the repository 8 | 9 | ### Gradle 10 | 11 | ```groovy 12 | repositories { 13 | maven { 14 | url 'https://jitpack.io' 15 | } 16 | } 17 | ``` 18 | 19 | I highly recommend using [John Engelman's Shadow gradle plugin](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) 20 | 21 | ### Maven 22 | 23 | ```xml 24 | 25 | 26 | jitpack.io 27 | https://jitpack.io 28 | 29 | 30 | ``` 31 | 32 | ## Bukkit 33 | 34 | ### Gradle 35 | 36 | ```groovy 37 | dependencies { 38 | implementation 'com.github.SaiintBrisson.command-framework:bukkit:1.0.0' 39 | } 40 | ``` 41 | 42 | ### Maven 43 | 44 | ```xml 45 | 46 | 47 | com.github.SaiintBrisson.command-framework 48 | bukkit 49 | 1.2.0 50 | compile 51 | 52 | 53 | ``` 54 | 55 | ## BungeeCord 56 | 57 | ### Gradle 58 | 59 | ```groovy 60 | dependencies { 61 | implementation 'com.github.SaiintBrisson.command-framework:bungee:1.0.0' 62 | } 63 | ``` 64 | 65 | ### Maven 66 | 67 | ```xml 68 | 69 | 70 | com.github.SaiintBrisson.command-framework 71 | bungee 72 | 1.2.0 73 | compile 74 | 75 | 76 | ``` 77 | 78 | ## Shared 79 | 80 | ### Gradle 81 | 82 | ```groovy 83 | dependencies { 84 | implementation 'com.github.SaiintBrisson.command-framework:shared:1.0.0' 85 | } 86 | ``` 87 | 88 | ### Maven 89 | 90 | ```xml 91 | 92 | 93 | com.github.SaiintBrisson.command-framework 94 | shared 95 | 1.2.0 96 | compile 97 | 98 | 99 | ``` -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saiintbrisson/command-framework/8a858ce826d08d68b083be33f5f94b14b88f5fdb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 23 16:11:17 BRT 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'command-framework' 2 | include 'shared' 3 | include 'bukkit' 4 | include 'bungee' 5 | -------------------------------------------------------------------------------- /shared/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'maven' 4 | } 5 | 6 | dependencies { 7 | dependencies { 8 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 9 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' 10 | } 11 | } 12 | 13 | test { 14 | useJUnitPlatform() 15 | } 16 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/CommandFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command; 18 | 19 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 20 | import me.saiintbrisson.minecraft.command.argument.TypeAdapter; 21 | import me.saiintbrisson.minecraft.command.argument.eval.MethodEvaluator; 22 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 23 | import me.saiintbrisson.minecraft.command.command.CommandInfo; 24 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 25 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 26 | import me.saiintbrisson.minecraft.command.message.MessageHolder; 27 | import org.jetbrains.annotations.Nullable; 28 | 29 | import java.util.Map; 30 | import java.util.concurrent.Executor; 31 | 32 | /** 33 | * The CommandFrame is the core of the framework, 34 | * it registers the commands, adapters {@link AdapterMap} 35 | * and message holders {@link MessageHolder} 36 | * 37 | * @author Luiz Carlos Mourão 38 | */ 39 | public interface CommandFrame> { 40 | 41 | P getPlugin(); 42 | AdapterMap getAdapterMap(); 43 | MessageHolder getMessageHolder(); 44 | 45 | Map getCommandMap(); 46 | MethodEvaluator getMethodEvaluator(); 47 | 48 | @Nullable 49 | Executor getExecutor(); 50 | 51 | C getCommand(String name); 52 | 53 | /** 54 | * Registers a new Adapter from that type. 55 | * @param type Class 56 | * @param adapter TypeAdapter 57 | * @param Generic value for the type 58 | */ 59 | default void registerAdapter(Class type, TypeAdapter adapter) { 60 | getAdapterMap().put(type, adapter); 61 | } 62 | 63 | /** 64 | * Registers multiple command object once 65 | * @param objects Object... 66 | */ 67 | void registerCommands(Object... objects); 68 | 69 | /** 70 | * Registers a single command with the CommandInfo and Executor 71 | * @param commandInfo CommandInfo 72 | * @param commandExecutor CommandExecutor 73 | */ 74 | void registerCommand(CommandInfo commandInfo, CommandExecutor commandExecutor); 75 | void registerCompleter(String name, CompleterExecutor completerExecutor); 76 | 77 | /** 78 | * Unregisters the command with the provided name 79 | * @param name String 80 | * 81 | * @return Boolean 82 | */ 83 | boolean unregisterCommand(String name); 84 | } 85 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/CommandInfoIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command; 18 | 19 | import lombok.RequiredArgsConstructor; 20 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 21 | 22 | import java.util.Iterator; 23 | 24 | /** 25 | * The CommandInfoIterator is a Iterator for 26 | * CommandHolder, it can access the next command from the list. 27 | * 28 | * @author Luiz Carlos Mourão 29 | */ 30 | @RequiredArgsConstructor 31 | public class CommandInfoIterator implements Iterator> { 32 | 33 | private final CommandHolder root; 34 | 35 | private int index = -1; 36 | private CommandInfoIterator current; 37 | 38 | @Override 39 | public boolean hasNext() { 40 | return (current != null && current.hasNext()) || index < root.getChildCommandList().size(); 41 | } 42 | 43 | @Override 44 | public CommandHolder next() { 45 | if (index == -1) { 46 | index++; 47 | return root; 48 | } 49 | 50 | if (current == null || !current.hasNext()) { 51 | current = new CommandInfoIterator(root.getChildCommandList().get(index)); 52 | index++; 53 | } 54 | 55 | return current.next(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Command.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.annotation; 18 | 19 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 20 | 21 | import java.lang.annotation.ElementType; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.RetentionPolicy; 24 | import java.lang.annotation.Target; 25 | 26 | /** 27 | * @author Luiz Carlos Mourão 28 | */ 29 | 30 | @Target(ElementType.METHOD) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface Command { 33 | /** 34 | * Defines the command name, sub-commands are split with dots 35 | *

36 | * Example:

37 | * parentcommand

38 | * parentcommand.subcommand

39 | * 40 | * @return the command name 41 | */ 42 | String name(); 43 | 44 | /** 45 | * @return the command aliases 46 | */ 47 | String[] aliases() default {}; 48 | 49 | /** 50 | * @return the command description 51 | */ 52 | String description() default ""; 53 | 54 | /** 55 | * @return the command usage example 56 | */ 57 | String usage() default ""; 58 | 59 | /** 60 | * @return the required permission to execute the command 61 | */ 62 | String permission() default ""; 63 | 64 | /** 65 | * @return the command target 66 | */ 67 | CommandTarget target() default CommandTarget.ALL; 68 | 69 | /** 70 | * Tells the executor how to run the command, 71 | * some implementations might ignore this option as they are async by default. 72 | * This option requires an executor. 73 | * @return whether the command should be ran asynchronously 74 | */ 75 | boolean async() default false; 76 | } 77 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Completer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.annotation; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * @author Luiz Carlos Mourão 26 | */ 27 | 28 | @Target(ElementType.METHOD) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface Completer { 31 | /** 32 | * @return the command to complete 33 | */ 34 | String name(); 35 | } 36 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/IgnoreQuote.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.annotation; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * Makes the argument parser ignore strings with whitespaces. 26 | * 27 | * @author Luiz Carlos Mourão 28 | */ 29 | 30 | @Target(ElementType.PARAMETER) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface IgnoreQuote {} 33 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/annotation/Optional.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.annotation; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * Allows a argument to be optional, nullable if primitive 26 | * 27 | * @author Luiz Carlos Mourão 28 | */ 29 | 30 | @Target(ElementType.PARAMETER) 31 | @Retention(RetentionPolicy.RUNTIME) 32 | public @interface Optional { 33 | /** 34 | * Required if the argument type is a primitive 35 | * 36 | * @return the default value for a argument 37 | */ 38 | String[] def() default {}; 39 | } 40 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/argument/AdapterMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.argument; 18 | 19 | import java.util.HashMap; 20 | 21 | /** 22 | * The AdapterMap contains adapters such as 23 | * primitive values and such. 24 | * 25 | *

It can be created with the default primitive types 26 | * or totally empty, after other values can be added

27 | * 28 | * @author Luiz Carlos Mourão 29 | */ 30 | public class AdapterMap extends HashMap, TypeAdapter> { 31 | 32 | /** 33 | * Creates a new AdapterMap that can be empty 34 | * or registered with the default values. 35 | * 36 | * @param registerDefault Boolean 37 | */ 38 | public AdapterMap(boolean registerDefault) { 39 | super(); 40 | if (!registerDefault) return; 41 | 42 | put(String.class, String::valueOf); 43 | put(Character.class, argument -> argument.charAt(0)); 44 | put(Integer.class, Integer::valueOf); 45 | put(Double.class, Double::valueOf); 46 | put(Float.class, Float::valueOf); 47 | put(Long.class, Long::valueOf); 48 | put(Boolean.class, Boolean::valueOf); 49 | put(Byte.class, Byte::valueOf); 50 | 51 | put(Character.TYPE, argument -> argument.charAt(0)); 52 | put(Integer.TYPE, Integer::parseInt); 53 | put(Double.TYPE, Double::parseDouble); 54 | put(Float.TYPE, Float::parseFloat); 55 | put(Long.TYPE, Long::parseLong); 56 | put(Boolean.TYPE, Boolean::parseBoolean); 57 | put(Byte.TYPE, Byte::parseByte); 58 | } 59 | 60 | @SuppressWarnings("unchecked") 61 | public TypeAdapter put(Class key, TypeAdapter value) { 62 | return (TypeAdapter) super.put(key, value); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/argument/Argument.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.argument; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Builder; 21 | import lombok.Getter; 22 | import lombok.NonNull; 23 | 24 | /** 25 | * The model for each argument in the command. 26 | *

It contains the main information of each argument 27 | * such as it's type and name.

28 | * 29 | * @author Luiz Carlos Mourão 30 | */ 31 | @Getter 32 | @Builder 33 | @AllArgsConstructor 34 | public class Argument { 35 | private final String name; 36 | 37 | private final Class type; 38 | private final TypeAdapter adapter; 39 | 40 | private final T defaultValue; 41 | 42 | private final boolean isNullable; 43 | private final boolean isArray; 44 | private final boolean ignoreQuote; 45 | } 46 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/argument/TypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.argument; 18 | 19 | /** 20 | * @author Luiz Carlos Mourão 21 | */ 22 | public interface TypeAdapter { 23 | 24 | T convert(String raw); 25 | 26 | default T convertNonNull(String raw) { 27 | final T result = convert(raw); 28 | 29 | if (result == null) { 30 | throw new NullPointerException(); 31 | } 32 | 33 | return result; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/argument/eval/ArgumentEvaluator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.argument.eval; 18 | 19 | import lombok.Getter; 20 | import lombok.RequiredArgsConstructor; 21 | import me.saiintbrisson.minecraft.command.argument.Argument; 22 | import me.saiintbrisson.minecraft.command.command.Context; 23 | import me.saiintbrisson.minecraft.command.exception.CommandException; 24 | import me.saiintbrisson.minecraft.command.message.MessageType; 25 | import me.saiintbrisson.minecraft.command.util.ArrayUtil; 26 | import me.saiintbrisson.minecraft.command.util.StringUtil; 27 | 28 | import java.lang.reflect.Array; 29 | import java.util.List; 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | 32 | /** 33 | * The ArgumentEvaluator is the main argument 34 | * parser of the framework, it reads the argument 35 | * and provides a useful object to invoke the methods. 36 | * 37 | * @param Argument 38 | * 39 | * @author Luiz Carlos Mourão 40 | */ 41 | @Getter 42 | @RequiredArgsConstructor 43 | public class ArgumentEvaluator { 44 | 45 | private final List> argumentList; 46 | 47 | public Object[] parseArguments(Context context) { 48 | Object[] parameters = new Object[0]; 49 | AtomicInteger currentArg = new AtomicInteger(0); 50 | 51 | for (Argument argument : argumentList) { 52 | if (Context.class.isAssignableFrom(argument.getType())) { 53 | parameters = ArrayUtil.add(parameters, context); 54 | continue; 55 | } 56 | 57 | String arg = readFullString(argument, currentArg, context); 58 | if (arg == null) { 59 | if (!argument.isNullable()) { 60 | throw new CommandException(MessageType.INCORRECT_USAGE, null); 61 | } 62 | 63 | parameters = ArrayUtil.add(parameters, argument.getDefaultValue()); 64 | currentArg.incrementAndGet(); 65 | continue; 66 | } 67 | 68 | Object object; 69 | if (argument.isArray()) { 70 | object = Array.newInstance(argument.getType(), 0); 71 | do { 72 | object = ArrayUtil.add( 73 | (Object[]) object, 74 | argument.getAdapter().convertNonNull(arg) 75 | ); 76 | } while ((arg = readFullString(argument, currentArg, context)) != null); 77 | } else { 78 | object = argument.getAdapter().convertNonNull(arg); 79 | } 80 | 81 | parameters = ArrayUtil.add(parameters, object); 82 | } 83 | 84 | return parameters; 85 | } 86 | 87 | private String readFullString(Argument argument, AtomicInteger currentArg, Context context) { 88 | String arg = context.getArg(currentArg.get()); 89 | if (arg == null) return null; 90 | 91 | currentArg.incrementAndGet(); 92 | if (!argument.isIgnoreQuote() && arg.charAt(0) == '"') { 93 | final StringBuilder builder = new StringBuilder(arg.substring(1)); 94 | while ((arg = context.getArg(currentArg.get())) != null) { 95 | builder.append(" "); 96 | currentArg.incrementAndGet(); 97 | 98 | final int length = arg.length(); 99 | if (arg.charAt(length - 1) == '"' && (length == 1 || arg.charAt(length - 2) != '\\')) { 100 | builder.append(arg, 0, length - 1); 101 | break; 102 | } 103 | 104 | builder.append(arg); 105 | } 106 | 107 | return builder.toString().replace("\\\"", "\""); 108 | } 109 | 110 | return arg; 111 | } 112 | 113 | 114 | public String buildUsage(String name) { 115 | final StringBuilder builder = new StringBuilder(name); 116 | for (Argument argument : argumentList) { 117 | if (Context.class.isAssignableFrom(argument.getType())) continue; 118 | 119 | builder.append(argument.isNullable() ? " [" : " <"); 120 | builder.append(StringUtil.uncapitalize(argument.getType().getSimpleName())); 121 | 122 | if (argument.isArray()) builder.append("..."); 123 | 124 | builder.append(argument.isNullable() ? "]" : ">"); 125 | } 126 | 127 | return builder.toString(); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/argument/eval/MethodEvaluator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.argument.eval; 18 | 19 | import lombok.RequiredArgsConstructor; 20 | import me.saiintbrisson.minecraft.command.annotation.IgnoreQuote; 21 | import me.saiintbrisson.minecraft.command.annotation.Optional; 22 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 23 | import me.saiintbrisson.minecraft.command.argument.Argument; 24 | import me.saiintbrisson.minecraft.command.argument.TypeAdapter; 25 | import me.saiintbrisson.minecraft.command.command.Context; 26 | import me.saiintbrisson.minecraft.command.exception.CommandException; 27 | import me.saiintbrisson.minecraft.command.exception.NoSuchConverterException; 28 | import me.saiintbrisson.minecraft.command.util.ArrayUtil; 29 | 30 | import java.lang.reflect.Array; 31 | import java.lang.reflect.Method; 32 | import java.lang.reflect.Parameter; 33 | import java.util.ArrayList; 34 | import java.util.List; 35 | 36 | /** 37 | * The ArgumentEvaluator is the main method 38 | * validator of the framework. 39 | * 40 | *

It validates if the method contains the correct 41 | * parameters and creates an {@link Optional} parameter 42 | * 43 | * @author Luiz Carlos Mourão 44 | */ 45 | @RequiredArgsConstructor 46 | public class MethodEvaluator { 47 | 48 | private final AdapterMap adapterMap; 49 | 50 | @SuppressWarnings("unchecked") 51 | public List> evaluateMethod(Method method) { 52 | final List> argumentList = new ArrayList<>(); 53 | 54 | final Parameter[] parameters = method.getParameters(); 55 | for (int i = 0; i < parameters.length; i++) { 56 | Parameter parameter = parameters[i]; 57 | 58 | Class type = parameter.getType(); 59 | boolean isArray = type.isArray(); 60 | final Argument.ArgumentBuilder builder = Argument 61 | .builder() 62 | .name(parameter.getName()) 63 | .type(type) 64 | .isArray(isArray) 65 | .ignoreQuote(parameter.isAnnotationPresent(IgnoreQuote.class)); 66 | 67 | if (Context.class.isAssignableFrom(type)) { 68 | argumentList.add(builder.build()); 69 | continue; 70 | } 71 | 72 | if (isArray) { 73 | if (i != parameters.length - 1) { 74 | throw new CommandException("Arrays must be the last parameter in a command, " + method.getName()); 75 | } 76 | 77 | builder.type(type = type.getComponentType()); 78 | } 79 | 80 | builder.adapter(adapterMap.get(type)); 81 | 82 | final Optional optional = parameter.getDeclaredAnnotation(Optional.class); 83 | if (optional == null) { 84 | argumentList.add(builder.build()); 85 | continue; 86 | } 87 | 88 | argumentList.add(createOptional(method, type, isArray, optional.def(), builder)); 89 | } 90 | 91 | return argumentList; 92 | } 93 | 94 | @SuppressWarnings("unchecked") 95 | private Argument createOptional(Method method, Class type, boolean isArray, 96 | String[] def, Argument.ArgumentBuilder builder 97 | ) { 98 | if (type.isPrimitive() && def.length == 0) { 99 | throw new IllegalArgumentException("Use wrappers instead of primitive types for nullability, " 100 | + method.getName()); 101 | } 102 | 103 | final TypeAdapter adapter = adapterMap.get(type); 104 | if (adapter == null) throw new NoSuchConverterException(type); 105 | 106 | builder.isNullable(true); 107 | 108 | if (isArray && def.length != 0) { 109 | builder.defaultValue(createArray(type, adapter, def)); 110 | } else if (def.length != 0) { 111 | builder.defaultValue(adapter.convertNonNull(def[0])); 112 | } 113 | 114 | return builder.build(); 115 | } 116 | 117 | private Object[] createArray(Class type, TypeAdapter adapter, String[] def) { 118 | Object[] value = (Object[]) Array.newInstance(type, 0); 119 | for (String arg : def) { 120 | value = ArrayUtil.add(value, adapter.convertNonNull(arg)); 121 | } 122 | 123 | return value; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/command/CommandHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.command; 18 | 19 | import me.saiintbrisson.minecraft.command.CommandInfoIterator; 20 | import me.saiintbrisson.minecraft.command.executor.CommandExecutor; 21 | import me.saiintbrisson.minecraft.command.executor.CompleterExecutor; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | import java.util.Iterator; 25 | import java.util.List; 26 | import java.util.Optional; 27 | 28 | /** 29 | * The CommandHolder is the main implementation of the 30 | * Command, it contains the main information about 31 | * that command {@link CommandInfo} position and Parent/Child commands 32 | * 33 | * @author Luiz Carlos Mourão 34 | */ 35 | public interface CommandHolder> extends Iterable> { 36 | 37 | int getPosition(); 38 | 39 | CommandExecutor getCommandExecutor(); 40 | CompleterExecutor getCompleterExecutor(); 41 | 42 | default Optional> getParentCommand() { 43 | return Optional.empty(); 44 | } 45 | List getChildCommandList(); 46 | C getChildCommand(String name); 47 | 48 | CommandInfo getCommandInfo(); 49 | 50 | String getName(); 51 | String getFancyName(); 52 | List getAliasesList(); 53 | 54 | String getPermission(); 55 | String getUsage(); 56 | String getDescription(); 57 | 58 | default boolean equals(String name) { 59 | if (getName().equalsIgnoreCase(name)) { 60 | return true; 61 | } 62 | 63 | for (String alias : getAliasesList()) { 64 | if (alias.equalsIgnoreCase(name)) return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | @NotNull 71 | @Override 72 | default Iterator> iterator() { 73 | return new CommandInfoIterator(this); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/command/CommandInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.command; 18 | 19 | import lombok.*; 20 | import me.saiintbrisson.minecraft.command.annotation.Command; 21 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 22 | 23 | /** 24 | * @author Luiz Carlos Mourão 25 | */ 26 | 27 | @Getter 28 | @Builder 29 | @AllArgsConstructor 30 | public class CommandInfo { 31 | 32 | /** 33 | * Defines the command name, sub-commands are split with dots 34 | *

35 | * Example:

36 | * parentcommand

37 | * parentcommand.subcommand

38 | */ 39 | @NonNull 40 | private final String name; 41 | 42 | /** 43 | * Defines the array of aliases of the command, 44 | * if it doesn't have aliases it return a empty 45 | * array of strings 46 | */ 47 | @NonNull 48 | @Builder.Default 49 | private final String[] aliases = new String[0]; 50 | 51 | /** 52 | * Defines the description of the command, 53 | * if it wasn't provided, it returns a empty 54 | * String 55 | */ 56 | @Setter 57 | @Builder.Default 58 | private String description = ""; 59 | 60 | /** 61 | * Defines the command usage for the MessageuHolder, 62 | * if it's empty, returns a empty String 63 | */ 64 | @Setter 65 | @Builder.Default 66 | private String usage = ""; 67 | 68 | /** 69 | * Defines the permission required to execute 70 | * the command, if it's empty the default permission 71 | * is a empty String 72 | */ 73 | @Setter 74 | @Builder.Default 75 | private String permission = ""; 76 | 77 | /** 78 | * Defines the CommandTarget of the command, 79 | * if it's empty, it returns a ALL target. 80 | */ 81 | @Setter 82 | @NonNull 83 | @Builder.Default 84 | private CommandTarget target = CommandTarget.ALL; 85 | 86 | /** 87 | * Tells the executor how to run the command, 88 | * some implementations might ignore this option as they are async by default. 89 | * This option requires an executor. 90 | */ 91 | @Builder.Default 92 | private final boolean async = false; 93 | 94 | public CommandInfo(Command command) { 95 | this( 96 | command.name(), 97 | command.aliases(), 98 | command.description(), 99 | command.usage(), 100 | command.permission(), 101 | command.target(), 102 | command.async() 103 | ); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/command/Context.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.command; 18 | 19 | import me.saiintbrisson.minecraft.command.CommandFrame; 20 | import me.saiintbrisson.minecraft.command.argument.TypeAdapter; 21 | import me.saiintbrisson.minecraft.command.exception.CommandException; 22 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 23 | 24 | import java.lang.reflect.Array; 25 | import java.util.Arrays; 26 | 27 | /** 28 | * The context is where all information from the command dispatcher 29 | * is stored, such as the sender, arguments and label 30 | * @author Luiz Carlos Mourão 31 | */ 32 | public interface Context { 33 | 34 | /** 35 | * Contains the label sent by the command 36 | * @return String 37 | */ 38 | String getLabel(); 39 | 40 | /** 41 | * The generic value can be either 42 | * a Console or Player 43 | * @return S 44 | */ 45 | S getSender(); 46 | 47 | /** 48 | * @return the executor type 49 | */ 50 | CommandTarget getTarget(); 51 | 52 | /** 53 | * Contains all arguments sent by the command 54 | * @return String[] of arguments 55 | */ 56 | String[] getArgs(); 57 | 58 | 59 | /** 60 | * @return the number of arguments 61 | */ 62 | default int argsCount() { 63 | return getArgs().length; 64 | } 65 | 66 | /** 67 | * @param index the index of the argument 68 | * @return the argument - null if the index is out of bounds 69 | */ 70 | default String getArg(int index) { 71 | try { 72 | return getArgs()[index]; 73 | } catch (ArrayIndexOutOfBoundsException e) { 74 | return null; 75 | } 76 | } 77 | 78 | @SuppressWarnings("unchecked") 79 | default T getArg(int index, Class type) { 80 | return (T) getCommandFrame().getAdapterMap().get(type).convertNonNull(getArg(index)); 81 | } 82 | 83 | /** 84 | * Gets all args between indexes from and to 85 | * 86 | * @param from defines the start of the array relative to the arguments, inclusive 87 | * @param to defines the end of the array relative to the arguments, exclusive 88 | * @return the arguments array - null if the indexes are out of bounds 89 | */ 90 | default String[] getArgs(int from, int to) { 91 | try { 92 | return Arrays.copyOfRange(getArgs(), from, to); 93 | } catch (ArrayIndexOutOfBoundsException e) { 94 | return null; 95 | } 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | default T[] getArgs(int from, int to, Class type) { 100 | try { 101 | final TypeAdapter adapter = getCommandFrame().getAdapterMap().get(type); 102 | final T[] instance = (T[]) Array.newInstance(type, to - from); 103 | 104 | for (int i = from; i <= to; i++) { 105 | instance[i - from] = (T) adapter.convertNonNull(getArg(i)); 106 | } 107 | 108 | return instance; 109 | } catch (ArrayIndexOutOfBoundsException e) { 110 | return null; 111 | } 112 | } 113 | 114 | 115 | /** 116 | * Sends a message to the executor 117 | * 118 | * @param message the message to be sent 119 | */ 120 | void sendMessage(String message); 121 | 122 | /** 123 | * Sends multiple messages to the executor 124 | * 125 | * @param messages the messages to be sent 126 | */ 127 | void sendMessage(String[] messages); 128 | 129 | /** 130 | * Sends a message formatting it with the String#format() method 131 | * 132 | * @param message the message to be sent 133 | * @param objects the objects to be inserted 134 | */ 135 | default void sendMessage(String message, Object... objects) { 136 | sendMessage(String.format(message, objects)); 137 | } 138 | 139 | 140 | /** 141 | * Tests whether the executor has a permission 142 | * 143 | * @param permission the permission to be tested 144 | * @param silent whether a exception should be thrown 145 | * @return the test result if silent 146 | */ 147 | boolean testPermission(String permission, boolean silent) throws CommandException; 148 | 149 | /** 150 | * Tests whether the executor is a target 151 | * 152 | * @param target the target to be tested 153 | * @param silent whether a exception should be thrown 154 | * @return the test result if silent 155 | */ 156 | boolean testTarget(CommandTarget target, boolean silent) throws CommandException; 157 | 158 | /** 159 | * @return this command's frame 160 | */ 161 | CommandFrame getCommandFrame(); 162 | 163 | /** 164 | * @return this command's holder 165 | */ 166 | CommandHolder getCommandHolder(); 167 | } 168 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/exception/CommandException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.exception; 18 | 19 | import lombok.Getter; 20 | import lombok.NoArgsConstructor; 21 | import me.saiintbrisson.minecraft.command.message.MessageType; 22 | 23 | /** 24 | * The CommandException is the default exception thrown 25 | * by the framework if any errors are thrown during the 26 | * execution of a command. 27 | * 28 | * @author Luiz Carlos Mourão 29 | */ 30 | @NoArgsConstructor 31 | public class CommandException extends RuntimeException { 32 | 33 | @Getter 34 | private MessageType messageType; 35 | 36 | public CommandException(final MessageType messageType, final String message) { 37 | super(message); 38 | this.messageType = messageType; 39 | } 40 | 41 | public CommandException(final Throwable cause) { 42 | super(cause); 43 | } 44 | 45 | public CommandException(final String message) { 46 | super(message); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/exception/NoSuchConverterException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.exception; 18 | 19 | /** 20 | * The NoSuchConverterException is thrown when there 21 | * isn't a converter for the type provided. 22 | * 23 | * @author Luiz Carlos Mourão 24 | */ 25 | public class NoSuchConverterException extends CommandException { 26 | 27 | public NoSuchConverterException(Class type) { 28 | super("No converter found for type " + type.getTypeName()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/executor/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.executor; 18 | 19 | import me.saiintbrisson.minecraft.command.command.Context; 20 | 21 | /** 22 | * The BukkitCommandExecutor is the main executor of each 23 | * method that is listed as a Command, it invokes the method 24 | * and executes everything inside. 25 | * 26 | * @author Luiz Carlos Mourão 27 | */ 28 | @FunctionalInterface 29 | public interface CommandExecutor { 30 | 31 | /** 32 | * Executes the command with the provided context 33 | *

Returns false if the execution wasn't successful

34 | * @param context Context 35 | * 36 | * @return boolean 37 | */ 38 | boolean execute(Context context); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/executor/CompleterExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.executor; 18 | 19 | import me.saiintbrisson.minecraft.command.command.Context; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * @author Luiz Carlos Mourão 25 | */ 26 | 27 | @FunctionalInterface 28 | public interface CompleterExecutor { 29 | 30 | List execute(Context context); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/message/MessageHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.message; 18 | 19 | import lombok.Getter; 20 | 21 | import java.util.EnumMap; 22 | import java.util.Locale; 23 | import java.util.PropertyResourceBundle; 24 | import java.util.ResourceBundle; 25 | 26 | /** 27 | * The MessageHolder stores the messages for each Error 28 | * thrown by the command. 29 | * 30 | * @author Luiz Carlos Mourão 31 | * @see MessageType 32 | */ 33 | @Getter 34 | public final class MessageHolder { 35 | 36 | private final EnumMap messageMap = new EnumMap<>(MessageType.class); 37 | 38 | public MessageHolder() { 39 | for (MessageType value : MessageType.values()) { 40 | messageMap.put(value, value.getDefMessage()); 41 | } 42 | } 43 | 44 | public String getMessage(MessageType type) { 45 | return messageMap.get(type); 46 | } 47 | 48 | public String getReplacing(MessageType type, String message) { 49 | return getMessage(type).replace(type.getPlaceHolder(), message); 50 | } 51 | 52 | public void setMessage(MessageType type, String message) { 53 | messageMap.put(type, message); 54 | } 55 | 56 | public void loadFromResources(String baseName, Locale locale) { 57 | final ResourceBundle bundle = PropertyResourceBundle.getBundle(baseName, locale); 58 | loadFromResources(bundle); 59 | } 60 | 61 | public void loadFromResources(String baseName) { 62 | final ResourceBundle bundle = PropertyResourceBundle.getBundle(baseName); 63 | loadFromResources(bundle); 64 | } 65 | 66 | public void loadFromResources(ResourceBundle bundle) { 67 | for (String s : bundle.keySet()) { 68 | try { 69 | final MessageType type = MessageType.valueOf(s); 70 | setMessage(type, bundle.getString(s).replace("&", "§")); 71 | } catch (IllegalArgumentException ignore) { 72 | } 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/message/MessageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.message; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Getter; 21 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 22 | 23 | /** 24 | * The MessageType contains the default messages for the errors 25 | * as well that values can be edited. 26 | * 27 | *

Placeholders can be put on each of those errors

28 | * @author Luiz Carlos Mourão 29 | */ 30 | @Getter 31 | @AllArgsConstructor 32 | public enum MessageType { 33 | 34 | /** 35 | * Used when a error is thrown, the {error} can be 36 | * used to send the message of error 37 | */ 38 | ERROR("{error}", 39 | "§cAn error has been thrown: §f{error}§c.") { 40 | @Override 41 | public String getDefault(CommandHolder commandHolder) { 42 | return ""; 43 | } 44 | }, 45 | /** 46 | * Used when a player doesn't have a permission, 47 | * the {permission} can be used to send the permission 48 | */ 49 | NO_PERMISSION("{permission}", 50 | "§cRequired permission: §f{permission}§c.") { 51 | @Override 52 | public String getDefault(CommandHolder commandHolder) { 53 | return commandHolder.getPermission(); 54 | } 55 | }, 56 | /** 57 | * Used when a player doesn't use the command correctly, 58 | * the {usage} can be used to send the correct usage 59 | */ 60 | INCORRECT_USAGE("{usage}", 61 | "§cCorrect usage: §e/{usage}§c.") { 62 | @Override 63 | public String getDefault(CommandHolder commandHolder) { 64 | return commandHolder.getUsage(); 65 | } 66 | }, 67 | /** 68 | * Used when a player doesn't use the target correctly, 69 | * the {target} can be used to send the correct target 70 | */ 71 | INCORRECT_TARGET("{target}", 72 | "§cYou cannot execute this command. Targeted to: §f{target}§c.") { 73 | @Override 74 | public String getDefault(CommandHolder commandHolder) { 75 | return commandHolder.getCommandInfo().getTarget().name(); 76 | } 77 | }; 78 | 79 | private final String placeHolder; 80 | private final String defMessage; 81 | 82 | public abstract String getDefault(CommandHolder commandHolder); 83 | } 84 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/target/CommandTarget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.target; 18 | 19 | /** 20 | * @author Luiz Carlos Mourão 21 | */ 22 | public enum CommandTarget { 23 | /** 24 | * The ALL target can be all senders listed below, 25 | * that means that it can receive command from 26 | * PLAYER and CONSOLE. 27 | */ 28 | ALL, 29 | /** 30 | * The PLAYER target only accepts Players 31 | * to execute that command 32 | */ 33 | PLAYER, 34 | /** 35 | * The CONSOLE target only accept the Console 36 | * to execute that command 37 | */ 38 | CONSOLE 39 | } 40 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/target/TargetValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Luiz Carlos Mourão Paes de Carvalho 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package me.saiintbrisson.minecraft.command.target; 18 | 19 | /** 20 | * The BukkitTargetValidator validates if the Target 21 | * is a correct and usable {@link CommandTarget} 22 | * 23 | * @author Luiz Carlos Mourão 24 | */ 25 | public interface TargetValidator { 26 | 27 | /** 28 | * Tries to validate the Command target and Sender object. 29 | *

Returns false if it wasn't validated

30 | * @param target CommandTarget 31 | * @param object Object 32 | * 33 | * @return Boolean 34 | */ 35 | boolean validate(CommandTarget target, Object object); 36 | 37 | /** 38 | * Returns the CommandTarget by the Sender object 39 | *

Example: The Player object returns a {@link CommandTarget} of PLAYER

40 | * @param object Object 41 | * 42 | * @return CommandTarget 43 | */ 44 | CommandTarget fromSender(Object object); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/util/ArrayUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package me.saiintbrisson.minecraft.command.util; 19 | 20 | import lombok.AccessLevel; 21 | import lombok.NoArgsConstructor; 22 | 23 | import java.lang.reflect.Array; 24 | 25 | /** 26 | * @author The Apache Software Foundation 27 | */ 28 | @SuppressWarnings("unchecked") 29 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 30 | public final class ArrayUtil { 31 | 32 | public static T[] copyOfRange(T[] original, int from, int to) { 33 | return copyOfRange(original, from, to, (Class) original.getClass()); 34 | } 35 | 36 | public static T[] copyOfRange(U[] original, int from, int to, Class newType) { 37 | int newLength = to - from; 38 | if (newLength < 0) 39 | throw new IllegalArgumentException(from + " > " + to); 40 | 41 | T[] copy = ((Object) newType == (Object) Object[].class) 42 | ? (T[]) new Object[newLength] 43 | : (T[]) Array.newInstance(newType.getComponentType(), newLength); 44 | 45 | System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); 46 | return copy; 47 | } 48 | 49 | public static Object[] add(Object[] array, Object element) { 50 | Class type; 51 | if (array != null) { 52 | type = array.getClass(); 53 | } else if (element != null) { 54 | type = element.getClass(); 55 | } else { 56 | type = Object.class; 57 | } 58 | 59 | Object[] newArray = (Object[]) copyArrayGrow1(array, type); 60 | newArray[newArray.length - 1] = element; 61 | return newArray; 62 | } 63 | 64 | private static Object copyArrayGrow1(Object array, Class newArrayComponentType) { 65 | if (array != null) { 66 | int arrayLength = Array.getLength(array); 67 | Object newArray = Array.newInstance(array.getClass().getComponentType(), arrayLength + 1); 68 | System.arraycopy(array, 0, newArray, 0, arrayLength); 69 | return newArray; 70 | } 71 | 72 | return Array.newInstance(newArrayComponentType, 1); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /shared/src/main/java/me/saiintbrisson/minecraft/command/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package me.saiintbrisson.minecraft.command.util; 19 | 20 | import lombok.AccessLevel; 21 | import lombok.NoArgsConstructor; 22 | 23 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 24 | public final class StringUtil { 25 | 26 | public static boolean startsWithIgnoreCase(String str, String prefix) { 27 | return startsWith(str, prefix, true); 28 | } 29 | 30 | private static boolean startsWith(String str, String prefix, boolean ignoreCase) { 31 | if (str == null || prefix == null) return (str == null && prefix == null); 32 | if (prefix.length() > str.length()) return false; 33 | 34 | return str.regionMatches(ignoreCase, 0, prefix, 0, prefix.length()); 35 | } 36 | 37 | public static boolean isEmpty(String str) { 38 | return str == null || str.isEmpty(); 39 | } 40 | 41 | public static int countMatches(String str, String sub) { 42 | if (isEmpty(str) || isEmpty(sub)) return 0; 43 | 44 | int count = 0; 45 | int idx = 0; 46 | while ((idx = str.indexOf(sub, idx)) != -1) { 47 | count++; 48 | idx += sub.length(); 49 | } 50 | 51 | return count; 52 | } 53 | 54 | public static String uncapitalize(String str) { 55 | if (str == null || str.length() == 0) return str; 56 | return Character.toLowerCase(str.charAt(0)) + str.substring(1); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /shared/src/test/java/ArgumentParsingTest.java: -------------------------------------------------------------------------------- 1 | import me.saiintbrisson.minecraft.command.annotation.IgnoreQuote; 2 | import me.saiintbrisson.minecraft.command.annotation.Optional; 3 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 4 | import me.saiintbrisson.minecraft.command.argument.Argument; 5 | import me.saiintbrisson.minecraft.command.argument.eval.ArgumentEvaluator; 6 | import me.saiintbrisson.minecraft.command.argument.eval.MethodEvaluator; 7 | import me.saiintbrisson.minecraft.command.command.Context; 8 | import me.saiintbrisson.minecraft.command.exception.CommandException; 9 | import me.saiintbrisson.minecraft.command.message.MessageType; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | /** 20 | * @author Luiz Carlos Mourão 21 | */ 22 | public class ArgumentParsingTest { 23 | @Test 24 | void parseMethod() throws NoSuchMethodException { 25 | final List> list = getArguments(); 26 | 27 | assertEquals(Context.class, list.get(0).getType()); 28 | assertEquals(String.class, list.get(1).getType()); 29 | final Argument arrayArgument = list.get(2); 30 | assertEquals(arrayArgument.getType(), String.class); 31 | assertTrue(arrayArgument.isArray()); 32 | assertTrue(arrayArgument.isNullable()); 33 | assertArrayEquals(new String[]{"Hello", "World!"}, (String[]) arrayArgument.getDefaultValue()); 34 | } 35 | 36 | @Test 37 | void parseArguments() throws NoSuchMethodException { 38 | final List> list = getArguments(); 39 | 40 | assertArrayEquals( 41 | new Object[]{"first", new String[]{"Hello", "World!"}}, 42 | getArguments(list, "first") 43 | ); 44 | 45 | assertArrayEquals( 46 | new Object[]{"first", new String[]{"second"}}, 47 | getArguments(list, "first", "second") 48 | ); 49 | 50 | assertArrayEquals( 51 | new Object[]{"first", new String[]{"second", "third"}}, 52 | getArguments(list, "first", "second", "third") 53 | ); 54 | } 55 | 56 | @Test 57 | void invokeTestCommand() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 58 | final AdapterMap adapterMap = new AdapterMap(true); 59 | final MethodEvaluator evaluator = new MethodEvaluator(adapterMap); 60 | 61 | final Method method = getClass().getMethod("testCommand", Context.class, String.class, String[].class); 62 | final List> list = evaluator.evaluateMethod(method); 63 | final ArgumentEvaluator argumentEvaluator = new ArgumentEvaluator<>(list); 64 | final TestContext context = new TestContext(new String[]{"1.0", "1.1", "1.2", "1.3"}, adapterMap); 65 | 66 | invokeCommand(method, argumentEvaluator, context); 67 | } 68 | 69 | public void testCommand(Context context, 70 | String firstArgument, 71 | @Optional(def = {"Hello", "World!"}) String[] secondArgument) { 72 | context.getArg(0, Double.class); 73 | assertArrayEquals(new Double[]{1.1, 1.2, 1.3}, context.getArgs(1, 4, Double.class)); 74 | } 75 | 76 | @Test 77 | void invokeIgnoreQuoteTestCommand() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 78 | final AdapterMap adapterMap = new AdapterMap(true); 79 | final MethodEvaluator evaluator = new MethodEvaluator(adapterMap); 80 | 81 | final Method method = getClass().getMethod("ignoreQuoteTestCommand", Context.class, String.class, String.class); 82 | final List> list = evaluator.evaluateMethod(method); 83 | final ArgumentEvaluator argumentEvaluator = new ArgumentEvaluator<>(list); 84 | final TestContext context = new TestContext(new String[]{"one", "\"argument1", "argument2\""}, adapterMap); 85 | 86 | invokeCommand(method, argumentEvaluator, context); 87 | } 88 | 89 | public void ignoreQuoteTestCommand(Context context, 90 | @IgnoreQuote String a, String b) { 91 | assertFalse(a.contains(" ")); 92 | assertTrue(b.contains(" ")); 93 | } 94 | 95 | private List> getArguments() throws NoSuchMethodException { 96 | final AdapterMap adapterMap = new AdapterMap(true); 97 | final MethodEvaluator evaluator = new MethodEvaluator(adapterMap); 98 | 99 | return evaluator.evaluateMethod( 100 | getClass().getMethod("testCommand", Context.class, String.class, String[].class) 101 | ); 102 | } 103 | 104 | private Object[] getArguments(List> list, String... arguments) { 105 | final ArgumentEvaluator argumentEvaluator = new ArgumentEvaluator<>(list); 106 | final TestContext context = new TestContext(arguments, null); 107 | final Object[] objects = argumentEvaluator.parseArguments(context); 108 | return Arrays.copyOfRange(objects, 1, objects.length); 109 | } 110 | 111 | private Object invokeCommand(Method method, ArgumentEvaluator evaluator, Context context) 112 | throws InvocationTargetException, IllegalAccessException { 113 | if (evaluator.getArgumentList().size() == 0) { 114 | return method.invoke(this); 115 | } 116 | 117 | final Object[] parameters; 118 | 119 | try { 120 | parameters = evaluator.parseArguments(context); 121 | } catch (Exception e) { 122 | throw new InvocationTargetException(new CommandException(MessageType.INCORRECT_USAGE, null)); 123 | } 124 | 125 | return method.invoke(this, parameters); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /shared/src/test/java/TestContext.java: -------------------------------------------------------------------------------- 1 | import me.saiintbrisson.minecraft.command.CommandFrame; 2 | import me.saiintbrisson.minecraft.command.argument.AdapterMap; 3 | import me.saiintbrisson.minecraft.command.argument.TypeAdapter; 4 | import me.saiintbrisson.minecraft.command.command.CommandHolder; 5 | import me.saiintbrisson.minecraft.command.command.Context; 6 | import me.saiintbrisson.minecraft.command.exception.CommandException; 7 | import me.saiintbrisson.minecraft.command.target.CommandTarget; 8 | 9 | import java.lang.reflect.Array; 10 | 11 | /** 12 | * @author Luiz Carlos Mourão 13 | */ 14 | public class TestContext implements Context { 15 | private final String[] args; 16 | private final AdapterMap adapterMap; 17 | 18 | public TestContext(String[] args, AdapterMap adapterMap) { 19 | this.args = args; 20 | this.adapterMap = adapterMap; 21 | } 22 | 23 | @Override 24 | public String getLabel() { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | @Override 29 | public Object getSender() { 30 | throw new UnsupportedOperationException(); 31 | } 32 | 33 | @Override 34 | public CommandTarget getTarget() { 35 | throw new UnsupportedOperationException(); 36 | } 37 | 38 | @Override 39 | public String[] getArgs() { 40 | return args; 41 | } 42 | 43 | @Override 44 | public T getArg(int index, Class type) { 45 | return (T) adapterMap.get(type).convertNonNull(getArg(index)); 46 | } 47 | 48 | public T[] getArgs(int from, int to, Class type) { 49 | try { 50 | final TypeAdapter adapter = adapterMap.get(type); 51 | final T[] instance = (T[]) Array.newInstance(type, to - from); 52 | 53 | for (int i = from; i < to; i++) { 54 | instance[i - from] = (T) adapter.convertNonNull(getArg(i)); 55 | } 56 | 57 | return instance; 58 | } catch (ArrayIndexOutOfBoundsException e) { 59 | return null; 60 | } 61 | } 62 | 63 | @Override 64 | public void sendMessage(String message) { 65 | 66 | } 67 | 68 | @Override 69 | public void sendMessage(String[] messages) { 70 | 71 | } 72 | 73 | @Override 74 | public boolean testPermission(String permission, boolean silent) throws CommandException { 75 | return false; 76 | } 77 | 78 | @Override 79 | public boolean testTarget(CommandTarget target, boolean silent) throws CommandException { 80 | return false; 81 | } 82 | 83 | @Override 84 | public CommandFrame getCommandFrame() { 85 | throw new UnsupportedOperationException(); 86 | } 87 | 88 | @Override 89 | public CommandHolder getCommandHolder() { 90 | throw new UnsupportedOperationException(); 91 | } 92 | } 93 | --------------------------------------------------------------------------------