├── .gitattributes ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── _config.yml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── me │ └── rojo8399 │ └── placeholderapi │ ├── Attach.java │ ├── ExpansionBuilder.java │ ├── IExpansion.java │ ├── Listening.java │ ├── NoValueException.java │ ├── Observer.java │ ├── Placeholder.java │ ├── PlaceholderService.java │ ├── Relational.java │ ├── Requires.java │ ├── Source.java │ ├── Token.java │ └── impl │ ├── Metrics.java │ ├── PlaceholderAPIPlugin.java │ ├── PlaceholderServiceImpl.java │ ├── commands │ ├── InfoCommand.java │ ├── ListCommand.java │ ├── ParseCommand.java │ └── RefreshCommand.java │ ├── configs │ ├── Config.java │ ├── JavascriptManager.java │ └── Messages.java │ ├── placeholder │ ├── Defaults.java │ ├── Expansion.java │ ├── ExpansionBuilderImpl.java │ ├── Store.java │ └── gen │ │ ├── ClassPlaceholderFactory.java │ │ ├── DefineableClassLoader.java │ │ └── InternalExpansion.java │ └── utils │ ├── TextUtils.java │ ├── TypeUtils.java │ ├── Version.java │ └── VersionRange.java └── resources └── assets └── placeholderapi ├── Logo.png └── config.conf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | To contribute to PlaceholderAPI, please follow these guidelines: 2 | 3 | ## Code formatting 4 | Make sure your code follows the [Java Code Conventions](http://www.oracle.com/technetwork/java/codeconventions-150003.pdf). Most modern compilers will have an auto-formatting option, so it is best to use that. 5 | 6 | ## Compiling 7 | Before submitting changes, make sure your code compiles + builds properly. You can use `gradle build` to compile and build your code automatically, so if the build passes you are good to go. 8 | 9 | ## Submitting a PR 10 | To submit your changes, create a pull request with a relatively short description of your changes. If the PR is not complete, please indicate that it is a `[WIP]` pull request. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Please provide these fields before submitting an issue. 2 | 3 | SpongeAPI version: 4 | 5 | PlaceholderAPI version: 6 | 7 | Plugins installed: 8 | 9 | Error log [if any]: (please use PasteBin or similar services to shorten error logs) 10 | 11 | Description: 12 | 13 | Steps to reproduce: 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build # 2 | ######### 3 | MANIFEST.MF 4 | dependency-reduced-pom.xml 5 | .checkstyle 6 | 7 | # Compiled # 8 | ############ 9 | bin 10 | build 11 | dist 12 | lib 13 | out 14 | run 15 | /target 16 | *.com 17 | *.class 18 | *.dll 19 | *.exe 20 | *.o 21 | *.so 22 | 23 | # Databases # 24 | ############# 25 | *.db 26 | *.sql 27 | *.sqlite 28 | 29 | # Packages # 30 | ############ 31 | *.7z 32 | *.dmg 33 | *.gz 34 | *.iso 35 | *.rar 36 | *.tar 37 | *.zip 38 | 39 | # Repository # 40 | ############## 41 | .git 42 | 43 | # Logging # 44 | ########### 45 | /logs 46 | *.log 47 | 48 | # Misc # 49 | ######## 50 | *.bak 51 | 52 | # System # 53 | ########## 54 | .DS_Store 55 | ehthumbs.db 56 | Thumbs.db 57 | 58 | # Project # 59 | ########### 60 | .classpath 61 | .externalToolBuilders 62 | .gradle 63 | .nb-gradle 64 | .idea 65 | .project 66 | .settings 67 | nbproject 68 | atlassian-ide-plugin.xml 69 | build.xml 70 | nb-configuration.xml 71 | *.iml 72 | *.ipr 73 | *.iws 74 | *.launch 75 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: java 3 | jdk: 4 | - oraclejdk8 5 | 6 | notifications: 7 | email: false 8 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Wundero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlaceholderAPI [![Build Status](https://travis-ci.org/rojo8399/PlaceholderAPI.svg?branch=master)](https://travis-ci.org/rojo8399/PlaceholderAPI) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) 2 | [![forthebadge](http://forthebadge.com/images/badges/made-with-crayons.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/fuck-it-ship-it.svg)](http://forthebadge.com) 3 | 4 | If you were sent here from another plugin, simply download this plugin and install it in your mods folder. After the first start of this plugin, you will need to enable the default expansions in the config file to be able to use those placeholders. Example if you want Player placeholders, open the config and set Player to True. Now you can use %player_name% as a placeholder. This same method applies for every available expansion. 5 | 6 | **There is a chance that a plugin may update and cause one of the internal placeholder hooks to break. If this happens, please report the issue immediately so we can update the placeholder hook to the latest version of the plugin that broke.** 7 | 8 | ---------- 9 | 10 | ## FEATURES 11 | 12 | * No lag 13 | * Fast updates 14 | * Easy contribution 15 | 16 | ### Planned 17 | * More built in placeholders 18 | * Vast plugin support (I'll try to create PR's to help the process) 19 | 20 | ---------- 21 | 22 | ## USAGE 23 | ### Server Owners 24 | Just drop this plugin into your mods folder and enable any default expansions you want to use in the config file. 25 | ### Developers 26 | View tutorials on how to use the API **[here](https://github.com/rojo8399/PlaceholderAPI/wiki/Developers/)**. 27 | 28 | If you're looking to contribute, the source code is available **[here](https://github.com/rojo8399/PlaceholderAPI)**. 29 | 30 | ---------- 31 | 32 | ## BUGS 33 | If you find any bugs or would like to suggest features, please create an issue **[here](https://github.com/rojo8399/PlaceholderAPI/issues)** 34 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.spongepowered.plugin' version '0.8.1' 3 | } 4 | 5 | group = 'me.rojo8399.placeholderapi' 6 | version = '4.5.1' 7 | description = 'An API for all of your placeholders.' 8 | 9 | compileJava.options.encoding = 'UTF-8' 10 | 11 | dependencies { 12 | compile 'org.slf4j:slf4j-api:1.7.21' 13 | compile 'org.reflections:reflections:0.9.10' 14 | compile 'ninja.leaping.configurate:configurate-hocon:3.2' 15 | compile 'org.spongepowered:spongeapi:7.1.0-SNAPSHOT' 16 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronaldburns/PlaceholderAPI/b58a67b5e5ee30697578d8e872bcd89c6f85e4a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 15 21:42:54 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'PlaceholderAPI' 2 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Attach.java: -------------------------------------------------------------------------------- 1 | package me.rojo8399.placeholderapi; 2 | 3 | import static java.lang.annotation.ElementType.FIELD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Attach this configurable field to a placeholder. 12 | * 13 | * The field this is attached to needs the Setting annotation as well. 14 | * 15 | * @author Wundero 16 | * 17 | */ 18 | @Documented 19 | @Retention(RUNTIME) 20 | @Target(FIELD) 21 | public @interface Attach { 22 | 23 | /** 24 | * Whether the placeholder is relational. 25 | */ 26 | boolean relational() default false; 27 | 28 | /** 29 | * The placeholder to attach to. 30 | */ 31 | String value(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/ExpansionBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import java.net.URL; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.Optional; 30 | import java.util.function.BiConsumer; 31 | import java.util.function.BiFunction; 32 | import java.util.function.Consumer; 33 | import java.util.function.Function; 34 | import java.util.function.Predicate; 35 | import java.util.function.Supplier; 36 | 37 | import me.rojo8399.placeholderapi.impl.placeholder.Expansion; 38 | 39 | /** 40 | * @author Wundero 41 | * 42 | */ 43 | public interface ExpansionBuilder> { 44 | /** 45 | * An interface which represents the parse function to execute. Used as 46 | * execution base. 47 | * 48 | * @author Wundero 49 | * 50 | * @param 51 | * The source parameter. 52 | * @param 53 | * The observer parameter. 54 | * @param 55 | * The value return type. 56 | */ 57 | @FunctionalInterface 58 | interface ExpansionFunction { 59 | /** 60 | * Parse the placeholder for the provided arguments. 61 | * 62 | * @param source 63 | * The source of the placeholder. 64 | * @param observer 65 | * The observer of the placeholder. 66 | * @param token 67 | * The token describing the placeholder. 68 | * @return The parsed value. 69 | * @throws Exception 70 | * Thrown if anyting goes wrong. 71 | */ 72 | V parse(S source, O observer, Optional token) throws Exception; 73 | } 74 | 75 | /** 76 | * Adds to the list of supported tokens for the expansion. 77 | * 78 | * @param tokens 79 | * The supported tokens. 80 | * @return This builder. 81 | */ 82 | B addTokens(List tokens); 83 | 84 | /** 85 | * Adds to the list of supported tokens for the expansion. 86 | * 87 | * @param tokens 88 | * The supported tokens. 89 | * @return This builder. 90 | */ 91 | default B addTokens(String... tokens) { 92 | return addTokens(Arrays.asList(tokens)); 93 | } 94 | 95 | /** 96 | * Sets the author of the expansion. 97 | * 98 | * @param author 99 | * The author of the expansion. 100 | * @return This builder. 101 | */ 102 | B author(String author); 103 | 104 | /** 105 | * This method will build the expansion. This is not a terminating operation on 106 | * the builder, so if you wish to reuse the builder under a new id, you could do 107 | * so. 108 | * 109 | * This method will throw an exception if the id, plugin or function have not 110 | * been specified. 111 | * 112 | * It is recommended NOT to use this method and instead use 113 | * {@link ExpansionBuilder#buildAndRegister()}. 114 | * 115 | * @return The expansion, if successfully built. 116 | * @throws Exception 117 | * If the expansion cannot be created. 118 | */ 119 | IExpansion build() throws Exception; 120 | 121 | /** 122 | * This method will build the expansion and then attemp to register the 123 | * expansion. This is not a terminating operation on the builder, so if you wish 124 | * to reuse the builder under a new id, you could do so. 125 | * 126 | * This method will throw an exception if the id, plugin or function have not 127 | * been specified. 128 | * 129 | * @return Whether the registration was successful. 130 | * @throws Exception 131 | * If the expansion cannot be created or registered. 132 | */ 133 | boolean buildAndRegister() throws Exception; 134 | 135 | /** 136 | * Add an object which holds config values. This object will be populated with 137 | * configuration options when the expansion is loaded and reloaded. 138 | * 139 | * @param config 140 | * The object to populate. 141 | * @return This builder. 142 | */ 143 | B config(Object config); 144 | 145 | /** 146 | * Execute a function for the parsing of this expansion. 147 | * 148 | * @param exec 149 | * The function to execute. 150 | * @return This builder. 151 | */ 152 | default B consumeDual(BiConsumer exec) { 153 | return function((s, o, t) -> { 154 | exec.accept(s, o); 155 | return null; 156 | }); 157 | } 158 | 159 | /** 160 | * Execute a function for the parsing of this expansion. 161 | * 162 | * @param exec 163 | * The function to execute. 164 | * @return This builder. 165 | */ 166 | default B consumeSingle(Consumer exec) { 167 | return function((s, o, t) -> { 168 | exec.accept(s); 169 | return null; 170 | }); 171 | } 172 | 173 | /** 174 | * Execute a function for the parsing of this expansion. 175 | * 176 | * @param exec 177 | * The function to execute. 178 | * @return This builder. 179 | */ 180 | default B consumeSingleToken(BiConsumer> exec) { 181 | return function((s, o, t) -> { 182 | exec.accept(s, t); 183 | return null; 184 | }); 185 | } 186 | 187 | /** 188 | * Execute a function for the parsing of this expansion. 189 | * 190 | * @param exec 191 | * The function to execute. 192 | * @return This builder. 193 | */ 194 | default B consumeToken(Consumer> exec) { 195 | return function((s, o, t) -> { 196 | exec.accept(t); 197 | return null; 198 | }); 199 | } 200 | 201 | /** 202 | * Utility method for getting the current state of the builder. Mainly just for 203 | * the methods in this interface. 204 | * 205 | * @return This builder, unchanged. 206 | */ 207 | B current(); 208 | 209 | /** 210 | * Sets the description of the expansion. 211 | * 212 | * @param description 213 | * The description of the expansion. 214 | * @return This builder. 215 | */ 216 | B description(String description); 217 | 218 | /** 219 | * Copy settings from the expansion provided in order to modify it. 220 | * 221 | * @param exp 222 | * The expansion from which to draw values. 223 | * @return This builder, with all fields modified to suit the expansion 224 | * provided. 225 | */ 226 | B from(Expansion exp); 227 | 228 | /** 229 | * Create a builder for a method in the object. 230 | * 231 | * This will attempt to find a method which has the "@Placeholder" annotation 232 | * linked to the provided id. If it exists, it will create the builder. This 233 | * will attempt to use the relational field to determine if it can parse that 234 | * method, which will by default be the normal method. If it does not find that 235 | * method, it will check for one which is not relational. 236 | * 237 | * This method alters the function, the plugin, the id and the relational 238 | * fields. Any other previously existing fields will be conserved. 239 | * 240 | * The provided id must exist on at least one method. Any repeated ids will be 241 | * ignored unless they are of different relational status. 242 | * 243 | * @param handle 244 | * The object containing the placeholder method. 245 | * @param id 246 | * The id of the placeholder method. 247 | * @param plugin 248 | * The plugin holding the expansion. 249 | * @return This builder. 250 | */ 251 | B from(Object handle, String id, Object plugin); 252 | 253 | /** 254 | * Copy settings from the expansion provided in order to modify it. This method 255 | * ignores the types of the provided expansion and attempts to cast them to its 256 | * own. 257 | * 258 | * @param exp 259 | * The expansion from which to draw values. 260 | * @return This builder, with all fields modified to suit the expansion 261 | * provided. 262 | * @throws IllegalArgumentException 263 | * If the provided expansion's fields do not match or are not 264 | * subclasses of this builder's fields. 265 | */ 266 | B fromUnknown(Expansion exp); 267 | 268 | /** 269 | * Execute a function for the parsing of this expansion. ExpansionFunction is a 270 | * functional interface, meaning the method code may use lambdas. 271 | * 272 | * @param function 273 | * The function to execute. 274 | * @return This builder. 275 | */ 276 | B function(ExpansionFunction function); 277 | 278 | /** 279 | * Execute a function for the parsing of this expansion. 280 | * 281 | * @param exec 282 | * The function to execute. 283 | * @return This builder. 284 | */ 285 | default B functionDual(BiFunction exec) { 286 | return function((s, o, t) -> exec.apply(s, o)); 287 | } 288 | 289 | /** 290 | * Execute a function for the parsing of this expansion. 291 | * 292 | * @param exec 293 | * The function to execute. 294 | * @return This builder. 295 | */ 296 | default B functionSingle(Function exec) { 297 | return function((s, o, t) -> exec.apply(s)); 298 | } 299 | 300 | /** 301 | * Execute a function for the parsing of this expansion. 302 | * 303 | * @param exec 304 | * The function to execute. 305 | * @return This builder. 306 | */ 307 | default B functionSingleToken(BiFunction, V> exec) { 308 | return function((s, o, t) -> exec.apply(s, t)); 309 | } 310 | 311 | /** 312 | * Execute a function for the parsing of this expansion. 313 | * 314 | * @param exec 315 | * The function to execute. 316 | * @return This builder. 317 | */ 318 | default B functionToken(Function, V> exec) { 319 | return function((s, o, t) -> exec.apply(t)); 320 | } 321 | 322 | /** 323 | * @return The author of the expansion. 324 | */ 325 | String getAuthor(); 326 | 327 | /** 328 | * @return The description of the expansion. 329 | */ 330 | String getDescription(); 331 | 332 | /** 333 | * @return The id of the expansion. 334 | */ 335 | String getId(); 336 | 337 | /** 338 | * @return The supported tokens for the expansion. 339 | */ 340 | List getTokens(); 341 | 342 | /** 343 | * @return The URL for the expansion. 344 | * @throws Exception 345 | * - If the url is not properly formatted or is null. 346 | */ 347 | URL getUrl() throws Exception; 348 | 349 | /** 350 | * @return The url for the expansion. 351 | */ 352 | String getUrlString(); 353 | 354 | /** 355 | * @return The version of the expansion. 356 | */ 357 | String getVersion(); 358 | 359 | /** 360 | * Set the id of this expansion. This is required to build the expansion and 361 | * cannot be null. 362 | * 363 | * @param id 364 | * The id to register this expansion under. 365 | * @return This builder. 366 | */ 367 | B id(String id); 368 | 369 | /** 370 | * @return Whether the expansion is relational. 371 | */ 372 | boolean isRelational(); 373 | 374 | /** 375 | * Register listeners via the placeholder. This will attempt to use the provided 376 | * plugin object for registration. 377 | * 378 | * This listener will be unregistered and then registered again on reload. 379 | */ 380 | B listen(Object listeners); 381 | 382 | /** 383 | * Set the plugin which holds this expansion. This method is required before 384 | * building and cannot accept a null plugin. 385 | * 386 | * @param plugin 387 | * The plugin which holds this expansion. 388 | * @return This builder. 389 | */ 390 | B plugin(Object plugin); 391 | 392 | /** 393 | * Execute a function for the parsing of this expansion. 394 | * 395 | * @param exec 396 | * The function to execute. 397 | * @return This builder. 398 | */ 399 | default B provide(Supplier exec) { 400 | return function((s, o, t) -> exec.get()); 401 | } 402 | 403 | /** 404 | * Set whether this expansion is relational. `Relational` means that this 405 | * expansion's parse will be called when the id "rel_[id]" is passed into the 406 | * service. It also means that, if this expansion's parsing returns null AND the 407 | * server's configuration allows it, it will try to return the value of the 408 | * placeholder of id "[id]" if it exists for the observer. 409 | * 410 | * Setting this to true in no way guarantees that it will use the observer, that 411 | * the observer will not be null, or that it requires the observer at all. 412 | * 413 | * Conversely, if this is false, that in no way guarantees that it will not use 414 | * the observer. 415 | * 416 | * @param relational 417 | * Whether this expansion is relational. 418 | * @return This builder. 419 | */ 420 | B relational(boolean relational); 421 | 422 | /** 423 | * Add a function to call upon reload of the placeholder. 424 | * 425 | * @param reload 426 | * The function to call when the expansion is reloaded. This is a 427 | * predicate simply because we provide the current state of the 428 | * expansion and require a boolean as to whether the reload was 429 | * successful. 430 | * @return This builder. 431 | */ 432 | B reloadFunction(Predicate> reload); 433 | 434 | /** 435 | * Reset the builder's settings. In this case, it returns a new builder. 436 | * 437 | * @return The new builder. 438 | */ 439 | B reset(); 440 | 441 | /** 442 | * Execute a function for the parsing of this expansion. 443 | * 444 | * @param exec 445 | * The function to execute. 446 | * @return This builder. 447 | */ 448 | default B run(Runnable exec) { 449 | return function((s, o, t) -> { 450 | exec.run(); 451 | return null; 452 | }); 453 | } 454 | 455 | /** 456 | * Sets the list of supported tokens for the expansion. 457 | * 458 | * @param tokens 459 | * The supported tokens. 460 | * @return This builder. 461 | */ 462 | B tokens(List tokens); 463 | 464 | /** 465 | * Sets the list of supported tokens for the expansion. 466 | * 467 | * @param tokens 468 | * The supported tokens. 469 | * @return This builder. 470 | */ 471 | default B tokens(String... tokens) { 472 | return tokens(Arrays.asList(tokens)); 473 | } 474 | 475 | /** 476 | * Sets the url of the expansion. This links to a website or source for 477 | * information or downloads on the internet, if the author so chooses to include 478 | * one. 479 | * 480 | * @param url 481 | * The url of the expansion. 482 | * @return This builder. 483 | * @throws Exception 484 | * If the url is malformed or null. 485 | */ 486 | B url(String url) throws Exception; 487 | 488 | /** 489 | * Sets the url of the expansion. This links to a website or source for 490 | * information or downloads on the internet, if the author so chooses to include 491 | * one. 492 | * 493 | * @param url 494 | * The url of the expansion. 495 | * @return This builder. 496 | */ 497 | default B url(URL url) { 498 | try { 499 | return url(url.toString()); 500 | } catch (Exception e) { 501 | // literally will never happen. Do nothing. 502 | return current(); 503 | } 504 | } 505 | 506 | /** 507 | * Sets the version of the expansion. 508 | * 509 | * @param version 510 | * The version of the expansion. 511 | * @return This builder. 512 | */ 513 | B version(String version); 514 | 515 | } 516 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/IExpansion.java: -------------------------------------------------------------------------------- 1 | package me.rojo8399.placeholderapi; 2 | 3 | import java.net.URL; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | public interface IExpansion { 8 | 9 | /** 10 | * Parse the given inputs into a placeholder value. 11 | * 12 | * @param source 13 | * The source of the placeholder. For instance, if someone types a 14 | * message in chat, the source would be the message sender. 15 | * @param observer 16 | * The observer of the placeholder. For instance, if someone types a 17 | * message in chat, the observer would be the message recipient. 18 | * @param token 19 | * The token of the placeholder. This is any string appended to the 20 | * placeholder after it's id, and can be used for contextual data. 21 | * @return The parsed value. 22 | * @throws Exception 23 | * Throws this exception in order to protect itself from hard 24 | * failure. 25 | */ 26 | V parse(S source, O observer, Optional token) throws Exception; 27 | 28 | /** 29 | * @return Whether the expansion is relational. 30 | */ 31 | boolean relational(); 32 | 33 | /** 34 | * @return The author of the expansion. 35 | */ 36 | String author(); 37 | 38 | /** 39 | * @return The description of the expansion. 40 | */ 41 | String description(); 42 | 43 | /** 44 | * Enable this expansion. 45 | */ 46 | default void enable() { 47 | setEnabled(true); 48 | } 49 | 50 | /** 51 | * Disable this expansion. 52 | */ 53 | default void disable() { 54 | setEnabled(false); 55 | } 56 | 57 | /** 58 | * @return Whether the expansion is enabled. 59 | */ 60 | boolean isEnabled(); 61 | 62 | /** 63 | * Set whether this expansion is enabled. 64 | * 65 | * @param enabled 66 | * The state to set the plugin to. 67 | */ 68 | void setEnabled(boolean enabled); 69 | 70 | /** 71 | * @return The plugin which owns this expansion. 72 | */ 73 | Object getPlugin(); 74 | 75 | /** 76 | * Decide what the user may have meant when inputting a token. 77 | * 78 | * @param token 79 | * The token which was inputted by the user. 80 | * @return A list of possible tokens which are approximate to the input. 81 | */ 82 | List getSuggestions(String token); 83 | 84 | /** 85 | * @return The id of the expansion. 86 | */ 87 | String id(); 88 | 89 | /** 90 | * @return The website of the expansion. 91 | */ 92 | URL url(); 93 | 94 | /** 95 | * @return The version of the expansion. 96 | */ 97 | String version(); 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Listening.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import java.lang.annotation.Documented; 27 | import java.lang.annotation.Retention; 28 | import java.lang.annotation.Target; 29 | 30 | import static java.lang.annotation.ElementType.TYPE; 31 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 32 | 33 | /** 34 | * This annotation denotes whether the class should be registered for listeners. 35 | */ 36 | @Documented 37 | @Retention(RUNTIME) 38 | @Target(TYPE) 39 | public @interface Listening { 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/NoValueException.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | import org.spongepowered.api.text.Text; 30 | 31 | import me.rojo8399.placeholderapi.impl.configs.Messages; 32 | 33 | /** 34 | * A no value exception representing when the returned placeholder should not 35 | * parse. 36 | * 37 | * @author Wundero 38 | */ 39 | public class NoValueException extends Exception { 40 | 41 | /** 42 | * 43 | */ 44 | private static final long serialVersionUID = 4162128874653399415L; 45 | 46 | private List suggestions = new ArrayList<>(); 47 | private Text message; 48 | 49 | /** 50 | * Create a new NoValueException. 51 | */ 52 | public NoValueException() { 53 | this(Messages.get().misc.invalid.t("token")); 54 | } 55 | 56 | /** 57 | * Create a new NoValueException. 58 | * 59 | * @param message 60 | * Unused. 61 | */ 62 | public NoValueException(String message) { 63 | super(message); 64 | } 65 | 66 | public NoValueException(Text message) { 67 | this.message = message; 68 | } 69 | 70 | public NoValueException(Text message, List suggestions) { 71 | this.message = message; 72 | this.suggestions = suggestions; 73 | } 74 | 75 | public NoValueException(String message, List suggestions) { 76 | super(message); 77 | this.suggestions = suggestions; 78 | } 79 | 80 | /** 81 | * Create a new NoValueException. 82 | * 83 | * @param message 84 | * Unused. 85 | * @param cause 86 | * Unused. 87 | */ 88 | public NoValueException(String message, Throwable cause) { 89 | super(message, cause); 90 | } 91 | 92 | /** 93 | * Create a new NoValueException. 94 | * 95 | * @param message 96 | * Unused. 97 | * @param cause 98 | * Unused. 99 | * @param enableSuppression 100 | * whether or not suppression is enabled or disabled 101 | * @param writableStackTrace 102 | * whether or not the stack trace should be writable 103 | */ 104 | public NoValueException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 105 | super(message, cause, enableSuppression, writableStackTrace); 106 | } 107 | 108 | /** 109 | * Create a new NoValueException. 110 | * 111 | * @param cause 112 | * Unused. 113 | */ 114 | public NoValueException(Throwable cause) { 115 | super(cause); 116 | } 117 | 118 | public List suggestions() { 119 | return suggestions == null ? new ArrayList<>() : suggestions; 120 | } 121 | 122 | public Text getTextMessage() { 123 | if (this.message == null) { 124 | if(super.getMessage()==null) { 125 | return Text.EMPTY; 126 | } 127 | return Text.of(super.getMessage()); 128 | } 129 | return this.message; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Observer.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import static java.lang.annotation.ElementType.PARAMETER; 27 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | @Documented 34 | @Retention(RUNTIME) 35 | @Target(PARAMETER) 36 | /** 37 | * This annotation denotes the object viewing the placeholders. Supported types: 38 | * Locatable, User, CommandSource 39 | */ 40 | public @interface Observer { 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Placeholder.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | @Documented 34 | @Retention(RUNTIME) 35 | @Target(METHOD) 36 | /** 37 | * This class denotes a placeholder method. Only methods with this annotation 38 | * will be registered. 39 | */ 40 | public @interface Placeholder { 41 | 42 | /** 43 | * @return The id of the placeholder. 44 | */ 45 | String id(); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Relational.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | @Documented 34 | @Retention(RUNTIME) 35 | @Target(METHOD) 36 | /** 37 | * This annotation denotes a method as being relational, provided the method is 38 | * already a Placeholder method. Methods with this annotation are parsed with 39 | * "rel_[id]" instead of just the id. 40 | * 41 | * Important: This annotation only specifies two things: That the id needs 42 | * "rel_" in front of it, and that it, depending on a configuration setting for 43 | * PlaceholderAPI, may fall back onto a non-relational placeholder of the same 44 | * id, using observer in favour of source. 45 | */ 46 | public @interface Relational { 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Requires.java: -------------------------------------------------------------------------------- 1 | package me.rojo8399.placeholderapi; 2 | 3 | import static java.lang.annotation.ElementType.METHOD; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | @Documented 11 | @Retention(RUNTIME) 12 | @Target(METHOD) 13 | public @interface Requires { 14 | 15 | /** 16 | * The required version range of Sponge in Maven version range syntax: 17 | * 18 | * 19 | * 20 | * 21 | * 22 | * 23 | * 24 | * 25 | * 26 | * 27 | * 28 | * 29 | * 30 | * 31 | * 32 | * 33 | * 34 | * 35 | * 36 | * 37 | * 38 | * 39 | * 40 | * 41 | * 42 | * 43 | * 44 | * 45 | * 46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * 53 | * 54 | * 55 | *
RangeMeaning
1.0Any dependency version, 1.0 is recommended
[1.0]x == 1.0
[1.0,)x >= 1.0
(1.0,)x > 1.0
(,1.0]x <= 1.0
(,1.0)x < 1.0
(1.0,2.0)1.0 < x < 2.0
[1.0,2.0]1.0 <= x <= 2.0
56 | * 57 | * @return The required version range, or an empty string if unspecified 58 | * @see Maven version range specification 59 | * @see Maven version design document 60 | */ 61 | public String spongeVersion() default "[5.2,)"; 62 | 63 | /** 64 | * An array of plugin dependencies for versions. Not particularly useful for 65 | * most cases, but can be useful if you have multiple different systems for 66 | * different versions of a plugin. 67 | * 68 | * Format: plugin_id:version For example: placeholderapi:[4.3,) 69 | * 70 | * The version matching is the same as described in the spongeVersion javadoc. 71 | * 72 | * @see {@code #spongeVersion()} 73 | */ 74 | public String[] plugins() default {}; 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Source.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import static java.lang.annotation.ElementType.PARAMETER; 27 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | @Documented 34 | @Retention(RUNTIME) 35 | @Target(PARAMETER) 36 | /** 37 | * This annotation denotes what object information should come from primarily. 38 | * Supported types: Locatable, User, CommandSource 39 | */ 40 | public @interface Source { 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/Token.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi; 25 | 26 | import static java.lang.annotation.ElementType.PARAMETER; 27 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 28 | 29 | import java.lang.annotation.Documented; 30 | import java.lang.annotation.Retention; 31 | import java.lang.annotation.Target; 32 | 33 | /** 34 | * This annotation denotes the token which will hold more intricate details 35 | * about the placeholders needed. The plugin will TRY to parse it into 36 | * the given type of the parameter, but there are no guarantees beyond the basic 37 | * primitives (and their box class), String and Optional<\String>. 38 | */ 39 | @Documented 40 | @Retention(RUNTIME) 41 | @Target(PARAMETER) 42 | public @interface Token { 43 | 44 | /** 45 | * 'Fix' the input token to be more useful. This will call toLowerCase on the 46 | * string value of the token. This does nothing if the token is optional. 47 | * 48 | * @return whether or not to fix the token 49 | */ 50 | boolean fix() default false; 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/PlaceholderServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl; 25 | 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | import java.util.function.Function; 32 | import java.util.stream.Collectors; 33 | 34 | import org.spongepowered.api.data.DataHolder; 35 | import org.spongepowered.api.service.permission.Subject; 36 | import org.spongepowered.api.text.Text; 37 | import org.spongepowered.api.text.TextTemplate; 38 | import org.spongepowered.api.text.action.TextActions; 39 | import org.spongepowered.api.text.channel.MessageReceiver; 40 | import org.spongepowered.api.text.format.TextColor; 41 | import org.spongepowered.api.text.format.TextColors; 42 | import org.spongepowered.api.world.Locatable; 43 | 44 | import com.google.common.base.Preconditions; 45 | import com.google.common.reflect.TypeToken; 46 | 47 | import me.rojo8399.placeholderapi.ExpansionBuilder; 48 | import me.rojo8399.placeholderapi.NoValueException; 49 | import me.rojo8399.placeholderapi.PlaceholderService; 50 | import me.rojo8399.placeholderapi.impl.configs.Messages; 51 | import me.rojo8399.placeholderapi.impl.placeholder.Expansion; 52 | import me.rojo8399.placeholderapi.impl.placeholder.ExpansionBuilderImpl; 53 | import me.rojo8399.placeholderapi.impl.placeholder.Store; 54 | import me.rojo8399.placeholderapi.impl.utils.TypeUtils; 55 | 56 | /** 57 | * Implement placeholder service - should not need to be replaced but is a 58 | * service in case someone decides they need to. 59 | */ 60 | public class PlaceholderServiceImpl implements PlaceholderService { 61 | 62 | private static PlaceholderServiceImpl instance; // lazy instantiation 63 | 64 | public static PlaceholderServiceImpl get() { 65 | return instance == null ? instance = new PlaceholderServiceImpl() : instance; 66 | } 67 | 68 | private Store store = Store.get(); 69 | 70 | private PlaceholderServiceImpl() { 71 | } 72 | 73 | /* 74 | * (non-Javadoc) 75 | * 76 | * @see me.rojo8399.placeholderapi.PlaceholderService#builder() 77 | */ 78 | @Override 79 | public ExpansionBuilder> builder(Class s, 80 | Class o, Class v) { 81 | return ExpansionBuilderImpl.builder(s, o, v); 82 | } 83 | 84 | /* 85 | * (non-Javadoc) 86 | * 87 | * @see me.rojo8399.placeholderapi.PlaceholderService#fillPlaceholders(org. 88 | * spongepowered.api.text.TextTemplate, java.lang.Object, java.lang.Object) 89 | */ 90 | @Override 91 | public Map fillPlaceholders(TextTemplate template, Object source, Object observer) { 92 | validate(source, observer); 93 | return rpt(source, observer, template, new HashMap<>()); 94 | } 95 | 96 | private Map fillToText(TextTemplate template, Object source, Object observer) { 97 | validate(source, observer); 98 | Map rpt = rpt(source, observer, template, new HashMap<>()); 99 | Map map = new HashMap<>(); 100 | rpt.forEach((key, value) -> { 101 | Optional obj = TypeUtils.tryCast(value, Text.class); 102 | obj.ifPresent(text -> map.put(key, text)); 103 | }); 104 | return map; 105 | } 106 | 107 | /* 108 | * (non-Javadoc) 109 | * 110 | * @see 111 | * me.rojo8399.placeholderapi.PlaceholderService#isRegistered(java.lang. 112 | * String) 113 | */ 114 | @Override 115 | public boolean isRegistered(String id) { 116 | return store.has(id); 117 | } 118 | 119 | /* 120 | * (non-Javadoc) 121 | * 122 | * @see me.rojo8399.placeholderapi.PlaceholderService#load(java.lang.Object, 123 | * java.lang.String, java.lang.Object) 124 | */ 125 | @Override 126 | public ExpansionBuilder load(Object handle, String id, Object plugin) { 127 | return ExpansionBuilderImpl.load(handle, id, plugin); 128 | } 129 | 130 | /* 131 | * (non-Javadoc) 132 | * 133 | * @see 134 | * me.rojo8399.placeholderapi.PlaceholderService#loadAll(java.lang.Object, 135 | * java.lang.Object) 136 | */ 137 | @Override 138 | public List> loadAll(Object handle, Object plugin) { 139 | return ExpansionBuilderImpl.loadAll(handle, plugin); 140 | } 141 | 142 | /* 143 | * (non-Javadoc) 144 | * 145 | * @see 146 | * me.rojo8399.placeholderapi.PlaceholderService#parse(java.lang.String, 147 | * java.lang.Object, java.lang.Object) 148 | */ 149 | @Override 150 | public Object parse(String placeholder, Object source, Object observer) { 151 | validate(source, observer); 152 | placeholder = placeholder.trim().toLowerCase(); 153 | Map replacement = rpt(source, observer, TextTemplate.of(TextTemplate.arg(placeholder)), null); 154 | if (replacement == null || replacement.isEmpty()) { 155 | return null; 156 | } 157 | if (!replacement.containsKey(placeholder)) { 158 | return null; 159 | } 160 | return replacement.get(placeholder); 161 | } 162 | 163 | public void refreshAll() { 164 | store.reloadAll(); 165 | } 166 | 167 | public boolean refreshPlaceholder(String id) { 168 | return store.reload(id); 169 | } 170 | 171 | /* 172 | * (non-Javadoc) 173 | * 174 | * @see me.rojo8399.placeholderapi.PlaceholderService#registerExpansion(me. 175 | * rojo8399.placeholderapi.placeholder.Expansion) 176 | */ 177 | @Override 178 | public boolean registerExpansion(Expansion expansion) { 179 | return store.register(expansion); 180 | } 181 | 182 | /* (non-Javadoc) 183 | * @see me.rojo8399.placeholderapi.PlaceholderService#registerTypeDeserializer(java.util.function.Function) 184 | */ 185 | @Override 186 | public void registerTypeDeserializer(TypeToken token, Function deserializer) { 187 | TypeUtils.registerDeserializer(token, deserializer); 188 | } 189 | 190 | /* 191 | * (non-Javadoc) 192 | * 193 | * @see 194 | * me.rojo8399.placeholderapi.PlaceholderService#replacePlaceholders(org. 195 | * spongepowered.api.text.TextTemplate, java.lang.Object, java.lang.Object) 196 | */ 197 | @Override 198 | public Text replacePlaceholders(TextTemplate template, Object source, Object observer) { 199 | return template.apply(fillToText(template, source, observer)).build(); 200 | } 201 | 202 | /* 203 | * Replace placeholders then parse value using the function 204 | */ 205 | private Map rpt(Object s, Object o, TextTemplate template, Map args) { 206 | if (args == null) { 207 | args = new HashMap<>(); 208 | } 209 | // For every existing argument 210 | for (String a : template.getArguments().keySet()) { 211 | if (args.containsKey(a)) { 212 | continue; 213 | } 214 | String format = a.toLowerCase(); 215 | boolean rel = !format.isEmpty() && format.toLowerCase().startsWith("rel"); 216 | if (rel) { 217 | format = format.substring(4); 218 | } 219 | int index = format.indexOf("_"); 220 | if (index == 0 || index == format.length()) { 221 | // We want to skip this but we cannot leave required arguments 222 | // so filler string is used. 223 | if (!template.getArguments().get(a).isOptional()) { 224 | args.put(a, template.getOpenArgString() + a + template.getCloseArgString()); 225 | } 226 | continue; 227 | } 228 | boolean noToken = false; 229 | if (index == -1) { 230 | noToken = true; 231 | index = format.length(); 232 | } 233 | String id = format.substring(0, index).toLowerCase(); 234 | if (!store.has(id)) { 235 | // Again, filler string. 236 | if (!template.getArguments().get(a).isOptional()) { 237 | Text suggestions = Text.NEW_LINE; 238 | suggestions = suggestions.concat(Text.of(Messages.get().misc.suggestions.t(), 239 | Text.joinWith(Text.of(", "), 240 | store.allIds().stream().filter(str -> TypeUtils.closeTo(id, str)).map(Text::of) 241 | .collect(Collectors.toList())))); 242 | args.put(a, 243 | Text.of(TextColors.WHITE, 244 | TextActions.showText(Text.of(Messages.get().misc.invalid.t("ID"), suggestions)), 245 | template.getOpenArgString(), TextColors.RED, a, TextColors.WHITE, 246 | template.getCloseArgString())); 247 | } 248 | continue; 249 | } 250 | String token = noToken ? null : format.substring(index + 1); 251 | Object value; 252 | boolean empty = true; 253 | Text errorMsg = Messages.get().placeholder.tokenNeeded.t(); 254 | List suggestions = new ArrayList<>(); 255 | try { 256 | value = store.parse(id, rel, s, o, Optional.ofNullable(token)); 257 | } catch (Exception e) { 258 | if (e instanceof NoValueException) { 259 | value = null; 260 | empty = false; 261 | errorMsg = ((NoValueException) e).getTextMessage(); 262 | suggestions = ((NoValueException) e).suggestions(); 263 | } else { 264 | String cl = ""; 265 | try { 266 | cl = e.getCause().getMessage() + " "; 267 | } catch (Exception ignored) { 268 | } 269 | value = Text.of(TextColors.RED, 270 | TextActions.showText(Text.of(TextColors.RED, "Check the console for details!")), 271 | "ERROR: " + cl + e.getMessage()); 272 | e.printStackTrace(); 273 | } 274 | } 275 | 276 | if (value == null && PlaceholderAPIPlugin.getInstance().getConfig().relationaltoregular && rel && empty) { 277 | try { 278 | value = store.parse(id, false, o, s, Optional.ofNullable(token)); 279 | } catch (Exception e) { 280 | if (e instanceof NoValueException) { 281 | value = null; 282 | empty = false; 283 | errorMsg = ((NoValueException) e).getTextMessage(); 284 | suggestions = ((NoValueException) e).suggestions(); 285 | } else { 286 | String cl = ""; 287 | try { 288 | cl = e.getCause().getMessage() + " "; 289 | } catch (Exception ignored) { 290 | } 291 | value = Text.of(TextColors.RED, 292 | TextActions.showText(Text.of(TextColors.RED, "Check the console for details!")), 293 | "ERROR: " + cl + e.getMessage()); 294 | e.printStackTrace(); 295 | } 296 | } 297 | } 298 | boolean enabled = store.get(id, false).orElseGet(() -> store.get(id, true).get()).isEnabled(); 299 | if (value == null && !empty) { 300 | Text arg; 301 | if (noToken) { 302 | if (enabled) { 303 | arg = Text.of(TextColors.RED, TextActions.showText(Text.of(TextColors.RED, errorMsg)), a); 304 | } else { 305 | arg = Text.of(TextColors.RED, TextActions.showText(Messages.get().placeholder.notEnabled.t()), 306 | a); 307 | } 308 | 309 | } else { 310 | TextColor idCol; 311 | if (store.has(id)) { 312 | idCol = enabled ? TextColors.WHITE : TextColors.RED; 313 | } else { 314 | idCol = TextColors.RED; 315 | } 316 | Text sug = Text.EMPTY; 317 | if (!suggestions.isEmpty()) { 318 | sug = Text.of(Text.NEW_LINE, Messages.get().misc.suggestions, Text.joinWith(Text.of(", "), 319 | suggestions.stream().map(Text::of).collect(Collectors.toList()))); 320 | } 321 | arg = Text.of(idCol, TextActions.showText(Text.of(TextColors.RED, errorMsg, sug)), id, 322 | TextColors.RED, "_" + token); 323 | } 324 | value = Text.of(TextColors.WHITE, template.getOpenArgString(), arg, TextColors.WHITE, 325 | template.getCloseArgString()); 326 | } else if (value == null) { 327 | value = Text.EMPTY; 328 | } 329 | args.put(a, value); 330 | } 331 | return args; 332 | } 333 | 334 | private void validate(Object source, Object observer) { 335 | Preconditions.checkArgument(verifySource(source), "Source is not the right type!"); 336 | Preconditions.checkArgument(verifySource(observer), "Observer is not the right type!"); 337 | } 338 | 339 | /* 340 | * (non-Javadoc) 341 | * 342 | * @see 343 | * me.rojo8399.placeholderapi.PlaceholderService#verifySource(java.lang. 344 | * Object) 345 | */ 346 | @Override 347 | public boolean verifySource(Object source) { 348 | return source == null || (source instanceof Locatable || source instanceof MessageReceiver 349 | || source instanceof Subject || source instanceof DataHolder); 350 | } 351 | 352 | } 353 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/commands/InfoCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.commands; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.regex.Pattern; 30 | import java.util.stream.Collectors; 31 | import java.util.stream.Stream; 32 | 33 | import org.spongepowered.api.command.CommandException; 34 | import org.spongepowered.api.command.CommandResult; 35 | import org.spongepowered.api.command.CommandSource; 36 | import org.spongepowered.api.command.args.CommandContext; 37 | import org.spongepowered.api.command.spec.CommandExecutor; 38 | import org.spongepowered.api.entity.living.player.Player; 39 | import org.spongepowered.api.text.Text; 40 | import org.spongepowered.api.text.action.TextActions; 41 | import org.spongepowered.api.text.format.TextColors; 42 | 43 | import me.rojo8399.placeholderapi.PlaceholderService; 44 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 45 | import me.rojo8399.placeholderapi.impl.configs.Messages; 46 | import me.rojo8399.placeholderapi.impl.placeholder.Expansion; 47 | import me.rojo8399.placeholderapi.impl.placeholder.Store; 48 | import me.rojo8399.placeholderapi.impl.utils.TextUtils; 49 | import me.rojo8399.placeholderapi.impl.utils.TypeUtils; 50 | 51 | public class InfoCommand implements CommandExecutor { 52 | 53 | private static final Pattern OPT = Pattern.compile("[<\\[({]([^ <>\\[\\]{}()]+)[>}\\])]", 54 | Pattern.CASE_INSENSITIVE); 55 | 56 | private static Text format(Expansion e, CommandSource src) { 57 | final String name = e.id(); 58 | String version = e.version(); 59 | String author = e.author(); 60 | final boolean rel = e.relational(); 61 | List tokens = e.tokens(); 62 | if (tokens == null) { 63 | tokens = new ArrayList<>(); 64 | } 65 | List supportedTokens = tokens.stream().map(s -> s == null || s.isEmpty() ? null : s) 66 | .map(s -> s == null ? null : s.toLowerCase().trim()).distinct() 67 | .sorted((s1, s2) -> s1 == null ? -1 : (s2 == null ? 1 : s1.compareTo(s2))).map(s -> { 68 | if (s == null) { 69 | return token(name, src, rel); 70 | } 71 | String s2 = name.concat("_" + s); 72 | if (OPT.matcher(s2).find()) { 73 | return token(s2, src, rel, true); 74 | } 75 | return token(s2, src, rel); 76 | }).collect(Collectors.toList()); 77 | boolean seeall = false; 78 | List t2 = new ArrayList<>(supportedTokens); 79 | if (supportedTokens.size() > 20) { 80 | supportedTokens = supportedTokens.subList(0, 20); 81 | seeall = true; 82 | } 83 | Text url = e.url() == null ? Text.EMPTY 84 | : Text.of(Text.NEW_LINE, TextColors.BLUE, TextActions.openUrl(e.url()), e.url().toString()); 85 | String d = e.description(); 86 | Text desc = d == null || d.isEmpty() ? Text.EMPTY : Text.of(Text.NEW_LINE, TextColors.AQUA, e.description()); 87 | Text reload = Text.of(Text.NEW_LINE, Messages.get().placeholder.clickReload.t(), " ", reload(e.id())); 88 | Text support = supportedTokens.isEmpty() ? Text.EMPTY 89 | : Text.of(Text.NEW_LINE, Messages.get().placeholder.supportedPlaceholders.t(), 90 | seeall ? (seeall(t2, rel)) : "", Text.NEW_LINE, Text.joinWith(Text.of(", "), supportedTokens)); 91 | return Text.of(TextColors.AQUA, (rel ? "rel_" : "") + name, TextColors.GREEN, " " + version, TextColors.GRAY, 92 | " ", Messages.get().misc.by.t(), " ", TextColors.GOLD, author, TextColors.GRAY, ".", reload, desc, url, 93 | support); 94 | } 95 | 96 | private static Text formatExpansion(String e, CommandSource src) { 97 | List> conts = Stream.of(Store.get().get(e, true), Store.get().get(e, false)) 98 | .flatMap(TypeUtils.unmapOptional()).collect(Collectors.toList()); 99 | Expansion norm; 100 | try { 101 | norm = conts.get(0); 102 | } catch (Exception ex) { 103 | return Messages.get().placeholder.improperRegistration.t(); 104 | } 105 | Expansion rel = null; 106 | if (conts.size() > 1) { 107 | rel = conts.get(1); 108 | } 109 | Text out = format(norm, src); 110 | if (rel != null) { 111 | out = out.concat(Text.NEW_LINE); 112 | out = out.concat(format(rel, src)); 113 | } 114 | return out; 115 | } 116 | 117 | private static Text reload(String token) { 118 | return Messages.get().placeholder.reloadButton.t().toBuilder() 119 | .onHover(TextActions.showText(Messages.get().placeholder.reloadButtonHover.t())) 120 | .onClick(TextActions.runCommand("/papi r " + token)).build(); 121 | } 122 | 123 | private static Text seeall(List tokens, boolean relational) { 124 | final Text t = Text.joinWith(Text.of(", "), tokens); 125 | Text h = relational ? Messages.get().placeholder.allPlaceholdersHoverRelational.t() 126 | : Messages.get().placeholder.allPlaceholdersHover.t(); 127 | Text a = relational ? Messages.get().placeholder.allSupportedPlaceholdersRelational.t() 128 | : Messages.get().placeholder.allSupportedPlaceholders.t(); 129 | Text m = Messages.get().placeholder.allPlaceholdersButton.t(); 130 | Text m2 = m.toBuilder().onClick(TextActions.executeCallback(s -> { 131 | s.sendMessage(a); 132 | s.sendMessage(t); 133 | })).onHover(TextActions.showText(Text.of(h))).build(); 134 | return Text.of(" ", m2); 135 | } 136 | 137 | private static Text token(String token, CommandSource src, boolean relational) { 138 | if (!(src instanceof Player)) { 139 | return Text.of(TextColors.GREEN, token); 140 | } 141 | String p = src.getName(); 142 | return Text.of(TextColors.GREEN, TextActions.showText(Messages.get().placeholder.parseButtonHover.t()), 143 | TextActions.runCommand("/papi p " + p + " %" + (relational ? "rel_" : "") + token + "%"), 144 | (relational ? "rel_" : "") + token); 145 | } 146 | 147 | private static Text token(String token, CommandSource src, boolean relational, boolean opt) { 148 | if (!opt) { 149 | return token(token, src, relational); 150 | } 151 | if (!(src instanceof Player)) { 152 | return Text.of(TextColors.GREEN, token); 153 | } 154 | String p = src.getName(); 155 | return Text.of(TextColors.GREEN, TextActions.showText(Messages.get().placeholder.parseButtonHover.t()), 156 | TextActions.suggestCommand("/papi p " + p + " %" + (relational ? "rel_" : "") + token + "%"), 157 | (relational ? "rel_" : "") + token); 158 | } 159 | 160 | @Override 161 | public CommandResult execute(CommandSource src, CommandContext args) throws CommandException { 162 | String placeholder = (String) args.getOne("placeholder") 163 | .orElseThrow(() -> new CommandException(Messages.get().placeholder.mustSpecify.t())); 164 | PlaceholderService service = PlaceholderAPIPlugin.getInstance().getGame().getServiceManager() 165 | .provideUnchecked(PlaceholderService.class); 166 | if (!service.isRegistered(placeholder)) { 167 | throw new CommandException(Messages.get().placeholder.invalidPlaceholder.t()); 168 | } 169 | Text barrier = TextUtils.repeat(Text.of(TextColors.GOLD, "="), 35); 170 | src.sendMessage(barrier); 171 | src.sendMessage(formatExpansion(placeholder, src)); 172 | src.sendMessage(barrier); 173 | return CommandResult.success(); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/commands/ListCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.commands; 25 | 26 | import java.util.List; 27 | import java.util.stream.Collectors; 28 | 29 | import org.spongepowered.api.command.CommandException; 30 | import org.spongepowered.api.command.CommandResult; 31 | import org.spongepowered.api.command.CommandSource; 32 | import org.spongepowered.api.command.args.CommandContext; 33 | import org.spongepowered.api.command.spec.CommandExecutor; 34 | import org.spongepowered.api.text.Text; 35 | import org.spongepowered.api.text.action.TextActions; 36 | import org.spongepowered.api.text.format.TextColors; 37 | 38 | import me.rojo8399.placeholderapi.PlaceholderService; 39 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 40 | import me.rojo8399.placeholderapi.impl.configs.Messages; 41 | import me.rojo8399.placeholderapi.impl.placeholder.Store; 42 | 43 | public class ListCommand implements CommandExecutor { 44 | 45 | @Override 46 | public CommandResult execute(CommandSource src, CommandContext args) throws CommandException { 47 | PlaceholderAPIPlugin.getInstance().getGame().getServiceManager().provide(PlaceholderService.class) 48 | .orElseThrow(() -> new CommandException(Messages.get().plugin.serviceUnavailable.t())); 49 | List l = Store.get().allIds().stream() 50 | .map(e -> Text.of(TextColors.GOLD, TextActions.runCommand("/papi i " + e), 51 | TextActions.showText(Messages.get().placeholder.infoButtonHover.t()), e)) 52 | .collect(Collectors.toList()); 53 | src.sendMessage(Messages.get().placeholder.availablePlaceholders.t()); 54 | src.sendMessage(Text.joinWith(Text.of(", "), l)); 55 | return CommandResult.success(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/commands/ParseCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.commands; 25 | 26 | import org.spongepowered.api.command.CommandException; 27 | import org.spongepowered.api.command.CommandResult; 28 | import org.spongepowered.api.command.CommandSource; 29 | import org.spongepowered.api.command.args.CommandContext; 30 | import org.spongepowered.api.command.spec.CommandExecutor; 31 | import org.spongepowered.api.entity.living.player.Player; 32 | import org.spongepowered.api.text.Text; 33 | 34 | import me.rojo8399.placeholderapi.PlaceholderService; 35 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 36 | import me.rojo8399.placeholderapi.impl.configs.Messages; 37 | 38 | public class ParseCommand implements CommandExecutor { 39 | 40 | @Override 41 | public CommandResult execute(CommandSource src, CommandContext args) throws CommandException { 42 | 43 | Player p = args.getOne("player").get(); 44 | String placeholder = args.getOne(Text.of("placeholders")).get(); 45 | 46 | PlaceholderService service = PlaceholderAPIPlugin.getInstance().getGame().getServiceManager() 47 | .provide(PlaceholderService.class) 48 | .orElseThrow(() -> new CommandException(Messages.get().plugin.serviceUnavailable.t())); 49 | 50 | Text t = service.replacePlaceholders(placeholder, p, src); 51 | if (t == null) { 52 | t = Text.EMPTY; 53 | } 54 | if (!t.isEmpty()) { 55 | src.sendMessage(t); 56 | } else { 57 | src.sendMessage(Messages.get().misc.noValue.t()); 58 | } 59 | return CommandResult.success(); 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/commands/RefreshCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.commands; 25 | 26 | import org.spongepowered.api.command.CommandException; 27 | import org.spongepowered.api.command.CommandResult; 28 | import org.spongepowered.api.command.CommandSource; 29 | import org.spongepowered.api.command.args.CommandContext; 30 | import org.spongepowered.api.command.spec.CommandExecutor; 31 | 32 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 33 | import me.rojo8399.placeholderapi.impl.PlaceholderServiceImpl; 34 | import me.rojo8399.placeholderapi.impl.configs.Messages; 35 | 36 | public class RefreshCommand implements CommandExecutor { 37 | 38 | @Override 39 | public CommandResult execute(CommandSource src, CommandContext args) throws CommandException { 40 | String placeholderid = args.getOne("id").orElse(null); 41 | if (placeholderid == null) { 42 | try { 43 | PlaceholderAPIPlugin.getInstance().reloadConfig(); 44 | PlaceholderServiceImpl.get().refreshAll(); 45 | src.sendMessage(Messages.get().plugin.reloadSuccess.t()); 46 | } catch (Exception e) { 47 | throw new CommandException(Messages.get().plugin.reloadFailed.t()); 48 | } 49 | return CommandResult.success(); 50 | } 51 | if (!PlaceholderServiceImpl.get().isRegistered(placeholderid)) { 52 | throw new CommandException(Messages.get().placeholder.invalidPlaceholder.t()); 53 | } 54 | boolean s = PlaceholderServiceImpl.get().refreshPlaceholder(placeholderid); 55 | if (!s) { 56 | src.sendMessage(Messages.get().placeholder.reloadFailed.t()); 57 | } else { 58 | src.sendMessage(Messages.get().placeholder.reloadSuccess.t()); 59 | } 60 | return s ? CommandResult.success() : CommandResult.successCount(0); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/configs/Config.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.configs; 25 | 26 | import com.google.common.reflect.TypeToken; 27 | 28 | import ninja.leaping.configurate.objectmapping.Setting; 29 | import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; 30 | 31 | @ConfigSerializable 32 | public class Config { 33 | public static final TypeToken type = TypeToken.of(Config.class); 34 | 35 | @Setting("date-time-format") 36 | public String dateFormat = "uuuu LLL dd HH:mm:ss"; 37 | 38 | @Setting("relational-parse-for-recipient") 39 | public boolean relationaltoregular = true; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/configs/JavascriptManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.configs; 25 | 26 | import java.io.BufferedReader; 27 | import java.io.File; 28 | import java.io.FileReader; 29 | import java.io.IOException; 30 | import java.io.Reader; 31 | import java.io.StringReader; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.stream.Collectors; 38 | 39 | import javax.script.ScriptEngine; 40 | import javax.script.ScriptException; 41 | 42 | /** 43 | * @author Wundero 44 | */ 45 | public class JavascriptManager { 46 | 47 | private File folder; 48 | private Map scripts = new HashMap<>(); 49 | 50 | public JavascriptManager(File scriptFolder) throws IOException { 51 | if (scriptFolder.exists() && scriptFolder.isFile()) { 52 | // Scripts folder incorrect 53 | scriptFolder.delete(); 54 | } 55 | if (!scriptFolder.exists()) { 56 | scriptFolder.mkdirs(); 57 | } 58 | this.folder = scriptFolder; 59 | // Add references to all scripts 60 | for (File sub : folder.listFiles((f, s) -> s.endsWith(".js"))) { 61 | BufferedReader r = new BufferedReader(new FileReader(sub)); 62 | String str = r.lines().reduce("", (s1, s2) -> s1 + "\n" + s2); 63 | r.close(); 64 | scripts.put(sub.getName().replace(".js", "").toLowerCase(), str); 65 | } 66 | } 67 | 68 | public Object eval(ScriptEngine engine, String token) { 69 | if (token.replace("_", "").isEmpty()) { 70 | // no script name 71 | return null; 72 | } 73 | if (token.contains("_")) { 74 | // script has args 75 | String[] arr = token.split("_"); 76 | if (arr.length == 1) { 77 | // script args not present, just script_ 78 | Reader r; 79 | if ((r = getScript(arr[0])) == null) { 80 | // script doesn't exist 81 | return null; 82 | } 83 | try { 84 | engine.put("args", null); 85 | // evaluate script 86 | return engine.eval(r); 87 | } catch (ScriptException e) { 88 | return "ERROR: " + e.getMessage(); 89 | } 90 | } else { 91 | Reader f = getScript(arr[0]); 92 | if (f == null) { 93 | return null; 94 | } 95 | // skip script name in args 96 | List l = Arrays.stream(arr).skip(1).collect(Collectors.toList()); 97 | // put args as array 98 | engine.put("args", l.toArray(new String[l.size() - 1])); 99 | try { 100 | // evaluate script 101 | return engine.eval(f); 102 | } catch (ScriptException e) { 103 | return "ERROR: " + e.getMessage(); 104 | } 105 | } 106 | } else { 107 | Reader r; 108 | if ((r = getScript(token)) == null) { 109 | return null; 110 | } 111 | try { 112 | engine.put("args", null); 113 | return engine.eval(r); 114 | } catch (ScriptException e) { 115 | return "ERROR: " + e.getMessage(); 116 | } 117 | } 118 | } 119 | 120 | public Reader getScript(String name) { 121 | if (!scripts.containsKey(name)) { 122 | // Prevents creating reader on null 123 | return null; 124 | } 125 | return new StringReader(scripts.get(name)); 126 | } 127 | 128 | public List getScriptNames() { 129 | return new ArrayList<>(scripts.keySet()); 130 | } 131 | 132 | public void reloadScripts() throws IOException { 133 | scripts.clear(); 134 | for (File sub : folder.listFiles((f, s) -> s.endsWith(".js"))) { 135 | BufferedReader r = new BufferedReader(new FileReader(sub)); 136 | String str = r.lines().reduce("", (s1, s2) -> s1 + "\n" + s2); 137 | r.close(); 138 | scripts.put(sub.getName().replace(".js", "").toLowerCase(), str); 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/configs/Messages.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.configs; 25 | 26 | import org.spongepowered.api.text.Text; 27 | import org.spongepowered.api.text.serializer.TextSerializers; 28 | 29 | import com.google.common.reflect.TypeToken; 30 | 31 | import ninja.leaping.configurate.objectmapping.Setting; 32 | import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; 33 | 34 | @ConfigSerializable 35 | public class Messages { 36 | 37 | @ConfigSerializable 38 | public static class Message { 39 | 40 | @Setting 41 | public String value; 42 | 43 | public Message(){} 44 | 45 | public Message(String s) { 46 | this.value = s; 47 | } 48 | 49 | public Text t(Object... args) { 50 | return Messages.t(value, args); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return value; 56 | } 57 | } 58 | 59 | @ConfigSerializable 60 | public static class Misc { 61 | @ConfigSerializable 62 | public static class Direction { 63 | @Setting 64 | public Message east = of("East"); 65 | @Setting 66 | public Message north = of("North"); 67 | @Setting 68 | public Message northeast = of("Northeast"); 69 | @Setting 70 | public Message northwest = of("Northwest"); 71 | @Setting 72 | public Message south = of("South"); 73 | @Setting 74 | public Message southeast = of("Southeast"); 75 | @Setting 76 | public Message southwest = of("Southwest"); 77 | @Setting 78 | public Message west = of("West"); 79 | } 80 | 81 | @Setting 82 | public Message by = of("by"); 83 | @Setting 84 | public Direction directions = new Direction(); 85 | @Setting("invalid") 86 | public Message invalid = of("&cThat is not a valid %s!"); 87 | @Setting("no-permission") 88 | public Message noPerm = of("&cYou are not allowed to do that!"); 89 | 90 | @Setting("no-value") 91 | public Message noValue = of("&cNo value present."); 92 | @Setting 93 | public Message suggestions = of("Suggestions: "); 94 | @Setting 95 | public Message version = of("&7version"); 96 | } 97 | 98 | @ConfigSerializable 99 | public static class Placeholders { 100 | @Setting("all-placeholders-button") 101 | public Message allPlaceholdersButton = of("&e[SEE ALL]"); 102 | @Setting("all-placeholders-hover") 103 | public Message allPlaceholdersHover = of("&bClick to see all placeholders!"); 104 | @Setting("all-relational-placeholders-hover") 105 | public Message allPlaceholdersHoverRelational = of("&bClick to see all relational placeholders!"); 106 | @Setting("all-supported-placeholders") 107 | public Message allSupportedPlaceholders = of("&6&lAll supported placeholders:"); 108 | @Setting("all-supported-relational-placeholders") 109 | public Message allSupportedPlaceholdersRelational = of("&6&lAll supported relational placeholders:"); 110 | @Setting("available-placeholders") 111 | public Message availablePlaceholders = of("&aAvailable placeholders:"); 112 | @Setting("click-to-reload") 113 | public Message clickReload = of("&bClick to reload:"); 114 | @Setting("currency-description") 115 | public Message curdesc = of("View information about the server's economy."); 116 | @Setting("improper-registration") 117 | public Message improperRegistration = of( 118 | "&cPlaceholder was not registered correctly! Please check the logs for details."); 119 | @Setting("info-button-hover") 120 | public Message infoButtonHover = of("&bClick to get more info!"); 121 | @Setting("invalid-placeholder") 122 | public Message invalidPlaceholder = of("&cThat is not a valid placeholder!"); 123 | @Setting("invalid-source-observer") 124 | public Message invalidSrcObs = of("&cThe provided types are invalid sources or observers!"); 125 | @Setting("javascript-description") 126 | public Message jsdesc = of("Execute JavaScripts."); 127 | @Setting("must-specify") 128 | public Message mustSpecify = of("&cYou must specify a placeholder!"); 129 | @Setting("placeholder-not-enabled") 130 | public Message notEnabled = of("&cPlaceholder not enabled!"); 131 | @Setting("parse-button-hover") 132 | public Message parseButtonHover = of("&bClick to parse this placeholder for you!"); 133 | @Setting("placeholder-disabled") 134 | public Message placeholderDisabled = of("&aPlaceholder disabled!"); 135 | @Setting("placeholder-enabled") 136 | public Message placeholderEnabled = of("&aPlaceholder enabled!"); 137 | @Setting("player-description") 138 | public Message playerdesc = of("View information about a player."); 139 | @Setting("user-description") 140 | public Message userdesc = of("View information about a user."); 141 | @Setting("rank-description") 142 | public Message rankdesc = of("View information about a player's rank."); 143 | @Setting("reload-button") 144 | public Message reloadButton = of("&c[RELOAD]"); 145 | @Setting("reload-button-hover") 146 | public Message reloadButtonHover = of("&bClick to reload this placeholder!"); 147 | @Setting("reload-failed") 148 | public Message reloadFailed = of("&cPlaceholder failed to reload!"); 149 | @Setting("reload-success") 150 | public Message reloadSuccess = of("&aPlaceholder reloaded successfully!"); 151 | @Setting("relational-player-description") 152 | public Message relplayerdesc = of("View information about a player relative to another player."); 153 | @Setting("relational-rank-description") 154 | public Message relrankdesc = of("View information about a player's rank relative to another player."); 155 | @Setting("server-description") 156 | public Message serverdesc = of("View information about the server."); 157 | @Setting("sound-description") 158 | public Message sounddesc = of("Play sounds to players."); 159 | @Setting("statistics-description") 160 | public Message statdesc = of("View a player's statistics."); 161 | @Setting("supported-placeholders") 162 | public Message supportedPlaceholders = of("&6Supported relational placeholders:"); 163 | @Setting("supported-relational-placeholders") 164 | public Message supportedPlaceholdersRelational = of("&6Supported relational placeholders:"); 165 | @Setting("time-description") 166 | public Message timedesc = of("View the current date and time."); 167 | @Setting("token-needed") 168 | public Message tokenNeeded = of("&cThis placeholder needs a token!"); 169 | } 170 | 171 | @ConfigSerializable 172 | public static class Plugins { 173 | @Setting("placeholders-reloaded") 174 | public Message reloadCount = of("&a%s placeholders reloaded! (&c%s failed to reload.&a)"); 175 | @Setting("reload-failed") 176 | public Message reloadFailed = of("&cPlaceholderAPI failed to reload!"); 177 | @Setting("reload-success") 178 | public Message reloadSuccess = of("&aPlaceholderAPI reloaded successfully!"); 179 | @Setting("service-unavailable") 180 | public Message serviceUnavailable = of("&cPlaceholders are unavailable!"); 181 | } 182 | 183 | private static Messages inst; 184 | 185 | public static final TypeToken type = TypeToken.of(Messages.class); 186 | 187 | public static Messages get() { 188 | return inst == null ? new Messages() : inst; 189 | } 190 | 191 | public static void init(Messages inst) { 192 | Messages.inst = inst; 193 | } 194 | 195 | private static Message of(String v) { 196 | return new Message(v); 197 | } 198 | 199 | public static Text t(String m, Object... args) { 200 | return TextSerializers.FORMATTING_CODE 201 | .deserialize((args == null || args.length == 0 ? m : String.format(m, args))); 202 | } 203 | 204 | @Setting 205 | public Misc misc = new Misc(); 206 | 207 | @Setting 208 | public Placeholders placeholder = new Placeholders(); 209 | 210 | @Setting 211 | public Plugins plugin = new Plugins(); 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/Expansion.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder; 25 | 26 | import java.lang.reflect.Type; 27 | import java.net.URL; 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | import java.util.List; 31 | import java.util.Optional; 32 | import java.util.stream.Collectors; 33 | import java.util.stream.Stream; 34 | 35 | import org.spongepowered.api.data.DataHolder; 36 | import org.spongepowered.api.service.permission.Subject; 37 | import org.spongepowered.api.text.channel.MessageReceiver; 38 | import org.spongepowered.api.world.Locatable; 39 | 40 | import com.google.common.base.Preconditions; 41 | 42 | import me.rojo8399.placeholderapi.IExpansion; 43 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 44 | import me.rojo8399.placeholderapi.impl.utils.TypeUtils; 45 | import ninja.leaping.configurate.ConfigurationNode; 46 | 47 | /** 48 | * @author Wundero 49 | * 50 | */ 51 | public abstract class Expansion implements IExpansion { 52 | 53 | private static String fix(String id) { 54 | id = id.toLowerCase().trim(); 55 | if (id.startsWith("rel_")) { 56 | id = id.substring(4); 57 | } 58 | return id.replace("_", "").replace(" ", ""); 59 | } 60 | 61 | private static boolean verifySource(Class param) { 62 | return MessageReceiver.class.isAssignableFrom(param) || Locatable.class.isAssignableFrom(param) 63 | || Subject.class.isAssignableFrom(param) || DataHolder.class.isAssignableFrom(param); 64 | } 65 | 66 | private String author; 67 | private Object configObject; 68 | private String desc; 69 | private boolean enabled = true; 70 | private String id; 71 | private final Class observerClass; 72 | private Object plugin; 73 | private boolean relational = false; 74 | private Runnable reloadListeners; 75 | private final Class sourceClass; 76 | private List tokens; 77 | private URL url; 78 | 79 | private final Class valueClass; 80 | 81 | public Class getValueClass() { 82 | return valueClass; 83 | } 84 | 85 | public Class getSourceClass() { 86 | return sourceClass; 87 | } 88 | 89 | public Class getObserverClass() { 90 | return observerClass; 91 | } 92 | 93 | private String ver; 94 | 95 | /** 96 | * Null constructor. Used by method wrapper generator. DO NOT USE THIS UNLESS 97 | * YOU KNOW WHAT YOU ARE DOING. 98 | * 99 | * @param id 100 | * The id of the expansion. 101 | */ 102 | protected Expansion(String id) { 103 | this(id, PlaceholderAPIPlugin.getInstance(), null, null, null, (URL) null, false, new ArrayList<>()); 104 | } 105 | 106 | /** 107 | * Create a new Expansion with the provided arguments. 108 | * 109 | * @param id 110 | * The expansion id. 111 | * @param plugin 112 | * The holding plugin. 113 | * @param author 114 | * The author of the expansion. 115 | * @param desc 116 | * The expansion's description. 117 | * @param ver 118 | * The expansion's version. 119 | * @param url 120 | * The url of the expansion. Can be null. 121 | * @param relational 122 | * Whether the expansion is relational. 123 | * @param tokens 124 | * The supported tokens. 125 | * @throws Exception 126 | * If the url is not formatted properly. 127 | */ 128 | public Expansion(String id, Object plugin, String author, String desc, String ver, String url, boolean relational, 129 | List tokens) throws Exception { 130 | this(id, plugin, author, desc, ver, url != null && !url.isEmpty() ? new URL(url) : null, relational, tokens); 131 | } 132 | 133 | /** 134 | * Create a new Expansion with the provided arguments. 135 | * 136 | * @param id 137 | * The expansion id. 138 | * @param plugin 139 | * The holding plugin. 140 | * @param author 141 | * The author of the expansion. 142 | * @param desc 143 | * The expansion's description. 144 | * @param ver 145 | * The expansion's version. 146 | * @param url 147 | * The url of the expansion. Can be null. 148 | * @param relational 149 | * Whether the expansion is relational. 150 | * @param tokens 151 | * The supported tokens. 152 | * @throws Exception 153 | * If the url is not formatted properly. 154 | */ 155 | public Expansion(String id, Object plugin, String author, String desc, String ver, String url, boolean relational, 156 | String... tokens) throws Exception { 157 | this(id, plugin, author, desc, ver, url != null && !url.isEmpty() ? new URL(url) : null, relational, tokens); 158 | } 159 | 160 | /** 161 | * Create a new Expansion with the provided arguments. 162 | * 163 | * @param id 164 | * The expansion id. 165 | * @param plugin 166 | * The holding plugin. 167 | * @param author 168 | * The author of the expansion. 169 | * @param desc 170 | * The expansion's description. 171 | * @param ver 172 | * The expansion's version. 173 | * @param url 174 | * The url of the expansion. Can be null. 175 | * @param relational 176 | * Whether the expansion is relational. 177 | * @param tokens 178 | * The supported tokens. 179 | */ 180 | public Expansion(String id, Object plugin, String author, String desc, String ver, URL url, boolean relational, 181 | List tokens) { 182 | this(id, plugin, author, desc, ver, url, relational, tokens, null, null, null); 183 | } 184 | 185 | /** 186 | * Create a new Expansion with the provided arguments. 187 | * 188 | * @param id 189 | * The expansion id. 190 | * @param plugin 191 | * The holding plugin. 192 | * @param author 193 | * The author of the expansion. 194 | * @param desc 195 | * The expansion's description. 196 | * @param ver 197 | * The expansion's version. 198 | * @param url 199 | * The url of the expansion. Can be null. 200 | * @param relational 201 | * Whether the expansion is relational. 202 | * @param tokens 203 | * The supported tokens. 204 | * @param source 205 | * The class representing the source type. 206 | * @param observer 207 | * The class representing the observer type. 208 | * @param value 209 | * The class representing the value type. 210 | */ 211 | public Expansion(String id, Object plugin, String author, String desc, String ver, URL url, boolean relational, 212 | List tokens, Class source, Class observer, Class value) { 213 | this.id = fix(Preconditions.checkNotNull(id)); 214 | this.plugin = Preconditions.checkNotNull(plugin); 215 | this.author = author; 216 | this.desc = desc; 217 | this.ver = ver; 218 | this.url = url; 219 | this.tokens = tokens; 220 | this.sourceClass = source; 221 | this.valueClass = value; 222 | this.observerClass = observer; 223 | this.relational = relational; 224 | } 225 | 226 | /** 227 | * Create a new Expansion with the provided arguments. 228 | * 229 | * @param id 230 | * The expansion id. 231 | * @param plugin 232 | * The holding plugin. 233 | * @param author 234 | * The author of the expansion. 235 | * @param desc 236 | * The expansion's description. 237 | * @param ver 238 | * The expansion's version. 239 | * @param url 240 | * The url of the expansion. Can be null. 241 | * @param relational 242 | * Whether the expansion is relational. 243 | * @param tokens 244 | * The supported tokens. 245 | */ 246 | public Expansion(String id, Object plugin, String author, String desc, String ver, URL url, boolean relational, 247 | String... tokens) { 248 | this(id, plugin, author, desc, ver, url, relational, Arrays.asList(tokens)); 249 | } 250 | 251 | /** 252 | * @return The author of the expansion. 253 | */ 254 | @Override 255 | public final String author() { 256 | return author; 257 | } 258 | 259 | /** 260 | * Attempt to cast an object to the Observer type. Returns null if it fails. 261 | * 262 | * @param observer 263 | * The object to cast. 264 | * @return The casted object. 265 | */ 266 | public final O convertObserver(Object observer) { 267 | if (observer == null) { 268 | return null; 269 | } 270 | try { 271 | return observerClass.cast(observer); 272 | } catch (Exception e) { 273 | return null; 274 | } 275 | } 276 | 277 | /** 278 | * Attempt to cast an object to the Source type. Returns null if it fails. 279 | * 280 | * @param source 281 | * The object to cast. 282 | * @return The casted object. 283 | */ 284 | public final S convertSource(Object source) { 285 | if (source == null) { 286 | return null; 287 | } 288 | try { 289 | return sourceClass.cast(source); 290 | } catch (Exception e) { 291 | return null; 292 | } 293 | } 294 | 295 | /** 296 | * @return The description of the expansion. 297 | */ 298 | @Override 299 | public final String description() { 300 | return desc; 301 | } 302 | 303 | /** 304 | * Disable this expansion. 305 | */ 306 | @Override 307 | public final void disable() { 308 | setEnabled(false); 309 | } 310 | 311 | /** 312 | * Enable this expansion. 313 | */ 314 | @Override 315 | public final void enable() { 316 | setEnabled(true); 317 | } 318 | 319 | /** 320 | * Get the configuration object. Will attempt to cast to requested type, returns 321 | * null if failed. 322 | * 323 | * @return The configuration object. 324 | */ 325 | @SuppressWarnings("unchecked") 326 | public T getConfiguration() { 327 | try { 328 | return (T) configObject; 329 | } catch (Exception e) { 330 | return null; 331 | } 332 | } 333 | 334 | /** 335 | * @return The holding plugin. 336 | */ 337 | @Override 338 | public Object getPlugin() { 339 | return plugin; 340 | } 341 | 342 | @Override 343 | public List getSuggestions(String token) { 344 | if (this.tokens.isEmpty()) { 345 | return new ArrayList<>(); 346 | } 347 | if (token == null || token.isEmpty()) { 348 | return tokens; 349 | } 350 | return tokens.stream().filter(s -> TypeUtils.closeTo(s, token)).collect(Collectors.toList()); 351 | } 352 | 353 | /** 354 | * @return The id of the expansion. 355 | */ 356 | @Override 357 | public final String id() { 358 | return id; 359 | } 360 | 361 | /** 362 | * @return Whether this expansion is enabled. 363 | */ 364 | @Override 365 | public final boolean isEnabled() { 366 | return this.enabled; 367 | } 368 | 369 | /** 370 | * Parse the placeholder for the provided arguments. 371 | * 372 | * @param source 373 | * The source of the placeholder. 374 | * @param observer 375 | * The observer of the placeholder. 376 | * @param token 377 | * The token describing the placeholder. 378 | * @return The parsed value. 379 | * @throws Exception 380 | * Thrown if anyting goes wrong. 381 | */ 382 | @Override 383 | public abstract V parse(S source, O observer, Optional token) throws Exception; 384 | 385 | /** 386 | * Populate the configuration of this expansion. Will update the values received 387 | * when calling getConfiguration(). 388 | */ 389 | public void populateConfig() { 390 | ConfigurationNode node = Store.get().getNode(this); 391 | if (node.getParent().isVirtual()) { 392 | node.getParent().getNode("enabled").setValue(enabled); 393 | } else { 394 | this.enabled = node.getParent().getNode("enabled").getBoolean(true); 395 | } 396 | populateConfigObject(); 397 | } 398 | 399 | protected void populateConfigObject() { 400 | if (getConfiguration() == null || PlaceholderAPIPlugin.getInstance() == null 401 | || PlaceholderAPIPlugin.getInstance().getRootConfig() == null) { 402 | return; 403 | } 404 | ConfigurationNode node = Store.get().getNode(this); 405 | if (node.isVirtual()) { 406 | try { 407 | saveConfigObject(); 408 | } catch (Exception ignored) { 409 | } 410 | } 411 | try { 412 | Store.get().fillExpansionConfig(this); 413 | } catch (Exception e1) { 414 | try { 415 | saveConfigObject(); 416 | } catch (Exception ignored) { 417 | } 418 | } 419 | } 420 | 421 | /** 422 | * Reload + basic reloading method calls. 423 | * 424 | * @return whether reload() returns true. 425 | */ 426 | final boolean refresh() { 427 | populateConfig(); 428 | reloadListeners(); 429 | return reload(); 430 | } 431 | 432 | /** 433 | * @return Whether the expansion is relational. 434 | */ 435 | @Override 436 | public final boolean relational() { 437 | return this.relational; 438 | } 439 | 440 | /** 441 | * Reload the placeholder. By default it does nothing, but it can be overridden 442 | * to do whatever the implementer needs to do on reload. 443 | * 444 | * @return Whether the reload was succesful. 445 | */ 446 | public boolean reload() { 447 | return true; 448 | } 449 | 450 | public final void reloadListeners() { 451 | if (reloadListeners != null) { 452 | reloadListeners.run(); 453 | } 454 | } 455 | 456 | public void saveConfig() { 457 | saveConfigObject(); 458 | ConfigurationNode node = Store.get().getNode(this); 459 | node.getParent().getNode("enabled").setValue(enabled); 460 | } 461 | 462 | protected void saveConfigObject() { 463 | if (getConfiguration() == null || PlaceholderAPIPlugin.getInstance() == null 464 | || PlaceholderAPIPlugin.getInstance().getRootConfig() == null) { 465 | return; 466 | } 467 | try { 468 | Store.get().saveExpansionConfig(this); 469 | PlaceholderAPIPlugin.getInstance().saveConfig(); 470 | } catch (Exception ignored) { 471 | } 472 | } 473 | 474 | void setConfig(Object o) { 475 | this.configObject = o; 476 | } 477 | 478 | /** 479 | * Enable or disable this expansion. 480 | * 481 | * @param enabled 482 | * Whether the expansion is enabled. 483 | */ 484 | @Override 485 | public final void setEnabled(boolean enabled) { 486 | this.enabled = enabled; 487 | saveConfig(); 488 | } 489 | 490 | final void setId(String id) { 491 | this.id = fix(id); 492 | } 493 | 494 | final void setRelational(boolean relational) { 495 | this.relational = relational; 496 | } 497 | 498 | /** 499 | * Set the function to call to reload listeners. Will be called upon reload. 500 | * 501 | * @param run 502 | * The code to execute. 503 | */ 504 | final void setReloadListeners(Runnable run) { 505 | this.reloadListeners = run; 506 | } 507 | 508 | /** 509 | * Set the valid tokens for this expansion. Useful for reloading a dynamic token 510 | * expansion. 511 | * 512 | * @param tokens 513 | * The new tokens to use. 514 | */ 515 | public void setTokens(List tokens) { 516 | this.tokens = tokens; 517 | } 518 | 519 | /** 520 | * Set the valid tokens for this expansion. Useful for reloading a dynamic token 521 | * expansion. 522 | * 523 | * @param tokens 524 | * The new tokens to use. 525 | */ 526 | public void setTokens(String... tokens) { 527 | setTokens(Arrays.asList(tokens)); 528 | } 529 | 530 | /** 531 | * Toggle whether this expansion is enabled. 532 | */ 533 | public final void toggleEnabled() { 534 | setEnabled(isEnabled()); 535 | } 536 | 537 | /** 538 | * @return The tokens this expansion supports. A null/empty token means %id% is 539 | * supported. 540 | */ 541 | public final List tokens() { 542 | return tokens; 543 | } 544 | 545 | /** 546 | * @return The url of the expansion. 547 | */ 548 | @Override 549 | public final URL url() { 550 | return url; 551 | } 552 | 553 | /** 554 | * Verify that the parameters of the parse method are extensions of User, 555 | * CommandSource or Locatable. 556 | * 557 | * @return Whether the parameters are valid. 558 | */ 559 | public final boolean verify() { 560 | Class clazz = this.getClass(); 561 | List> params = Arrays.stream(clazz.getDeclaredMethods()) 562 | .filter(m -> m.getName().equalsIgnoreCase("parse") && Arrays.stream(m.getGenericParameterTypes()).map(Type::getTypeName).anyMatch(s -> s.contains("java.util.Optional"))) 563 | .map(m -> Arrays.asList(m.getParameterTypes())).map(List::stream).reduce(Stream.empty(), Stream::concat) 564 | .collect(Collectors.toList()); 565 | try { 566 | return verifySource(params.get(0)) && verifySource(params.get(1)); 567 | } catch (Exception e) { 568 | e.printStackTrace(); 569 | return false; 570 | } 571 | } 572 | 573 | /** 574 | * @return The version of the expansion. 575 | */ 576 | @Override 577 | public final String version() { 578 | return ver; 579 | } 580 | 581 | } -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/ExpansionBuilderImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder; 25 | 26 | import java.lang.reflect.Method; 27 | import java.net.URL; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Optional; 32 | import java.util.function.Predicate; 33 | import java.util.stream.Collectors; 34 | 35 | import org.spongepowered.api.Sponge; 36 | import org.spongepowered.api.data.DataHolder; 37 | import org.spongepowered.api.plugin.PluginContainer; 38 | import org.spongepowered.api.service.permission.Subject; 39 | import org.spongepowered.api.text.channel.MessageReceiver; 40 | import org.spongepowered.api.world.Locatable; 41 | 42 | import com.google.common.base.Preconditions; 43 | 44 | import me.rojo8399.placeholderapi.ExpansionBuilder; 45 | import me.rojo8399.placeholderapi.Placeholder; 46 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 47 | 48 | /** 49 | * @author Wundero 50 | * 51 | */ 52 | public class ExpansionBuilderImpl implements ExpansionBuilder> { 53 | 54 | /** 55 | * Create a new ExpansionBuilder based on the source, observer and value types. 56 | * 57 | * Source and observer types must both be an instance or subinstance of 58 | * CommandSource, User or Locatable. The value type can be anything. 59 | * 60 | * @return The newly created builder. 61 | */ 62 | public static ExpansionBuilderImpl builder(Class s, Class o, 63 | Class v) { 64 | return new ExpansionBuilderImpl<>(true, s, o, v); 65 | } 66 | 67 | private static String fix(String id) { 68 | id = id.toLowerCase().trim(); 69 | if (id.startsWith("rel_")) { 70 | id = id.substring(4); 71 | } 72 | return id.replace("_", "").replace(" ", ""); 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | private static ExpansionBuilderImpl from(ExpansionBuilderImpl builder, 77 | final Expansion exp) { 78 | if (!exp.verify()) { 79 | return (ExpansionBuilderImpl) builder; 80 | } 81 | ExpansionBuilderImpl n = unverified(src(exp, builder), obs(exp, builder), val(exp, builder)); 82 | n.relational(builder.relational).id(builder.id).author(builder.auth).description(builder.desc).config(builder.config).tokens(builder.tokens) 83 | .version(builder.ver); 84 | try { 85 | n.url(builder.url); 86 | } catch (Exception ignored) { 87 | } 88 | if (builder.plugin != null) { 89 | n.plugin(builder.plugin); 90 | } 91 | if (n.config == null) { 92 | n.config = exp.getConfiguration(); 93 | } 94 | n.func = exp::parse; 95 | return n; 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | private static Class obs(Expansion e, ExpansionBuilderImpl b) { 100 | if (e.getObserverClass() == null) { 101 | return (Class) b.getObserverClass(); 102 | } 103 | return e.getObserverClass(); 104 | } 105 | 106 | @SuppressWarnings("unchecked") 107 | private static Class val(Expansion e, ExpansionBuilderImpl b) { 108 | if (e.getValueClass() == null) { 109 | return (Class) b.getValueClass(); 110 | } 111 | return e.getValueClass(); 112 | } 113 | 114 | @SuppressWarnings("unchecked") 115 | private static Class src(Expansion e, ExpansionBuilderImpl b) { 116 | if (e.getSourceClass() == null) { 117 | return (Class) b.getSourceClass(); 118 | } 119 | return e.getSourceClass(); 120 | } 121 | 122 | private static ExpansionBuilderImpl lfm(Object o, Method m, Object p, boolean rel) { 123 | Store s = Store.get(); 124 | return unverified(s.getSourceType(m).orElse(Locatable.class), s.getObserverType(m).orElse(Locatable.class), 125 | m.getReturnType()).relational(rel).id(m.getAnnotation(Placeholder.class).id()).frommethod(o, m, p); 126 | } 127 | 128 | public static ExpansionBuilderImpl load(Object src, String id, Object plugin) { 129 | Method m = Store.find(src, id, false); 130 | boolean relational = false; 131 | if (m == null) { 132 | m = Store.find(src, id, true); 133 | relational = true; 134 | if (m == null) { 135 | throw new IllegalArgumentException("No placeholder exists with that id!"); 136 | } 137 | } 138 | return lfm(src, m, plugin, relational); 139 | } 140 | 141 | /** 142 | * Attempt to create expansion builders for every "@Placeholder" annotated 143 | * method in the provided object. Every expansion builder returned will have the 144 | * id, plugin, function and relational fields filled out already, so all that 145 | * remains are optional fields, and any builder in this list can be built 146 | * immediately. 147 | * 148 | * @param object 149 | * The object in which the methods reside. This object cannot be 150 | * null. 151 | * @param plugin 152 | * The plugin which holds the expansions. 153 | * @return A list of builders for each method in the object. 154 | */ 155 | public static List> loadAll(Object object, Object plugin) { 156 | Map methods = Store.findAll(object); 157 | @SuppressWarnings("rawtypes") 158 | List l = methods.entrySet().stream().map(e -> lfm(object, e.getKey(), plugin, e.getValue())) 159 | .collect(Collectors.toList()); 160 | return l.stream().map(e -> (ExpansionBuilderImpl) e).collect(Collectors.toList()); 161 | } 162 | 163 | public static ExpansionBuilderImpl unverified(Class s, Class o, 164 | Class v) { 165 | return new ExpansionBuilderImpl<>(false, s, o, v); 166 | } 167 | 168 | private static boolean verifySource(Class param) { 169 | return param == null || MessageReceiver.class.isAssignableFrom(param) || Locatable.class.isAssignableFrom(param) 170 | || Subject.class.isAssignableFrom(param) || DataHolder.class.isAssignableFrom(param); 171 | } 172 | 173 | private ExpansionFunction func; 174 | 175 | private String id, auth, ver = "1.0", desc, url; 176 | private Class observerClass; 177 | private Object plugin, config, listeners; 178 | private boolean relational = false; 179 | private Predicate> reload = (func) -> true; 180 | private Class returnClass; 181 | private Class sourceClass; 182 | private List tokens = new ArrayList<>(); 183 | private boolean verify; 184 | 185 | private ExpansionBuilderImpl(boolean verify, Class s, Class o, Class v) { 186 | this.verify = verify; 187 | this.sourceClass = s; 188 | this.returnClass = v; 189 | this.observerClass = o; 190 | } 191 | 192 | /** 193 | * Adds to the list of supported tokens for the expansion. 194 | * 195 | * @param tokens 196 | * The supported tokens. 197 | * @return This builder. 198 | */ 199 | @Override 200 | public ExpansionBuilderImpl addTokens(List tokens) { 201 | if (this.tokens == null) { 202 | return tokens(tokens); 203 | } 204 | this.tokens.addAll(tokens); 205 | return this; 206 | } 207 | 208 | /** 209 | * Sets the author of the expansion. 210 | * 211 | * @param author 212 | * The author of the expansion. 213 | * @return This builder. 214 | */ 215 | @Override 216 | public ExpansionBuilderImpl author(String author) { 217 | this.auth = author; 218 | return this; 219 | } 220 | 221 | /** 222 | * This method will build the expansion. This is not a terminating operation on 223 | * the builder, so if you wish to reuse the builder under a new id, you could do 224 | * so. 225 | * 226 | * This method will throw an exception if the id, plugin or function have not 227 | * been specified. 228 | * 229 | * @return Whether the registration was successful. 230 | * @throws Exception 231 | * If the expansion cannot be created. 232 | */ 233 | @Override 234 | public Expansion build() throws Exception { 235 | if (id == null || id.isEmpty()) { 236 | throw new IllegalStateException("ID must be specified!"); 237 | } 238 | if (func == null) { 239 | throw new IllegalStateException("Function must be specified!"); 240 | } 241 | if (plugin == null) { 242 | throw new IllegalStateException("Plugin cannot be null!"); 243 | } 244 | if (verify) { 245 | Preconditions.checkArgument(verify()); 246 | } 247 | Expansion exp = new Expansion(id, plugin, auth, desc, ver, 248 | (url == null || url.isEmpty()) ? null : new URL(url), relational, tokens, this.sourceClass, 249 | this.observerClass, this.returnClass) { 250 | @Override 251 | public V parse(S source, O observer, Optional token) throws Exception { 252 | return func.parse(source, observer, token); 253 | } 254 | 255 | @Override 256 | public boolean reload() { 257 | return reload.test(this); 258 | } 259 | }; 260 | if (config != null) { 261 | exp.setConfig(config); 262 | } 263 | if (listeners != null) { 264 | exp.setReloadListeners(() -> { 265 | PlaceholderAPIPlugin.getInstance().unregisterListeners(listeners); 266 | PlaceholderAPIPlugin.getInstance().registerListeners(listeners, plugin); 267 | }); 268 | } 269 | id = null; 270 | return exp; 271 | } 272 | 273 | /** 274 | * This method will build the expansion and then attemp to register the 275 | * expansion. 276 | * 277 | * This method will throw an exception if the id, plugin or function have not 278 | * been specified. 279 | * 280 | * @return Whether the registration was successful. 281 | * @throws Exception 282 | * If the expansion cannot be created or registered. 283 | */ 284 | @Override 285 | public boolean buildAndRegister() throws Exception { 286 | Expansion exp = build(); 287 | return Store.get().register(exp); 288 | } 289 | 290 | /** 291 | * Add an object which holds config values. This object will be populated with 292 | * configuration options when the expansion is loaded and reloaded. 293 | * 294 | * @param config 295 | * The object to populate. 296 | * @return This builder. 297 | */ 298 | @Override 299 | public ExpansionBuilderImpl config(Object config) { 300 | this.config = config; 301 | return this; 302 | } 303 | 304 | /* 305 | * (non-Javadoc) 306 | * 307 | * @see me.rojo8399.placeholderapi.ExpansionBuilder#current() 308 | */ 309 | @Override 310 | public ExpansionBuilderImpl current() { 311 | return this; 312 | } 313 | 314 | /** 315 | * Sets the description of the expansion. 316 | * 317 | * @param description 318 | * The description of the expansion. 319 | * @return This builder. 320 | */ 321 | @Override 322 | public ExpansionBuilderImpl description(String description) { 323 | this.desc = description; 324 | return this; 325 | } 326 | 327 | /** 328 | * Copy settings from the expansion provided in order to modify it. 329 | * 330 | * @param exp 331 | * The expansion from which to draw values. 332 | * @return This builder, with all fields modified to suit the expansion 333 | * provided. 334 | */ 335 | @Override 336 | public ExpansionBuilderImpl from(Expansion exp) { 337 | this.id = exp.id(); 338 | this.auth = exp.author(); 339 | this.desc = exp.description(); 340 | this.ver = exp.version(); 341 | this.url = exp.url().toString(); 342 | this.tokens = exp.tokens(); 343 | this.plugin = exp.getPlugin(); 344 | this.config = exp.getConfiguration(); 345 | this.relational = exp.relational(); 346 | this.func = exp::parse; 347 | this.sourceClass = exp.getSourceClass(); 348 | this.observerClass = exp.getObserverClass(); 349 | this.returnClass = exp.getValueClass(); 350 | return this; 351 | } 352 | 353 | /** 354 | * Create a builder for a method in the object. 355 | * 356 | * This will attempt to find a method which has the "@Placeholder" annotation 357 | * linked to the provided id. If it exists, it will create the builder. This 358 | * will attempt to use the relational field to determine if it can parse that 359 | * method, which will by default be the normal method. If it does not find that 360 | * method, it will check for one which is not relational. 361 | * 362 | * This method alters the function, the plugin, the id and the relational 363 | * fields. Any other previously existing fields will be conserved. 364 | * 365 | * The provided id must exist on at least one method. Any repeated ids will be 366 | * ignored unless they are of different relational status. 367 | * 368 | * @param obj 369 | * The object containing the placeholder method. 370 | * @param id 371 | * The id of the placeholder method. 372 | * @param plugin 373 | * The plugin holding the expansion. 374 | * @return This builder. 375 | */ 376 | @SuppressWarnings("unchecked") 377 | @Override 378 | public ExpansionBuilderImpl from(Object obj, String id, Object plugin) { 379 | Method m = Store.find(obj, id, relational); 380 | if (m == null) { 381 | m = Store.find(obj, id, !relational); 382 | if (m == null) { 383 | throw new IllegalArgumentException("No placeholder by that ID found!"); 384 | } 385 | } 386 | return frommethod(obj, m, plugin); 387 | } 388 | 389 | @SuppressWarnings({ "rawtypes" }) 390 | private ExpansionBuilderImpl frommethod(Object o, Method m, Object p) { 391 | Expansion exp = Store.get().createForMethod(m, o, p); 392 | if (exp == null) { 393 | return this; 394 | } 395 | return from(this.id(exp.id()).plugin(p), exp); 396 | } 397 | 398 | /** 399 | * Copy settings from the expansion provided in order to modify it. This method 400 | * ignores the types of the provided expansion and attempts to cast them to its 401 | * own. 402 | * 403 | * @param exp 404 | * The expansion from which to draw values. 405 | * @return This builder, with all fields modified to suit the expansion 406 | * provided. 407 | */ 408 | @SuppressWarnings("unchecked") 409 | @Override 410 | public ExpansionBuilderImpl fromUnknown(Expansion exp) { 411 | if (!(exp.getObserverClass().isAssignableFrom(this.getObserverClass()) 412 | && exp.getSourceClass().isAssignableFrom(this.getSourceClass()) 413 | && this.getValueClass().isAssignableFrom(exp.getValueClass()))) { 414 | throw new IllegalArgumentException("Expansion types not supported!"); 415 | } 416 | return from((Expansion) exp); 417 | } 418 | 419 | /** 420 | * Execute a function for the parsing of this expansion. 421 | * 422 | * @param exec 423 | * The function to execute. 424 | * @return This builder. 425 | */ 426 | @Override 427 | public ExpansionBuilderImpl function(ExpansionFunction exec) { 428 | this.func = exec; 429 | return this; 430 | } 431 | 432 | /** 433 | * @return The author of the expansion. 434 | */ 435 | @Override 436 | public String getAuthor() { 437 | return auth; 438 | } 439 | 440 | /** 441 | * @return The description of the expansion. 442 | */ 443 | @Override 444 | public String getDescription() { 445 | return desc; 446 | } 447 | 448 | /** 449 | * @return The id of the expansion. 450 | */ 451 | @Override 452 | public String getId() { 453 | return id; 454 | } 455 | 456 | /** 457 | * @return The class that represents the observer object on parsing. 458 | */ 459 | public final Class getObserverClass() { 460 | return observerClass; 461 | } 462 | 463 | /** 464 | * @return The class that represents the source object on parsing. 465 | */ 466 | public final Class getSourceClass() { 467 | return sourceClass; 468 | } 469 | 470 | /** 471 | * @return The supported tokens for the expansion. 472 | */ 473 | @Override 474 | public List getTokens() { 475 | return tokens; 476 | } 477 | 478 | /** 479 | * @return The URL for the expansion. 480 | * @throws Exception 481 | * - If the url is not properly formatted or is null. 482 | */ 483 | @Override 484 | public URL getUrl() throws Exception { 485 | return new URL(url); 486 | } 487 | 488 | /** 489 | * @return The url for the expansion. 490 | */ 491 | @Override 492 | public String getUrlString() { 493 | return url; 494 | } 495 | 496 | /** 497 | * @return The class that represents the returned value on parsing. 498 | */ 499 | public final Class getValueClass() { 500 | return returnClass; 501 | } 502 | 503 | /** 504 | * @return The version of the expansion. 505 | */ 506 | @Override 507 | public String getVersion() { 508 | return ver; 509 | } 510 | 511 | /** 512 | * Set the id of this expansion. This is required to build the expansion and 513 | * cannot be null. 514 | * 515 | * @param id 516 | * The id to register this expansion under. 517 | * @return This builder. 518 | */ 519 | @Override 520 | public ExpansionBuilderImpl id(String id) { 521 | this.id = fix(id); 522 | return this; 523 | } 524 | 525 | /** 526 | * @return Whether the expansion is relational. 527 | */ 528 | @Override 529 | public boolean isRelational() { 530 | return relational; 531 | } 532 | 533 | @Override 534 | public ExpansionBuilderImpl listen(Object o) { 535 | if (o != null) { 536 | this.listeners = o; 537 | } 538 | return this; 539 | } 540 | 541 | /* 542 | * Used for type verification 543 | */ 544 | @SuppressWarnings("unused") 545 | private V parse(S s, O o, Optional t) { 546 | return null; 547 | } 548 | 549 | /** 550 | * Set the plugin which holds this expansion. This method is required before 551 | * building and cannot accept a null plugin. 552 | * 553 | * @param plugin 554 | * The plugin which holds this expansion. 555 | * @return This builder. 556 | */ 557 | @Override 558 | public ExpansionBuilderImpl plugin(Object plugin) { 559 | Optional plox = Sponge.getPluginManager().fromInstance(plugin); 560 | if (!plox.isPresent()) { 561 | throw new IllegalArgumentException("Plugin object is not valid!"); 562 | } 563 | this.plugin = plugin; 564 | return this; 565 | } 566 | 567 | /** 568 | * Set whether this expansion is relational. `Relational` means that this 569 | * expansion's parse will be called when the id "rel_[id]" is passed into the 570 | * service. It also means that, if this expansion's parsing returns null AND the 571 | * server's configuration allows it, it will try to return the value of the 572 | * placeholder of id "[id]" if it exists for the observer. 573 | * 574 | * Setting this to true in no way guarantees that it will use the observer, that 575 | * the observer will not be null, or that it requires the observer at all. 576 | * 577 | * Conversely, if this is false, that in no way guarantees that it will not use 578 | * the observer. 579 | * 580 | * @param relational 581 | * Whether this expansion is relational. 582 | * @return This builder. 583 | */ 584 | @Override 585 | public ExpansionBuilderImpl relational(boolean relational) { 586 | this.relational = relational; 587 | return this; 588 | } 589 | 590 | /** 591 | * Add a function to call upon reload of the placeholder. 592 | * 593 | * @param reload 594 | * The function to call when the expansion is reloaded. This is a 595 | * predicate simply because we provide the current state of the 596 | * expansion and require a boolean as to whether the reload was 597 | * successful. 598 | * @return This builder. 599 | */ 600 | @Override 601 | public ExpansionBuilderImpl reloadFunction(Predicate> reload) { 602 | this.reload = reload == null ? f -> true : reload; 603 | return this; 604 | } 605 | 606 | /** 607 | * Reset the builder's settings. In this case, it returns a new builder. 608 | * 609 | * @return The new builder. 610 | */ 611 | @Override 612 | public ExpansionBuilderImpl reset() { 613 | return unverified(getSourceClass(), getObserverClass(), getValueClass()); 614 | } 615 | 616 | /** 617 | * Sets the list of supported tokens for the expansion. 618 | * 619 | * @param tokens 620 | * The supported tokens. 621 | * @return This builder. 622 | */ 623 | @Override 624 | public ExpansionBuilderImpl tokens(List tokens) { 625 | this.tokens = tokens; 626 | return this; 627 | } 628 | 629 | /** 630 | * Sets the url of the expansion. This links to a website or source for 631 | * information or downloads on the internet, if the author so chooses to include 632 | * one. 633 | * 634 | * @param url 635 | * The url of the expansion. 636 | * @return This builder. 637 | * @throws Exception 638 | * If the url is malformed or null. 639 | */ 640 | @Override 641 | public ExpansionBuilderImpl url(String url) throws Exception { 642 | this.url = new URL(url).toString(); 643 | return this; 644 | } 645 | 646 | /** 647 | * Verify that the source and observer types are valid extensions of supported 648 | * classes. 649 | * 650 | * @return Whether the class is verified. 651 | */ 652 | public boolean verify() { 653 | boolean out = false; 654 | try { 655 | out = verifySource(sourceClass) && verifySource(observerClass); 656 | } catch (Exception ignored) { 657 | } 658 | if (out) { 659 | verify = false; 660 | } 661 | return out; 662 | } 663 | 664 | /** 665 | * Sets the version of the expansion. 666 | * 667 | * @param version 668 | * The version of the expansion. 669 | * @return This builder. 670 | */ 671 | @Override 672 | public ExpansionBuilderImpl version(String version) { 673 | this.ver = version; 674 | return this; 675 | } 676 | 677 | } 678 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/Store.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder; 25 | 26 | import java.lang.annotation.Annotation; 27 | import java.lang.reflect.Field; 28 | import java.lang.reflect.Method; 29 | import java.lang.reflect.Modifier; 30 | import java.lang.reflect.Parameter; 31 | import java.util.Arrays; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Optional; 36 | import java.util.concurrent.ConcurrentHashMap; 37 | import java.util.stream.Collectors; 38 | import java.util.stream.Stream; 39 | 40 | import org.spongepowered.api.Platform.Component; 41 | import org.spongepowered.api.Sponge; 42 | import org.spongepowered.api.data.DataHolder; 43 | import org.spongepowered.api.plugin.PluginContainer; 44 | import org.spongepowered.api.service.permission.Subject; 45 | import org.spongepowered.api.text.channel.MessageReceiver; 46 | import org.spongepowered.api.world.Locatable; 47 | 48 | import me.rojo8399.placeholderapi.Attach; 49 | import me.rojo8399.placeholderapi.Listening; 50 | import me.rojo8399.placeholderapi.NoValueException; 51 | import me.rojo8399.placeholderapi.Observer; 52 | import me.rojo8399.placeholderapi.Placeholder; 53 | import me.rojo8399.placeholderapi.Relational; 54 | import me.rojo8399.placeholderapi.Requires; 55 | import me.rojo8399.placeholderapi.Source; 56 | import me.rojo8399.placeholderapi.Token; 57 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 58 | import me.rojo8399.placeholderapi.impl.configs.Messages; 59 | import me.rojo8399.placeholderapi.impl.placeholder.gen.ClassPlaceholderFactory; 60 | import me.rojo8399.placeholderapi.impl.placeholder.gen.DefineableClassLoader; 61 | import me.rojo8399.placeholderapi.impl.placeholder.gen.InternalExpansion; 62 | import me.rojo8399.placeholderapi.impl.utils.TypeUtils; 63 | import ninja.leaping.configurate.ConfigurationNode; 64 | import ninja.leaping.configurate.objectmapping.ObjectMapper; 65 | import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; 66 | 67 | /** 68 | * @author Wundero 69 | * 70 | */ 71 | public class Store { 72 | 73 | private static Store instance; 74 | 75 | static Method find(Object object, String id, boolean rel) { 76 | for (Method m : object.getClass().getMethods()) { 77 | Placeholder p; 78 | if ((p = m.getAnnotation(Placeholder.class)) != null && fix(p.id()).equals(fix(id))) { 79 | if (rel) { 80 | if (m.isAnnotationPresent(Relational.class)) { 81 | return m; 82 | } else { 83 | continue; 84 | } 85 | } 86 | return m; 87 | } 88 | } 89 | return null; 90 | } 91 | 92 | static Map findAll(Object object) { 93 | Class c = object.getClass(); 94 | if (!Modifier.isPublic(c.getModifiers())) { 95 | throw new IllegalArgumentException("Class must be public!"); 96 | } 97 | Map out = new HashMap<>(); 98 | for (Method m : c.getMethods()) { 99 | if (!Modifier.isPublic(m.getModifiers())) { 100 | continue; 101 | } 102 | if (m.isAnnotationPresent(Placeholder.class)) { 103 | out.put(m, m.isAnnotationPresent(Relational.class)); 104 | } 105 | } 106 | return out; 107 | } 108 | 109 | private static String fix(String id) { 110 | id = id.toLowerCase().trim(); 111 | if (id.startsWith("rel_")) { 112 | id = id.substring(4); 113 | } 114 | return id.replace("_", "").replace(" ", ""); 115 | } 116 | 117 | public static Store get() { 118 | if (instance == null) { 119 | instance = new Store(); 120 | } 121 | return instance; 122 | } 123 | 124 | private static boolean verifySource(Parameter param) { 125 | return MessageReceiver.class.isAssignableFrom(param.getType()) 126 | || Locatable.class.isAssignableFrom(param.getType()) || Subject.class.isAssignableFrom(param.getType()) 127 | || DataHolder.class.isAssignableFrom(param.getType()); 128 | } 129 | 130 | private final DefineableClassLoader classLoader = new DefineableClassLoader( 131 | Sponge.getEventManager().getClass().getClassLoader()); 132 | 133 | private final ClassPlaceholderFactory factory = new ClassPlaceholderFactory( 134 | "me.rojo8399.placeholderapi.placeholder", classLoader); 135 | 136 | private Map> normal = new ConcurrentHashMap<>(), rel = new ConcurrentHashMap<>(); 137 | 138 | private Store() { 139 | } 140 | 141 | public List allIds() { 142 | return Stream.concat(getMap(true).entrySet().stream(), getMap(false).entrySet().stream()) 143 | .filter(e -> e.getValue().isEnabled()).map(Map.Entry::getKey).distinct().collect(Collectors.toList()); 144 | } 145 | 146 | Expansion createForMethod(Method m, Object o, Object plugin) { 147 | Class c = o.getClass(); 148 | boolean l = c.isAnnotationPresent(Listening.class), co = c.isAnnotationPresent(ConfigSerializable.class); 149 | int code = verify(o, m); 150 | if (code > 0) { 151 | PlaceholderAPIPlugin.getInstance().getLogger() 152 | .warn("Method " + m.getName() + " in " + o.getClass().getName() + " cannot be loaded!"); 153 | switch (code) { 154 | case 1: 155 | PlaceholderAPIPlugin.getInstance().getLogger() 156 | .warn("This should not happen! Please report this bug on GitHub!"); 157 | break; 158 | case 5: 159 | case 6: 160 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Placeholder already registered!"); 161 | break; 162 | case 7: 163 | case 4: 164 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Method contains incorrect or extra parameters!"); 165 | break; 166 | case 8: 167 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Sponge Version not supported by placeholder!"); 168 | break; 169 | case 9: 170 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Missing plugin dependency!"); 171 | } 172 | return null; 173 | } 174 | Placeholder p = m.getAnnotation(Placeholder.class); 175 | boolean r = m.isAnnotationPresent(Relational.class); 176 | Expansion pl; 177 | try { 178 | pl = factory.create(o, m); 179 | } catch (Exception e) { 180 | PlaceholderAPIPlugin.getInstance().getLogger().warn("An exception occured while creating the placeholder!"); 181 | e.printStackTrace(); 182 | return null; 183 | } 184 | if (co) { 185 | try { 186 | this.fillExpansionConfig(pl); 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | } 190 | } 191 | if (l) { 192 | final Object o2 = o; 193 | pl.setReloadListeners(() -> { 194 | PlaceholderAPIPlugin.getInstance().unregisterListeners(o2); 195 | PlaceholderAPIPlugin.getInstance().registerListeners(o2, plugin); 196 | }); 197 | } 198 | pl.setId(p.id()); 199 | pl.setRelational(r); 200 | pl.reloadListeners(); 201 | pl.refresh(); 202 | return pl; 203 | } 204 | 205 | /** 206 | * Super sketchy deserialization ;) 207 | */ 208 | public void fillExpansionConfig(Expansion exp) throws Exception { 209 | Class fieldData = Class.forName(ObjectMapper.class.getName() + "$FieldData"); 210 | Class expClass = exp.getConfiguration().getClass(); 211 | ObjectMapper mapper = ObjectMapper.forClass(expClass); 212 | Field fieldDataMap = mapper.getClass().getDeclaredField("cachedFields"); 213 | fieldDataMap.setAccessible(true); 214 | @SuppressWarnings("unchecked") 215 | Map map = (Map) fieldDataMap.get(mapper); 216 | for (Map.Entry entry : map.entrySet()) { 217 | Object fd = entry.getValue(); 218 | Field fx = fieldData.getDeclaredField("field"); 219 | fx.setAccessible(true); 220 | Field actual = (Field) fx.get(fd); 221 | actual.setAccessible(true); 222 | if (actual.isAnnotationPresent(Attach.class) 223 | && actual.getAnnotation(Attach.class).value().equalsIgnoreCase(exp.id()) 224 | && actual.getAnnotation(Attach.class).relational() == exp.relational()) { 225 | fieldData.getDeclaredMethod("deserializeFrom", Object.class, ConfigurationNode.class).invoke(fd, 226 | exp.getConfiguration(), getNode(exp).getNode(entry.getKey())); 227 | } else if (!actual.isAnnotationPresent(Attach.class)) { 228 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Field " + fx.getName() + " in placeholder id=" 229 | + exp.id() + "\'s config is not attached to a placeholder!"); 230 | } 231 | } 232 | } 233 | 234 | public Optional> get(String id, boolean relational) { 235 | if (!has(id, relational)) { 236 | return Optional.empty(); 237 | } 238 | return Optional.ofNullable(getMap(relational).get(id)); 239 | } 240 | 241 | private Map> getMap(boolean rel) { 242 | return rel ? this.rel : normal; 243 | } 244 | 245 | public ConfigurationNode getNode(Expansion exp) { 246 | if (exp.getConfiguration() == null) { 247 | return null; 248 | } 249 | String plid = Sponge.getPluginManager().fromInstance(exp.getPlugin()).get().getId(); 250 | return PlaceholderAPIPlugin.getInstance().getRootConfig().getNode("expansions", plid, 251 | (exp.relational() ? "rel_" : "") + exp.id(), "data"); 252 | } 253 | 254 | public Optional> getObserverType(Method m) { 255 | return getType(m, Observer.class); 256 | } 257 | 258 | public Optional> getSourceType(Method m) { 259 | return getType(m, Source.class); 260 | } 261 | 262 | public Optional> getTokenType(Method m) { 263 | return getType(m, Token.class); 264 | } 265 | 266 | private Optional> getType(Method m, Class annotation) { 267 | List params = Arrays.asList(m.getParameters()); 268 | if (params.stream().noneMatch(p -> p.isAnnotationPresent(annotation))) { 269 | return Optional.empty(); 270 | } 271 | return Optional.of(params.stream().filter(p -> p.isAnnotationPresent(annotation)).findAny().get().getType()); 272 | } 273 | 274 | public boolean has(String id) { 275 | return has(id, true) || has(id, false); 276 | } 277 | 278 | public boolean has(String id, boolean relational) { 279 | if (id == null) { 280 | return false; 281 | } 282 | return getMap(relational).containsKey(fix(id)); 283 | } 284 | 285 | public List ids(boolean relational) { 286 | return getMap(relational).entrySet().stream().filter(e -> e.getValue().isEnabled()).map(Map.Entry::getKey) 287 | .collect(Collectors.toList()); 288 | } 289 | 290 | public boolean isBoth(String id) { 291 | return isRelational(id) && isNormal(id); 292 | } 293 | 294 | public boolean isNormal(String id) { 295 | return normal.containsKey(fix(id)); 296 | } 297 | 298 | public boolean isRelational(String id) { 299 | return rel.containsKey(fix(id)); 300 | } 301 | 302 | public Object parse(String id, boolean relational, Object src, Object obs, Optional token) 303 | throws Exception { 304 | if (!has(id, relational)) { 305 | return null; 306 | } 307 | @SuppressWarnings("unchecked") 308 | Expansion exp = (Expansion) get(id, relational).get(); 309 | if (!exp.isEnabled()) { 310 | throw new NoValueException(Messages.get().placeholder.notEnabled.t()); 311 | } 312 | try { 313 | if (src == null || obs == null) { 314 | if (src != null && exp.getSourceClass().isAssignableFrom(src.getClass())) { 315 | return exp.parse(exp.convertSource(src), null, token); 316 | } else if (obs != null && exp.getSourceClass().isAssignableFrom(obs.getClass())) { 317 | return exp.parse(null, exp.convertObserver(obs), token); 318 | } else { 319 | return exp.parse(null, null, token); 320 | } 321 | } 322 | if (exp.getSourceClass().isAssignableFrom(src.getClass()) 323 | && exp.getObserverClass().isAssignableFrom(obs.getClass())) { 324 | return exp.parse(exp.convertSource(src), exp.convertObserver(obs), token); 325 | } 326 | } catch (NoValueException e) { 327 | throw new NoValueException(e.getTextMessage(), 328 | e.suggestions().isEmpty() ? exp.getSuggestions(token.orElse(null)) : e.suggestions()); 329 | } 330 | throw new NoValueException(Messages.get().placeholder.invalidSrcObs.t()); 331 | } 332 | 333 | public Optional parse(String id, boolean relational, Object src, Object obs, Optional token, 334 | Class expected) throws Exception { 335 | Object o = parse(id, relational, src, obs, token); 336 | return TypeUtils.tryCast(o, expected); 337 | } 338 | 339 | public boolean register(Expansion expansion) { 340 | String id = fix(expansion.id()); 341 | if (getMap(expansion.relational()).containsKey(id)) { 342 | return false; 343 | } 344 | expansion.populateConfig(); 345 | expansion.reloadListeners(); 346 | getMap(expansion.relational()).put(id, expansion); 347 | return true; 348 | } 349 | 350 | private boolean reload(Expansion e, String id, boolean rel) { 351 | if (!e.refresh()) { 352 | return false; 353 | } 354 | boolean out = true; 355 | if (e instanceof InternalExpansion) { 356 | Object handle = ((InternalExpansion) e).getHandle(); 357 | getMap(rel).remove(id); 358 | try { 359 | out = ExpansionBuilderImpl.builder(e.getSourceClass(), e.getObserverClass(), e.getValueClass()) 360 | .fromUnknown(e).from(handle, id, e.getPlugin()).buildAndRegister(); 361 | } catch (Exception e1) { 362 | return false; 363 | } 364 | } 365 | return out; 366 | } 367 | 368 | public boolean reload(String id) { 369 | if (!has(id)) { 370 | return false; 371 | } 372 | id = fix(id); 373 | Optional> rel = get(id, true); 374 | Optional> norm = get(id, false); 375 | boolean out = true; 376 | if (rel.isPresent()) { 377 | out = reload(rel.get(), id, true); 378 | } 379 | if (norm.isPresent()) { 380 | out = out && reload(norm.get(), id, false); 381 | } 382 | return out; 383 | } 384 | 385 | public int reloadAll() { 386 | return Stream.concat(getMap(true).keySet().stream(), getMap(false).keySet().stream()).distinct() 387 | .map(this::reload).map(b -> b ? 1 : 0).reduce(0, TypeUtils::add); 388 | } 389 | 390 | public void saveAll() { 391 | Stream.concat(getMap(true).values().stream(), getMap(false).values().stream()).forEach(Expansion::saveConfig); 392 | } 393 | 394 | public void saveExpansionConfig(Expansion exp) throws Exception { 395 | Class objectMapper = ObjectMapper.class; 396 | Class fieldData = Class.forName(objectMapper.getName() + "$FieldData"); 397 | Class expClass = exp.getConfiguration().getClass(); 398 | ObjectMapper mapper = ObjectMapper.forClass(expClass); 399 | Field fieldDataMap = mapper.getClass().getDeclaredField("cachedFields"); 400 | fieldDataMap.setAccessible(true); 401 | @SuppressWarnings("unchecked") 402 | Map map = (Map) fieldDataMap.get(mapper); 403 | for (Map.Entry entry : map.entrySet()) { 404 | Object fd = entry.getValue(); 405 | Field fx = fieldData.getDeclaredField("field"); 406 | fx.setAccessible(true); 407 | Field actual = (Field) fx.get(fd); 408 | actual.setAccessible(true); 409 | if (actual.isAnnotationPresent(Attach.class) 410 | && actual.getAnnotation(Attach.class).value().equalsIgnoreCase(exp.id()) 411 | && actual.getAnnotation(Attach.class).relational() == exp.relational()) { 412 | fieldData.getDeclaredMethod("serializeTo", Object.class, ConfigurationNode.class).invoke(fd, 413 | exp.getConfiguration(), getNode(exp).getNode(entry.getKey())); 414 | } else if (!actual.isAnnotationPresent(Attach.class)) { 415 | PlaceholderAPIPlugin.getInstance().getLogger().warn("Field " + fx.getName() + " in placeholder id=" 416 | + exp.id() + "\'s config is not attached to a placeholder!"); 417 | } 418 | } 419 | } 420 | 421 | private int verify(Object object, Method m) { 422 | Placeholder p; 423 | if ((p = m.getAnnotation(Placeholder.class)) != null) { 424 | String id = fix(p.id()); 425 | List params = Arrays.asList(m.getParameters()); 426 | boolean relational = m.isAnnotationPresent(Relational.class); 427 | if (relational) { 428 | if (this.rel.containsKey(id)) { 429 | return 5; 430 | } 431 | } else { 432 | if (this.normal.containsKey(id)) { 433 | return 6; 434 | } 435 | } 436 | if (!params.stream().map(px -> { 437 | if (px.getAnnotation(Token.class) != null) { 438 | return true; 439 | } 440 | if (px.getAnnotation(Source.class) != null || px.getAnnotation(Observer.class) != null) { 441 | return verifySource(px); 442 | } 443 | return false; 444 | }).reduce(true, TypeUtils::and)) { 445 | return 7; 446 | } 447 | if (params.stream().map(px -> { 448 | if (px.getAnnotation(Token.class) != null) { 449 | return true; 450 | } 451 | if (px.getAnnotation(Source.class) != null || px.getAnnotation(Observer.class) != null) { 452 | return verifySource(px); 453 | } 454 | return false; 455 | }).filter(px -> px).count() != params.size()) { 456 | return 4; 457 | } 458 | if (m.isAnnotationPresent(Requires.class)) { 459 | Requires r = m.getAnnotation(Requires.class); 460 | String spv = Sponge.getPlatform().getContainer(Component.API).getVersion().orElse("5.1"); 461 | if (!r.spongeVersion().isEmpty() && !TypeUtils.matchVersion(r.spongeVersion(), spv)) { 462 | return 8; 463 | } 464 | for (String pl : r.plugins()) { 465 | if (!pl.contains(":")) { 466 | // is only pl id 467 | if (!Sponge.getPluginManager().isLoaded(pl)) { 468 | return 9; 469 | } 470 | } 471 | String pid = pl.split(":")[0]; 472 | String ver = pl.replace(pid + ":", ""); 473 | Optional plc = Sponge.getPluginManager().getPlugin(pid); 474 | if (plc.isPresent()) { 475 | PluginContainer pc = plc.get(); 476 | String pcv = pc.getVersion().orElse(null); 477 | if (pcv == null) { 478 | return 9; 479 | } 480 | if (!TypeUtils.matchVersion(ver, pcv)) { 481 | return 9; 482 | } 483 | } else { 484 | return 9; 485 | } 486 | } 487 | } 488 | return 0; 489 | } 490 | return 1; 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/gen/ClassPlaceholderFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder.gen; 25 | 26 | import com.google.common.cache.CacheBuilder; 27 | import com.google.common.cache.CacheLoader; 28 | import com.google.common.cache.LoadingCache; 29 | import me.rojo8399.placeholderapi.*; 30 | import me.rojo8399.placeholderapi.Observer; 31 | import me.rojo8399.placeholderapi.impl.placeholder.Expansion; 32 | import me.rojo8399.placeholderapi.impl.utils.TypeUtils; 33 | import org.objectweb.asm.*; 34 | import org.spongepowered.api.world.Locatable; 35 | 36 | import javax.annotation.Nullable; 37 | import java.lang.reflect.Method; 38 | import java.lang.reflect.Modifier; 39 | import java.lang.reflect.Parameter; 40 | import java.lang.reflect.ParameterizedType; 41 | import java.util.*; 42 | import java.util.concurrent.atomic.AtomicInteger; 43 | import java.util.function.Consumer; 44 | 45 | import static com.google.common.base.Preconditions.checkArgument; 46 | import static com.google.common.base.Preconditions.checkNotNull; 47 | import static org.objectweb.asm.Opcodes.*; 48 | 49 | /** 50 | * @author Wundero 51 | * 52 | */ 53 | public class ClassPlaceholderFactory { 54 | 55 | private static final String OBJECT_NAME = Type.getInternalName(Object.class); 56 | private static final String OPT_NAME = Type.getInternalName(Optional.class); 57 | private static final Map, Integer> order; 58 | 59 | private static final String PLACEHOLDER_NAME = Type.getInternalName(InternalExpansion.class); 60 | 61 | private static final String STRING_NAME = Type.getInternalName(String.class); 62 | 63 | private static final String TLC_SIG, TRIM_SIG; 64 | 65 | static { 66 | order = new HashMap<>(); 67 | order.put(Source.class, 0); 68 | order.put(Observer.class, 1); 69 | order.put(Token.class, 2); 70 | String f; 71 | try { 72 | f = Type.getMethodDescriptor(String.class.getMethod("toLowerCase")); 73 | } catch (Exception e) { 74 | f = "()Ljava/lang/String;"; 75 | } 76 | TLC_SIG = f; 77 | try { 78 | f = Type.getMethodDescriptor(String.class.getMethod("trim")); 79 | } catch (Exception e) { 80 | f = "()Ljava/lang/String;"; 81 | } 82 | TRIM_SIG = f; 83 | } 84 | 85 | private static Class boxedPrim(Class primClass) { 86 | if (primClass.isPrimitive()) { 87 | if (primClass.equals(int.class)) { 88 | return Integer.class; 89 | } 90 | if (primClass.equals(char.class)) { 91 | return Character.class; 92 | } 93 | if (primClass.equals(byte.class)) { 94 | return Byte.class; 95 | } 96 | if (primClass.equals(boolean.class)) { 97 | return Boolean.class; 98 | } 99 | if (primClass.equals(long.class)) { 100 | return Long.class; 101 | } 102 | if (primClass.equals(short.class)) { 103 | return Short.class; 104 | } 105 | if (primClass.equals(float.class)) { 106 | return Float.class; 107 | } 108 | if (primClass.equals(double.class)) { 109 | return Double.class; 110 | } 111 | } 112 | return primClass; 113 | } 114 | 115 | private static void boxToPrim(MethodVisitor mv, Class p, int varloc) { 116 | if (!p.isPrimitive()) { 117 | return; 118 | } 119 | String boxName = null, typeName = null, value = Type.getInternalName(p); 120 | int store = ISTORE, load = ILOAD; 121 | Consumer converter = (mv2) -> { 122 | }; 123 | if (p.equals(int.class)) { 124 | boxName = "Integer"; 125 | typeName = "I"; 126 | } 127 | if (p.equals(char.class)) { 128 | boxName = "Character"; 129 | typeName = "C"; 130 | converter = mv2 -> mv2.visitInsn(I2C); 131 | } 132 | if (p.equals(byte.class)) { 133 | boxName = "Byte"; 134 | typeName = "B"; 135 | converter = mv2 -> mv2.visitInsn(I2B); 136 | } 137 | if (p.equals(boolean.class)) { 138 | boxName = "Boolean"; 139 | typeName = "Z"; 140 | converter = mv2 -> mv2.visitInsn(I2B); 141 | } 142 | if (p.equals(long.class)) { 143 | boxName = "Long"; 144 | typeName = "J"; 145 | load = LLOAD; 146 | store = LSTORE; 147 | } 148 | if (p.equals(short.class)) { 149 | boxName = "Short"; 150 | typeName = "S"; 151 | converter = mv2 -> mv2.visitInsn(I2S); 152 | } 153 | if (p.equals(float.class)) { 154 | boxName = "Float"; 155 | typeName = "F"; 156 | load = FLOAD; 157 | store = FSTORE; 158 | } 159 | if (p.equals(double.class)) { 160 | boxName = "Double"; 161 | typeName = "D"; 162 | load = DLOAD; 163 | store = DSTORE; 164 | } 165 | if (typeName == null) { 166 | return; 167 | } 168 | mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/" + boxName, value + "Value", "()" + typeName, false); 169 | mv.visitVarInsn(store, varloc); 170 | mv.visitVarInsn(load, varloc); 171 | converter.accept(mv); 172 | } 173 | 174 | private static int getOrder(Parameter p) { 175 | for (java.lang.annotation.Annotation a : p.getAnnotations()) { 176 | if (order.containsKey(a.annotationType())) { 177 | return order.get(a.annotationType()); 178 | } 179 | } 180 | return 0; 181 | } 182 | 183 | private static void nullCheck(MethodVisitor mv, boolean nullable, Consumer success, 184 | boolean throwError) { 185 | if (nullable) { 186 | return; 187 | } 188 | // assume null check obj already loaded to stack 189 | Label no = new Label(); 190 | mv.visitJumpInsn(IFNONNULL, no); 191 | if (throwError) { 192 | mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(NoValueException.class)); 193 | mv.visitVarInsn(ASTORE, 7); 194 | mv.visitVarInsn(ALOAD, 7); 195 | mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(NoValueException.class), "", "()V", 196 | false); 197 | mv.visitVarInsn(ALOAD, 7); 198 | mv.visitInsn(Opcodes.ATHROW); 199 | } else { 200 | mv.visitInsn(ACONST_NULL); 201 | mv.visitInsn(ARETURN); 202 | } 203 | mv.visitLabel(no); 204 | success.accept(mv); 205 | } 206 | 207 | private static void returnInsn(MethodVisitor mv, Class clazz) { 208 | if (clazz.isPrimitive()) { 209 | if (clazz.equals(float.class)) { 210 | mv.visitInsn(FRETURN); 211 | return; 212 | } 213 | if (clazz.equals(long.class)) { 214 | mv.visitInsn(LRETURN); 215 | return; 216 | } 217 | if (clazz.equals(double.class)) { 218 | mv.visitInsn(DRETURN); 219 | return; 220 | } 221 | mv.visitInsn(IRETURN); 222 | return; 223 | } 224 | mv.visitInsn(ARETURN); 225 | } 226 | 227 | private static void skipIfNull(MethodVisitor mv, Consumer success) { 228 | // assume null check obj already loaded to stack 229 | Label no = new Label(); 230 | mv.visitJumpInsn(IFNULL, no); 231 | success.accept(mv); 232 | mv.visitLabel(no); 233 | } 234 | 235 | private static void tryCatch(MethodVisitor mv, Consumer t, Consumer c) { 236 | Label st = new Label(), et = new Label(), sc = new Label(), ec = new Label(); 237 | mv.visitTryCatchBlock(st, et, sc, "java/lang/Exception"); 238 | mv.visitLabel(st); 239 | t.accept(mv); 240 | mv.visitLabel(et); 241 | mv.visitJumpInsn(GOTO, ec); 242 | mv.visitLabel(sc); 243 | c.accept(mv); 244 | mv.visitLabel(ec); 245 | } 246 | 247 | private static void unboxFromPrim(MethodVisitor mv, Class p) { 248 | if (!p.isPrimitive()) { 249 | return; 250 | } 251 | String boxName = null, typeName = null; 252 | if (p.equals(int.class)) { 253 | boxName = "Integer"; 254 | typeName = "I"; 255 | } 256 | if (p.equals(char.class)) { 257 | boxName = "Character"; 258 | typeName = "C"; 259 | } 260 | if (p.equals(byte.class)) { 261 | boxName = "Byte"; 262 | typeName = "B"; 263 | } 264 | if (p.equals(boolean.class)) { 265 | boxName = "Boolean"; 266 | typeName = "Z"; 267 | } 268 | if (p.equals(long.class)) { 269 | boxName = "Long"; 270 | typeName = "J"; 271 | } 272 | if (p.equals(short.class)) { 273 | boxName = "Short"; 274 | typeName = "S"; 275 | } 276 | if (p.equals(float.class)) { 277 | boxName = "Float"; 278 | typeName = "F"; 279 | } 280 | if (p.equals(double.class)) { 281 | boxName = "Double"; 282 | typeName = "D"; 283 | } 284 | if (typeName == null) { 285 | return; 286 | } 287 | mv.visitMethodInsn(INVOKESTATIC, "java/lang/" + boxName, "valueOf", 288 | "(" + typeName + ")Ljava/lang/" + boxName + ";", false); 289 | } 290 | 291 | private static void utilsTryCast(MethodVisitor mv, Class expected, boolean orNull) { 292 | // assume obj to cast is already on stack and only that obj is on stack 293 | mv.visitLdcInsn(Type.getType(expected)); 294 | mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); 295 | mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(TypeUtils.class), "tryCast", 296 | "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/Boolean;)Ljava/util/Optional;", false); 297 | if (orNull) { 298 | mv.visitInsn(ACONST_NULL); 299 | mv.visitMethodInsn(INVOKEVIRTUAL, OPT_NAME, "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", false); 300 | } 301 | } 302 | 303 | private final LoadingCache>> cache = CacheBuilder.newBuilder() 304 | .concurrencyLevel(1).weakValues().build(new CacheLoader>>() { 305 | @Override 306 | public Class> load(Method method) throws Exception { 307 | return createClass(method); 308 | } 309 | }); 310 | 311 | private DefineableClassLoader classLoader; 312 | 313 | private AtomicInteger iid = new AtomicInteger(); 314 | 315 | private String targetPackage; 316 | 317 | public ClassPlaceholderFactory(String targetPackage, DefineableClassLoader loader) { 318 | checkNotNull(targetPackage, "targetPackage"); 319 | checkArgument(!targetPackage.isEmpty(), "targetPackage cannot be empty"); 320 | this.targetPackage = targetPackage + '.'; 321 | this.classLoader = checkNotNull(loader, "classLoader"); 322 | } 323 | 324 | public Expansion create(Object handle, Method method) throws Exception { 325 | if (!Modifier.isPublic(handle.getClass().getModifiers())) { 326 | throw new IllegalArgumentException("Class must be public!"); 327 | } 328 | if (!Modifier.isPublic(method.getModifiers())) { 329 | throw new IllegalArgumentException("Method must be public!"); 330 | } 331 | return this.cache.get(method).getConstructor(method.getDeclaringClass()).newInstance(handle); 332 | } 333 | 334 | Class> createClass(Method method) { 335 | Class handle = method.getDeclaringClass(); 336 | if (!Modifier.isPublic(handle.getModifiers())) { 337 | throw new IllegalArgumentException("Class must be public!"); 338 | } 339 | if (!Modifier.isPublic(method.getModifiers())) { 340 | throw new IllegalArgumentException("Method must be public!"); 341 | } 342 | String id = method.getAnnotation(Placeholder.class).id(); 343 | String name = this.targetPackage + id + "Placeholder_" + handle.getSimpleName() + "_" + method.getName() 344 | + iid.incrementAndGet(); 345 | byte[] bytes = generateClass(name, handle, method); 346 | /* 347 | Files.write(new File(name + ".class").toPath(), bytes); 348 | System.out.println("written " + name); 349 | */ 350 | return this.classLoader.defineClass(name, bytes); 351 | } 352 | 353 | private byte[] generateClass(String name, Class handle, Method method) { 354 | name = name.replace(".", "/"); 355 | final String handleName = Type.getInternalName(handle); 356 | final String handleDescriptor = Type.getDescriptor(handle); 357 | List pm = Arrays.asList(method.getParameters()); 358 | boolean token = pm.stream().anyMatch(p -> p.isAnnotationPresent(Token.class)); 359 | final boolean source = pm.stream().anyMatch(p -> p.isAnnotationPresent(Source.class)); 360 | final boolean observer = pm.stream().anyMatch(p -> p.isAnnotationPresent(Observer.class)); 361 | final boolean srcNullable = pm.stream().filter(p -> p.isAnnotationPresent(Source.class)) 362 | .anyMatch(p -> p.isAnnotationPresent(Nullable.class)); 363 | final boolean optionalTokenType = token && pm.stream().anyMatch(p -> p.getType().equals(Optional.class)); 364 | final Optional> tokenClass = token 365 | ? pm.stream().filter(p -> p.isAnnotationPresent(Token.class)).findAny().map(pr -> { 366 | if (optionalTokenType) { 367 | return (Class) ((ParameterizedType) pr.getParameterizedType()).getActualTypeArguments()[0]; 368 | } else { 369 | return pr.getType(); 370 | } 371 | }) 372 | : Optional.empty(); 373 | token = token && tokenClass.isPresent(); 374 | final boolean obsNullable = pm.stream().filter(p -> p.isAnnotationPresent(Observer.class)) 375 | .anyMatch(p -> p.isAnnotationPresent(Nullable.class)); 376 | final boolean tokNullable = token && !optionalTokenType && pm.stream() 377 | .filter(p -> p.isAnnotationPresent(Token.class)).anyMatch(p -> p.isAnnotationPresent(Nullable.class)); 378 | final boolean fixToken = token && pm.stream().filter(p -> p.isAnnotationPresent(Token.class)) 379 | .map(p -> p.getAnnotation(Token.class)).anyMatch(Token::fix); 380 | final Optional> sourceType = pm.stream().filter(p -> p.isAnnotationPresent(Source.class)).findFirst() 381 | .map(Parameter::getType); 382 | final Optional> observerType = pm.stream().filter(p -> p.isAnnotationPresent(Observer.class)) 383 | .findFirst().map(Parameter::getType); 384 | Class returnType = method.getReturnType(); 385 | if (returnType.equals(Void.TYPE)) { 386 | returnType = Object.class; 387 | } 388 | String retString = Type.getDescriptor(returnType); 389 | String parseMethodDescriptor = "(L" + Type.getInternalName(sourceType.orElse(Locatable.class)) + ";L" 390 | + Type.getInternalName(observerType.orElse(Locatable.class)) + ";L" + OPT_NAME + ";)" + retString; 391 | String externalParseDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/util/Optional;)Ljava/lang/Object;"; 392 | String methodDescriptor = Type.getMethodDescriptor(method); 393 | ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 394 | MethodVisitor mv; 395 | cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, name, null, PLACEHOLDER_NAME, null); 396 | { 397 | mv = cw.visitMethod(ACC_PUBLIC, "", '(' + handleDescriptor + ")V", null, null); 398 | mv.visitCode(); 399 | mv.visitVarInsn(ALOAD, 0); 400 | mv.visitLdcInsn(method.getAnnotation(Placeholder.class).id()); 401 | mv.visitVarInsn(ALOAD, 1); 402 | mv.visitMethodInsn(INVOKESPECIAL, PLACEHOLDER_NAME, "", 403 | "(L" + STRING_NAME + ";L" + OBJECT_NAME + ";)V", false); 404 | mv.visitInsn(RETURN); 405 | mv.visitMaxs(0, 0); 406 | mv.visitEnd(); 407 | } 408 | { 409 | mv = cw.visitMethod(ACC_PUBLIC, "parse", parseMethodDescriptor, null, 410 | new String[] { "java/lang/Exception" }); 411 | mv.visitCode(); 412 | mv.visitVarInsn(ALOAD, 0); 413 | mv.visitFieldInsn(GETFIELD, name, "handle", "L" + OBJECT_NAME + ";"); 414 | mv.visitTypeInsn(CHECKCAST, handleName); 415 | if (token) { 416 | if (!String.class.isAssignableFrom(tokenClass.get()) || (tokenClass.get().isArray() 417 | && String.class.isAssignableFrom(tokenClass.get().getComponentType()))) { 418 | mv.visitVarInsn(ALOAD, 3); 419 | mv.visitInsn(ACONST_NULL); 420 | mv.visitMethodInsn(INVOKEVIRTUAL, OPT_NAME, "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", 421 | false); 422 | if (optionalTokenType) { 423 | utilsTryCast(mv, boxedPrim(tokenClass.get()), false); 424 | mv.visitVarInsn(ASTORE, 3); 425 | } else { 426 | mv.visitVarInsn(ASTORE, 4); 427 | mv.visitVarInsn(ALOAD, 4); 428 | skipIfNull(mv, mv2 -> { 429 | mv2.visitVarInsn(ALOAD, 4); 430 | utilsTryCast(mv2, boxedPrim(tokenClass.get()), true); 431 | mv2.visitVarInsn(ASTORE, 4); 432 | }); 433 | } 434 | } else { 435 | if (!optionalTokenType) { 436 | mv.visitVarInsn(ALOAD, 3); 437 | mv.visitInsn(ACONST_NULL); 438 | mv.visitMethodInsn(INVOKEVIRTUAL, OPT_NAME, "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", 439 | false); 440 | mv.visitVarInsn(ASTORE, 4); 441 | if (fixToken) { 442 | mv.visitVarInsn(ALOAD, 4); 443 | skipIfNull(mv, mv2 -> { 444 | mv2.visitVarInsn(ALOAD, 4); 445 | mv2.visitTypeInsn(CHECKCAST, STRING_NAME); 446 | mv2.visitMethodInsn(INVOKEVIRTUAL, STRING_NAME, "toLowerCase", TLC_SIG, false); 447 | mv2.visitMethodInsn(INVOKEVIRTUAL, STRING_NAME, "trim", TRIM_SIG, false); 448 | mv2.visitVarInsn(ASTORE, 4); 449 | }); 450 | } 451 | } 452 | } 453 | } 454 | for (int i = 0; i < method.getParameterCount(); i++) { 455 | int x; 456 | Parameter p = method.getParameters()[i]; 457 | x = getOrder(p); 458 | if (x == 2 && !optionalTokenType) { 459 | x = 3; 460 | } 461 | mv.visitVarInsn(ALOAD, x + 1); 462 | boolean nullable = false; 463 | switch (x) { 464 | case 0: 465 | nullable = srcNullable; 466 | break; 467 | case 1: 468 | nullable = obsNullable; 469 | break; 470 | case 3: 471 | nullable = tokNullable; 472 | break; 473 | } 474 | final int x1 = x; 475 | nullCheck(mv, nullable, mv2 -> mv2.visitVarInsn(ALOAD, x1 + 1), x == 3 && token); 476 | mv.visitTypeInsn(CHECKCAST, Type.getInternalName(boxedPrim(p.getType()))); 477 | boxToPrim(mv, p.getType(), x1 + 1); 478 | } 479 | mv.visitMethodInsn(INVOKEVIRTUAL, handleName, method.getName(), methodDescriptor, false); 480 | if (method.getReturnType().equals(Void.TYPE)) { 481 | mv.visitLdcInsn(""); 482 | } 483 | if (!retString.startsWith("L")) { 484 | returnInsn(mv, returnType); 485 | } else { 486 | mv.visitInsn(ARETURN); 487 | } 488 | mv.visitMaxs(10, 10); 489 | mv.visitEnd(); 490 | } 491 | { 492 | mv = cw.visitMethod(ACC_PUBLIC, "parse", externalParseDescriptor, null, 493 | new String[] { "java/lang/Exception" }); 494 | mv.visitCode(); 495 | mv.visitVarInsn(ALOAD, 0); 496 | if (sourceType.isPresent() && source) { 497 | mv.visitVarInsn(ALOAD, 1); 498 | tryCatch(mv, mv2 -> mv2.visitTypeInsn(CHECKCAST, Type.getInternalName(sourceType.get())), mv2 -> { 499 | mv2.visitLdcInsn(""); 500 | mv2.visitInsn(ARETURN); 501 | }); 502 | } else { 503 | mv.visitInsn(ACONST_NULL); 504 | } 505 | if (observerType.isPresent() && observer) { 506 | mv.visitVarInsn(ALOAD, 2); 507 | tryCatch(mv, mv2 -> mv2.visitTypeInsn(CHECKCAST, Type.getInternalName(observerType.get())), mv2 -> { 508 | mv2.visitLdcInsn(""); 509 | mv2.visitInsn(ARETURN); 510 | }); 511 | } else { 512 | mv.visitInsn(ACONST_NULL); 513 | } 514 | mv.visitVarInsn(ALOAD, 3); 515 | mv.visitMethodInsn(INVOKEVIRTUAL, name, "parse", parseMethodDescriptor, false); 516 | unboxFromPrim(mv, returnType); 517 | mv.visitInsn(ARETURN); 518 | mv.visitMaxs(0, 0); 519 | mv.visitEnd(); 520 | } 521 | cw.visitEnd(); 522 | return cw.toByteArray(); 523 | } 524 | 525 | } 526 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/gen/DefineableClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder.gen; 25 | 26 | /** 27 | * @author Wundero 28 | * 29 | */ 30 | public class DefineableClassLoader extends ClassLoader { 31 | 32 | public DefineableClassLoader(ClassLoader loader) { 33 | super(loader); 34 | } 35 | 36 | @SuppressWarnings("unchecked") 37 | public Class defineClass(String name, byte[] b) { 38 | return (Class) defineClass(name, b, 0, b.length); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/placeholder/gen/InternalExpansion.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.placeholder.gen; 25 | 26 | import me.rojo8399.placeholderapi.impl.placeholder.Expansion; 27 | 28 | /** 29 | * @author Wundero 30 | * 31 | */ 32 | public abstract class InternalExpansion extends Expansion { 33 | 34 | protected Object handle; 35 | 36 | public InternalExpansion(String id, Object handle) { 37 | super(id); 38 | this.handle = handle; 39 | } 40 | 41 | @SuppressWarnings("unchecked") 42 | @Override 43 | public T getConfiguration() { 44 | try { 45 | return (T) handle; 46 | } catch (Exception e) { 47 | return null; 48 | } 49 | } 50 | 51 | public Object getHandle() { 52 | return handle; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/utils/TextUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.utils; 25 | 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | import java.util.function.Function; 32 | import java.util.regex.Matcher; 33 | import java.util.regex.Pattern; 34 | 35 | import javax.annotation.Nullable; 36 | 37 | import org.spongepowered.api.data.key.Keys; 38 | import org.spongepowered.api.item.inventory.ItemStack; 39 | import org.spongepowered.api.item.inventory.ItemStackSnapshot; 40 | import org.spongepowered.api.text.LiteralText; 41 | import org.spongepowered.api.text.Text; 42 | import org.spongepowered.api.text.TextTemplate; 43 | import org.spongepowered.api.text.action.TextActions; 44 | import org.spongepowered.api.text.format.TextFormat; 45 | import org.spongepowered.api.text.serializer.TextSerializers; 46 | 47 | /** 48 | * @author Wundero 49 | * 50 | */ 51 | public class TextUtils { 52 | 53 | private static Text fix(Text to, TextFormat l) { 54 | return to.toBuilder().format(l.merge(to.getFormat())).build(); 55 | } 56 | 57 | /** 58 | * Turn a text into a list of texts 59 | */ 60 | public static List flatten(Text text) { 61 | if (text == null || text.isEmpty()) { 62 | return new ArrayList<>(); 63 | } 64 | List out = new ArrayList<>(); 65 | List children = text.getChildren(); 66 | out.add(text.toBuilder().removeAll().build()); 67 | children.forEach(c -> out.addAll(flatten(c))); 68 | return out; 69 | } 70 | 71 | /** 72 | * Get the last TextFormat object for a text and it's children. 73 | */ 74 | public static TextFormat getLastFormat(Text text) { 75 | if (text.getChildren().isEmpty()) { 76 | return text.getFormat(); 77 | } 78 | return text.getChildren().stream().map(Text::getFormat).reduce(TextFormat::merge) 79 | .orElse(text.getFormat()); 80 | } 81 | 82 | private static TextTemplate multi(String p, Pattern pattern, Text t) { 83 | TextTemplate out = TextTemplate.of(); 84 | Matcher m = pattern.matcher(p); 85 | if(!m.find()) { 86 | return TextTemplate.of(t); 87 | } 88 | String p2 = m.group(); 89 | String ex = m.group(1); 90 | String pre = p2.substring(0, p2.indexOf(ex)); 91 | String post = p2.substring(p2.indexOf(ex) + ex.length()); 92 | String pt = p.substring(0, p.indexOf(p2)); 93 | String ppt = p.substring(p.indexOf(p2) + p2.length()); 94 | boolean recurse = false; 95 | if (pattern.matcher(ppt).find()) { 96 | recurse = true; 97 | } 98 | Text.Builder ptt = Text.builder(pt).format(t.getFormat()); 99 | Text.Builder pptt = Text.builder(ppt).format(t.getFormat()); 100 | t.getClickAction().ifPresent(c -> { 101 | ptt.onClick(c); 102 | pptt.onClick(c); 103 | }); 104 | t.getShiftClickAction().ifPresent(c -> { 105 | ptt.onShiftClick(c); 106 | pptt.onShiftClick(c); 107 | }); 108 | t.getHoverAction().ifPresent(c -> { 109 | ptt.onHover(c); 110 | pptt.onHover(c); 111 | }); 112 | Text pretext = ptt.build(); 113 | Text posttext = pptt.build(); 114 | if (recurse) { 115 | return out 116 | .concat(TextTemplate.of(pre, post, 117 | new Object[] { pretext, TextTemplate.arg(ex).format(t.getFormat()) })) 118 | .concat(multi(ppt, pattern, t)); 119 | } else { 120 | return out.concat(TextTemplate.of(pre, post, 121 | new Object[] { pretext, TextTemplate.arg(ex).format(t.getFormat()), posttext })); 122 | } 123 | } 124 | 125 | public static Text ofItem(@Nullable ItemStack item) { 126 | if (item == null) { 127 | return Text.EMPTY; 128 | } 129 | String q = item.getQuantity() > 1 ? " (" + item.getQuantity() + ")" : ""; 130 | return Text.of(TextActions.showItem(item.createSnapshot()), item.getOrElse(Keys.DISPLAY_NAME, Text.of(item)), 131 | q); 132 | } 133 | 134 | public static Text ofItem(@Nullable ItemStackSnapshot item) { 135 | if (item == null) { 136 | return Text.EMPTY; 137 | } 138 | return Text.of(TextActions.showItem(item), item.getOrElse(Keys.DISPLAY_NAME, Text.of(item))); 139 | } 140 | 141 | /** 142 | * Find all variables in a string and parse it into a text template. 143 | */ 144 | public static TextTemplate parse(final String i, Pattern placeholderPattern) { 145 | if (i == null) { 146 | return null; 147 | } 148 | Function parser = TextSerializers.FORMATTING_CODE::deserialize; 149 | if (placeholderPattern == null) { 150 | placeholderPattern = Pattern.compile("[%]([^ %]+)[%]", Pattern.CASE_INSENSITIVE); 151 | } 152 | String in = i; 153 | if (!placeholderPattern.matcher(in).find()) { 154 | // No placeholders exist 155 | return TextTemplate.of(parser.apply(in)); 156 | } 157 | // What is not a placeholder - can be empty 158 | String[] textParts = in.split(placeholderPattern.pattern()); 159 | Matcher matcher = placeholderPattern.matcher(in); 160 | // Check if empty and create starting template 161 | TextTemplate out = textParts.length == 0 ? TextTemplate.of() : TextTemplate.of(parser.apply(textParts[0])); 162 | int x = 1; 163 | TextFormat last = textParts.length == 0 ? TextFormat.NONE : getLastFormat(parser.apply(textParts[0])); 164 | while ((matcher = matcher.reset(in)).find()) { 165 | String mg = matcher.group().substring(1); // Get actual placeholder 166 | mg = mg.substring(0, mg.length() - 1); 167 | // Get format for arg 168 | if (x <= textParts.length) { 169 | last = last.merge(getLastFormat(parser.apply(textParts[x - 1]))); 170 | } 171 | // Make arg 172 | out = out.concat(TextTemplate.of(TextTemplate.arg(mg).format(last))); 173 | if (x < textParts.length) { 174 | // If there exists a part to insert 175 | out = out.concat(TextTemplate.of(fix(parser.apply(textParts[x]), last))); 176 | } 177 | in = matcher.replaceFirst(""); 178 | x++; 179 | } 180 | return out; 181 | } 182 | 183 | /** 184 | * Make a text object by repeating a text n times. 185 | */ 186 | public static Text repeat(Text original, int times) { 187 | Text out = original; 188 | for (int i = 0; i < times; i++) { 189 | out = out.concat(original); 190 | } 191 | return out; 192 | } 193 | 194 | public static Text replace(Text original, String o, String n) { 195 | List texts = flatten(original); 196 | return texts.stream().map(text -> replace0(text, o, n)).reduce(Text.of(), Text::concat); 197 | } 198 | 199 | private static Text replace0(Text or, String o, String n) { 200 | if (!(or instanceof LiteralText)) { 201 | return or; 202 | } 203 | LiteralText lt = (LiteralText) or; 204 | return lt.toBuilder().content(lt.getContent().replace(o, n)).build(); 205 | } 206 | 207 | /** 208 | * Convert from Text to TextTemplate 209 | */ 210 | public static TextTemplate toTemplate(Text text, Pattern pattern) { 211 | List flat = flatten(text); 212 | TextTemplate out = TextTemplate.EMPTY; 213 | for (Text t : flat) { 214 | String p = t.toPlain(); 215 | Matcher m; 216 | if ((m = pattern.matcher(p)).matches()) { 217 | String ex = m.group(1); 218 | String pre = p.substring(0, p.indexOf(ex)); 219 | String post = p.substring(p.indexOf(ex) + ex.length()); 220 | out = out.concat( 221 | TextTemplate.of(pre, post, new Object[] { TextTemplate.arg(ex).format(t.getFormat()) })); 222 | } else if ((m = pattern.matcher(p)).find()) { 223 | out = out.concat(multi(p, pattern, t)); 224 | } else { 225 | out = out.concat(TextTemplate.of(t)); 226 | } 227 | } 228 | return out; 229 | } 230 | 231 | /** 232 | * Convert from TextTemplate to Text 233 | */ 234 | public static Text toText(TextTemplate template, Optional> preexisting) { 235 | Map a = preexisting.orElse(new HashMap<>()); 236 | template.getArguments().entrySet().stream().filter(e -> !a.containsKey(e.getKey())).forEach( 237 | e -> a.put(e.getKey(), template.getOpenArgString() + e.getKey() + template.getCloseArgString())); 238 | return template.apply(a).build(); 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/utils/TypeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2017 Wundero 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | package me.rojo8399.placeholderapi.impl.utils; 25 | 26 | import java.lang.invoke.MethodHandle; 27 | import java.lang.invoke.MethodHandles; 28 | import java.lang.reflect.Method; 29 | import java.lang.reflect.Modifier; 30 | import java.time.Duration; 31 | import java.time.Instant; 32 | import java.time.LocalDateTime; 33 | import java.time.ZoneId; 34 | import java.util.*; 35 | import java.util.function.Function; 36 | import java.util.function.Supplier; 37 | import java.util.regex.Pattern; 38 | import java.util.stream.Collectors; 39 | import java.util.stream.Stream; 40 | 41 | import org.apache.commons.lang3.StringUtils; 42 | import org.spongepowered.api.command.CommandSource; 43 | import org.spongepowered.api.data.value.BaseValue; 44 | import org.spongepowered.api.item.inventory.ItemStack; 45 | import org.spongepowered.api.text.Text; 46 | import org.spongepowered.api.text.serializer.TextSerializers; 47 | 48 | import com.google.common.base.Preconditions; 49 | import com.google.common.reflect.TypeToken; 50 | 51 | import me.rojo8399.placeholderapi.impl.PlaceholderAPIPlugin; 52 | import me.rojo8399.placeholderapi.impl.PlaceholderServiceImpl; 53 | 54 | public class TypeUtils { 55 | 56 | private static Map, Function> deserializers = new HashMap<>(); 57 | 58 | private static final Pattern STRING_TO_VAL_PATTERN = Pattern 59 | .compile("(parse.*)|(valueOf)|(deserialize)|(fromString)|(from)", Pattern.CASE_INSENSITIVE); 60 | 61 | public static int add(int one, int two) { 62 | return one + two; 63 | } 64 | 65 | public static boolean and(boolean one, boolean two) { 66 | return one && two; 67 | } 68 | 69 | /** 70 | * Case insensitive 71 | */ 72 | public static boolean closeTo(String a, String b) { 73 | if (a == null && b == null) { 74 | return true; 75 | } 76 | if (a == null || b == null) { 77 | return false; 78 | } 79 | if (a.isEmpty() && b.isEmpty()) { 80 | return true; 81 | } 82 | if (a.isEmpty() || b.isEmpty()) { 83 | return false; 84 | } 85 | if (a.equalsIgnoreCase(b)) { 86 | return true; 87 | } 88 | a = a.toLowerCase(); 89 | b = b.toLowerCase(); 90 | if (StringUtils.getJaroWinklerDistance(a, b) > 0.7) { 91 | return true; 92 | } 93 | if (a.contains(b) && b.length() > 1) { 94 | return true; 95 | } 96 | return b.contains(a) && a.length() > 1; 97 | } 98 | 99 | @SuppressWarnings("unchecked") 100 | public static T convertPrimitive(String val, Class primitiveClass) { 101 | val = val.toLowerCase().trim(); 102 | if (primitiveClass.equals(char.class) || primitiveClass.equals(Character.class)) { 103 | return (T) (Character) val.charAt(0); 104 | } 105 | if (primitiveClass.equals(int.class) || primitiveClass.equals(Integer.class)) { 106 | return (T) Integer.valueOf(val); 107 | } 108 | if (primitiveClass.equals(long.class) || primitiveClass.equals(Long.class)) { 109 | return (T) Long.valueOf(val); 110 | } 111 | if (primitiveClass.equals(short.class) || primitiveClass.equals(Short.class)) { 112 | return (T) Short.valueOf(val); 113 | } 114 | if (primitiveClass.equals(byte.class) || primitiveClass.equals(Byte.class)) { 115 | return (T) Byte.valueOf(val); 116 | } 117 | if (primitiveClass.equals(double.class) || primitiveClass.equals(Double.class)) { 118 | return (T) Double.valueOf(val); 119 | } 120 | if (primitiveClass.equals(float.class) || primitiveClass.equals(Float.class)) { 121 | return (T) Float.valueOf(val); 122 | } 123 | if (primitiveClass.equals(boolean.class) || primitiveClass.equals(Boolean.class)) { 124 | return (T) (Boolean) isTrue(val); 125 | } 126 | throw new IllegalArgumentException("Class is not primitive or a wrapper!"); 127 | } 128 | 129 | public static String formatDuration(Duration duration) { 130 | long seconds = duration.getSeconds(); 131 | long absSeconds = Math.abs(seconds); 132 | String positive = String.format("%d h %d m %d s", absSeconds / 3600, (absSeconds % 3600) / 60, absSeconds % 60); 133 | return seconds < 0 ? "-" + positive : positive; 134 | } 135 | 136 | public static boolean isTrue(String val) { 137 | switch (val.toLowerCase()) { 138 | case "t": 139 | case "true": 140 | case "1": 141 | case "y": 142 | case "yes": 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | public static int mult(int one, int two) { 149 | return one * two; 150 | } 151 | 152 | public static boolean nand(boolean one, boolean two) { 153 | return !and(one, two); 154 | } 155 | 156 | public static boolean nor(boolean one, boolean two) { 157 | return !or(one, two); 158 | } 159 | 160 | public static boolean or(boolean one, boolean two) { 161 | return one || two; 162 | } 163 | 164 | /** 165 | * The required version range of the dependency in Maven version range 166 | * syntax: 167 | * 168 | * 169 | * 170 | * 171 | * 172 | * 173 | * 174 | * 175 | * 176 | * 177 | * 178 | * 179 | * 180 | * 181 | * 182 | * 183 | * 184 | * 185 | * 186 | * 187 | * 188 | * 189 | * 190 | * 191 | * 192 | * 193 | * 194 | * 195 | * 196 | * 197 | * 198 | * 199 | * 200 | * 201 | * 202 | * 203 | * 204 | * 205 | *
RangeMeaning
1.0Any dependency version, 1.0 is recommended
[1.0]x == 1.0
[1.0,)x >= 1.0
(1.0,)x > 1.0
(,1.0]x <= 1.0
(,1.0)x < 1.0
(1.0,2.0)1.0 < x < 2.0
[1.0,2.0]1.0 <= x <= 2.0
206 | * 207 | * @return The required version range, or an empty string if unspecified 208 | * @see Maven version range specification 209 | * @see Maven version design document 210 | */ 211 | public static boolean matchVersion(String pattern, String actual) { 212 | VersionRange range = new VersionRange(pattern); 213 | Version ver = new Version(actual); 214 | return range.isInRange(ver); 215 | } 216 | 217 | public static void registerDeserializer(TypeToken token, Function fun) { 218 | Preconditions.checkNotNull(fun, "deserializer"); 219 | Preconditions.checkNotNull(token, "token"); 220 | deserializers.put(token, fun); 221 | } 222 | 223 | public static int sub(int one, int two) { 224 | return one - two; 225 | } 226 | 227 | @SuppressWarnings("unchecked") 228 | public static Optional tryCast(Object val, final Class expected) { 229 | if (val == null) { 230 | return Optional.empty(); 231 | } 232 | if (expected == null) { 233 | throw new IllegalArgumentException("Must provide an expected class!"); 234 | } 235 | if (val instanceof BaseValue && !BaseValue.class.isAssignableFrom(expected)) { 236 | return tryCast(((BaseValue) val).get(), expected); 237 | } 238 | if (val instanceof Supplier) { 239 | Supplier fun = (Supplier) val; 240 | return tryCast(fun.get(), expected); 241 | } 242 | if (Text.class.isAssignableFrom(expected)) { 243 | if (val instanceof Text) { 244 | return TypeUtils.tryOptional(() -> expected.cast(val)); 245 | } else { 246 | if (val instanceof ItemStack) { 247 | return TypeUtils.tryOptional(() -> expected.cast(TextUtils.ofItem((ItemStack) val))); 248 | } 249 | if (val instanceof Instant) { 250 | return TypeUtils.tryOptional(() -> expected.cast( 251 | TextSerializers.FORMATTING_CODE.deserialize(PlaceholderAPIPlugin.getInstance().formatter() 252 | .format(LocalDateTime.ofInstant((Instant) val, ZoneId.systemDefault()))))); 253 | } 254 | if (val instanceof Duration) { 255 | String dur = formatDuration((Duration) val); 256 | return TypeUtils.tryOptional(() -> expected.cast(TextSerializers.FORMATTING_CODE.deserialize(dur))); 257 | } 258 | if (val instanceof LocalDateTime) { 259 | return TypeUtils.tryOptional(() -> expected.cast(TextSerializers.FORMATTING_CODE 260 | .deserialize(PlaceholderAPIPlugin.getInstance().formatter().format((LocalDateTime) val)))); 261 | } 262 | if (val instanceof CommandSource) { 263 | return TypeUtils.tryOptional(() -> expected.cast(TextSerializers.FORMATTING_CODE 264 | .deserialize(String.valueOf(((CommandSource) val).getName())))); 265 | } 266 | if (val.getClass().isArray()) { 267 | List l2 = unboxPrimitiveArray(val).stream() 268 | .map(o -> tryCast(o, (Class) expected)).flatMap(unmapOptional()) 269 | .collect(Collectors.toList()); 270 | return TypeUtils.tryOptional(() -> expected.cast(Text.joinWith(Text.of(", "), l2))); 271 | } 272 | if (val instanceof Iterable) { 273 | Iterable l = (Iterable) val; 274 | // should be safe cause we already checked assignability 275 | @SuppressWarnings("serial") 276 | final List l2 = new ArrayList() { 277 | { 278 | for (Object o : l) { 279 | add(o); 280 | } 281 | } 282 | }.stream().map(o -> tryCast(o, (Class) expected)).flatMap(unmapOptional()) 283 | .collect(Collectors.toList()); 284 | return TypeUtils.tryOptional(() -> expected.cast(Text.joinWith(Text.of(", "), l2))); 285 | } 286 | return TypeUtils.tryOptional( 287 | () -> expected.cast(TextSerializers.FORMATTING_CODE.deserialize(String.valueOf(val)))); 288 | } 289 | } 290 | if (val instanceof String) { 291 | if (String.class.isAssignableFrom(expected)) { 292 | return tryOptional(() -> expected.cast(val)); 293 | } 294 | if (expected.isArray() && String.class.isAssignableFrom(expected.getComponentType())) { 295 | String v = (String) val; 296 | if (v.isEmpty()) { 297 | return Optional.empty(); 298 | } 299 | if (!v.contains("_")) { 300 | return tryOptional(() -> expected.cast(new String[] { v })); 301 | } 302 | String[] x = v.split("_"); 303 | if (x.length == 0) { 304 | return Optional.empty(); 305 | } 306 | boolean ne = false; 307 | for (String s : x) { 308 | ne = ne || !s.isEmpty(); 309 | } 310 | if (!ne) { 311 | return Optional.empty(); 312 | } 313 | return tryOptional(() -> expected.cast(x)); 314 | } 315 | if (List.class.isAssignableFrom(expected) 316 | && String.class.isAssignableFrom(expected.getTypeParameters()[0].getGenericDeclaration())) { 317 | String v = (String) val; 318 | if (v.isEmpty()) { 319 | return Optional.empty(); 320 | } 321 | if (!v.contains("_")) { 322 | return tryOptional(() -> expected.cast(Collections.singletonList(v))); 323 | } 324 | String[] x = v.split("_"); 325 | if (x.length == 0) { 326 | return Optional.empty(); 327 | } 328 | boolean ne = false; 329 | for (String s : x) { 330 | ne = ne || !s.isEmpty(); 331 | } 332 | if (!ne) { 333 | return Optional.empty(); 334 | } 335 | return tryOptional(() -> expected.cast(Arrays.asList(x))); 336 | } 337 | Optional opt = tryOptional(() -> convertPrimitive((String) val, expected)); 338 | if (opt.isPresent()) { 339 | return opt; 340 | } 341 | opt = deserializers.entrySet().stream() 342 | .filter(e -> e.getKey().isSubtypeOf(expected) || e.getKey().getRawType().equals(expected)) 343 | .map(Map.Entry::getValue).map(f -> tryOptional(() -> f.apply((String) val))).flatMap(unmapOptional()) 344 | .findAny().flatMap(o -> tryOptional(() -> expected.cast(o))); 345 | if (opt.isPresent()) { 346 | return opt; 347 | } 348 | try { 349 | // should theoretically match any string -> object conversions, such as deser 350 | 351 | // for now im filtering against method names as well just to avoid issues where 352 | // expected result is not obtained due to weird methods, might change in future 353 | Method method = Arrays.stream(expected.getDeclaredMethods()) 354 | .filter(m -> Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers())) 355 | .filter(m -> Arrays.stream(m.getParameterTypes()).anyMatch(c -> c.equals(String.class))) 356 | .filter(m -> m.getReturnType().equals(expected) || m.getReturnType().equals(Optional.class)) 357 | .filter(m -> STRING_TO_VAL_PATTERN.matcher(m.getName()).find()).findAny().get(); // error if no 358 | Object valout = method.invoke(null, (String) val); 359 | if (valout == null) { 360 | return Optional.empty(); 361 | } 362 | if (expected.isInstance(valout)) { 363 | // Register a new deserializer once we confirm it works. Should prevent 364 | // extremely long parsing from happening multiple times. 365 | final MethodHandle mh = MethodHandles.publicLookup().unreflect(method); 366 | PlaceholderServiceImpl.get().registerTypeDeserializer(TypeToken.of(expected), str -> { 367 | try { 368 | return expected.cast(mh.invokeExact((String) val)); 369 | } catch (Throwable e1) { 370 | throw new RuntimeException(e1); 371 | } 372 | }); 373 | return tryOptional(() -> expected.cast(valout)); 374 | } 375 | if (valout instanceof Optional) { 376 | Optional valopt = (Optional) valout; 377 | if (!valopt.isPresent()) { 378 | return Optional.empty(); 379 | } 380 | Object v = valopt.get(); 381 | if (expected.isInstance(v)) { 382 | // Register a new deserializer once we confirm it works. Should prevent 383 | // extremely long parsing from happening multiple times. 384 | final MethodHandle mh = MethodHandles.publicLookup().unreflect(method); 385 | PlaceholderServiceImpl.get().registerTypeDeserializer(TypeToken.of(expected), str -> { 386 | try { 387 | Optional optx = (Optional) mh.invokeExact((String) val); 388 | return expected.cast(optx.get()); 389 | } catch (Throwable e1) { 390 | throw new RuntimeException(e1); 391 | } 392 | }); 393 | return tryOptional(() -> expected.cast(v)); 394 | } else { 395 | return Optional.empty(); 396 | } 397 | } 398 | return Optional.empty(); 399 | } catch (Exception e) { 400 | // fires if no method found, if invoke throws, if something else goes wrong 401 | return Optional.empty(); 402 | } 403 | } 404 | return TypeUtils.tryOptional(() -> expected.cast(val)); 405 | } 406 | 407 | public static Optional tryCast(Object val, final Class expected, Boolean fixStrings) { 408 | if (val instanceof String && fixStrings) { 409 | return tryCast(((String) val).toLowerCase().trim(), expected); 410 | } else { 411 | return tryCast(val, expected); 412 | } 413 | } 414 | 415 | /** 416 | * Will only work on unchecked but catchable exceptions. 417 | */ 418 | public static Optional tryOptional(Supplier fun) { 419 | try { 420 | return Optional.ofNullable(fun.get()); 421 | } catch (Exception e) { 422 | return Optional.empty(); 423 | } 424 | } 425 | 426 | public static T tryOrNull(Supplier funct) { 427 | try { 428 | return funct.get(); 429 | } catch (Exception e) { 430 | return null; 431 | } 432 | } 433 | 434 | public static List unboxPrimitiveArray(Object primArr) { 435 | if (primArr.getClass().isArray()) { 436 | if (primArr.getClass().getComponentType().isPrimitive()) { 437 | Class primClazz = primArr.getClass().getComponentType(); 438 | if (primClazz.equals(int.class)) { 439 | int[] a = (int[]) primArr; 440 | List out = new ArrayList<>(); 441 | for (int i : a) { 442 | out.add(i); 443 | } 444 | return out; 445 | } 446 | if (primClazz.equals(long.class)) { 447 | long[] a = (long[]) primArr; 448 | List out = new ArrayList<>(); 449 | for (long i : a) { 450 | out.add(i); 451 | } 452 | return out; 453 | } 454 | if (primClazz.equals(short.class)) { 455 | short[] a = (short[]) primArr; 456 | List out = new ArrayList<>(); 457 | for (short i : a) { 458 | out.add(i); 459 | } 460 | return out; 461 | } 462 | if (primClazz.equals(byte.class)) { 463 | byte[] a = (byte[]) primArr; 464 | List out = new ArrayList<>(); 465 | for (byte i : a) { 466 | out.add(i); 467 | } 468 | return out; 469 | } 470 | if (primClazz.equals(char.class)) { 471 | char[] a = (char[]) primArr; 472 | List out = new ArrayList<>(); 473 | for (char i : a) { 474 | out.add(i); 475 | } 476 | return out; 477 | } 478 | if (primClazz.equals(boolean.class)) { 479 | boolean[] a = (boolean[]) primArr; 480 | List out = new ArrayList<>(); 481 | for (boolean i : a) { 482 | out.add(i); 483 | } 484 | return out; 485 | } 486 | if (primClazz.equals(float.class)) { 487 | float[] a = (float[]) primArr; 488 | List out = new ArrayList<>(); 489 | for (float i : a) { 490 | out.add(i); 491 | } 492 | return out; 493 | } 494 | if (primClazz.equals(double.class)) { 495 | double[] a = (double[]) primArr; 496 | List out = new ArrayList<>(); 497 | for (double i : a) { 498 | out.add(i); 499 | } 500 | return out; 501 | } 502 | } 503 | return Arrays.asList((Object[]) primArr); 504 | } else { 505 | return Collections.singletonList(primArr); 506 | } 507 | } 508 | 509 | public static Function, Stream> unmapOptional() { 510 | return (opt) -> Stream.of(opt).filter(Optional::isPresent).map(Optional::get); 511 | } 512 | 513 | public static Stream unmapOptional(Stream> stream) { 514 | return stream.flatMap(unmapOptional()); 515 | } 516 | 517 | public static boolean xnor(boolean o, boolean t) { 518 | return (o && t) || (!o && !t); 519 | } 520 | 521 | public static boolean xor(boolean o, boolean t) { 522 | return !xnor(o, t); 523 | } 524 | 525 | } 526 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/utils/Version.java: -------------------------------------------------------------------------------- 1 | package me.rojo8399.placeholderapi.impl.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Version implements Comparable { 7 | 8 | private String actual; 9 | // ordered DO NOT SORT 10 | private List subs; 11 | 12 | public Version(String from) { 13 | from = from.toLowerCase(); 14 | if (from.contains("v")) { 15 | from = from.replace("v", ""); 16 | } 17 | String[] parts = from.split("\\."); 18 | actual = from; 19 | subs = new ArrayList<>(); 20 | for (String s : parts) { 21 | subs.add(new SubVersion(s)); 22 | } 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return actual; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object other) { 32 | return other instanceof Version && ((Version) other).actual.equals(this.actual); 33 | } 34 | 35 | private static class SubVersion implements Comparable { 36 | private int intVal; 37 | 38 | public SubVersion(String val) { 39 | try { 40 | this.intVal = Integer.parseInt(val); 41 | } catch (Exception e) { 42 | StringBuilder top = new StringBuilder(); 43 | for (Character c : val.toCharArray()) { 44 | if (c < 48 || c > 57) { 45 | break; 46 | } 47 | top.append(c); 48 | } 49 | this.intVal = Integer.parseInt(top.toString()); 50 | } 51 | } 52 | 53 | @Override 54 | public int compareTo(SubVersion o) { 55 | return Integer.compare(intVal, o.intVal); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "" + intVal; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object other) { 65 | return other instanceof SubVersion && ((SubVersion) other).intVal == this.intVal; 66 | } 67 | } 68 | 69 | @Override 70 | public int compareTo(Version o) { 71 | List svx = o.subs; 72 | for (int i = 0; i < subs.size(); i++) { 73 | SubVersion sv = subs.get(i); 74 | if (svx.size() > i) { 75 | int dif = sv.intVal - svx.get(i).intVal; 76 | if (dif != 0) { 77 | return dif; 78 | } 79 | } else { 80 | return sv.intVal; 81 | } 82 | } 83 | if (svx.size() > subs.size()) { 84 | for (int i = subs.size(); i < svx.size(); i++) { 85 | if (svx.get(i).intVal != 0) { 86 | return -svx.get(i).intVal; 87 | } 88 | } 89 | return 0; 90 | } 91 | return 0; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/me/rojo8399/placeholderapi/impl/utils/VersionRange.java: -------------------------------------------------------------------------------- 1 | package me.rojo8399.placeholderapi.impl.utils; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class VersionRange { 6 | 7 | private String actual; 8 | private Version leftSide, rightSide; 9 | private boolean leftExc, rightExc; 10 | private boolean retall = false; 11 | 12 | private static final Pattern VERSION = Pattern.compile("((?:\\d)(?:\\.\\d)*)", Pattern.CASE_INSENSITIVE); 13 | 14 | public VersionRange(String from) { 15 | this.actual = from; 16 | if (VERSION.matcher(from).matches()) { 17 | retall = true; 18 | } else { 19 | leftExc = from.startsWith("("); 20 | rightExc = from.endsWith(")"); 21 | if (rightExc) { 22 | from = from.replace(")", ""); 23 | } else { 24 | from = from.replace("]", ""); 25 | } 26 | if (leftExc) { 27 | from = from.replace("(", ""); 28 | } else { 29 | from = from.replace("[", ""); 30 | } 31 | if (from.contains(",")) { 32 | String[] parts = from.split(","); 33 | if (from.startsWith(",")) { 34 | rightSide = new Version(from.replace(",", "")); 35 | } else { 36 | leftSide = new Version(parts[0]); 37 | if (parts.length > 1 && !parts[1].isEmpty()) { 38 | rightSide = new Version(parts[1]); 39 | } 40 | } 41 | } else { 42 | leftSide = rightSide = new Version(from); 43 | } 44 | } 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return actual; 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | final int prime = 31; 55 | int result = 1; 56 | result = prime * result + ((actual == null) ? 0 : actual.hashCode()); 57 | return result; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object obj) { 62 | if (this == obj) 63 | return true; 64 | if (obj == null) 65 | return false; 66 | if (!(obj instanceof VersionRange)) 67 | return false; 68 | VersionRange other = (VersionRange) obj; 69 | if (actual == null) { 70 | return other.actual == null; 71 | } else return actual.equals(other.actual); 72 | } 73 | 74 | public boolean isInRange(Version ver) { 75 | if (retall) { 76 | return true; 77 | } 78 | int leftcomp = leftSide == null ? -1 : leftSide.compareTo(ver); 79 | int rightcomp = rightSide == null ? 1 : rightSide.compareTo(ver); 80 | int leftbound = leftExc ? -1 : 0; 81 | int rightbound = rightExc ? 1 : 0; 82 | if (leftcomp <= leftbound || leftSide == null) { 83 | return rightcomp >= rightbound || rightSide == null; 84 | } 85 | return false; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/resources/assets/placeholderapi/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronaldburns/PlaceholderAPI/b58a67b5e5ee30697578d8e872bcd89c6f85e4a7/src/main/resources/assets/placeholderapi/Logo.png -------------------------------------------------------------------------------- /src/main/resources/assets/placeholderapi/config.conf: -------------------------------------------------------------------------------- 1 | date-time-format="uuuu LLL dd HH:mm:ss" 2 | relational-parse-for-recipient=true --------------------------------------------------------------------------------