├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs ├── CommandNode.1.gif ├── CommandNode.1.png ├── CommandNode.2.png └── CommandNode.4.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ └── engine │ ├── command │ ├── ArgumentCheckResult.java │ ├── BaseCommandManager.java │ ├── Command.java │ ├── CommandExceptionHandler.java │ ├── CommandFailure.java │ ├── CommandManager.java │ ├── CommandParser.java │ ├── CommandSender.java │ ├── anno │ │ ├── ArgumentHandler.java │ │ ├── ClassAnnotationCommand.java │ │ ├── Command.java │ │ ├── CommandBuilderGetter.java │ │ ├── Ignore.java │ │ ├── MethodAnnotationCommand.java │ │ ├── NodeAnnotationCommand.java │ │ ├── NodeBuilder.java │ │ ├── NodeCommandBuilder.java │ │ ├── Permission.java │ │ ├── Provide.java │ │ ├── Required.java │ │ ├── Sender.java │ │ ├── Suggester.java │ │ ├── Tip.java │ │ └── UseProvide.java │ ├── argument │ │ ├── Argument.java │ │ ├── ArgumentManager.java │ │ ├── FunctionArgument.java │ │ ├── SimpleArgument.java │ │ ├── SimpleArgumentManager.java │ │ └── base │ │ │ ├── BooleanArgument.java │ │ │ ├── DoubleArgument.java │ │ │ ├── FloatArgument.java │ │ │ ├── IntegerArgument.java │ │ │ ├── LongArgument.java │ │ │ ├── ShortArgument.java │ │ │ └── StringArgument.java │ ├── impl │ │ ├── DefaultCommandParser.java │ │ ├── DefaultExceptionHandler.java │ │ └── SimpleCommandManager.java │ ├── simple │ │ ├── CommandArgumentChecker.java │ │ ├── CommandExecutor.java │ │ ├── CommandSuggester.java │ │ ├── CommandTips.java │ │ └── SimpleCommand.java │ ├── suggestion │ │ ├── NamedSuggester.java │ │ ├── SimpleSuggesterManager.java │ │ ├── Suggester.java │ │ └── SuggesterManager.java │ └── util │ │ ├── ClassUtil.java │ │ ├── CommandNodeUtil.java │ │ ├── StringArgs.java │ │ ├── SuggesterHelper.java │ │ ├── Type.java │ │ ├── asm │ │ └── ClassDefiner.java │ │ ├── context │ │ ├── Context.java │ │ ├── ContextNode.java │ │ └── LinkedContext.java │ │ └── node │ │ ├── ArgumentNode.java │ │ ├── CommandNode.java │ │ ├── EmptyArgumentNode.java │ │ ├── EnumNode.java │ │ ├── MultiArgumentNode.java │ │ ├── Nodeable.java │ │ ├── ParseResult.java │ │ ├── RequiredNode.java │ │ └── SenderNode.java │ └── permission │ ├── HashPermissible.java │ └── Permissible.java └── test └── java └── main ├── ClassNodeCommandTest.java ├── Entity.java ├── HashPermissibleTest.java ├── Location.java ├── LocationProvider.java ├── MethodNodeCommandTest.java ├── MultiThreadTest.java ├── NodeSortTest.java ├── TestEnum.java ├── TestSender.java ├── World.java ├── WorldArgument.java └── swing ├── ConsoleSender.java ├── EntityArgument.java ├── EntityManager.java ├── MethodCommand.java ├── SwingEntity.java ├── SwingTest.java └── TextArea.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle/ 3 | 4 | # Idea 5 | .idea/ 6 | out/ 7 | 8 | # Eclipse 9 | .classpath 10 | .project 11 | .settings/ 12 | 13 | # Java 14 | *.class 15 | /build/* 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cap 2 | [![](https://api.codeclimate.com/v1/badges/687ed1fae703c5786d17/maintainability)](https://codeclimate.com/github/UnknownDomainGame/Cap/maintainability) 3 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/77686ec6a9d24ac386b842b5fceb09c2)](https://www.codacy.com/gh/UnknownDomainGames/Cap/dashboard?utm_source=github.com&utm_medium=referral&utm_content=UnknownDomainGames/Cap&utm_campaign=Badge_Grade) 4 | 5 | Command & permission library 6 | 7 | ## How to use 8 | 9 | ### Handler method: 10 | ```java 11 | @Command("say") 12 | public void say(@Sender CommandSender sender,String message){ 13 | System.out.println(sender.getSenderName()+": "+message); 14 | } 15 | ``` 16 | 17 | ### Register 18 | 19 | ```java 20 | SimpleCommandManager simpleCommandManager = new SimpleCommandManager(); 21 | MethodAnnotationCommand.getBuilder(simpleCommandManager) 22 | .addCommandHandler(this) 23 | .register(); 24 | ``` 25 | 26 | ### Execute 27 | 28 | ```java 29 | simpleCommandManager.execute(testSender, "say hello!"); 30 | ``` 31 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | apply plugin: "idea" 4 | 5 | group 'com.github.unknowndomaingame' 6 | version '1.0.0' 7 | 8 | sourceCompatibility = targetCompatibility = 11 9 | 10 | tasks.withType(JavaCompile) { 11 | options.encoding = "UTF-8" 12 | } 13 | 14 | repositories { 15 | maven { 16 | url 'https://maven.aliyun.com/repository/public' 17 | } 18 | jcenter() 19 | mavenCentral() 20 | } 21 | 22 | jar { 23 | from { 24 | configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } 25 | } 26 | } 27 | 28 | test { 29 | useJUnitPlatform() 30 | } 31 | 32 | dependencies { 33 | compile 'com.google.guava:guava:30.1-jre' 34 | compile 'org.apache.commons:commons-lang3:3.11' 35 | 36 | implementation group: 'org.ow2.asm', name: 'asm', version: '7.1' 37 | implementation group: 'org.ow2.asm', name: 'asm-tree', version: '7.1' 38 | implementation group: 'org.ow2.asm', name: 'asm-util', version: '7.1' 39 | implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.1' 40 | 41 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' 42 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0' 43 | } 44 | -------------------------------------------------------------------------------- /docs/CommandNode.1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnknownDomainGames/Cap/c1395a0a1d598f3fe83cf884ad2e36bb50013040/docs/CommandNode.1.gif -------------------------------------------------------------------------------- /docs/CommandNode.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnknownDomainGames/Cap/c1395a0a1d598f3fe83cf884ad2e36bb50013040/docs/CommandNode.1.png -------------------------------------------------------------------------------- /docs/CommandNode.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnknownDomainGames/Cap/c1395a0a1d598f3fe83cf884ad2e36bb50013040/docs/CommandNode.2.png -------------------------------------------------------------------------------- /docs/CommandNode.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnknownDomainGames/Cap/c1395a0a1d598f3fe83cf884ad2e36bb50013040/docs/CommandNode.4.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnknownDomainGames/Cap/c1395a0a1d598f3fe83cf884ad2e36bb50013040/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Cap' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/engine/command/ArgumentCheckResult.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | public class ArgumentCheckResult { 4 | 5 | private String helpMessage; 6 | 7 | private boolean valid; 8 | 9 | private ArgumentCheckResult(String helpMessage, boolean valid) { 10 | this.helpMessage = helpMessage; 11 | this.valid = valid; 12 | } 13 | 14 | public static ArgumentCheckResult Valid() { 15 | return new ArgumentCheckResult(null, true); 16 | } 17 | 18 | public static ArgumentCheckResult Error(String helpMessage) { 19 | return new ArgumentCheckResult(helpMessage, false); 20 | } 21 | 22 | public String getHelpMessage() { 23 | return helpMessage; 24 | } 25 | 26 | public boolean isValid() { 27 | return valid; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/engine/command/BaseCommandManager.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | public abstract class BaseCommandManager implements CommandManager { 7 | 8 | private final Map commands = new HashMap<>(); 9 | private final CommandParser parser = createCommandParser(); 10 | private final CommandExceptionHandler exceptionHandler = createExceptionHandler(); 11 | 12 | protected abstract CommandParser createCommandParser(); 13 | 14 | protected abstract CommandExceptionHandler createExceptionHandler(); 15 | 16 | @Override 17 | public void registerCommand(Command command) { 18 | if (hasCommand(command.getName())) 19 | throw new RuntimeException("Command \"" + command.getName() + "\" already exists"); 20 | commands.put(command.getName().toLowerCase(), command); 21 | } 22 | 23 | @Override 24 | public Collection registeredCommands() { 25 | return commands.values(); 26 | } 27 | 28 | @Override 29 | public Optional getCommand(String name) { 30 | return Optional.ofNullable(commands.get(name.toLowerCase())); 31 | } 32 | 33 | @Override 34 | public boolean hasCommand(String name) { 35 | return commands.containsKey(name.toLowerCase()); 36 | } 37 | 38 | @Override 39 | public void execute(CommandSender sender, String command) { 40 | CommandParser.Result parsedCommand = parser.parse(command); 41 | execute(sender, parsedCommand.getName(), parsedCommand.getArgs()); 42 | } 43 | 44 | @Override 45 | public void execute(CommandSender sender, String name, String... args) { 46 | Command command = commands.get(name.toLowerCase()); 47 | if (command == null) { 48 | sender.sendCommandFailure(new CommandFailure(CommandFailure.Type.COMMAND_NOT_FOUND, sender, name, args)); 49 | return; 50 | } 51 | try { 52 | command.execute(sender, args != null ? args : new String[0]); 53 | } catch (Exception e) { 54 | exceptionHandler.handleOnExecuting(e); 55 | } 56 | } 57 | 58 | @Override 59 | public List complete(CommandSender sender, String command) { 60 | CommandParser.Result result = parser.parse(command); 61 | return complete(sender, result.getName(), result.getArgs()); 62 | } 63 | 64 | @Override 65 | public List complete(CommandSender sender, String commandName, String... args) { 66 | Command command = commands.get(commandName.toLowerCase()); 67 | if (command == null) { 68 | return commands.keySet() 69 | .stream() 70 | .filter(name -> name.startsWith(commandName)) 71 | .collect(Collectors.toList()); 72 | } 73 | 74 | if (args == null || args.length == 0) { 75 | return List.of(); 76 | } 77 | 78 | try { 79 | return command.suggest(sender, args); 80 | } catch (Exception e) { 81 | exceptionHandler.handleOnSuggesting(e); 82 | return List.of(); 83 | } 84 | } 85 | 86 | @Override 87 | public List getTips(CommandSender sender, String command) { 88 | CommandParser.Result result = parser.parse(command); 89 | return getTips(sender, result.getName(), result.getArgs()); 90 | } 91 | 92 | @Override 93 | public List getTips(CommandSender sender, String name, String... args) { 94 | if (name == null || name.isEmpty()) 95 | return List.of(); 96 | Command command = commands.get(name.toLowerCase()); 97 | if (command == null) 98 | return List.of(); 99 | try { 100 | return command.getTips(sender, args); 101 | } catch (Exception e) { 102 | exceptionHandler.handleOnGettingTips(e); 103 | return List.of(); 104 | } 105 | } 106 | 107 | @Override 108 | public ArgumentCheckResult checkLastArgument(CommandSender sender, String command) { 109 | CommandParser.Result result = this.parser.parse(command); 110 | return checkLastArgument(sender, result.getName(), result.getArgs()); 111 | } 112 | 113 | @Override 114 | public ArgumentCheckResult checkLastArgument(CommandSender sender, String name, String... args) { 115 | Command command = commands.get(name.toLowerCase()); 116 | if (command == null) 117 | return ArgumentCheckResult.Error("/" + name + " command not found"); // TODO: L10n 118 | try { 119 | return command.checkLastArgument(sender, args); 120 | } catch (Exception e) { 121 | exceptionHandler.handleOnCheckingArgument(e); 122 | return ArgumentCheckResult.Error("Caught an exception when check last argument"); // TODO: L10n 123 | } 124 | } 125 | 126 | @Override 127 | public void unregisterCommand(String name) { 128 | commands.remove(name); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/engine/command/Command.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public abstract class Command { 7 | 8 | private final String name; 9 | private String description; 10 | private String helpMessage; 11 | 12 | public Command(String name) { 13 | this(name, "/" + name); 14 | } 15 | 16 | public Command(String name, String description) { 17 | this(name, description, ""); 18 | } 19 | 20 | public Command(String name, String description, String helpMessage) { 21 | this.name = name; 22 | this.description = description; 23 | this.helpMessage = helpMessage; 24 | } 25 | 26 | public abstract void execute(CommandSender sender, String[] args); 27 | 28 | public abstract List suggest(CommandSender sender, String[] args); 29 | 30 | public abstract List getTips(CommandSender sender,String[] args); 31 | 32 | public abstract ArgumentCheckResult checkLastArgument(CommandSender sender, String[] args); 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public String getDescription() { 39 | return description; 40 | } 41 | 42 | public void setDescription(String description) { 43 | this.description = description; 44 | } 45 | 46 | public String getHelpMessage() { 47 | return helpMessage; 48 | } 49 | 50 | public void setHelpMessage(String helpMessage) { 51 | this.helpMessage = helpMessage; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | Command command = (Command) o; 59 | return Objects.equals(name, command.name) && 60 | description.equals(command.description) && 61 | helpMessage.equals(command.helpMessage); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(name, description, helpMessage); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/engine/command/CommandExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | public interface CommandExceptionHandler { 4 | void handleOnExecuting(Exception e); 5 | 6 | void handleOnSuggesting(Exception e); 7 | 8 | void handleOnGettingTips(Exception e); 9 | 10 | void handleOnCheckingArgument(Exception e); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/engine/command/CommandFailure.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | import java.util.Arrays; 4 | 5 | public final class CommandFailure { 6 | 7 | private final Type type; 8 | private final CommandSender sender; 9 | private final String commandName; 10 | private final Command command; 11 | private final String[] args; 12 | private final Object message; 13 | 14 | public enum Type { 15 | COMMAND_NOT_FOUND, 16 | COMMAND_WRONG_USAGE, 17 | COMMAND_WRONG_SENDER, 18 | COMMAND_ILLEGAL_ARGUMENT, 19 | PERMISSION_NOT_ENOUGH, 20 | CUSTOM 21 | } 22 | 23 | public CommandFailure(Type type, CommandSender sender, Command command, String[] args) { 24 | this(type, sender, command, args, null); 25 | } 26 | 27 | public CommandFailure(Type type, CommandSender sender, Command command, String[] args, Object message) { 28 | this.type = type; 29 | this.sender = sender; 30 | this.commandName = command.getName(); 31 | this.command = command; 32 | this.args = args; 33 | this.message = message; 34 | } 35 | 36 | public CommandFailure(Type type, CommandSender sender, String commandName, String[] args) { 37 | this(type, sender, commandName, args, null); 38 | } 39 | 40 | public CommandFailure(Type type, CommandSender sender, String commandName, String[] args, Object message) { 41 | this.type = type; 42 | this.sender = sender; 43 | this.commandName = commandName; 44 | this.command = null; 45 | this.args = args; 46 | this.message = message; 47 | } 48 | 49 | public Type getType() { 50 | return type; 51 | } 52 | 53 | public CommandSender getSender() { 54 | return sender; 55 | } 56 | 57 | public String getCommandName() { 58 | return commandName; 59 | } 60 | 61 | public Command getCommand() { 62 | return command; 63 | } 64 | 65 | public String[] getArgs() { 66 | return args; 67 | } 68 | 69 | public Object getMessage() { 70 | return message; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "CommandException{" + 76 | "type=" + type + 77 | ", sender=" + sender + 78 | ", commandName='" + commandName + '\'' + 79 | ", commandInstance=" + command + 80 | ", args=" + Arrays.toString(args) + 81 | ", message=" + message + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/engine/command/CommandManager.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | public interface CommandManager { 8 | 9 | void registerCommand(Command command); 10 | 11 | void unregisterCommand(String name); 12 | 13 | Collection registeredCommands(); 14 | 15 | Optional getCommand(String name); 16 | 17 | boolean hasCommand(String name); 18 | 19 | void execute(CommandSender sender, String command); 20 | 21 | void execute(CommandSender sender, String name, String... args); 22 | 23 | List complete(CommandSender sender, String command); 24 | 25 | List complete(CommandSender sender, String name, String... args); 26 | 27 | List getTips(CommandSender sender, String command); 28 | 29 | List getTips(CommandSender sender, String name, String... args); 30 | 31 | ArgumentCheckResult checkLastArgument(CommandSender sender, String command); 32 | 33 | ArgumentCheckResult checkLastArgument(CommandSender sender, String name, String... args); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/engine/command/CommandParser.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | public interface CommandParser { 4 | 5 | Result parse(String command); 6 | 7 | final class Result { 8 | private final String name; 9 | private final String[] args; 10 | 11 | public Result(String name, String[] args) { 12 | this.name = name; 13 | this.args = args; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public String[] getArgs() { 21 | return args; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/engine/command/CommandSender.java: -------------------------------------------------------------------------------- 1 | package engine.command; 2 | 3 | import engine.permission.Permissible; 4 | 5 | public interface CommandSender extends Permissible { 6 | 7 | String getSenderName(); 8 | 9 | void sendMessage(String message); 10 | 11 | void sendCommandFailure(CommandFailure failure); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/ArgumentHandler.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ArgumentHandler { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/ClassAnnotationCommand.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.Command; 4 | import engine.command.CommandManager; 5 | import engine.command.argument.ArgumentManager; 6 | import engine.command.suggestion.SuggesterManager; 7 | import engine.command.util.CommandNodeUtil; 8 | import engine.command.util.node.CommandNode; 9 | import engine.command.util.node.Nodeable; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.function.Consumer; 15 | 16 | public class ClassAnnotationCommand extends NodeAnnotationCommand { 17 | 18 | public ClassAnnotationCommand(String name, String description, String helpMessage) { 19 | super(name, description, helpMessage); 20 | } 21 | 22 | public static class ClassAnnotationBuilder extends NodeBuilder { 23 | 24 | private List commandHandlerList = new ArrayList<>(); 25 | 26 | private ClassAnnotationBuilder(CommandManager commandManager) { 27 | super(commandManager); 28 | } 29 | 30 | @Override 31 | public ClassAnnotationBuilder setArgumentManager(ArgumentManager argumentManager) { 32 | return (ClassAnnotationBuilder) super.setArgumentManager(argumentManager); 33 | } 34 | 35 | @Override 36 | public ClassAnnotationBuilder setSuggesterManager(SuggesterManager suggesterManager) { 37 | return (ClassAnnotationBuilder) super.setSuggesterManager(suggesterManager); 38 | } 39 | 40 | @Override 41 | public ClassAnnotationBuilder addProvider(Object object) { 42 | return (ClassAnnotationBuilder) super.addProvider(object); 43 | } 44 | 45 | public ClassAnnotationBuilder caseCommand(String commandName, String desc, String helpMessage, Runnable commandHandler) { 46 | commandHandlerList.add(new CommandHandlerWrapper(commandName, desc, helpMessage, commandHandler)); 47 | return this; 48 | } 49 | 50 | public ClassAnnotationBuilder caseCommand(String commandName, String desc, Runnable commandHandler) { 51 | return caseCommand(commandName, desc, "", commandHandler); 52 | } 53 | 54 | public ClassAnnotationBuilder caseCommand(String commandName, Runnable commandHandler) { 55 | return caseCommand(commandName, "", commandHandler); 56 | } 57 | 58 | protected List build() { 59 | List commands = new ArrayList<>(); 60 | CommandNodeUtil.ClassUtil classAnnotationUtil = CommandNodeUtil.getClassUtil(argumentManager, suggesterManager); 61 | providerList.forEach(object -> classAnnotationUtil.addProvider(object)); 62 | for (CommandHandlerWrapper wrapper : commandHandlerList) { 63 | Runnable commandHandler = wrapper.instance; 64 | String commandName = wrapper.commandName; 65 | String desc = wrapper.desc; 66 | String helpMessage = wrapper.help; 67 | 68 | 69 | List nodeList = new ArrayList<>(); 70 | 71 | Command command = commandManager.getCommand(commandName).orElse(null); 72 | 73 | if (command == null) 74 | command = new ClassAnnotationCommand(commandName, desc, helpMessage); 75 | 76 | if (!(command instanceof Nodeable)) 77 | throw new RuntimeException("命令: " + commandName + " 已注册,且不支持"); 78 | 79 | Nodeable nodeable = (Nodeable) command; 80 | 81 | nodeList.add(nodeable.getNode()); 82 | 83 | Class clazz = commandHandler.getClass(); 84 | 85 | Field[] fields = clazz.getFields(); 86 | 87 | for (Field field : fields) { 88 | if (field.getAnnotation(Ignore.class) != null) 89 | continue; 90 | List fieldNodes = classAnnotationUtil.parseField(field); 91 | ArrayList branches = new ArrayList<>(); 92 | for (CommandNode node : nodeList) { 93 | for (CommandNode child : fieldNodes) { 94 | CommandNode topCloneChild = CommandNodeUtil.getTopParent(child).clone(); 95 | node.addChild(topCloneChild); 96 | branches.addAll(CommandNodeUtil.getAllLeafNode(topCloneChild)); 97 | } 98 | } 99 | nodeList = branches; 100 | } 101 | 102 | Consumer> executeConsumer = objects -> { 103 | int ignored = 0; 104 | for (int i = 0; i < objects.size(); i++) { 105 | Field field = fields[i + ignored]; 106 | if (field.getAnnotation(Ignore.class) != null) { 107 | ignored++; 108 | i--; 109 | continue; 110 | } 111 | try { 112 | field.setAccessible(true); 113 | field.set(commandHandler, objects.get(i)); 114 | } catch (IllegalAccessException e) { 115 | e.printStackTrace(); 116 | } 117 | } 118 | commandHandler.run(); 119 | }; 120 | 121 | for (CommandNode node : nodeList) { 122 | node.setExecutor(executeConsumer); 123 | } 124 | 125 | try { 126 | Permission permission = commandHandler.getClass().getMethod("run", new Class[0]).getAnnotation(Permission.class); 127 | if (permission != null) { 128 | nodeList.forEach(node -> node.setPermissionExpression(permission.value())); 129 | } 130 | } catch (NoSuchMethodException e) { 131 | e.printStackTrace(); 132 | } 133 | 134 | commands.add(command); 135 | } 136 | return commands; 137 | } 138 | 139 | private class CommandHandlerWrapper { 140 | public final String commandName; 141 | public final String desc; 142 | public final String help; 143 | public final Runnable instance; 144 | 145 | public CommandHandlerWrapper(String commandName, String desc, String help, Runnable instance) { 146 | this.commandName = commandName; 147 | this.desc = desc; 148 | this.help = help; 149 | this.instance = instance; 150 | } 151 | } 152 | } 153 | 154 | public static ClassAnnotationBuilder getBuilder(CommandManager commandManager) { 155 | return new ClassAnnotationBuilder(commandManager); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Command.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Command { 11 | String value(); 12 | 13 | String desc() default ""; 14 | 15 | String helpMessage() default ""; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/CommandBuilderGetter.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.CommandManager; 4 | 5 | public abstract class CommandBuilderGetter { 6 | public abstract T getBuilder(CommandManager commandManager); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Ignore.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Ignore { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/MethodAnnotationCommand.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.Command; 4 | import engine.command.CommandManager; 5 | import engine.command.argument.ArgumentManager; 6 | import engine.command.suggestion.SuggesterManager; 7 | import engine.command.util.CommandNodeUtil; 8 | import engine.command.util.node.CommandNode; 9 | import engine.command.util.node.Nodeable; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Parameter; 14 | import java.util.*; 15 | 16 | public class MethodAnnotationCommand extends NodeAnnotationCommand implements Nodeable { 17 | 18 | private MethodAnnotationCommand(String name, String description, String helpMessage) { 19 | super(name, description, helpMessage); 20 | } 21 | 22 | public static AnnotationCommandBuilder getBuilder(CommandManager commandManager) { 23 | return new AnnotationCommandBuilder(commandManager); 24 | } 25 | 26 | public static class AnnotationCommandBuilder extends NodeBuilder { 27 | 28 | private Set commandHandler = new HashSet<>(); 29 | 30 | private AnnotationCommandBuilder(CommandManager commandManager) { 31 | super(commandManager); 32 | } 33 | 34 | public AnnotationCommandBuilder setArgumentManager(ArgumentManager argumentManager) { 35 | return (AnnotationCommandBuilder) super.setArgumentManager(argumentManager); 36 | } 37 | 38 | public AnnotationCommandBuilder setSuggesterManager(SuggesterManager suggesterManager) { 39 | return (AnnotationCommandBuilder) super.setSuggesterManager(suggesterManager); 40 | } 41 | 42 | public AnnotationCommandBuilder addProvider(Object object) { 43 | return (AnnotationCommandBuilder) super.addProvider(object); 44 | } 45 | 46 | public AnnotationCommandBuilder addCommandHandler(Object object) { 47 | commandHandler.add(object); 48 | return this; 49 | } 50 | 51 | protected List build() { 52 | return commandHandler.stream() 53 | .map(object -> parse(object)) 54 | .collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); 55 | } 56 | 57 | private List parse(Object o) { 58 | 59 | ArrayList list = new ArrayList(); 60 | 61 | CommandNodeUtil annotationUtil = CommandNodeUtil.getMethodUtil(argumentManager, suggesterManager); 62 | 63 | providerList.forEach(object -> annotationUtil.addProvider(object)); 64 | 65 | for (Method method : o.getClass().getMethods()) { 66 | 67 | engine.command.anno.Command commandAnnotation = method.getAnnotation(engine.command.anno.Command.class); 68 | 69 | if (commandAnnotation == null) { 70 | continue; 71 | } 72 | 73 | Command command = commandManager.getCommand(commandAnnotation.value()).orElse(null); 74 | 75 | if (command != null && !(command instanceof Nodeable)) { 76 | throw new RuntimeException("command already exist " + command.getName() + " and not Nodeable"); 77 | } 78 | if (command == null) { 79 | for (Command parsedCommand : list) { 80 | if (parsedCommand.getName().equals(commandAnnotation.value())) 81 | command = parsedCommand; 82 | } 83 | } 84 | 85 | Nodeable nodeable = (Nodeable) command; 86 | 87 | if (nodeable == null) { 88 | nodeable = new MethodAnnotationCommand(commandAnnotation.value(), commandAnnotation.desc(), commandAnnotation.helpMessage()); 89 | } 90 | 91 | 92 | List nodes = null; 93 | 94 | for (Parameter parameter : method.getParameters()) { 95 | List children = annotationUtil.parseParameter(parameter); 96 | if (nodes == null) { 97 | nodes = children; 98 | continue; 99 | } 100 | TreeSet topNodes = new TreeSet<>(); 101 | for (CommandNode child : children) { 102 | topNodes.add(CommandNodeUtil.getTopParent(child)); 103 | } 104 | List branchNodes = new ArrayList<>(); 105 | for (CommandNode parent : nodes) { 106 | for (CommandNode topNode : topNodes) { 107 | parent.addChild(topNode); 108 | branchNodes.addAll(CommandNodeUtil.getAllLeafNode(topNode)); 109 | } 110 | } 111 | nodes = branchNodes; 112 | } 113 | 114 | nodes.forEach(commandNode -> commandNode.setExecutor((objects -> { 115 | try { 116 | method.invoke(o, objects.toArray()); 117 | } catch (IllegalAccessException e) { 118 | e.printStackTrace(); 119 | } catch (InvocationTargetException e) { 120 | e.printStackTrace(); 121 | } 122 | }))); 123 | 124 | Permission permission = method.getAnnotation(Permission.class); 125 | if (permission != null) { 126 | nodes.forEach(commandNode -> 127 | commandNode.setPermissionExpression(permission.value())); 128 | } 129 | 130 | CommandNode mainNode = nodeable.getNode(); 131 | 132 | for (CommandNode node : nodes) { 133 | CommandNode clone = CommandNodeUtil.getTopParent(node).clone(); 134 | mainNode.addChild(clone); 135 | } 136 | 137 | list.add((Command) nodeable); 138 | } 139 | 140 | return list; 141 | } 142 | 143 | } 144 | } -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/NodeAnnotationCommand.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.Command; 4 | import engine.command.*; 5 | import engine.command.argument.ArgumentManager; 6 | import engine.command.argument.SimpleArgumentManager; 7 | import engine.command.suggestion.SimpleSuggesterManager; 8 | import engine.command.suggestion.SuggesterManager; 9 | import engine.command.util.CommandNodeUtil; 10 | import engine.command.util.StringArgs; 11 | import engine.command.util.SuggesterHelper; 12 | import engine.command.util.Type; 13 | import engine.command.util.context.ContextNode; 14 | import engine.command.util.context.LinkedContext; 15 | import engine.command.util.node.*; 16 | import engine.permission.Permissible; 17 | 18 | import java.util.*; 19 | import java.util.stream.Collectors; 20 | 21 | public class NodeAnnotationCommand extends Command implements Nodeable { 22 | 23 | private static final PermissionWrapper TRUE = new PermissionWrapper(null, "") { 24 | @Override 25 | public boolean hasPermission() { 26 | return true; 27 | } 28 | }; 29 | 30 | private static final PermissionWrapper FALSE = new PermissionWrapper(null, "") { 31 | @Override 32 | public boolean hasPermission() { 33 | return false; 34 | } 35 | }; 36 | 37 | protected final static ArgumentManager staticArgumentManage = new SimpleArgumentManager(); 38 | 39 | protected final static SuggesterManager staticSuggesterManager = new SimpleSuggesterManager(); 40 | 41 | public static final CommandBuilderGetter CLASS = new ClassBuilderGetter(); 42 | 43 | public static final CommandBuilderGetter METHOD = new MethodBuilderGetter(); 44 | 45 | private CommandNode node = new EmptyArgumentNode(); 46 | 47 | public NodeAnnotationCommand(String name, String description, String helpMessage) { 48 | super(name, description, helpMessage); 49 | } 50 | 51 | @Override 52 | public void execute(CommandSender sender, String[] args) { 53 | SimpleLinkedContext context = new SimpleLinkedContext(sender); 54 | if (args == null || args.length == 0) { 55 | if (node.canExecuteCommand()) { 56 | if (!hasPermission(sender, node.getPermissionExpression())) { 57 | permissionNotEnough(sender, args, node.getPermissionExpression()); 58 | return; 59 | } 60 | node.getExecutor().accept(List.of()); 61 | } else { 62 | Map.Entry parseResult = parseArgs(context, args); 63 | if (parseResult != null) { 64 | CommandNode resultNode = parseResult.getKey(); 65 | if (resultNode.canExecuteCommand()) { 66 | if (!hasPermission(sender, resultNode.getPermissionExpression())) { 67 | permissionNotEnough(sender, args, resultNode.getPermissionExpression()); 68 | return; 69 | } 70 | execute(parseResult.getValue(), resultNode); 71 | return; 72 | } 73 | } 74 | commandWrongUsage(sender, args); 75 | } 76 | } else { 77 | Map.Entry parseResult = parseArgs(context, args); 78 | CommandNode resultNode = parseResult.getKey(); 79 | if (CommandNodeUtil.getRequiredArgsSumFromParent2Child(resultNode) != args.length) { 80 | commandWrongUsage(sender, args); 81 | return; 82 | } 83 | if (!resultNode.canExecuteCommand()) { 84 | commandWrongUsage(sender, args); 85 | return; 86 | } 87 | if (!hasPermission(sender, resultNode.getPermissionExpression())) { 88 | permissionNotEnough(sender, args, resultNode.getPermissionExpression()); 89 | return; 90 | } 91 | execute(parseResult.getValue(), resultNode); 92 | } 93 | } 94 | 95 | private void execute(SimpleLinkedContext context, CommandNode node) { 96 | ContextNode contextNode = context.root.getNext(); 97 | while (contextNode != null) { 98 | contextNode.getOwner().collect(contextNode); 99 | contextNode = contextNode.getNext(); 100 | } 101 | node.getExecutor().accept(context.valueToArray()); 102 | } 103 | 104 | private void permissionNotEnough(CommandSender sender, String[] args, String requiredPermissions) { 105 | sender.sendCommandFailure( 106 | new CommandFailure(CommandFailure.Type.PERMISSION_NOT_ENOUGH, sender, this, args, requiredPermissions)); 107 | } 108 | 109 | private void commandWrongUsage(CommandSender sender, String[] args) { 110 | sender.sendCommandFailure( 111 | new CommandFailure(CommandFailure.Type.COMMAND_WRONG_USAGE, sender, this, args, null)); 112 | } 113 | 114 | private boolean hasPermission(Permissible permissible, String permissionExpression) { 115 | if (permissionExpression == null) 116 | return true; 117 | Stack permissionWrapperStack = new Stack<>(); 118 | Stack operatorStack = new Stack<>(); 119 | 120 | StringBuilder stringBuilder = new StringBuilder(); 121 | 122 | for (char c : permissionExpression.toCharArray()) { 123 | switch (c) { 124 | case '(': 125 | case '!': 126 | stringBuilder.delete(0, stringBuilder.length()); 127 | operatorStack.push(c); 128 | break; 129 | case '|': { 130 | String s = stringBuilder.toString(); 131 | if (!s.isBlank()) { 132 | permissionWrapperStack.push(new PermissionWrapper(permissible, s.trim())); 133 | } 134 | stringBuilder.delete(0, stringBuilder.length()); 135 | 136 | if (!operatorStack.isEmpty()) { 137 | char peek = operatorStack.peek().charValue(); 138 | if (peek == c || '(' == peek) { 139 | operatorStack.push(c); 140 | break; 141 | } 142 | while (!operatorStack.isEmpty() && peek != '(' && (peek == '|' || peek == '&' || peek == '!')) { 143 | computePermission(operatorStack.pop(), permissionWrapperStack); 144 | peek = operatorStack.peek(); 145 | } 146 | } 147 | 148 | operatorStack.push(c); 149 | break; 150 | } 151 | case '&': { 152 | String s = stringBuilder.toString(); 153 | if (!s.isBlank()) { 154 | permissionWrapperStack.push(new PermissionWrapper(permissible, s.trim())); 155 | } 156 | stringBuilder.delete(0, stringBuilder.length()); 157 | operatorStack.push(c); 158 | break; 159 | } 160 | case ')': { 161 | String s = stringBuilder.toString(); 162 | if (!s.isBlank()) { 163 | permissionWrapperStack.push(new PermissionWrapper(permissible, s.trim())); 164 | } 165 | stringBuilder.delete(0, stringBuilder.length()); 166 | 167 | char peek = operatorStack.peek().charValue(); 168 | while (peek != '(') { 169 | computePermission(operatorStack.pop(), permissionWrapperStack); 170 | peek = operatorStack.peek(); 171 | } 172 | operatorStack.pop(); 173 | break; 174 | } 175 | default: 176 | stringBuilder.append(c); 177 | } 178 | } 179 | 180 | String s = stringBuilder.toString(); 181 | if (!s.isBlank()) 182 | permissionWrapperStack.push(new PermissionWrapper(permissible, s.trim())); 183 | 184 | while (!operatorStack.isEmpty()) 185 | computePermission(operatorStack.pop(), permissionWrapperStack); 186 | 187 | return permissionWrapperStack.peek().hasPermission(); 188 | } 189 | 190 | private void computePermission(char operator, Stack stack) { 191 | switch (operator) { 192 | case '!': 193 | stack.push(stack.pop().hasPermission() ? FALSE : TRUE); 194 | break; 195 | case '|': { 196 | PermissionWrapper pop2 = stack.pop(); 197 | PermissionWrapper pop1 = stack.pop(); 198 | stack.push(pop1.hasPermission() || pop2.hasPermission() ? TRUE : FALSE); 199 | break; 200 | } 201 | case '&': { 202 | PermissionWrapper pop2 = stack.pop(); 203 | PermissionWrapper pop1 = stack.pop(); 204 | stack.push(pop1.hasPermission() && pop2.hasPermission() ? TRUE : FALSE); 205 | break; 206 | } 207 | default: 208 | throw new IllegalStateException("operator: " + operator); 209 | } 210 | } 211 | 212 | private static class PermissionWrapper { 213 | Permissible permissible; 214 | 215 | String permission; 216 | 217 | public PermissionWrapper(Permissible permissible, String permission) { 218 | this.permissible = permissible; 219 | this.permission = permission; 220 | } 221 | 222 | public boolean hasPermission() { 223 | return permissible.hasPermission(permission); 224 | } 225 | 226 | } 227 | 228 | private Map.Entry parseArgs(SimpleLinkedContext context, String[] args) { 229 | 230 | StringArgs stringArgs = new StringArgs(args); 231 | 232 | HashMap results = new HashMap<>(); 233 | //解析递归从根Node开始 234 | parse(node, context, stringArgs, results); 235 | List> executableNodes = results.entrySet().stream().filter(entry -> entry.getKey().canExecuteCommand()).collect(Collectors.toList()); 236 | 237 | //筛选最佳结果 238 | if (!executableNodes.isEmpty()) { 239 | return filterResult(executableNodes); 240 | } else { 241 | return filterResult(results.entrySet()); 242 | } 243 | } 244 | 245 | private Map.Entry filterResult(Collection> results) { 246 | Map.Entry entry = null; 247 | CommandNode bestResult = null; 248 | int bestNodeDepth = 0; 249 | for (Map.Entry result : results) { 250 | int depth = CommandNodeUtil.getDepth(result.getKey()); 251 | if (bestNodeCheck(bestResult, bestNodeDepth, depth)) { 252 | bestResult = result.getKey(); 253 | bestNodeDepth = depth; 254 | entry = result; 255 | } 256 | } 257 | return entry; 258 | } 259 | 260 | private boolean bestNodeCheck(CommandNode bestNode, int bestNodeDepth, int checkNodeDepth) { 261 | if (bestNode == null) 262 | return true; 263 | return checkNodeDepth > bestNodeDepth; 264 | } 265 | 266 | private void parse(CommandNode node, SimpleLinkedContext context, StringArgs stringArgs, HashMap result) { 267 | //如果当前的Args指针+Node需要的指针小于等于Args的长度 268 | if (stringArgs.getIndex() + node.getRequiredArgsNum() <= stringArgs.getLength()) { 269 | ParseResult parseResult = node.parse(context, stringArgs); 270 | if (parseResult.isFail()) { 271 | if (!result.containsKey(node.getParent())) 272 | result.put(node.getParent(), (SimpleLinkedContext) context.clone()); 273 | return; 274 | } else if (parseResult.getValue() != null) { 275 | context.add(node, parseResult.getValue()); 276 | } 277 | //所有叶子节点都是可执行节点,如果不是那肯定是构建树时出了问题 278 | //假如Node能执行命令,则必然是叶子节点,直接加入待选结果 279 | if (node.canExecuteCommand()) { 280 | result.put(node, (SimpleLinkedContext) context.clone()); 281 | } else { 282 | //保存当前指针 283 | int index = stringArgs.getIndex(); 284 | for (CommandNode child : node.getChildren()) { 285 | //子节点递归解析 286 | parse(child, context, stringArgs, result); 287 | //重设指针 288 | stringArgs.setIndex(index); 289 | ContextNode lastNode = context.getLastNode(); 290 | 291 | while (lastNode.getOwner() != null && !lastNode.getOwner().equals(node)) { 292 | lastNode = lastNode.getPre(); 293 | } 294 | lastNode.setNext(null); 295 | } 296 | } 297 | } else { 298 | //假如不满足条件,则直接将父Node加入待选结果 299 | if (!result.containsKey(node.getParent())) 300 | result.put(node.getParent(), (SimpleLinkedContext) context.clone()); 301 | } 302 | } 303 | 304 | @Override 305 | public List suggest(CommandSender sender, String[] args) { 306 | StringArgs stringArgs = new StringArgs(Arrays.copyOfRange(args, 0, args.length - 1)); 307 | HashMap results = new HashMap<>(); 308 | parse(node, new SimpleLinkedContext(sender), stringArgs, results); 309 | HashSet suggests = new HashSet<>(); 310 | for (CommandNode node : results.keySet().stream().filter(node1 -> leafNodePermissionEnough(sender, node1) && CommandNodeUtil.getRequiredArgsSumFromParent2Child(node1) == args.length - 1).collect(Collectors.toList())) { 311 | for (CommandNode child : node.getChildren()) { 312 | if (child.getSuggester() != null) { 313 | suggests.addAll(child.getSuggester().suggest(sender, getName(), args)); 314 | } 315 | } 316 | } 317 | return SuggesterHelper.filterStartWith(new ArrayList<>(suggests), args[args.length - 1]); 318 | } 319 | 320 | private boolean leafNodePermissionEnough(CommandSender sender, CommandNode node) { 321 | Collection allLeafNode = CommandNodeUtil.getAllLeafNode(node); 322 | for (CommandNode commandNode : allLeafNode) { 323 | if (commandNode.getPermissionExpression() == null || sender.hasPermission(commandNode.getPermissionExpression())) 324 | return true; 325 | } 326 | return false; 327 | } 328 | 329 | @Override 330 | public List getTips(CommandSender sender, String[] args) { 331 | StringArgs stringArgs = new StringArgs(Arrays.copyOfRange(args, 0, args.length - 1)); 332 | HashMap results = new HashMap<>(); 333 | parse(node, new SimpleLinkedContext(sender), stringArgs, results); 334 | 335 | CommandNode nearestNode = null; 336 | int nearestDepth = Integer.MAX_VALUE; 337 | 338 | for (CommandNode result : results.keySet()) { 339 | for (CommandNode child : result.getChildren()) { 340 | int depth = CommandNodeUtil.getDepth(child); 341 | if (depth < nearestDepth) { 342 | nearestNode = result; 343 | nearestDepth = depth; 344 | } 345 | } 346 | } 347 | 348 | if (nearestNode == null || nearestNode.getChildren().isEmpty()) { 349 | return Collections.EMPTY_LIST; 350 | } 351 | List nodes = CommandNodeUtil.getShortestPath(nearestNode); 352 | List tips = nodes.stream() 353 | .filter(node1 -> !(node1 instanceof SenderNode)).map(node -> node.hasTip() ? node.getTip() : "") 354 | .collect(Collectors.toList()); 355 | return tips; 356 | } 357 | 358 | @Override 359 | public ArgumentCheckResult checkLastArgument(CommandSender sender, String[] args) { 360 | if (args == null || args.length == 0) { 361 | return ArgumentCheckResult.Valid(); 362 | } 363 | StringArgs stringArgs = new StringArgs(Arrays.copyOfRange(args, 0, args.length - 1)); 364 | HashMap results = new HashMap<>(); 365 | parse(node, new SimpleLinkedContext(sender), stringArgs, results); 366 | 367 | StringArgs args1 = new StringArgs(args); 368 | for (CommandNode node : results.keySet().stream().filter(node1 -> leafNodePermissionEnough(sender, node1)).collect(Collectors.toList())) { 369 | for (CommandNode child : node.getChildren()) { 370 | args1.setIndex(args.length - 1); 371 | if (child.parse(results.get(node), args1).isSuccess()) 372 | return ArgumentCheckResult.Valid(); 373 | } 374 | } 375 | return ArgumentCheckResult.Error("/" + this.getName() + " " + formatArgs(args) + " <- wrong"); 376 | } 377 | 378 | private String formatArgs(String[] args) { 379 | return Arrays.stream(args).map(s -> s + " ").collect(Collectors.joining()); 380 | } 381 | 382 | @Override 383 | public CommandNode getNode() { 384 | return node; 385 | } 386 | 387 | private static class ClassBuilderGetter extends CommandBuilderGetter { 388 | 389 | public ClassAnnotationCommand.ClassAnnotationBuilder getBuilder(CommandManager commandManager) { 390 | return ClassAnnotationCommand.getBuilder(commandManager); 391 | } 392 | } 393 | 394 | private static class MethodBuilderGetter extends CommandBuilderGetter { 395 | 396 | public MethodAnnotationCommand.AnnotationCommandBuilder getBuilder(CommandManager commandManager) { 397 | return MethodAnnotationCommand.getBuilder(commandManager); 398 | } 399 | } 400 | 401 | private static class SimpleLinkedContext implements LinkedContext { 402 | 403 | private final CommandSender sender; 404 | private final SimpleContextNode root = new SimpleContextNode(null, null); 405 | 406 | public SimpleLinkedContext(CommandSender sender) { 407 | this.sender = sender; 408 | } 409 | 410 | @Override 411 | public CommandSender getSender() { 412 | return sender; 413 | } 414 | 415 | @Override 416 | public int first(Type type) { 417 | for (int i = 0; i < size(); i++) { 418 | if (getTypeAt(i).is(type)) 419 | return i; 420 | } 421 | return -1; 422 | } 423 | 424 | @Override 425 | public int last(Type type) { 426 | for (int i = size() - 1; i >= 0; i--) { 427 | if (getTypeAt(i).is(type)) 428 | return i; 429 | } 430 | return -1; 431 | } 432 | 433 | @Override 434 | public Type getTypeAt(int index) { 435 | return Type.of(getValueAt(index).getClass()); 436 | } 437 | 438 | @Override 439 | public Object getValueAt(int index) { 440 | return getNodeAt(index).getValue(); 441 | } 442 | 443 | private ContextNode getNodeAt(int index) { 444 | ContextNode node = root.getNext(); 445 | while (index != 0) { 446 | index--; 447 | if (node == null) { 448 | throw new IllegalStateException(); 449 | } 450 | node = node.getNext(); 451 | } 452 | return node; 453 | } 454 | 455 | @Override 456 | public int size() { 457 | int i = 0; 458 | ContextNode node = root.getNext(); 459 | if (node != null) { 460 | i++; 461 | node = node.getNext(); 462 | } 463 | return i; 464 | } 465 | 466 | private ContextNode getLastNode() { 467 | ContextNode node = root; 468 | while (node.getNext() != null) { 469 | node = node.getNext(); 470 | } 471 | return node; 472 | } 473 | 474 | @Override 475 | public void add(CommandNode handler, Object object) { 476 | setNext(getLastNode(), new SimpleContextNode(handler, object)); 477 | } 478 | 479 | private void setNext(ContextNode node, ContextNode next) { 480 | node.setNext(next); 481 | next.setPre(node); 482 | } 483 | 484 | @Override 485 | public void add(CommandNode handler, int index, Object object) { 486 | ContextNode node = new SimpleContextNode(handler, object); 487 | ContextNode nodeAt = getNodeAt(index); 488 | if (nodeAt.getNext() != null) { 489 | ContextNode next = nodeAt.getNext(); 490 | setNext(nodeAt, node); 491 | setNext(node, next); 492 | return; 493 | } 494 | setNext(nodeAt, node); 495 | } 496 | 497 | @Override 498 | public void remove(int index) { 499 | ContextNode nodeAt = getNodeAt(index); 500 | ContextNode pre = nodeAt.getPre(); 501 | if (nodeAt.getNext() != null) { 502 | ContextNode next = nodeAt.getNext(); 503 | setNext(pre, next); 504 | } else { 505 | pre.setNext(null); 506 | } 507 | } 508 | 509 | @Override 510 | public void removeLast() { 511 | ContextNode pre = getLastNode().getPre(); 512 | pre.setNext(null); 513 | } 514 | 515 | @Override 516 | public List valueToArray() { 517 | List list = new ArrayList<>(); 518 | ContextNode node = root.getNext(); 519 | while (node != null) { 520 | list.add(node.getValue()); 521 | node = node.getNext(); 522 | } 523 | return list; 524 | } 525 | 526 | @Override 527 | public LinkedContext clone() { 528 | SimpleLinkedContext simpleLinkedContext = new SimpleLinkedContext(sender); 529 | if (this.root.getNext() != null) 530 | simpleLinkedContext.root.setNext(this.root.getNext().clone()); 531 | return simpleLinkedContext; 532 | } 533 | 534 | private static class SimpleContextNode implements ContextNode { 535 | 536 | private CommandNode owner; 537 | private Object value; 538 | 539 | private ContextNode next; 540 | private ContextNode pre; 541 | 542 | public SimpleContextNode(CommandNode owner, Object value) { 543 | this.owner = owner; 544 | this.value = value; 545 | } 546 | 547 | @Override 548 | public Object getValue() { 549 | return value; 550 | } 551 | 552 | @Override 553 | public void setNext(ContextNode node) { 554 | this.next = node; 555 | } 556 | 557 | @Override 558 | public void setPre(ContextNode node) { 559 | this.pre = node; 560 | } 561 | 562 | @Override 563 | public ContextNode getNext() { 564 | return next; 565 | } 566 | 567 | @Override 568 | public ContextNode getPre() { 569 | return pre; 570 | } 571 | 572 | @Override 573 | public CommandNode getOwner() { 574 | return owner; 575 | } 576 | 577 | @Override 578 | public void setValue(Object value) { 579 | this.value = value; 580 | } 581 | 582 | @Override 583 | public String toString() { 584 | return "SimpleContextNode{" + 585 | "owner=" + owner + 586 | ", value=" + value + 587 | '}'; 588 | } 589 | 590 | public SimpleContextNode clone() { 591 | SimpleContextNode simpleContextNode = new SimpleContextNode(owner, value); 592 | if (this.next != null) { 593 | ContextNode cloneNext = this.next.clone(); 594 | simpleContextNode.setNext(cloneNext); 595 | cloneNext.setPre(simpleContextNode); 596 | } 597 | return simpleContextNode; 598 | } 599 | } 600 | } 601 | 602 | } 603 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/NodeBuilder.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.CommandManager; 4 | import engine.command.argument.ArgumentManager; 5 | import engine.command.suggestion.SuggesterManager; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static engine.command.anno.NodeAnnotationCommand.staticArgumentManage; 11 | import static engine.command.anno.NodeAnnotationCommand.staticSuggesterManager; 12 | 13 | public abstract class NodeBuilder { 14 | 15 | protected ArgumentManager argumentManager = staticArgumentManage; 16 | 17 | protected CommandManager commandManager; 18 | 19 | protected SuggesterManager suggesterManager = staticSuggesterManager; 20 | 21 | protected List providerList = new ArrayList<>(); 22 | 23 | protected NodeBuilder(CommandManager commandManager) { 24 | this.commandManager = commandManager; 25 | } 26 | 27 | public NodeBuilder setArgumentManager(ArgumentManager argumentManager) { 28 | this.argumentManager = argumentManager; 29 | return this; 30 | } 31 | 32 | public NodeBuilder setSuggesterManager(SuggesterManager suggesterManager) { 33 | this.suggesterManager = suggesterManager; 34 | return this; 35 | } 36 | 37 | public NodeBuilder addProvider(Object object){ 38 | this.providerList.add(object); 39 | return this; 40 | } 41 | 42 | protected abstract List build(); 43 | 44 | public void register() { 45 | build().stream() 46 | .filter(command -> !commandManager.hasCommand(command.getName())) 47 | .forEach(command -> commandManager.registerCommand(command)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/NodeCommandBuilder.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.CommandManager; 4 | import engine.command.argument.ArgumentManager; 5 | import engine.command.suggestion.SuggesterManager; 6 | 7 | import java.util.List; 8 | 9 | import static engine.command.anno.NodeAnnotationCommand.staticArgumentManage; 10 | import static engine.command.anno.NodeAnnotationCommand.staticSuggesterManager; 11 | 12 | public abstract class NodeCommandBuilder { 13 | 14 | private CommandManager commandManager; 15 | 16 | private ArgumentManager argumentManager = staticArgumentManage; 17 | 18 | private SuggesterManager suggesterManager = staticSuggesterManager; 19 | 20 | public NodeCommandBuilder setArgumentManager(ArgumentManager argumentManager) { 21 | this.argumentManager = argumentManager; 22 | return this; 23 | } 24 | 25 | public NodeCommandBuilder setSuggesterManager(SuggesterManager suggesterManager) { 26 | this.suggesterManager = suggesterManager; 27 | return this; 28 | } 29 | 30 | public NodeCommandBuilder addProvider(Object provider){ 31 | 32 | return this; 33 | } 34 | 35 | protected abstract List build(); 36 | 37 | public void register() { 38 | build().stream() 39 | .filter(command -> !commandManager.hasCommand(command.getName())) 40 | .forEach(command -> commandManager.registerCommand(command)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Permission.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Permission { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Provide.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Provide { 11 | String name() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Required.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Required { 11 | 12 | String value() default ""; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Sender.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import engine.command.CommandSender; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Indicates that a parameter with annotations which is extends CommandSender. 12 | */ 13 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface Sender { 16 | 17 | Class[] value() default {}; 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Suggester.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Suggester { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/Tip.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Tip { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/anno/UseProvide.java: -------------------------------------------------------------------------------- 1 | package engine.command.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface UseProvide { 11 | String[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/Argument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument; 2 | 3 | import engine.command.suggestion.Suggester; 4 | import engine.command.util.context.Context; 5 | 6 | import java.util.Optional; 7 | 8 | public abstract class Argument { 9 | 10 | public abstract String getName(); 11 | 12 | public abstract Class responsibleClass(); 13 | 14 | public abstract Optional parse(Context context, String arg); 15 | 16 | public abstract Suggester getSuggester(); 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/ArgumentManager.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument; 2 | 3 | public interface ArgumentManager { 4 | 5 | void setClassDefaultArgument(Argument argument); 6 | 7 | void appendArgument(Argument argument); 8 | 9 | void removeArgument(Argument argument); 10 | 11 | void removeArgument(String argumentName); 12 | 13 | Argument getArgument(Class clazz); 14 | 15 | Argument getArgument(String argumentName); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/FunctionArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument; 2 | 3 | import engine.command.suggestion.Suggester; 4 | import engine.command.util.context.Context; 5 | 6 | import java.util.Optional; 7 | import java.util.function.BiFunction; 8 | 9 | public class FunctionArgument extends SimpleArgument { 10 | 11 | private BiFunction> parseFunction; 12 | private Suggester suggester; 13 | 14 | private FunctionArgument(Class responsibleClass, String argumentName) { 15 | super(responsibleClass, argumentName); 16 | } 17 | 18 | @Override 19 | public Optional parse(Context context, String arg) { 20 | return Optional.empty(); 21 | } 22 | 23 | @Override 24 | public Suggester getSuggester() { 25 | return suggester; 26 | } 27 | 28 | public static class FunctionArgumentBuilder { 29 | 30 | private FunctionArgument argument; 31 | 32 | private FunctionArgumentBuilder(FunctionArgument argument) { 33 | this.argument = argument; 34 | } 35 | 36 | public FunctionArgumentBuilder setParse(BiFunction> function) { 37 | argument.parseFunction = function; 38 | return this; 39 | } 40 | 41 | public FunctionArgumentBuilder setSuggester(Suggester suggester) { 42 | argument.suggester = suggester; 43 | return this; 44 | } 45 | 46 | public FunctionArgument get() { 47 | return argument; 48 | } 49 | 50 | } 51 | 52 | public static FunctionArgumentBuilder getBuilder(Class clazz, String name) { 53 | return new FunctionArgumentBuilder<>(new FunctionArgument<>(clazz, name)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/SimpleArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument; 2 | 3 | import java.util.Objects; 4 | 5 | public abstract class SimpleArgument extends Argument { 6 | 7 | private String argumentName; 8 | 9 | private Class responsibleClass; 10 | 11 | public SimpleArgument(Class responsibleClass, String argumentName) { 12 | this.argumentName = argumentName; 13 | this.responsibleClass = responsibleClass; 14 | } 15 | 16 | @Override 17 | public String getName() { 18 | return argumentName; 19 | } 20 | 21 | @Override 22 | public Class responsibleClass() { 23 | return responsibleClass; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "SimpleArgument{" + 29 | "argumentName='" + argumentName + '\'' + 30 | ", responsibleClass=" + responsibleClass + 31 | '}'; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | SimpleArgument that = (SimpleArgument) o; 39 | return Objects.equals(argumentName, that.argumentName) && 40 | Objects.equals(responsibleClass, that.responsibleClass); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(argumentName, responsibleClass); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/SimpleArgumentManager.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument; 2 | 3 | import engine.command.argument.base.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | 8 | public class SimpleArgumentManager implements ArgumentManager { 9 | 10 | private static List baseArguments = List.of(new IntegerArgument(), new StringArgument(), new BooleanArgument(), new FloatArgument(), new DoubleArgument(), new LongArgument(), new ShortArgument()); 11 | 12 | private HashMap argumentByClass = new HashMap<>(); 13 | private HashMap argumentByName = new HashMap<>(); 14 | 15 | public SimpleArgumentManager() { 16 | baseArguments.stream().forEach(this::setClassDefaultArgument); 17 | } 18 | 19 | @Override 20 | public void setClassDefaultArgument(Argument argument) { 21 | argumentByClass.put(argument.responsibleClass(), argument); 22 | } 23 | 24 | @Override 25 | public void appendArgument(Argument argument) { 26 | if (argumentByName.containsKey(argument.getName())) { 27 | throw new RuntimeException("argument already exist"); 28 | } 29 | argumentByName.put(argument.getName(), argument); 30 | } 31 | 32 | public void appendArgumentAndSetDefaultIfNotExist(Argument argument) { 33 | appendArgument(argument); 34 | if (!argumentByClass.containsKey(argument.responsibleClass())) { 35 | setClassDefaultArgument(argument); 36 | } 37 | } 38 | 39 | @Override 40 | public void removeArgument(Argument argument) { 41 | removeArgument(argument.getName()); 42 | } 43 | 44 | @Override 45 | public void removeArgument(String argumentName) { 46 | argumentByName.remove(argumentName); 47 | } 48 | 49 | @Override 50 | public Argument getArgument(Class clazz) { 51 | return argumentByClass.get(clazz); 52 | } 53 | 54 | @Override 55 | public Argument getArgument(String argumentName) { 56 | return argumentByName.get(argumentName); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/BooleanArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.stream.Collectors; 10 | 11 | public class BooleanArgument extends Argument { 12 | @Override 13 | public String getName() { 14 | return "Boolean"; 15 | } 16 | 17 | @Override 18 | public Class responsibleClass() { 19 | return Boolean.class; 20 | } 21 | 22 | /** 23 | * arg must be 'true' or 'false'. 24 | * do not use Boolean.valueOf or Boolean.parseBoolean,cause: 25 | * if arg is normal string it will return false. 26 | * @param arg 27 | * @return 28 | */ 29 | @Override 30 | public Optional parse(Context context, String arg) { 31 | if(arg.equals("true")){ 32 | return Optional.of(true); 33 | } 34 | else if(arg.equals("false")){ 35 | return Optional.of(false); 36 | } 37 | return Optional.empty(); 38 | } 39 | 40 | @Override 41 | public Suggester getSuggester() { 42 | return (sender, command, args) -> { 43 | List completeSet = List.of("true", "false"); 44 | if (args != null && !args[args.length - 1].isEmpty()){ 45 | return completeSet.stream().filter(completeName -> completeName.startsWith(args[args.length - 1])).collect(Collectors.toList()); 46 | } 47 | return completeSet; 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/DoubleArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class DoubleArgument extends Argument { 11 | @Override 12 | public String getName() { 13 | return "Double"; 14 | } 15 | 16 | @Override 17 | public Class responsibleClass() { 18 | return Double.class; 19 | } 20 | 21 | @Override 22 | public Optional parse(Context context, String arg) { 23 | try { 24 | return Optional.of(Double.valueOf(arg)); 25 | }catch (Exception e){ 26 | return Optional.empty(); 27 | } 28 | } 29 | 30 | @Override 31 | public Suggester getSuggester() { 32 | return (sender, command, args) -> List.of("[double]"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/FloatArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class FloatArgument extends Argument { 11 | @Override 12 | public String getName() { 13 | return "Float"; 14 | } 15 | 16 | @Override 17 | public Class responsibleClass() { 18 | return Float.class; 19 | } 20 | 21 | @Override 22 | public Optional parse(Context context, String arg) { 23 | try { 24 | return Optional.of(Float.valueOf(arg)); 25 | }catch (Exception e){ 26 | return Optional.empty(); 27 | } 28 | } 29 | 30 | @Override 31 | public Suggester getSuggester() { 32 | return (sender, command, args) -> List.of("[float]"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/IntegerArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | 4 | import engine.command.argument.SimpleArgument; 5 | import engine.command.suggestion.Suggester; 6 | import engine.command.util.context.Context; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class IntegerArgument extends SimpleArgument { 12 | 13 | public IntegerArgument() { 14 | super(Integer.class,"Integer"); 15 | } 16 | 17 | @Override 18 | public Optional parse(Context context, String arg) { 19 | try { 20 | return Optional.of(Integer.valueOf(arg)); 21 | }catch (Exception e){ 22 | return Optional.empty(); 23 | } 24 | } 25 | 26 | @Override 27 | public Suggester getSuggester() { 28 | return (sender, command, args) -> List.of("[num]"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/LongArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.SimpleArgument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class LongArgument extends SimpleArgument { 11 | public LongArgument() { 12 | super(Long.class,"Long"); 13 | } 14 | 15 | @Override 16 | public Optional parse(Context context, String arg) { 17 | try { 18 | return Optional.of(Long.valueOf(arg)); 19 | }catch (Exception e){ 20 | return Optional.empty(); 21 | } 22 | } 23 | 24 | @Override 25 | public Suggester getSuggester() { 26 | return (sender, command, args) -> List.of("[num]"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/ShortArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.SimpleArgument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class ShortArgument extends SimpleArgument { 11 | public ShortArgument() { 12 | super(Short.class,"Short"); 13 | } 14 | 15 | @Override 16 | public Optional parse(Context context, String arg) { 17 | try { 18 | return Optional.of(Short.valueOf(arg)); 19 | }catch (Exception e){ 20 | return Optional.empty(); 21 | } 22 | } 23 | 24 | @Override 25 | public Suggester getSuggester() { 26 | return (sender, command, args) -> List.of("[num]"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/engine/command/argument/base/StringArgument.java: -------------------------------------------------------------------------------- 1 | package engine.command.argument.base; 2 | 3 | import engine.command.argument.SimpleArgument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class StringArgument extends SimpleArgument { 11 | 12 | public StringArgument() { 13 | super(String.class, "String"); 14 | } 15 | 16 | @Override 17 | public Optional parse(Context context, String arg) { 18 | return Optional.of(arg); 19 | } 20 | 21 | @Override 22 | public Suggester getSuggester() { 23 | return (sender, command, args) -> { 24 | String s = args[args.length - 1]; 25 | if (s.isEmpty()) { 26 | return List.of("[text]"); 27 | } else return List.of(); 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/engine/command/impl/DefaultCommandParser.java: -------------------------------------------------------------------------------- 1 | package engine.command.impl; 2 | 3 | import engine.command.CommandParser; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class DefaultCommandParser implements CommandParser { 10 | @Override 11 | public Result parse(String command) { 12 | List args = new ArrayList<>(); 13 | boolean quotes = false; 14 | boolean escape = false; 15 | StringBuilder sb = new StringBuilder(); 16 | 17 | for (int i = 0; i < command.length(); i++) { 18 | char c = command.charAt(i); 19 | 20 | if ((c == ' ' || c == ' ') && !quotes) { 21 | args.add(sb.toString()); 22 | sb = new StringBuilder(); 23 | } else if (c == '"') { 24 | if (escape) 25 | sb.append(c); 26 | else 27 | quotes = !quotes; 28 | } else if (c == '\\') { 29 | if (escape) { 30 | escape = false; 31 | sb.append(c); 32 | } else { 33 | escape = true; 34 | } 35 | } else { 36 | sb.append(c); 37 | escape = false; 38 | } 39 | } 40 | args.add(sb.toString()); 41 | String[] argsArray = args.toArray(new String[0]); 42 | return new CommandParser.Result(argsArray[0], Arrays.copyOfRange(argsArray, 1, argsArray.length)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/engine/command/impl/DefaultExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package engine.command.impl; 2 | 3 | import engine.command.CommandExceptionHandler; 4 | 5 | public class DefaultExceptionHandler implements CommandExceptionHandler { 6 | @Override 7 | public void handleOnExecuting(Exception e) { 8 | System.out.println("Caught an exception when executing command."); 9 | e.printStackTrace(); 10 | } 11 | 12 | @Override 13 | public void handleOnSuggesting(Exception e) { 14 | System.out.println("Caught an exception when suggesting command."); 15 | e.printStackTrace(); 16 | } 17 | 18 | @Override 19 | public void handleOnGettingTips(Exception e) { 20 | System.out.println("Caught an exception when getting tips."); 21 | e.printStackTrace(); 22 | } 23 | 24 | @Override 25 | public void handleOnCheckingArgument(Exception e) { 26 | System.out.println("Caught an exception when checking argument."); 27 | e.printStackTrace(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/engine/command/impl/SimpleCommandManager.java: -------------------------------------------------------------------------------- 1 | package engine.command.impl; 2 | 3 | import engine.command.BaseCommandManager; 4 | import engine.command.CommandExceptionHandler; 5 | import engine.command.CommandParser; 6 | 7 | public class SimpleCommandManager extends BaseCommandManager { 8 | 9 | @Override 10 | protected CommandParser createCommandParser() { 11 | return new DefaultCommandParser(); 12 | } 13 | 14 | @Override 15 | protected CommandExceptionHandler createExceptionHandler() { 16 | return new DefaultExceptionHandler(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/engine/command/simple/CommandArgumentChecker.java: -------------------------------------------------------------------------------- 1 | package engine.command.simple; 2 | 3 | import engine.command.ArgumentCheckResult; 4 | import engine.command.CommandSender; 5 | 6 | @FunctionalInterface 7 | public interface CommandArgumentChecker { 8 | ArgumentCheckResult checkArguments(CommandSender sender, String[] args); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/engine/command/simple/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | package engine.command.simple; 2 | 3 | import engine.command.Command; 4 | import engine.command.CommandSender; 5 | 6 | @FunctionalInterface 7 | public interface CommandExecutor { 8 | void execute(CommandSender sender, Command command, String[] args); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/engine/command/simple/CommandSuggester.java: -------------------------------------------------------------------------------- 1 | package engine.command.simple; 2 | 3 | import engine.command.Command; 4 | import engine.command.CommandSender; 5 | 6 | import java.util.List; 7 | 8 | @FunctionalInterface 9 | public interface CommandSuggester { 10 | List suggest(CommandSender sender, Command command, String[] args); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/engine/command/simple/CommandTips.java: -------------------------------------------------------------------------------- 1 | package engine.command.simple; 2 | 3 | import engine.command.CommandSender; 4 | 5 | import java.util.List; 6 | 7 | @FunctionalInterface 8 | public interface CommandTips { 9 | List getTips(CommandSender sender, String[] args); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/engine/command/simple/SimpleCommand.java: -------------------------------------------------------------------------------- 1 | package engine.command.simple; 2 | 3 | import engine.command.ArgumentCheckResult; 4 | import engine.command.Command; 5 | import engine.command.CommandSender; 6 | 7 | import java.util.List; 8 | 9 | public class SimpleCommand extends Command { 10 | 11 | private CommandExecutor executor; 12 | private CommandSuggester completer; 13 | private CommandArgumentChecker argumentChecker; 14 | private CommandTips tips; 15 | 16 | public SimpleCommand(String name) { 17 | super(name); 18 | } 19 | 20 | public SimpleCommand(String name, CommandExecutor executor) { 21 | super(name); 22 | this.executor = executor; 23 | } 24 | 25 | @Override 26 | public void execute(CommandSender sender, String[] args) { 27 | executor.execute(sender, this, args); 28 | } 29 | 30 | @Override 31 | public List suggest(CommandSender sender, String[] args) { 32 | if (completer == null) return List.of(); 33 | return completer.suggest(sender, this, args); 34 | } 35 | 36 | @Override 37 | public List getTips(CommandSender sender, String[] args) { 38 | if (tips == null) return List.of(); 39 | return tips.getTips(sender, args); 40 | } 41 | 42 | @Override 43 | public ArgumentCheckResult checkLastArgument(CommandSender sender, String[] args) { 44 | if (argumentChecker != null) return argumentChecker.checkArguments(sender, args); 45 | return ArgumentCheckResult.Valid(); 46 | } 47 | 48 | public void setExecutor(CommandExecutor executor) { 49 | this.executor = executor; 50 | } 51 | 52 | public void setCompleter(CommandSuggester completer) { 53 | this.completer = completer; 54 | } 55 | 56 | public void setArgumentChecker(CommandArgumentChecker argumentChecker) { 57 | this.argumentChecker = argumentChecker; 58 | } 59 | 60 | public void setTips(CommandTips tips) { 61 | this.tips = tips; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/engine/command/suggestion/NamedSuggester.java: -------------------------------------------------------------------------------- 1 | package engine.command.suggestion; 2 | 3 | public interface NamedSuggester extends Suggester { 4 | String getName(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/engine/command/suggestion/SimpleSuggesterManager.java: -------------------------------------------------------------------------------- 1 | package engine.command.suggestion; 2 | 3 | import java.util.HashMap; 4 | 5 | public class SimpleSuggesterManager implements SuggesterManager { 6 | 7 | private HashMap suggesterHashMapByClass = new HashMap<>(); 8 | private HashMap suggesterHashMapByName = new HashMap<>(); 9 | 10 | @Override 11 | public void putSuggester(NamedSuggester completer) { 12 | if (suggesterHashMapByName.containsKey(completer.getName())){ 13 | throw new RuntimeException("suggester name already exist"); 14 | } 15 | suggesterHashMapByName.put(completer.getName(), completer); 16 | } 17 | 18 | @Override 19 | public void setClassSuggester(Class clazz, Suggester suggester) { 20 | suggesterHashMapByClass.put(clazz, suggester); 21 | } 22 | 23 | @Override 24 | public Suggester getSuggester(String name) { 25 | return suggesterHashMapByName.get(name); 26 | } 27 | 28 | @Override 29 | public Suggester getSuggester(Class clazz) { 30 | return suggesterHashMapByClass.get(clazz); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/engine/command/suggestion/Suggester.java: -------------------------------------------------------------------------------- 1 | package engine.command.suggestion; 2 | 3 | import engine.command.CommandSender; 4 | 5 | import java.util.List; 6 | 7 | @FunctionalInterface 8 | public interface Suggester { 9 | List suggest(CommandSender sender, String command, String[] args); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/engine/command/suggestion/SuggesterManager.java: -------------------------------------------------------------------------------- 1 | package engine.command.suggestion; 2 | 3 | public interface SuggesterManager { 4 | 5 | void putSuggester(NamedSuggester completer); 6 | 7 | void setClassSuggester(Class clazz, Suggester suggester); 8 | 9 | Suggester getSuggester(String name); 10 | 11 | Suggester getSuggester(Class clazz); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/ClassUtil.java: -------------------------------------------------------------------------------- 1 | package engine.command.util; 2 | 3 | public interface ClassUtil { 4 | 5 | static Class packing(Class clazz) { 6 | switch (clazz.getName()) { 7 | case "int": 8 | return Integer.class; 9 | case "float": 10 | return Float.class; 11 | case "boolean": 12 | return Boolean.class; 13 | case "double": 14 | return Double.class; 15 | case "char": 16 | return Character.class; 17 | case "long": 18 | return Long.class; 19 | case "short": 20 | return Short.class; 21 | } 22 | return clazz; 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/CommandNodeUtil.java: -------------------------------------------------------------------------------- 1 | package engine.command.util; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Multimap; 6 | import engine.command.anno.*; 7 | import engine.command.argument.Argument; 8 | import engine.command.argument.ArgumentManager; 9 | import engine.command.suggestion.SuggesterManager; 10 | import engine.command.util.node.*; 11 | 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.lang.reflect.Parameter; 16 | import java.util.*; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | public class CommandNodeUtil { 21 | 22 | protected ArgumentManager argumentManager; 23 | protected SuggesterManager suggesterManager; 24 | 25 | private Multimap> provideCommandNodeMap = HashMultimap.create(); 26 | 27 | private HashMap> nameProvideCommandNodeMap = new HashMap<>(); 28 | 29 | public static CommandNodeUtil getMethodUtil(ArgumentManager argumentManager, SuggesterManager suggesterManager) { 30 | return new CommandNodeUtil(argumentManager, suggesterManager); 31 | } 32 | 33 | public static ClassUtil getClassUtil(ArgumentManager argumentManager, SuggesterManager suggesterManager) { 34 | return new ClassUtil(argumentManager, suggesterManager); 35 | } 36 | 37 | private CommandNodeUtil(ArgumentManager argumentManager, SuggesterManager suggesterManager) { 38 | this.argumentManager = argumentManager; 39 | this.suggesterManager = suggesterManager; 40 | } 41 | 42 | public void addProvider(Object object) { 43 | Class providerClass = object.getClass(); 44 | Arrays.stream(providerClass.getConstructors()) 45 | .filter(constructor -> constructor.getAnnotation(Provide.class) != null) 46 | .filter(constructor -> constructor.getParameterCount() != 0) 47 | .forEach(constructor -> handleProvider(providerClass, (Provide) constructor.getAnnotation(Provide.class), 48 | constructor.getParameters(), objects -> { 49 | try { 50 | constructor.setAccessible(true); 51 | return constructor.newInstance(objects); 52 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 53 | e.printStackTrace(); 54 | } 55 | return null; 56 | })); 57 | Arrays.stream(providerClass.getMethods()) 58 | .filter(method -> method.getAnnotation(Provide.class) != null) 59 | .filter(method -> method.getParameterCount() != 0) 60 | .forEach(method -> handleProvider(method.getReturnType(), method.getAnnotation(Provide.class), 61 | method.getParameters(), objects -> { 62 | try { 63 | method.setAccessible(true); 64 | return method.invoke(object, objects); 65 | } catch (IllegalAccessException | InvocationTargetException e) { 66 | e.printStackTrace(); 67 | } 68 | return null; 69 | })); 70 | } 71 | 72 | private void handleProvider(Class providerClass, Provide provide, Parameter[] parameters, Function instanceFunction) { 73 | ArrayList list = new ArrayList(constructMultiArgument(parameters, instanceFunction).stream() 74 | .map(CommandNodeUtil::getTopParent) 75 | .collect(Collectors.toSet())); 76 | if (provide.name() != null && !provide.name().isEmpty()) { 77 | if (nameProvideCommandNodeMap.containsKey(provide.name())) 78 | throw new RuntimeException("name of provider already exist.existed name: " + provide.name()); 79 | nameProvideCommandNodeMap.put(provide.name(), list); 80 | } 81 | provideCommandNodeMap.put(providerClass, list); 82 | } 83 | 84 | public List parseParameter(Parameter parameter) { 85 | Annotation[] annotations = parameter.getAnnotations(); 86 | List node = foundMainNode(parameter.getType(), annotations); 87 | setCustomAnnotation(node, annotations); 88 | return node; 89 | } 90 | 91 | public List foundMainNode(Class clazz, Annotation[] annotations) { 92 | for (Annotation annotation : annotations) { 93 | if (annotation.annotationType() == Sender.class) { 94 | Sender sender = (Sender) annotation; 95 | Class[] senderClass = sender.value(); 96 | if (senderClass == null || senderClass.length == 0) { 97 | return handleSender(clazz); 98 | } else { 99 | return handleSender(senderClass); 100 | } 101 | } else if (annotation.annotationType() == Required.class) { 102 | return handleRequired(((Required) annotation).value()); 103 | } else if (annotation.annotationType() == ArgumentHandler.class) { 104 | return handleArgumentName(((ArgumentHandler) annotation).value()); 105 | } else if (annotation.annotationType() == UseProvide.class) { 106 | UseProvide useProvide = (UseProvide) annotation; 107 | List provides = Arrays.asList(useProvide.value()); 108 | for (String provide : provides) 109 | if (!nameProvideCommandNodeMap.containsKey(provide)) { 110 | throw new RuntimeException("provide: " + provide + " can not found"); 111 | } 112 | return cloneList(provides.stream() 113 | .map(name -> nameProvideCommandNodeMap.get(name)) 114 | .collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll)); 115 | } 116 | } 117 | List nodeList = handleArgument(engine.command.util.ClassUtil .packing(clazz)); 118 | if (!nodeList.isEmpty()) { 119 | return nodeList; 120 | } 121 | 122 | nodeList = foundNodesByProvider(clazz); 123 | 124 | if (!nodeList.isEmpty()) { 125 | return nodeList; 126 | } 127 | if (clazz.isEnum()) { 128 | return handleEnum(clazz); 129 | } 130 | 131 | throw new RuntimeException(clazz + " no argument or provide"); 132 | } 133 | 134 | private List cloneList(Collection list) { 135 | List cloneList = new ArrayList<>(); 136 | list.forEach(node -> cloneList.add(node.clone())); 137 | return cloneList; 138 | } 139 | 140 | public List handleSender(Class... classes) { 141 | return Lists.newArrayList(new SenderNode(classes)); 142 | } 143 | 144 | public List handleRequired(String required) { 145 | return Lists.newArrayList(new RequiredNode(required)); 146 | } 147 | 148 | public List handleArgumentName(String argumentName) { 149 | Argument argument = argumentManager.getArgument(argumentName); 150 | CommandNode node = new ArgumentNode(argument); 151 | node.setSuggester(suggesterManager.getSuggester(argument.responsibleClass())); 152 | return Lists.newArrayList(node); 153 | } 154 | 155 | public List foundNodesByProvider(Class clazz) { 156 | if (!provideCommandNodeMap.containsKey(clazz)) 157 | return Collections.EMPTY_LIST; 158 | return cloneList(provideCommandNodeMap.get(clazz).stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll)); 159 | } 160 | 161 | public List handleArgument(Class clazz) { 162 | Argument argument = argumentManager.getArgument(clazz); 163 | if (argument == null) { 164 | return Collections.EMPTY_LIST; 165 | } else { 166 | CommandNode node = new ArgumentNode(argument); 167 | node.setSuggester(suggesterManager.getSuggester(clazz)); 168 | return Lists.newArrayList(node); 169 | } 170 | } 171 | 172 | public List constructMultiArgument(Parameter[] parameters, Function constructFunction) { 173 | List nodes = parseParameter(parameters[0]); 174 | for (int i = 1; i < parameters.length; i++) { 175 | List children = parseParameter(parameters[i]); 176 | List cloneChildren = new ArrayList<>(); 177 | for (CommandNode parent : nodes) { 178 | for (CommandNode child : children) { 179 | CommandNode clone = CommandNodeUtil.getTopParent(child).clone(); 180 | cloneChildren.addAll(getAllLeafNode(clone)); 181 | parent.addChild(clone); 182 | } 183 | } 184 | nodes = cloneChildren; 185 | } 186 | for (int i = 0; i < nodes.size(); i++) { 187 | CommandNode node = nodes.get(i); 188 | MultiArgumentNode multiArgumentNode = new MultiArgumentNode(node, constructFunction, CommandNodeUtil.getDepth(node)); 189 | if (node.getParent() != null) { 190 | CommandNode parent = node.getParent(); 191 | parent.removeChild(node); 192 | parent.addChild(multiArgumentNode); 193 | } 194 | nodes.set(i, multiArgumentNode); 195 | } 196 | return nodes; 197 | } 198 | 199 | public List handleEnum(Class enumClazz) { 200 | return Lists.newArrayList(new EnumNode(enumClazz)); 201 | } 202 | 203 | public void setCustomAnnotation(List nodes, Annotation[] annotations) { 204 | for (Annotation annotation : annotations) { 205 | if (annotation.annotationType() == Suggester.class) { 206 | Suggester complete = (Suggester) annotation; 207 | nodes.stream().forEach(node -> node.setSuggester(suggesterManager.getSuggester(complete.value()))); 208 | } 209 | 210 | if (annotation.annotationType() == Tip.class) { 211 | Tip tip = (Tip) annotation; 212 | nodes.stream().forEach(node -> node.setTip(tip.value())); 213 | } 214 | } 215 | } 216 | 217 | /** 218 | * 统计包括node及其所有父node所需的args. 219 | * 220 | * @param node 221 | * @return 222 | */ 223 | public static int getRequiredArgsSumFromParent2Child(CommandNode node) { 224 | int i = 0; 225 | while (true) { 226 | if (node == null) { 227 | return i; 228 | } 229 | i += node.getRequiredArgsNum(); 230 | if (node.getParent() == null) { 231 | return i; 232 | } 233 | node = node.getParent(); 234 | } 235 | } 236 | 237 | public static CommandNode getTopParent(CommandNode child) { 238 | while (true) { 239 | if (child.getParent() == null) { 240 | return child; 241 | } 242 | child = child.getParent(); 243 | } 244 | } 245 | 246 | public static int getDepth(CommandNode node) { 247 | if (node == null) 248 | return 0; 249 | int i = 0; 250 | while (node.getParent() != null) { 251 | i++; 252 | node = node.getParent(); 253 | } 254 | return i; 255 | } 256 | 257 | public static List getLinkedFromParent2Child(CommandNode child) { 258 | ArrayList list = new ArrayList<>(); 259 | list.add(child); 260 | while (true) { 261 | if (child.getParent() == null) { 262 | Collections.reverse(list); 263 | return list; 264 | } 265 | list.add(child.getParent()); 266 | child = child.getParent(); 267 | } 268 | } 269 | 270 | public static Collection getAllLeafNode(CommandNode clone) { 271 | ArrayList list = new ArrayList<>(); 272 | List arrayList = new LinkedList<>(); 273 | arrayList.add(clone); 274 | while (!arrayList.isEmpty()) { 275 | CommandNode node = arrayList.remove(0); 276 | if (node.getChildren().isEmpty()) { 277 | list.add(node); 278 | } else { 279 | arrayList.addAll(node.getChildren()); 280 | } 281 | } 282 | return list; 283 | } 284 | 285 | 286 | /** 287 | * 返回从当前node到最近结束的node的路径. 288 | * 结束指可以执行命令 289 | * 不包含传入的node 290 | * 291 | * @param node 292 | * @return 293 | */ 294 | public static List getShortestPath(CommandNode node) { 295 | CommandNode node1 = breadthFirstSearch(node); 296 | List path = new LinkedList<>(); 297 | path.add(node1); 298 | while (true) { 299 | if (node1.getParent() == node) { 300 | return path; 301 | } 302 | path.add(0, node1.getParent()); 303 | node1 = node1.getParent(); 304 | } 305 | } 306 | 307 | private static CommandNode breadthFirstSearch(CommandNode commandNode) { 308 | List arrayList = new LinkedList<>(); 309 | arrayList.add(commandNode); 310 | while (!arrayList.isEmpty()) { 311 | CommandNode node = arrayList.remove(0); 312 | if (node.canExecuteCommand()) { 313 | return node; 314 | } 315 | arrayList.addAll(node.getChildren()); 316 | } 317 | return null; 318 | } 319 | 320 | public static class ClassUtil extends CommandNodeUtil { 321 | 322 | public ClassUtil(ArgumentManager argumentManager, SuggesterManager suggesterManager) { 323 | super(argumentManager, suggesterManager); 324 | } 325 | 326 | public List parseField(Field field) { 327 | Annotation[] annotations = field.getAnnotations(); 328 | List nodeList = new ArrayList<>(); 329 | nodeList.addAll(foundMainNode(field.getType(), annotations)); 330 | setCustomAnnotation(nodeList, annotations); 331 | return nodeList; 332 | } 333 | } 334 | 335 | public static void showLink(CommandNode commandNode) { 336 | 337 | ArrayList list1 = new ArrayList<>(); 338 | 339 | list1.add(commandNode); 340 | while (commandNode.getParent() != null) { 341 | list1.add(commandNode.getParent()); 342 | commandNode = commandNode.getParent(); 343 | } 344 | 345 | List list2 = list1.stream().map(CommandNodeUtil::getNodeDescription).collect(Collectors.toList()); 346 | Collections.reverse(list2); 347 | System.out.println(list2.toString()); 348 | } 349 | 350 | public static String getNodeDescription(CommandNode node) { 351 | StringBuilder sb = new StringBuilder(); 352 | sb.append(node.getClass().getSimpleName()); 353 | sb.append("(" + Integer.toHexString(node.hashCode()) + ")"); 354 | if (node instanceof ArgumentNode) { 355 | ArgumentNode argumentNode = (ArgumentNode) node; 356 | sb.append(":"); 357 | sb.append(argumentNode.getArgument().getName()); 358 | } else if (node instanceof SenderNode) { 359 | SenderNode senderNode = (SenderNode) node; 360 | sb.append(":"); 361 | sb.append(Arrays.stream(senderNode.getAllowedSenders()).map(Class::getSimpleName).collect(Collectors.toList())); 362 | } else if (node instanceof RequiredNode) { 363 | RequiredNode requiredNode = (RequiredNode) node; 364 | sb.append(":"); 365 | sb.append(requiredNode.getRequire()); 366 | } 367 | return sb.toString(); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/StringArgs.java: -------------------------------------------------------------------------------- 1 | package engine.command.util; 2 | 3 | import java.util.Arrays; 4 | 5 | public class StringArgs { 6 | 7 | private final String[] args; 8 | 9 | private int index; 10 | 11 | public StringArgs(String[] args) { 12 | this.args = args; 13 | } 14 | 15 | public String next() { 16 | return args[index++]; 17 | } 18 | 19 | public boolean hasNext() { 20 | return index < args.length; 21 | } 22 | 23 | public int getLength() { 24 | return args.length; 25 | } 26 | 27 | public int getIndex() { 28 | return index; 29 | } 30 | 31 | public void setIndex(int index) { 32 | this.index = index; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "StringArgs{" + 38 | "args=" + Arrays.toString(args) + 39 | ", index=" + index + 40 | '}'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/SuggesterHelper.java: -------------------------------------------------------------------------------- 1 | package engine.command.util; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | public class SuggesterHelper { 7 | 8 | public static List filterStartWith(List list, String start) { 9 | return list.stream() 10 | .filter(s -> s.startsWith(start)) 11 | .collect(Collectors.toList()); 12 | } 13 | 14 | public static List filterStartWithLastString(List list, String[] array) { 15 | if(array.length==0) 16 | return List.of(); 17 | return list.stream() 18 | .filter(s -> s.startsWith(array[array.length - 1])) 19 | .collect(Collectors.toList()); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/Type.java: -------------------------------------------------------------------------------- 1 | package engine.command.util; 2 | 3 | import java.util.HashMap; 4 | 5 | public class Type { 6 | 7 | private static final HashMap typeCache = new HashMap<>(); 8 | 9 | private Class clazz; 10 | 11 | public Type(Class clazz) { 12 | this.clazz = clazz; 13 | } 14 | 15 | public boolean is(Type type) { 16 | return clazz.equals(type.clazz); 17 | } 18 | 19 | public static Type of(Class clazz) { 20 | return typeCache.computeIfAbsent(clazz, Type::new); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/asm/ClassDefiner.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.asm; 2 | 3 | public interface ClassDefiner { 4 | 5 | Class define(String className,byte[] bytes); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/context/Context.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.context; 2 | 3 | import engine.command.CommandSender; 4 | import engine.command.util.Type; 5 | 6 | public interface Context { 7 | 8 | CommandSender getSender(); 9 | 10 | int size(); 11 | 12 | int first(Type type); 13 | 14 | int last(Type type); 15 | 16 | Type getTypeAt(int index); 17 | 18 | Object getValueAt(int index); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/context/ContextNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.context; 2 | 3 | import engine.command.util.node.CommandNode; 4 | 5 | public interface ContextNode { 6 | 7 | Object getValue(); 8 | 9 | void setValue(Object value); 10 | 11 | CommandNode getOwner(); 12 | 13 | void setNext(ContextNode node); 14 | 15 | void setPre(ContextNode node); 16 | 17 | ContextNode getNext(); 18 | 19 | ContextNode getPre(); 20 | 21 | ContextNode clone(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/context/LinkedContext.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.context; 2 | 3 | import engine.command.util.node.CommandNode; 4 | 5 | import java.util.List; 6 | 7 | public interface LinkedContext extends Context { 8 | 9 | void add(CommandNode handler, Object object); 10 | 11 | void add(CommandNode handler, int index, Object object); 12 | 13 | void remove(int index); 14 | 15 | void removeLast(); 16 | 17 | List valueToArray(); 18 | 19 | LinkedContext clone(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/ArgumentNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.StringArgs; 6 | import engine.command.util.context.LinkedContext; 7 | 8 | import java.util.Optional; 9 | 10 | public class ArgumentNode extends CommandNode { 11 | 12 | private Argument argument; 13 | 14 | public ArgumentNode(Argument argument) { 15 | this.argument = argument; 16 | if (argument != null) { 17 | setTip(argument.getName()); 18 | } 19 | } 20 | 21 | @Override 22 | public int getRequiredArgsNum() { 23 | return 1; 24 | } 25 | 26 | @Override 27 | public ParseResult parse(LinkedContext context, StringArgs args) { 28 | String next = args.next(); 29 | if (next.isEmpty()) { 30 | return ParseResult.fail(); 31 | } 32 | Optional parse = argument.parse(context, next); 33 | if (parse.isPresent()) { 34 | return ParseResult.success(parse.get()); 35 | } else { 36 | return ParseResult.fail(); 37 | } 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "ArgumentNode{" + 43 | "argument=" + argument + 44 | '}'; 45 | } 46 | 47 | public Argument getArgument() { 48 | return argument; 49 | } 50 | 51 | public void setArgument(Argument argument) { 52 | this.argument = argument; 53 | } 54 | 55 | @Override 56 | public Suggester getSuggester() { 57 | if (super.getSuggester() != null) { 58 | return super.getSuggester(); 59 | } 60 | return argument.getSuggester(); 61 | } 62 | 63 | @Override 64 | public int priority() { 65 | return 0 + (argument.getName().equals("String") ? -1 : 0) + (argument.responsibleClass().equals(String.class) ? -1 : 0); 66 | } 67 | 68 | @Override 69 | public boolean same(CommandNode node) { 70 | if (super.same(node) && node instanceof ArgumentNode) { 71 | return argument.equals(((ArgumentNode) node).argument); 72 | } 73 | return false; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/CommandNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.suggestion.Suggester; 4 | import engine.command.util.StringArgs; 5 | import engine.command.util.context.ContextNode; 6 | import engine.command.util.context.LinkedContext; 7 | 8 | import java.util.*; 9 | import java.util.function.Consumer; 10 | 11 | public abstract class CommandNode implements Cloneable, Comparable { 12 | 13 | private CommandNode parent; 14 | 15 | private Consumer> executor; 16 | 17 | private List children = new ArrayList<>(); 18 | 19 | private String permissionExpression; 20 | 21 | private Suggester suggester; 22 | 23 | private String tip; 24 | 25 | public CommandNode() { 26 | } 27 | 28 | public abstract ParseResult parse(LinkedContext context, StringArgs args); 29 | 30 | public abstract int getRequiredArgsNum(); 31 | 32 | public void collect(ContextNode node){ 33 | 34 | } 35 | 36 | public CommandNode getParent() { 37 | return parent; 38 | } 39 | 40 | public Collection getChildren() { 41 | return children; 42 | } 43 | 44 | public void addChild(CommandNode commandNode) { 45 | if (commandNode.executor != null) { 46 | for (CommandNode child : children) { 47 | if (child.same(commandNode) && commandNode.executor.equals(child.executor)) 48 | return; 49 | } 50 | add(commandNode); 51 | return; 52 | } 53 | CommandNode node = matchChild(commandNode); 54 | if (node == null) { 55 | add(commandNode); 56 | } else { 57 | for (CommandNode child : commandNode.getChildren()) { 58 | node.addChild(child); 59 | } 60 | } 61 | } 62 | 63 | private void add(CommandNode commandNode) { 64 | commandNode.setParent(this); 65 | this.children.add(commandNode); 66 | Collections.sort(children); 67 | } 68 | 69 | private CommandNode matchChild(CommandNode commandNode) { 70 | for (CommandNode child : children) { 71 | if (child.executor == null && child.same(commandNode)) 72 | return child; 73 | } 74 | return null; 75 | } 76 | 77 | public void removeChild(CommandNode commandNode) { 78 | if (this.children.remove(commandNode)) { 79 | commandNode.setParent(null); 80 | } 81 | } 82 | 83 | public boolean canExecuteCommand() { 84 | return getExecutor() != null; 85 | } 86 | 87 | protected void setParent(CommandNode parent) { 88 | this.parent = parent; 89 | } 90 | 91 | public void setExecutor(Consumer> executor) { 92 | this.executor = executor; 93 | } 94 | 95 | public Consumer> getExecutor() { 96 | return executor; 97 | } 98 | 99 | public String getPermissionExpression() { 100 | return permissionExpression; 101 | } 102 | 103 | public void setPermissionExpression(String permission) { 104 | this.permissionExpression = permission; 105 | } 106 | 107 | public Suggester getSuggester() { 108 | return suggester; 109 | } 110 | 111 | public void setSuggester(Suggester suggester) { 112 | this.suggester = suggester; 113 | } 114 | 115 | public String getTip() { 116 | return tip; 117 | } 118 | 119 | public void setTip(String tip) { 120 | this.tip = tip; 121 | } 122 | 123 | public boolean hasTip() { 124 | return tip != null; 125 | } 126 | 127 | @Override 128 | public CommandNode clone() { 129 | CommandNode node = null; 130 | try { 131 | node = (CommandNode) super.clone(); 132 | } catch (CloneNotSupportedException e) { 133 | e.printStackTrace(); 134 | } 135 | node.children = new ArrayList<>(); 136 | for (CommandNode child : children) { 137 | node.addChild(child.clone()); 138 | } 139 | return node; 140 | } 141 | 142 | public boolean same(CommandNode node) { 143 | return node != null && node instanceof CommandNode && 144 | Objects.equals(suggester, node.suggester); 145 | } 146 | 147 | @Override 148 | public int compareTo(CommandNode o) { 149 | return o.priority() - priority(); 150 | } 151 | 152 | public abstract int priority(); 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/EmptyArgumentNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.StringArgs; 6 | import engine.command.util.context.Context; 7 | import engine.command.util.context.LinkedContext; 8 | 9 | import java.util.Optional; 10 | 11 | public class EmptyArgumentNode extends ArgumentNode { 12 | 13 | public EmptyArgumentNode() { 14 | super(new Argument() { 15 | @Override 16 | public String getName() { 17 | return "Empty"; 18 | } 19 | 20 | @Override 21 | public Class responsibleClass() { 22 | return this.getClass(); 23 | } 24 | 25 | @Override 26 | public Optional parse(Context context, String arg) { 27 | return Optional.empty(); 28 | } 29 | 30 | @Override 31 | public Suggester getSuggester() { 32 | return null; 33 | } 34 | }); 35 | } 36 | 37 | @Override 38 | public int getRequiredArgsNum() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public ParseResult parse(LinkedContext context, StringArgs args) { 44 | return ParseResult.success(); 45 | } 46 | 47 | @Override 48 | public boolean hasTip() { 49 | return false; 50 | } 51 | 52 | @Override 53 | public boolean same(CommandNode node) { 54 | return super.same(node) && node instanceof EmptyArgumentNode; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/EnumNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.suggestion.Suggester; 4 | import engine.command.util.StringArgs; 5 | import engine.command.util.context.LinkedContext; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | 13 | public class EnumNode extends CommandNode { 14 | 15 | private final Class enumClass; 16 | 17 | private List enumNames; 18 | 19 | private HashMap enumMap = new HashMap<>(); 20 | 21 | public EnumNode(Class enumClass) { 22 | this.enumClass = enumClass; 23 | try { 24 | Object[] enums = (Object[]) enumClass.getMethod("values").invoke(null); 25 | Method nameMethod = enumClass.getMethod("name"); 26 | ArrayList list = new ArrayList(); 27 | for(Object o : enums) 28 | list.add(nameMethod.invoke(o)); 29 | enumNames = list; 30 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | @Override 36 | public int getRequiredArgsNum() { 37 | return 1; 38 | } 39 | 40 | @Override 41 | public ParseResult parse(LinkedContext context, StringArgs args) { 42 | String name = args.next(); 43 | if (!enumNames.contains(name)) { 44 | return ParseResult.fail(); 45 | } 46 | if (enumMap.containsKey(name)) { 47 | return ParseResult.success(enumMap.get(name)); 48 | } 49 | try { 50 | Object o = enumClass.getMethod("valueOf", String.class).invoke(null, name); 51 | enumMap.put(name, o); 52 | return ParseResult.success(o); 53 | } catch (IllegalAccessException e) { 54 | e.printStackTrace(); 55 | } catch (InvocationTargetException e) { 56 | e.printStackTrace(); 57 | } catch (NoSuchMethodException e) { 58 | e.printStackTrace(); 59 | } 60 | return ParseResult.fail(); 61 | } 62 | 63 | public Suggester getSuggester() { 64 | return (sender, command, args) -> enumNames; 65 | } 66 | 67 | @Override 68 | public int priority() { 69 | return 2; 70 | } 71 | 72 | @Override 73 | public boolean same(CommandNode node) { 74 | if (super.same(node) && node instanceof EnumNode) { 75 | return ((EnumNode) node).enumClass.equals(enumClass); 76 | } 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/MultiArgumentNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.util.StringArgs; 4 | import engine.command.util.context.ContextNode; 5 | import engine.command.util.context.LinkedContext; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.function.Function; 10 | 11 | public class MultiArgumentNode extends CommandNode { 12 | 13 | private final CommandNode commandNode; 14 | private final Function instanceFunction; 15 | private final int argsNum; 16 | 17 | public MultiArgumentNode(CommandNode commandNode, Function instanceFunction, int argsNum) { 18 | this.commandNode = commandNode; 19 | this.instanceFunction = instanceFunction; 20 | this.argsNum = argsNum; 21 | } 22 | 23 | @Override 24 | public int getRequiredArgsNum() { 25 | return commandNode.getRequiredArgsNum(); 26 | } 27 | 28 | @Override 29 | public ParseResult parse(LinkedContext sender, StringArgs args) { 30 | return commandNode.parse(sender, args); 31 | } 32 | 33 | @Override 34 | public void collect(ContextNode node) { 35 | List objectList = new ArrayList<>(); 36 | ContextNode headNode = node; 37 | for (int i = 0; i < argsNum; i++) { 38 | headNode = headNode.getPre(); 39 | } 40 | ContextNode pre = headNode.getPre(); 41 | for (int i = 0; i < argsNum + 1; i++) { 42 | objectList.add(headNode.getValue()); 43 | headNode = headNode.getNext(); 44 | } 45 | node.setValue(instanceFunction.apply(objectList.toArray())); 46 | pre.setNext(node); 47 | node.setPre(pre); 48 | } 49 | 50 | @Override 51 | public String getTip() { 52 | return commandNode.getTip(); 53 | } 54 | 55 | @Override 56 | public void setTip(String tip) { 57 | commandNode.setTip(tip); 58 | } 59 | 60 | @Override 61 | public boolean hasTip() { 62 | return commandNode.hasTip(); 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "MultiArgumentNode{" + 68 | "commandNode=" + commandNode + 69 | ", instanceFunction=" + instanceFunction + 70 | ", argsNum=" + argsNum + 71 | '}'; 72 | } 73 | 74 | @Override 75 | public int priority() { 76 | return commandNode.priority(); 77 | } 78 | 79 | @Override 80 | public boolean same(CommandNode node) { 81 | if (super.same(node) && node instanceof MultiArgumentNode) { 82 | MultiArgumentNode multiArgumentNode = (MultiArgumentNode) node; 83 | return argsNum == multiArgumentNode.argsNum && 84 | commandNode.same(multiArgumentNode.commandNode) && 85 | instanceFunction.equals(multiArgumentNode.instanceFunction); 86 | } 87 | return false; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/Nodeable.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | public interface Nodeable { 4 | 5 | CommandNode getNode(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/ParseResult.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | public class ParseResult { 4 | 5 | private static final ParseResult EMPTY_SUCCESS = success(null); 6 | private static final ParseResult FAIL = new ParseResult(false,null); 7 | 8 | private final boolean success; 9 | private final Object value; 10 | 11 | private ParseResult(boolean success, Object value) { 12 | this.success = success; 13 | this.value = value; 14 | } 15 | 16 | public boolean isSuccess() { 17 | return success; 18 | } 19 | 20 | public boolean isFail(){ 21 | return !success; 22 | } 23 | 24 | public Object getValue() { 25 | return value; 26 | } 27 | 28 | public static ParseResult success(Object value) { 29 | return new ParseResult(true, value); 30 | } 31 | 32 | public static ParseResult success() { 33 | return EMPTY_SUCCESS; 34 | } 35 | 36 | public static ParseResult fail() { 37 | return FAIL; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/RequiredNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.suggestion.Suggester; 4 | import engine.command.util.StringArgs; 5 | import engine.command.util.context.LinkedContext; 6 | 7 | import java.util.List; 8 | 9 | public class RequiredNode extends CommandNode { 10 | 11 | private final String require; 12 | 13 | public RequiredNode(String require) { 14 | this.require = require; 15 | } 16 | 17 | @Override 18 | public int getRequiredArgsNum() { 19 | return 1; 20 | } 21 | 22 | @Override 23 | public ParseResult parse(LinkedContext context, StringArgs args) { 24 | if (args.next().equals(require)) { 25 | return ParseResult.success(require); 26 | } 27 | return ParseResult.fail(); 28 | } 29 | 30 | @Override 31 | public Suggester getSuggester() { 32 | return (sender, command, args) -> List.of(require); 33 | } 34 | 35 | public String getRequire() { 36 | return require; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "RequiredNode{" + 42 | "require='" + require + '\'' + 43 | '}'; 44 | } 45 | 46 | @Override 47 | public int priority() { 48 | return 10; 49 | } 50 | 51 | @Override 52 | public boolean same(CommandNode node) { 53 | if (super.same(node) && node instanceof RequiredNode) { 54 | return ((RequiredNode) node).require.equals(require); 55 | } 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/engine/command/util/node/SenderNode.java: -------------------------------------------------------------------------------- 1 | package engine.command.util.node; 2 | 3 | import engine.command.CommandSender; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.StringArgs; 6 | import engine.command.util.context.LinkedContext; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | 11 | public class SenderNode extends CommandNode { 12 | 13 | private Class[] allowedSenders; 14 | 15 | public SenderNode(Class... clazz) { 16 | allowedSenders = clazz; 17 | } 18 | 19 | @Override 20 | public int getRequiredArgsNum() { 21 | return 0; 22 | } 23 | 24 | @Override 25 | public ParseResult parse(LinkedContext context, StringArgs args) { 26 | if (allowedSender(context.getSender())) { 27 | return ParseResult.success(context.getSender()); 28 | } 29 | return ParseResult.fail(); 30 | } 31 | 32 | public boolean allowedSender(CommandSender sender) { 33 | for (Class clazz : allowedSenders) { 34 | if (clazz.isAssignableFrom(sender.getClass())) { 35 | return true; 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | public Class[] getAllowedSenders() { 42 | return allowedSenders; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "SenderNode{" + 48 | "allowedSender=" + Arrays.toString(allowedSenders) + 49 | '}'; 50 | } 51 | 52 | public boolean hasTip() { 53 | return false; 54 | } 55 | 56 | @Override 57 | public Suggester getSuggester() { 58 | return (sender, command, args) -> Collections.EMPTY_LIST; 59 | } 60 | 61 | @Override 62 | public int priority() { 63 | return -5; 64 | } 65 | 66 | @Override 67 | public boolean same(CommandNode node) { 68 | if (super.same(node) && node instanceof SenderNode) { 69 | return Arrays.equals(((SenderNode) node).allowedSenders, allowedSenders); 70 | } 71 | return false; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/engine/permission/HashPermissible.java: -------------------------------------------------------------------------------- 1 | package engine.permission; 2 | 3 | import org.apache.commons.lang3.Validate; 4 | 5 | import javax.annotation.Nonnull; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.concurrent.locks.ReadWriteLock; 9 | import java.util.concurrent.locks.ReentrantReadWriteLock; 10 | 11 | public class HashPermissible implements Permissible { 12 | 13 | private final Map permissionMap = new HashMap<>(); 14 | 15 | private final ReadWriteLock lock = new ReentrantReadWriteLock(true); 16 | 17 | @Override 18 | public boolean hasPermission(@Nonnull String permission) { 19 | Validate.notEmpty(permission); 20 | try { 21 | lock.readLock().lock(); 22 | if (permissionMap.containsKey(permission)) { 23 | return permissionMap.get(permission); 24 | } 25 | while (true) { 26 | int lastDot = permission.lastIndexOf('.'); 27 | if (lastDot <= 0) { 28 | break; 29 | } 30 | permission = permission.substring(0, lastDot); 31 | if (permissionMap.containsKey(permission)) { 32 | return permissionMap.get(permission); 33 | } 34 | } 35 | } finally { 36 | lock.readLock().unlock(); 37 | } 38 | return false; 39 | } 40 | 41 | @Override 42 | public void setPermission(@Nonnull String permission, boolean bool) { 43 | Validate.notEmpty(permission); 44 | lock.writeLock().lock(); 45 | permissionMap.put(permission, bool); 46 | lock.writeLock().unlock(); 47 | } 48 | 49 | @Override 50 | public void removePermission(String permission) { 51 | lock.writeLock().lock(); 52 | permissionMap.remove(permission); 53 | lock.writeLock().unlock(); 54 | } 55 | 56 | @Override 57 | public void clearPermission() { 58 | lock.writeLock().lock(); 59 | this.permissionMap.clear(); 60 | lock.writeLock().unlock(); 61 | } 62 | 63 | @Override 64 | public Map toPermissionMap() { 65 | return Map.copyOf(permissionMap); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/engine/permission/Permissible.java: -------------------------------------------------------------------------------- 1 | package engine.permission; 2 | 3 | import javax.annotation.Nonnull; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | 7 | public interface Permissible { 8 | 9 | boolean hasPermission(@Nonnull String permission); 10 | 11 | default boolean hasPermission(Collection permissions) { 12 | for (String permission : permissions) { 13 | if(!hasPermission(permission)) 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | void setPermission(@Nonnull String permission, boolean bool); 20 | 21 | void removePermission(String permission); 22 | 23 | void clearPermission(); 24 | 25 | Map toPermissionMap(); 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/main/ClassNodeCommandTest.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.Command; 4 | import engine.command.CommandFailure; 5 | import engine.command.CommandSender; 6 | import engine.command.anno.*; 7 | import engine.command.argument.SimpleArgumentManager; 8 | import engine.command.impl.SimpleCommandManager; 9 | import engine.permission.HashPermissible; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.util.Map; 14 | import java.util.Random; 15 | 16 | public class ClassNodeCommandTest { 17 | 18 | SimpleCommandManager simpleCommandManager = new SimpleCommandManager(); 19 | private TestSender testSender = new TestSender("methodNodeTest", string -> message = string, c -> message = c.getType().name()); 20 | 21 | private String message; 22 | 23 | @Test 24 | void commandAttribute() { 25 | ClassAnnotationCommand.getBuilder(simpleCommandManager) 26 | .caseCommand("attribute", "test", "help", () -> { 27 | }) 28 | .register(); 29 | Command command = simpleCommandManager.getCommand("attribute").get(); 30 | Assertions.assertEquals("test", command.getDescription()); 31 | Assertions.assertEquals("help", command.getHelpMessage()); 32 | } 33 | 34 | @Test 35 | void use() { 36 | ClassAnnotationCommand.getBuilder(simpleCommandManager) 37 | .caseCommand("use", new Runnable() { 38 | @Sender 39 | public CommandSender sender; 40 | 41 | @Override 42 | public void run() { 43 | sender.sendMessage(sender.getSenderName()); 44 | } 45 | }) 46 | .register(); 47 | simpleCommandManager.execute(testSender, "use"); 48 | Assertions.assertEquals(message, testSender.getSenderName()); 49 | } 50 | 51 | @Test 52 | void test() { 53 | Entity entitySender = new Entity() { 54 | private HashPermissible permissible = new HashPermissible(); 55 | 56 | @Override 57 | public World getWorld() { 58 | return new World("testWorld"); 59 | } 60 | 61 | @Override 62 | public void sendMessage(String message) { 63 | ClassNodeCommandTest.this.message = message; 64 | } 65 | 66 | @Override 67 | public String getSenderName() { 68 | return "entity"; 69 | } 70 | 71 | @Override 72 | public void sendCommandFailure(CommandFailure failure) { 73 | message = failure.getType().name(); 74 | } 75 | 76 | @Override 77 | public boolean hasPermission(String permission) { 78 | return permissible.hasPermission(permission); 79 | } 80 | 81 | @Override 82 | public void setPermission(String permission, boolean bool) { 83 | permissible.setPermission(permission, bool); 84 | } 85 | 86 | @Override 87 | public void removePermission(String permission) { 88 | permissible.removePermission(permission); 89 | } 90 | 91 | @Override 92 | public void clearPermission() { 93 | permissible.clearPermission(); 94 | } 95 | 96 | @Override 97 | public Map toPermissionMap() { 98 | return permissible.toPermissionMap(); 99 | } 100 | }; 101 | SimpleArgumentManager argumentManager = new SimpleArgumentManager(); 102 | argumentManager.appendArgumentAndSetDefaultIfNotExist(new WorldArgument()); 103 | ClassAnnotationCommand.getBuilder(simpleCommandManager) 104 | .addProvider(new Object() { 105 | @Provide 106 | public Random random(int seed) { 107 | return new Random(seed); 108 | } 109 | } 110 | ) 111 | .setArgumentManager(argumentManager) 112 | .addProvider(new LocationProvider()) 113 | .caseCommand("test", new Runnable() { 114 | @Sender 115 | public CommandSender sender; 116 | 117 | public Location location; 118 | 119 | public Random random; 120 | 121 | @Ignore 122 | public int i = 123; 123 | 124 | public String text; 125 | 126 | @Override 127 | @Permission("test") 128 | public void run() { 129 | sender.sendMessage(sender.getSenderName() + location + random.nextInt() + text); 130 | } 131 | 132 | }).register(); 133 | 134 | int seed = 12356; 135 | World world = new World("abc"); 136 | Location location = new Location(world, 1, 2, 3); 137 | 138 | String text = "耗子女装"; 139 | 140 | Command command = simpleCommandManager.getCommand("test").get(); 141 | 142 | command.execute(entitySender, new String[]{"1", "2", "3", Integer.valueOf(seed).toString(), text}); 143 | Assertions.assertEquals(CommandFailure.Type.PERMISSION_NOT_ENOUGH.name(), message); 144 | 145 | entitySender.setPermission("test", true); 146 | 147 | simpleCommandManager.execute(entitySender, String.format("test %f %f %f %d %s", location.getX(), location.getY(), location.getZ(), seed, text)); 148 | 149 | Assertions.assertEquals(message, entitySender.getSenderName() + new Location(entitySender.getWorld(), location.getX(), location.getY(), location.getZ()) + new Random(seed).nextInt() + text); 150 | 151 | simpleCommandManager.execute(entitySender, String.format("test %s %f %f %f %d %s", world.getWorldName(), location.getX(), location.getY(), location.getZ(), seed, text)); 152 | 153 | Assertions.assertEquals(message, entitySender.getSenderName() + location + new Random(seed).nextInt() + text); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/main/Entity.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.CommandSender; 4 | 5 | public interface Entity extends CommandSender { 6 | World getWorld(); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/main/HashPermissibleTest.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.permission.HashPermissible; 4 | import engine.permission.Permissible; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Random; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | public class HashPermissibleTest { 15 | 16 | @Test 17 | public void permissibleTest() { 18 | Permissible permissible = new HashPermissible(); 19 | List truePermissions = getRandomPermissions(); 20 | List falsePermissions = getRandomPermissions(); 21 | 22 | truePermissions.forEach(permission->permissible.setPermission(permission,true)); 23 | 24 | Thread thread = new Thread(()-> falsePermissions.forEach(permission->permissible.setPermission(permission,false))); 25 | thread.start(); 26 | 27 | truePermissions.forEach(permission-> assertTrue(permissible.hasPermission(permission))); 28 | 29 | List parentTestPermissions = getRandomPermissions(); 30 | Permissible permissible2 = new HashPermissible(); 31 | parentTestPermissions.stream().filter(s->s.indexOf('.')>0).forEach(s->permissible2.setPermission(s.substring(0,s.indexOf('.')),true)); 32 | parentTestPermissions.stream().filter(s->s.indexOf('.')>0).forEach(s-> assertTrue(permissible2.hasPermission(s))); 33 | parentTestPermissions.stream().filter(s->s.indexOf('.')<=0).forEach(s-> assertFalse(permissible2.hasPermission(s))); 34 | } 35 | 36 | private List getRandomPermissions(){ 37 | ArrayList permission = new ArrayList<>(); 38 | Random random = new Random(System.currentTimeMillis()); 39 | for(int i = 0;i<100;i++) 40 | permission.add(new Double(random.nextDouble()).toString()+new Double(random.nextDouble()).toString()); 41 | return permission; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/main/Location.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import java.util.Objects; 4 | 5 | public class Location { 6 | 7 | private World world; 8 | private double x; 9 | private double y; 10 | private double z; 11 | 12 | 13 | public Location(World world, double x, double y, double z) { 14 | this.world = world; 15 | this.x = x; 16 | this.y = y; 17 | this.z = z; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | Location location = (Location) o; 25 | return Double.compare(location.x, x) == 0 && 26 | Double.compare(location.y, y) == 0 && 27 | Double.compare(location.z, z) == 0 && 28 | Objects.equals(world, location.world); 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return Objects.hash(world, x, y, z); 34 | } 35 | 36 | public World getWorld() { 37 | return world; 38 | } 39 | 40 | public void setWorld(World world) { 41 | this.world = world; 42 | } 43 | 44 | public double getX() { 45 | return x; 46 | } 47 | 48 | public void setX(double x) { 49 | this.x = x; 50 | } 51 | 52 | public double getY() { 53 | return y; 54 | } 55 | 56 | public void setY(double y) { 57 | this.y = y; 58 | } 59 | 60 | public double getZ() { 61 | return z; 62 | } 63 | 64 | public void setZ(double z) { 65 | this.z = z; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "Location{" + 71 | "world=" + world + 72 | ", x=" + x + 73 | ", y=" + y + 74 | ", z=" + z + 75 | '}'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/main/LocationProvider.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.anno.Provide; 4 | import engine.command.anno.Sender; 5 | import engine.command.anno.Tip; 6 | 7 | public class LocationProvider { 8 | 9 | @Provide 10 | public Location a(@Sender Entity entity, @Tip("x") double x, @Tip("y") double y, @Tip("z") double z){ 11 | return b(entity.getWorld(),x,y,z); 12 | } 13 | 14 | @Provide 15 | public Location b(@Tip("world") World world,@Tip("x") double x,@Tip("y") double y,@Tip("z") double z){ 16 | return new Location(world,x,y,z); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/main/MethodNodeCommandTest.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.BaseCommandManager; 4 | import engine.command.CommandFailure; 5 | import engine.command.CommandSender; 6 | import engine.command.anno.*; 7 | import engine.command.argument.Argument; 8 | import engine.command.argument.ArgumentManager; 9 | import engine.command.argument.SimpleArgumentManager; 10 | import engine.command.impl.SimpleCommandManager; 11 | import engine.command.suggestion.NamedSuggester; 12 | import engine.command.suggestion.SimpleSuggesterManager; 13 | import engine.command.suggestion.Suggester; 14 | import engine.command.suggestion.SuggesterManager; 15 | import engine.command.util.SuggesterHelper; 16 | import engine.command.util.context.Context; 17 | import engine.permission.HashPermissible; 18 | import org.junit.jupiter.api.Assertions; 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.*; 22 | 23 | 24 | public class MethodNodeCommandTest { 25 | 26 | private TestSender testSender = new TestSender("methodNodeTest", string -> message = string, commandException -> message = commandException.getType().name()); 27 | 28 | public String message; 29 | 30 | @Test 31 | public void argumentTest() { 32 | 33 | SimpleCommandManager simpleCommandManager = new SimpleCommandManager(); 34 | ArgumentManager argumentManager = new SimpleArgumentManager(); 35 | argumentManager.appendArgument(new Argument() { 36 | @Override 37 | public String getName() { 38 | return "testArgument"; 39 | } 40 | 41 | @Override 42 | public Class responsibleClass() { 43 | return String.class; 44 | } 45 | 46 | @Override 47 | public Optional parse(Context context, String arg) { 48 | return Optional.of("test" + arg); 49 | } 50 | 51 | @Override 52 | public Suggester getSuggester() { 53 | return ((sender, command, args) -> Collections.EMPTY_LIST); 54 | } 55 | }); 56 | 57 | argumentManager.setClassDefaultArgument(new Argument() { 58 | @Override 59 | public String getName() { 60 | return "random"; 61 | } 62 | 63 | @Override 64 | public Class responsibleClass() { 65 | return Random.class; 66 | } 67 | 68 | @Override 69 | public Optional parse(Context context, String arg) { 70 | return Optional.of(new Random(Integer.valueOf(arg))); 71 | } 72 | 73 | @Override 74 | public Suggester getSuggester() { 75 | return (sender, command, args) -> Collections.EMPTY_LIST; 76 | } 77 | }); 78 | 79 | MethodAnnotationCommand.getBuilder(simpleCommandManager) 80 | .setArgumentManager(argumentManager) 81 | .addCommandHandler(new ArgumentTestClass()) 82 | .register(); 83 | 84 | int randomSeed = 12314; 85 | 86 | simpleCommandManager.execute(testSender, "argument " + randomSeed + " argument"); 87 | 88 | Random random = new Random(randomSeed); 89 | Assertions.assertEquals(message, random.nextInt() + "testargument"); 90 | } 91 | 92 | public class ArgumentTestClass { 93 | @Command("argument") 94 | public void argument(Random random, @ArgumentHandler("testArgument") String argumentMessage) { 95 | message = Integer.valueOf(random.nextInt()).toString() + argumentMessage; 96 | } 97 | } 98 | 99 | @Test 100 | void suggestTest() { 101 | 102 | SimpleCommandManager simpleCommandManager = new SimpleCommandManager(); 103 | SuggesterManager suggesterManager = new SimpleSuggesterManager(); 104 | suggesterManager.putSuggester(new NamedSuggester() { 105 | @Override 106 | public String getName() { 107 | return "suggestTest"; 108 | } 109 | 110 | @Override 111 | public List suggest(CommandSender sender, String command, String[] args) { 112 | return List.of("test"); 113 | } 114 | }); 115 | 116 | MethodAnnotationCommand.getBuilder(simpleCommandManager) 117 | .addCommandHandler(new SuggesterTestClass()) 118 | .setSuggesterManager(suggesterManager) 119 | .register(); 120 | 121 | List completeResult = simpleCommandManager.complete(testSender, "suggest "); 122 | Assertions.assertEquals(1, completeResult.size()); 123 | Assertions.assertEquals("test", completeResult.get(0)); 124 | } 125 | 126 | public class SuggesterTestClass { 127 | @Command("suggest") 128 | public void suggest(@engine.command.anno.Suggester("suggestTest") String a) { 129 | } 130 | } 131 | 132 | @Test 133 | void provide() { 134 | Entity testEntity = new Entity() { 135 | HashPermissible permissible = new HashPermissible(); 136 | 137 | @Override 138 | public World getWorld() { 139 | return new World("EntityWorld"); 140 | } 141 | 142 | @Override 143 | public void sendMessage(String message) { 144 | } 145 | 146 | @Override 147 | public String getSenderName() { 148 | return "entity"; 149 | } 150 | 151 | @Override 152 | public void sendCommandFailure(CommandFailure failure) { 153 | System.out.println(failure.toString()); 154 | } 155 | 156 | @Override 157 | public boolean hasPermission(String permission) { 158 | return permissible.hasPermission(permission); 159 | } 160 | 161 | @Override 162 | public void setPermission(String permission, boolean bool) { 163 | permissible.setPermission(permission, bool); 164 | } 165 | 166 | @Override 167 | public void removePermission(String permission) { 168 | permissible.removePermission(permission); 169 | } 170 | 171 | @Override 172 | public void clearPermission() { 173 | permissible.clearPermission(); 174 | } 175 | 176 | @Override 177 | public Map toPermissionMap() { 178 | return permissible.toPermissionMap(); 179 | } 180 | }; 181 | 182 | testEntity.setPermission("player.admin", true); 183 | 184 | BaseCommandManager commandManager = new SimpleCommandManager(); 185 | ArgumentManager argumentManager = new SimpleArgumentManager(); 186 | argumentManager.setClassDefaultArgument(new WorldArgument()); 187 | 188 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 189 | .setArgumentManager(argumentManager) 190 | .addProvider(new LocationProvider()) 191 | .addCommandHandler(new ProvideTest()) 192 | .register(); 193 | 194 | 195 | World commandWorld = new World("commandWorld"); 196 | 197 | commandManager.execute(testEntity, "location 11 1 2 3 \"hello world\" commandWorld 4 5 6"); 198 | Assertions.assertEquals(message, 11 + new Location(testEntity.getWorld(), 1, 2, 3).toString() + "hello world" + new Location(commandWorld, 4, 5, 6).toString()); 199 | commandManager.execute(testEntity, "location 12 commandWorld 1 2 3 \"hello world\" 4 5 6"); 200 | Assertions.assertEquals(message, 12 + new Location(commandWorld, 1, 2, 3).toString() + "hello world" + new Location(testEntity.getWorld(), 4, 5, 6).toString()); 201 | commandManager.execute(testEntity, "location 13 commandWorld 1 2 3 \"hello world\" commandWorld 4 5 6"); 202 | Assertions.assertEquals(message, 13 + new Location(commandWorld, 1, 2, 3).toString() + "hello world" + new Location(commandWorld, 4, 5, 6).toString()); 203 | commandManager.execute(testEntity, "aaa bbb ccc"); 204 | } 205 | 206 | public class ProvideTest { 207 | @Command("location") 208 | @Permission("player.admin | player.location & player.teleport") 209 | public void location(int i, Location location, String b, Location location2) { 210 | message = i + location.toString() + b + location2.toString(); 211 | } 212 | 213 | @Command("location") 214 | public void location(int i, Location location) { 215 | message = i + location.toString(); 216 | } 217 | 218 | } 219 | 220 | @Test 221 | void commandTest1() { 222 | BaseCommandManager commandManager = new SimpleCommandManager(); 223 | 224 | ArgumentManager argumentManager = new SimpleArgumentManager(); 225 | argumentManager.setClassDefaultArgument(new WorldArgument()); 226 | 227 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 228 | .setArgumentManager(argumentManager) 229 | .addCommandHandler(new CommandTest()) 230 | .register(); 231 | } 232 | 233 | public class CommandTest { 234 | 235 | @Command("command") 236 | public void command2(@Sender CommandSender sender, @Required("a") String s) { 237 | message = sender.getSenderName() + "a"; 238 | } 239 | 240 | @Command("command") 241 | public void command1(@Sender CommandSender sender, @Required("a") String a, String s) { 242 | message = sender.getSenderName() + "a" + s; 243 | } 244 | 245 | @Command("command") 246 | public void command1(@Sender TestSender sender) { 247 | message = sender.getSenderName(); 248 | } 249 | 250 | } 251 | 252 | private HashMap bank = new HashMap<>(); 253 | 254 | @Test 255 | void moneyTest() { 256 | 257 | HashMap entityHashMap = new HashMap<>(); 258 | 259 | entityHashMap.put("asd", new TestSender("asd", null, null)); 260 | entityHashMap.put("123", new TestSender("123", null, null)); 261 | entityHashMap.put("zxc", new TestSender("zxc", null, null)); 262 | 263 | BaseCommandManager commandManager = new SimpleCommandManager(); 264 | ArgumentManager argumentManager = new SimpleArgumentManager(); 265 | argumentManager.setClassDefaultArgument(new Argument() { 266 | @Override 267 | public String getName() { 268 | return "TestSender"; 269 | } 270 | 271 | @Override 272 | public Class responsibleClass() { 273 | return TestSender.class; 274 | } 275 | 276 | @Override 277 | public Optional parse(Context context, String arg) { 278 | return Optional.ofNullable(entityHashMap.get(arg)); 279 | } 280 | 281 | @Override 282 | public String toString() { 283 | return "class:" + getClass().getName(); 284 | } 285 | 286 | @Override 287 | public Suggester getSuggester() { 288 | return null; 289 | } 290 | }); 291 | 292 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 293 | .setArgumentManager(argumentManager) 294 | .addCommandHandler(new moneyTest()) 295 | .register(); 296 | 297 | commandManager.execute(testSender, "money"); 298 | Assertions.assertEquals(message, "0.0"); 299 | 300 | commandManager.execute(testSender, "money asd"); 301 | Assertions.assertEquals(message, "0.0"); 302 | 303 | commandManager.execute(testSender, "money set 100"); 304 | commandManager.execute(testSender, "money"); 305 | Assertions.assertEquals(message, "100.0"); 306 | 307 | commandManager.execute(testSender, "money set asd 100"); 308 | commandManager.execute(testSender, "money asd"); 309 | Assertions.assertEquals(message, "100.0"); 310 | } 311 | 312 | public class moneyTest { 313 | 314 | @Command("money") 315 | public void money(@Sender CommandSender sender) { 316 | sender.sendMessage(bank.getOrDefault(sender.getSenderName(), 0d) + ""); 317 | } 318 | 319 | @Command("money") 320 | public void money(@Sender CommandSender sender, TestSender who) { 321 | sender.sendMessage(bank.getOrDefault(who.getSenderName(), 0d) + ""); 322 | } 323 | 324 | @Command("money") 325 | public void setMoney(@Sender CommandSender sender, @Required("set") String s, double many) { 326 | bank.put(sender.getSenderName(), many); 327 | } 328 | 329 | @Command("money") 330 | public void setMoney(@Sender CommandSender sender, @Required("set") String s, TestSender playerEntity, double many) { 331 | bank.put(playerEntity.getSenderName(), many); 332 | } 333 | 334 | } 335 | 336 | 337 | @Test 338 | void test2() { 339 | 340 | HashMap entityHashMap = new HashMap<>(); 341 | 342 | entityHashMap.put("asd", new TestSender("asd", null, null)); 343 | entityHashMap.put("123", new TestSender("123", null, null)); 344 | entityHashMap.put("zxc", new TestSender("zxc", null, null)); 345 | 346 | BaseCommandManager commandManager = new SimpleCommandManager(); 347 | ArgumentManager argumentManager = new SimpleArgumentManager(); 348 | argumentManager.setClassDefaultArgument(new Argument() { 349 | @Override 350 | public String getName() { 351 | return "TestSender"; 352 | } 353 | 354 | @Override 355 | public Class responsibleClass() { 356 | return TestSender.class; 357 | } 358 | 359 | @Override 360 | public Optional parse(Context context, String arg) { 361 | return Optional.ofNullable(entityHashMap.get(arg)); 362 | } 363 | 364 | @Override 365 | public String toString() { 366 | return "class:" + getClass().getName(); 367 | } 368 | 369 | @Override 370 | public Suggester getSuggester() { 371 | return (sender, command, args) -> SuggesterHelper.filterStartWith(new ArrayList<>(entityHashMap.keySet()), args[args.length - 1]); 372 | } 373 | }); 374 | 375 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 376 | .setArgumentManager(argumentManager) 377 | .addCommandHandler(new test2()) 378 | .register(); 379 | 380 | commandManager.execute(testSender, "test asd"); 381 | Assertions.assertEquals(message, "asd"); 382 | commandManager.execute(testSender, "test asd 123"); 383 | Assertions.assertEquals(message, "123"); 384 | commandManager.execute(testSender, "test 100"); 385 | Assertions.assertEquals(message, "100.0"); 386 | 387 | Assertions.assertEquals(commandManager.complete(testSender, "test asd ").toString(), "[123, asd, zxc]"); 388 | } 389 | 390 | public class test2 { 391 | @Command("test") 392 | public void testCommand1(@Sender TestSender sender, TestSender testSender) { 393 | message = testSender.getSenderName(); 394 | } 395 | 396 | @Command("test") 397 | public void testCommand2(TestSender sender, TestSender testSender) { 398 | message = testSender.getSenderName(); 399 | } 400 | 401 | @Command("test") 402 | public void testCommand2(@Sender TestSender sender, @Sender TestSender sender2, double s) { 403 | message = Double.toString(s); 404 | } 405 | 406 | 407 | } 408 | 409 | @Test 410 | public void teleportTest() { 411 | HashMap entityHashMap = new HashMap<>(); 412 | TestSender asdSender = new TestSender("asd", message -> this.message = message, null); 413 | TestSender sender = new TestSender("123", message -> this.message = message, null); 414 | TestSender zxcSender = new TestSender("zxc", message -> this.message = message, null); 415 | 416 | entityHashMap.put("asd", asdSender); 417 | entityHashMap.put("123", sender); 418 | entityHashMap.put("zxc", zxcSender); 419 | 420 | BaseCommandManager commandManager = new SimpleCommandManager(); 421 | ArgumentManager argumentManager = new SimpleArgumentManager(); 422 | argumentManager.setClassDefaultArgument(new WorldArgument()); 423 | argumentManager.setClassDefaultArgument(new Argument() { 424 | @Override 425 | public String getName() { 426 | return "TestSender"; 427 | } 428 | 429 | @Override 430 | public Class responsibleClass() { 431 | return TestSender.class; 432 | } 433 | 434 | @Override 435 | public Optional parse(Context context, String arg) { 436 | return Optional.ofNullable(entityHashMap.get(arg)); 437 | } 438 | 439 | @Override 440 | public String toString() { 441 | return "class:" + getClass().getName(); 442 | } 443 | 444 | @Override 445 | public Suggester getSuggester() { 446 | return (sender, command, args) -> SuggesterHelper.filterStartWith(new ArrayList<>(entityHashMap.keySet()), args[args.length - 1]); 447 | } 448 | }); 449 | 450 | 451 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 452 | .setArgumentManager(argumentManager) 453 | .addProvider(new LocationProvider()) 454 | .addCommandHandler(new teleportTestClass()) 455 | .register(); 456 | 457 | commandManager.execute(asdSender, "tp zxc"); 458 | Assertions.assertEquals(message, "asd tp zxc"); 459 | 460 | } 461 | 462 | public class teleportTestClass { 463 | 464 | @Command("tp") 465 | public void tp(@Sender TestSender sender, Location location) { 466 | sender.sendMessage(sender.getSenderName() + " tp " + location.toString()); 467 | } 468 | 469 | @Command("tp") 470 | public void tp(@Sender TestSender sender, TestSender player) { 471 | sender.sendMessage(sender.getSenderName() + " tp " + player.getSenderName()); 472 | } 473 | 474 | @Command("tp") 475 | public void tp1(TestSender player, Location location) { 476 | tp(player, location); 477 | } 478 | 479 | @Command("tp") 480 | public void tp1(TestSender player, TestSender location) { 481 | tp(player, location); 482 | } 483 | 484 | } 485 | 486 | 487 | } 488 | -------------------------------------------------------------------------------- /src/test/java/main/MultiThreadTest.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.BaseCommandManager; 4 | import engine.command.CommandSender; 5 | import engine.command.anno.Command; 6 | import engine.command.anno.NodeAnnotationCommand; 7 | import engine.command.anno.Sender; 8 | import engine.command.impl.SimpleCommandManager; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.Random; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | import java.util.function.Supplier; 15 | 16 | public class MultiThreadTest { 17 | 18 | String thread1; 19 | String thread2; 20 | String thread3; 21 | 22 | boolean stop = false; 23 | 24 | AtomicInteger executeTimes = new AtomicInteger(); 25 | 26 | BaseCommandManager commandManager; 27 | 28 | @Test 29 | public void test() { 30 | 31 | commandManager = new SimpleCommandManager(); 32 | 33 | NodeAnnotationCommand.METHOD.getBuilder(commandManager) 34 | .addCommandHandler(this) 35 | .register(); 36 | 37 | Thread t1 = new Thread(() -> { 38 | TestSender testSender = new TestSender("thread 1", null, null); 39 | Random random = new Random(System.currentTimeMillis()); 40 | while (!stop) { 41 | executeCommand("thread1", random, testSender, () -> thread1); 42 | } 43 | }); 44 | Thread t2 = new Thread(() -> { 45 | TestSender testSender = new TestSender("thread 2", null, null); 46 | Random random = new Random(System.currentTimeMillis()); 47 | while (!stop) { 48 | executeCommand("thread2", random, testSender, () -> thread2); 49 | } 50 | }); 51 | Thread t3 = new Thread(() -> { 52 | TestSender testSender = new TestSender("thread 3", null, null); 53 | Random random = new Random(System.currentTimeMillis()); 54 | while (!stop) { 55 | executeCommand("thread3", random, testSender, () -> thread3); 56 | } 57 | }); 58 | t1.setName("thread-1"); 59 | t2.setName("thread-2"); 60 | t3.setName("thread-3"); 61 | t1.start(); 62 | t2.start(); 63 | t3.start(); 64 | 65 | try { 66 | Thread.sleep(10 * 1000); 67 | } catch (InterruptedException e) { 68 | e.printStackTrace(); 69 | } 70 | stop = true; 71 | System.out.println("execute times: " + executeTimes.get()); 72 | } 73 | 74 | private void executeCommand(String threadName, Random random, TestSender sender, Supplier supplier) { 75 | String s; 76 | if (random.nextInt(2) == 0) 77 | s = String.valueOf(random.nextInt(100000)); 78 | else 79 | s = TestEnum.values()[random.nextInt(TestEnum.values().length)].name(); 80 | commandManager.execute(sender, "test " + threadName + " " + s); 81 | Assertions.assertEquals(supplier.get(), sender.getSenderName() + s); 82 | executeTimes.incrementAndGet(); 83 | } 84 | 85 | @Command("test") 86 | public void setString(@Sender CommandSender sender, String fieldName, String value) { 87 | switch (fieldName) { 88 | case "thread1": 89 | thread1 = sender.getSenderName() + value; 90 | break; 91 | case "thread2": 92 | thread2 = sender.getSenderName() + value; 93 | break; 94 | case "thread3": 95 | thread3 = sender.getSenderName() + value; 96 | break; 97 | } 98 | } 99 | 100 | @Command("test") 101 | public void setString(@Sender CommandSender sender, String fieldName, TestEnum value) { 102 | switch (fieldName) { 103 | case "thread1": 104 | thread1 = sender.getSenderName() + value.name(); 105 | break; 106 | case "thread2": 107 | thread2 = sender.getSenderName() + value.name(); 108 | break; 109 | case "thread3": 110 | thread3 = sender.getSenderName() + value.name(); 111 | break; 112 | } 113 | } 114 | 115 | public enum TestEnum { 116 | STOP, START, RUNNING, END 117 | } 118 | 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/main/NodeSortTest.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.CommandSender; 4 | import engine.command.argument.base.IntegerArgument; 5 | import engine.command.argument.base.StringArgument; 6 | import engine.command.util.node.*; 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class NodeSortTest { 10 | 11 | @Test 12 | void sortTest() { 13 | 14 | EmptyArgumentNode emptyArgumentNode = new EmptyArgumentNode(); 15 | 16 | ArgumentNode integerArgumentNode = new ArgumentNode(new IntegerArgument()); 17 | ArgumentNode argumentNode = new ArgumentNode(new StringArgument()); 18 | EnumNode enumNode = new EnumNode(TestEnum.class); 19 | MultiArgumentNode multiArgumentNode = new MultiArgumentNode(new ArgumentNode(new IntegerArgument()),null,1); 20 | MultiArgumentNode multiArgumentNode2 = new MultiArgumentNode(new RequiredNode("abc"),null,1); 21 | RequiredNode requiredNode = new RequiredNode("123"); 22 | SenderNode senderNode = new SenderNode(CommandSender.class); 23 | 24 | emptyArgumentNode.addChild(argumentNode); 25 | emptyArgumentNode.addChild(enumNode); 26 | emptyArgumentNode.addChild(multiArgumentNode); 27 | emptyArgumentNode.addChild(requiredNode); 28 | emptyArgumentNode.addChild(senderNode); 29 | emptyArgumentNode.addChild(integerArgumentNode); 30 | emptyArgumentNode.addChild(multiArgumentNode2); 31 | 32 | for(CommandNode node : emptyArgumentNode.getChildren()){ 33 | System.out.print(node.getClass().getName()); 34 | if(node instanceof ArgumentNode){ 35 | ArgumentNode argumentNode1 = (ArgumentNode) node; 36 | System.out.print(" "+argumentNode1.getArgument().getName()); 37 | } 38 | System.out.println(); 39 | } 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/main/TestEnum.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | public enum TestEnum { 4 | 5 | A,B,C 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/main/TestSender.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.CommandFailure; 4 | import engine.command.CommandSender; 5 | import engine.permission.HashPermissible; 6 | 7 | import java.util.Map; 8 | import java.util.function.Consumer; 9 | 10 | public class TestSender implements CommandSender { 11 | 12 | private String name; 13 | 14 | private HashPermissible permissible = new HashPermissible(); 15 | 16 | private Consumer sendConsumer; 17 | 18 | private Consumer commandExceptionConsumer; 19 | 20 | public TestSender(String name, Consumer sendConsumer, Consumer commandExceptionConsumer) { 21 | this.name = name; 22 | this.sendConsumer = sendConsumer; 23 | this.commandExceptionConsumer = commandExceptionConsumer; 24 | } 25 | 26 | @Override 27 | public void sendMessage(String message) { 28 | if (sendConsumer != null) 29 | sendConsumer.accept(message); 30 | } 31 | 32 | @Override 33 | public String getSenderName() { 34 | return name; 35 | } 36 | 37 | @Override 38 | public void sendCommandFailure(CommandFailure failure) { 39 | System.out.println(failure); 40 | if (commandExceptionConsumer != null) 41 | commandExceptionConsumer.accept(failure); 42 | } 43 | 44 | @Override 45 | public boolean hasPermission(String permission) { 46 | return permissible.hasPermission(permission); 47 | } 48 | 49 | @Override 50 | public void setPermission(String permission, boolean bool) { 51 | permissible.setPermission(permission, bool); 52 | } 53 | 54 | @Override 55 | public void removePermission(String permission) { 56 | permissible.removePermission(permission); 57 | } 58 | 59 | @Override 60 | public void clearPermission() { 61 | permissible.clearPermission(); 62 | } 63 | 64 | @Override 65 | public Map toPermissionMap() { 66 | return permissible.toPermissionMap(); 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "TestSender{" + 72 | "name='" + name + '\'' + 73 | '}'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/main/World.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | public class World { 4 | 5 | private String worldName; 6 | 7 | public World(String worldName) { 8 | this.worldName = worldName; 9 | } 10 | 11 | public String getWorldName() { 12 | return worldName; 13 | } 14 | 15 | public void setWorldName(String worldName) { 16 | this.worldName = worldName; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return "World{" + 22 | "worldName='" + worldName + '\'' + 23 | '}'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/main/WorldArgument.java: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class WorldArgument extends Argument { 11 | @Override 12 | public String getName() { 13 | return "World"; 14 | } 15 | 16 | @Override 17 | public Class responsibleClass() { 18 | return World.class; 19 | } 20 | 21 | @Override 22 | public Optional parse(Context context, String arg) { 23 | return Optional.of(new World(arg)); 24 | } 25 | 26 | @Override 27 | public Suggester getSuggester() { 28 | return (sender, command, args) -> List.of("world"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/main/swing/ConsoleSender.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | 4 | import engine.command.CommandFailure; 5 | import engine.command.CommandSender; 6 | 7 | import java.util.Map; 8 | 9 | public class ConsoleSender implements CommandSender { 10 | @Override 11 | public String getSenderName() { 12 | return "console"; 13 | } 14 | 15 | @Override 16 | public void sendMessage(String message) { 17 | System.out.println(message); 18 | } 19 | 20 | @Override 21 | public void sendCommandFailure(CommandFailure failure) { 22 | System.out.println(failure.toString()); 23 | } 24 | 25 | @Override 26 | public boolean hasPermission(String permission) { 27 | return true; 28 | } 29 | 30 | @Override 31 | public void setPermission(String permission, boolean bool) { 32 | throw new UnsupportedOperationException("Cannot set permission to console"); 33 | } 34 | 35 | @Override 36 | public void removePermission(String permission) { 37 | throw new UnsupportedOperationException("Cannot remove permission to console"); 38 | } 39 | 40 | @Override 41 | public void clearPermission() { 42 | throw new UnsupportedOperationException("Cannot clear permission to console"); 43 | } 44 | 45 | @Override 46 | public Map toPermissionMap() { 47 | return Map.of(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/main/swing/EntityArgument.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import engine.command.argument.Argument; 4 | import engine.command.suggestion.Suggester; 5 | import engine.command.util.context.Context; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.stream.Collectors; 10 | 11 | public class EntityArgument extends Argument { 12 | @Override 13 | public String getName() { 14 | return "Entity"; 15 | } 16 | 17 | @Override 18 | public Class responsibleClass() { 19 | return SwingEntity.class; 20 | } 21 | 22 | @Override 23 | public Optional parse(Context context, String arg) { 24 | return Optional.ofNullable(SwingTest.getInstance().getEntityManager().getEntity(arg)); 25 | } 26 | 27 | @Override 28 | public Suggester getSuggester() { 29 | return (sender, command, args) -> { 30 | List entityNames = SwingTest.getInstance().getEntityManager().getEntities().stream().map(SwingEntity::getName).collect(Collectors.toList()); 31 | if (args != null && !args[args.length - 1].isEmpty()) 32 | return entityNames.stream().filter(name -> name.startsWith(args[args.length - 1])).collect(Collectors.toList()); 33 | return entityNames; 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/main/swing/EntityManager.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class EntityManager { 7 | 8 | private List entities = new ArrayList<>(); 9 | 10 | public void addEntity(SwingEntity swingEntity){ 11 | entities.add(swingEntity); 12 | } 13 | 14 | public List getEntities(){ 15 | return entities; 16 | } 17 | 18 | public SwingEntity getEntity(String name){ 19 | return entities.stream().filter(swingEntity -> swingEntity.getName().equals(name)).findAny().orElse(null); 20 | } 21 | 22 | public void removeEntity(SwingEntity swingEntity){ 23 | this.entities.remove(swingEntity); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/main/swing/MethodCommand.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import engine.command.CommandSender; 4 | import engine.command.anno.Command; 5 | import engine.command.anno.Permission; 6 | import engine.command.anno.Sender; 7 | import engine.command.anno.Tip; 8 | import main.Location; 9 | 10 | public class MethodCommand { 11 | 12 | @Command("dress") 13 | @Permission("dress") 14 | public void dress(@Tip("who") SwingEntity who) { 15 | if (who.hasPermission("dress")) 16 | System.out.println(who.getName() + "穿上了裙子"); 17 | else System.out.println(who.getName()+" 无权穿上裙子!"); 18 | } 19 | 20 | @Command("tell") 21 | public void tell(@Sender CommandSender sender, @Tip("who") SwingEntity swingEntity, @Tip("message") String message) { 22 | swingEntity.sendMessage(" [" + sender.getSenderName() + "]: " + message); 23 | } 24 | 25 | @Command("addEntity") 26 | public void addEntity(@Tip("name") String entityName) { 27 | SwingTest.getInstance().getEntityManager().addEntity(new SwingEntity(entityName)); 28 | } 29 | 30 | @Command("removeEntity") 31 | public void removeEntity(@Tip("entity") SwingEntity swingEntity) { 32 | SwingTest.getInstance().getEntityManager().removeEntity(swingEntity); 33 | } 34 | 35 | @Command("permission") 36 | public void setPermission(@Tip("entity") SwingEntity swingEntity, @Tip("permission") String permission, @Tip("true/false") boolean bool) { 37 | swingEntity.setPermission(permission, bool); 38 | } 39 | 40 | @Command("test") 41 | public void commandTest(@Sender CommandSender sender, Location location){ 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/main/swing/SwingEntity.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import engine.command.CommandFailure; 4 | import engine.permission.HashPermissible; 5 | import main.Entity; 6 | import main.World; 7 | 8 | import java.util.Map; 9 | 10 | public class SwingEntity implements Entity { 11 | 12 | private static World world = new World("SwingWorld"); 13 | private String name; 14 | private HashPermissible permissible = new HashPermissible(); 15 | 16 | public SwingEntity(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | @Override 25 | public String getSenderName() { 26 | return getName(); 27 | } 28 | 29 | @Override 30 | public void sendCommandFailure(CommandFailure failure) { 31 | sendMessage(failure.toString()); 32 | } 33 | 34 | @Override 35 | public void sendMessage(String message) { 36 | System.out.println(name + " receive message:" + message); 37 | } 38 | 39 | @Override 40 | public boolean hasPermission(String permission) { 41 | return permissible.hasPermission(permission); 42 | } 43 | 44 | @Override 45 | public void setPermission(String permission, boolean bool) { 46 | permissible.setPermission(permission, bool); 47 | } 48 | 49 | @Override 50 | public void removePermission(String permission) { 51 | permissible.removePermission(permission); 52 | } 53 | 54 | @Override 55 | public void clearPermission() { 56 | permissible.clearPermission(); 57 | } 58 | 59 | @Override 60 | public Map toPermissionMap() { 61 | return permissible.toPermissionMap(); 62 | } 63 | 64 | @Override 65 | public World getWorld() { 66 | return world; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/main/swing/SwingTest.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import engine.command.ArgumentCheckResult; 4 | import engine.command.BaseCommandManager; 5 | import engine.command.CommandManager; 6 | import engine.command.CommandParser; 7 | import engine.command.anno.MethodAnnotationCommand; 8 | import engine.command.argument.ArgumentManager; 9 | import engine.command.argument.SimpleArgumentManager; 10 | import engine.command.impl.SimpleCommandManager; 11 | import engine.command.suggestion.SimpleSuggesterManager; 12 | import engine.command.suggestion.SuggesterManager; 13 | import main.LocationProvider; 14 | import main.WorldArgument; 15 | 16 | import javax.swing.*; 17 | import java.awt.*; 18 | import java.awt.event.AWTEventListener; 19 | import java.awt.event.KeyAdapter; 20 | import java.awt.event.KeyEvent; 21 | import java.lang.reflect.Field; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | 27 | public class SwingTest { 28 | 29 | private static SwingTest INSTANCE; 30 | private SimpleCommandManager commandManager = new SimpleCommandManager(); 31 | private SuggesterManager suggesterManager = new SimpleSuggesterManager(); 32 | private ArgumentManager argumentManager = new SimpleArgumentManager(); 33 | private ConsoleSender consoleSender = new ConsoleSender(); 34 | private EntityManager entityManager = new EntityManager(); 35 | 36 | private CommandParser resolve; 37 | 38 | public SwingTest() { 39 | INSTANCE = this; 40 | } 41 | 42 | public static SwingTest getInstance() { 43 | return INSTANCE; 44 | } 45 | 46 | public EntityManager getEntityManager() { 47 | return entityManager; 48 | } 49 | 50 | public static void main(String[] args) { 51 | SwingTest swingTest = new SwingTest(); 52 | swingTest.commandReady(); 53 | swingTest.show(); 54 | } 55 | 56 | public void commandReady() { 57 | 58 | EntityArgument entityArgument = new EntityArgument(); 59 | 60 | argumentManager.setClassDefaultArgument(entityArgument); 61 | argumentManager.setClassDefaultArgument(new WorldArgument()); 62 | 63 | MethodAnnotationCommand.getBuilder(commandManager) 64 | .setArgumentManager(argumentManager) 65 | .addProvider(new LocationProvider()) 66 | .setSuggesterManager(suggesterManager) 67 | .addCommandHandler(new MethodCommand()) 68 | .register(); 69 | 70 | 71 | try { 72 | Field field = BaseCommandManager.class.getDeclaredField("resolver"); 73 | field.setAccessible(true); 74 | resolve = (CommandParser) field.get(commandManager); 75 | } catch (NoSuchFieldException | IllegalAccessException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | public CommandManager getCommandManager() { 81 | return commandManager; 82 | } 83 | 84 | public SuggesterManager getSuggesterManager() { 85 | return suggesterManager; 86 | } 87 | 88 | public ArgumentManager getArgumentManager() { 89 | return argumentManager; 90 | } 91 | 92 | public void show() { 93 | JFrame jFrame = new JFrame("Swing example"); 94 | 95 | jFrame.setSize(800, 800); 96 | jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 97 | 98 | jFrame.setLayout(new BorderLayout()); 99 | 100 | JTextArea textArea = new TextArea(); 101 | JTextField textField = new JTextField(); 102 | JLabel label = new JLabel(""); 103 | 104 | jFrame.add(textArea, BorderLayout.CENTER); 105 | jFrame.add(textField, BorderLayout.SOUTH); 106 | 107 | textArea.setLayout(new BorderLayout()); 108 | textArea.add(label, BorderLayout.SOUTH); 109 | 110 | textArea.setEditable(false); 111 | 112 | textField.addKeyListener(new KeyAdapter() { 113 | 114 | @Override 115 | public void keyReleased(KeyEvent e) { 116 | String text = textField.getText(); 117 | if (e.getKeyChar() == KeyEvent.VK_ENTER) { 118 | System.out.println(text); 119 | if (text.startsWith("/")) 120 | commandManager.execute(consoleSender, text.substring(1)); 121 | textField.setText(""); 122 | setTips(); 123 | } else if (e.getKeyChar() == KeyEvent.VK_SPACE) { 124 | CommandParser.Result result = resolve.parse(text.substring(1)); 125 | ArgumentCheckResult argumentCheckResult = commandManager.checkLastArgument(consoleSender, result.getName(), Arrays.copyOfRange(result.getArgs(), 0, result.getArgs().length - 1)); 126 | if (!argumentCheckResult.isValid()) { 127 | System.out.println(argumentCheckResult.getHelpMessage()); 128 | } 129 | if (text.startsWith("/")) { 130 | setTips(); 131 | } 132 | } else { 133 | if (text.startsWith("/")) { 134 | setTips(); 135 | } 136 | } 137 | } 138 | 139 | private void setTips() { 140 | if (textField.getText().isEmpty()) { 141 | label.setText(""); 142 | return; 143 | } 144 | String text = textField.getText().substring(1); 145 | CommandParser.Result result = resolve.parse(text); 146 | if (result.getName() != null && result.getArgs().length != 0) { 147 | List tips = commandManager.getTips(consoleSender, result.getName(), result.getArgs()); 148 | String tipsString = tips.stream().map(str -> "<" + str + "> ").collect(Collectors.joining()); 149 | String space = getSpace("/" + result.getName() + " " + Arrays.stream(Arrays.copyOfRange(result.getArgs(), 0, result.getArgs().length - 1)).map(str -> str + " ").collect(Collectors.joining()) + " "); 150 | label.setText(space + tipsString); 151 | } else label.setText(""); 152 | } 153 | 154 | private String getSpace(String s) { 155 | FontMetrics fontMetrics = textField.getFontMetrics(textField.getFont()); 156 | int textWidth = fontMetrics.stringWidth(s); 157 | int halfWidth = fontMetrics.charWidth(' '); 158 | int fullWidth = fontMetrics.charWidth(' '); 159 | int fullNum = 0; 160 | while (true) { 161 | 162 | if ((textWidth - fullWidth * fullNum) % halfWidth <= 2) { 163 | StringBuilder sb = new StringBuilder(); 164 | for (int i = 0; i < (textWidth - fullWidth * fullNum) / halfWidth; i++) 165 | sb.append(' '); 166 | for (int i = 0; i < fullNum; i++) 167 | sb.append(' '); 168 | return sb.toString(); 169 | } 170 | fullNum++; 171 | } 172 | } 173 | 174 | }); 175 | 176 | Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { 177 | 178 | List completeList = new ArrayList<>(); 179 | int completeIndex = 0; 180 | 181 | @Override 182 | public void eventDispatched(AWTEvent event) { 183 | if (event instanceof KeyEvent && event.getID() == KeyEvent.KEY_PRESSED) { 184 | KeyEvent keyEvent = (KeyEvent) event; 185 | if (keyEvent.getKeyChar() == KeyEvent.VK_TAB) { 186 | keyEvent.consume(); 187 | 188 | String text = textField.getText(); 189 | if (text.startsWith("/")) { 190 | CommandParser.Result result = resolve.parse(text.substring(1)); 191 | if (result.getArgs() == null || result.getArgs().length == 0) { 192 | if (!completeList.contains(result.getName())) { 193 | completeList = commandManager.complete(consoleSender, result.getName(), result.getArgs()); 194 | System.out.println("suggest: " + completeList.toString()); 195 | completeIndex = -1; 196 | } 197 | if (completeList.isEmpty()) 198 | return; 199 | completeIndex++; 200 | completeIndex %= completeList.size(); 201 | setCommand(completeList.get(completeIndex), result.getArgs()); 202 | } else { 203 | if (!completeList.contains(result.getArgs()[result.getArgs().length - 1])) { 204 | completeList = commandManager.complete(consoleSender, result.getName(), result.getArgs()); 205 | System.out.println("suggest: " + completeList.toString()); 206 | completeIndex = -1; 207 | } 208 | if (completeList.isEmpty()) 209 | return; 210 | completeIndex++; 211 | completeIndex %= completeList.size(); 212 | result.getArgs()[result.getArgs().length - 1] = completeList.get(completeIndex); 213 | setCommand(result.getName(), result.getArgs()); 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | private void setCommand(String command, String[] args) { 221 | StringBuilder stringBuilder = new StringBuilder(); 222 | stringBuilder.append("/").append(command); 223 | Arrays.stream(args).forEach(arg -> stringBuilder.append(" " + arg)); 224 | textField.setText(stringBuilder.toString()); 225 | } 226 | }, AWTEvent.KEY_EVENT_MASK); 227 | 228 | jFrame.setVisible(true); 229 | } 230 | 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/test/java/main/swing/TextArea.java: -------------------------------------------------------------------------------- 1 | package main.swing; 2 | 3 | import javax.swing.*; 4 | import java.io.PrintStream; 5 | import java.util.concurrent.CopyOnWriteArrayList; 6 | 7 | public class TextArea extends JTextArea { 8 | 9 | private CopyOnWriteArrayList cache = new CopyOnWriteArrayList<>(); 10 | private boolean stop = false; 11 | 12 | public TextArea() { 13 | 14 | Thread thread = new Thread(() -> { 15 | 16 | Runtime.getRuntime().addShutdownHook(new Thread(() -> stop = true)); 17 | 18 | while (!stop) { 19 | for (String s : cache) 20 | append(s); 21 | cache.clear(); 22 | try { 23 | Thread.sleep(50); 24 | } catch (InterruptedException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | }); 29 | 30 | thread.start(); 31 | 32 | System.setOut(new PrintStream(System.out) { 33 | public void println(String x) { 34 | TextArea.this.cache.add(x + "\n"); 35 | } 36 | 37 | public void println(Object o) { 38 | println(o.toString()); 39 | } 40 | }); 41 | } 42 | } 43 | --------------------------------------------------------------------------------