Compare commits
	
		
			10 Commits
		
	
	
		
			03d1adf981
			...
			b64126a01b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b64126a01b | ||
|   | 8169fd6dbd | ||
|   | 632e6f2dcd | ||
|   | 18d4917098 | ||
|   | 955e6aa8b2 | ||
|   | 956cfd0811 | ||
|   | 4dcab43b6a | ||
|   | ebe778fbaf | ||
|   | 11312c2807 | ||
|   | 6ea9553e63 | 
							
								
								
									
										12
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| # | ||||
| # https://help.github.com/articles/dealing-with-line-endings/ | ||||
| # | ||||
| # Linux start script should use lf | ||||
| /gradlew        text eol=lf | ||||
| 
 | ||||
| # These are Windows script files and should use crlf | ||||
| *.bat           text eol=crlf | ||||
| 
 | ||||
| # Binary files should be left untouched | ||||
| *.jar           binary | ||||
| 
 | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # Ignore Gradle project-specific cache directory | ||||
| .gradle | ||||
| 
 | ||||
| # Ignore Gradle build output directory | ||||
| build | ||||
| 
 | ||||
| # Custom | ||||
| .idea | ||||
							
								
								
									
										67
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								build.gradle
									
									
									
									
									
								
							| @ -1,10 +1,73 @@ | ||||
| plugins { | ||||
|     id 'GroowtConventions' | ||||
|     id 'java-library' | ||||
|     id 'maven-publish' | ||||
| } | ||||
| 
 | ||||
| group = 'com.jessebrault.di' | ||||
| version = '0.1.0' | ||||
| 
 | ||||
| repositories { | ||||
|     mavenCentral() | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     api libs.jakarta.inject | ||||
|     api libs.groovy | ||||
|     compileOnlyApi libs.jetbrains.anotations | ||||
|     implementation libs.slf4j.api, libs.groovy | ||||
|     implementation libs.slf4j.api | ||||
| 
 | ||||
|     testImplementation libs.junit.jupiter.api | ||||
|     testRuntimeOnly libs.log4j.core, libs.log4j.slf4jBinding | ||||
| } | ||||
| 
 | ||||
| test { | ||||
|     testLogging.showStandardStreams = true | ||||
| } | ||||
| 
 | ||||
| testing { | ||||
|     suites { | ||||
|         test { | ||||
|             useJUnitJupiter() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| java { | ||||
|     withSourcesJar() | ||||
|     toolchain { | ||||
|         languageVersion = JavaLanguageVersion.of(21) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| jar { | ||||
|     archiveBaseName = 'di' | ||||
| } | ||||
| 
 | ||||
| sourcesJar { | ||||
|     archiveBaseName = 'di' | ||||
| } | ||||
| 
 | ||||
| publishing { | ||||
|     repositories { | ||||
|         maven { | ||||
|             name = "Gitea" | ||||
|             url = uri("https://git.jessebrault.com/api/packages/jessebrault/maven") | ||||
| 
 | ||||
|             credentials(HttpHeaderCredentials) { | ||||
|                 name = "Authorization" | ||||
|                 value = "token ${System.getenv("GITEA_ACCESS_TOKEN")}" | ||||
|             } | ||||
| 
 | ||||
|             authentication { | ||||
|                 header(HttpHeaderAuthentication) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     publications { | ||||
|         create('di', MavenPublication) { | ||||
|             artifactId = 'di' | ||||
|             from components.java | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										7
									
								
								gradle.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gradle.properties
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| # This file was generated by the Gradle 'init' task. | ||||
| # https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties | ||||
| 
 | ||||
| org.gradle.configuration-cache=true | ||||
| org.gradle.parallel=true | ||||
| org.gradle.caching=true | ||||
| 
 | ||||
							
								
								
									
										19
									
								
								gradle/libs.versions.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								gradle/libs.versions.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| # This file was generated by the Gradle 'init' task. | ||||
| # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format | ||||
| 
 | ||||
| [versions] | ||||
| groovy = '4.0.27' | ||||
| jakarta-inject = '2.0.1' | ||||
| jetbrains-annotations = '26.0.2' | ||||
| junit = '5.13.0' | ||||
| log4j = '2.24.3' | ||||
| slf4j = '2.0.17' | ||||
| 
 | ||||
| [libraries] | ||||
| groovy = { module = 'org.apache.groovy:groovy', version.ref = 'groovy' } | ||||
| jakarta-inject = { module = 'jakarta.inject:jakarta.inject-api', version.ref = 'jakarta-inject' } | ||||
| jetbrains-anotations = { module = 'org.jetbrains:annotations', version.ref = 'jetbrains-annotations' } | ||||
| slf4j-api = { module = 'org.slf4j:slf4j-api', version.ref = 'slf4j' } | ||||
| junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api', version.ref = 'junit' } | ||||
| log4j-core = { module = 'org.apache.logging.log4j:log4j-core', version.ref = 'log4j' } | ||||
| log4j-slf4jBinding = { module = 'org.apache.logging.log4j:log4j-slf4j2-impl', version.ref = 'log4j' } | ||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										7
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip | ||||
| networkTimeout=10000 | ||||
| validateDistributionUrl=true | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
							
								
								
									
										251
									
								
								gradlew
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										251
									
								
								gradlew
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @ -0,0 +1,251 @@ | ||||
| #!/bin/sh | ||||
| 
 | ||||
| # | ||||
| # Copyright © 2015-2021 the original authors. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      https://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # SPDX-License-Identifier: Apache-2.0 | ||||
| # | ||||
| 
 | ||||
| ############################################################################## | ||||
| # | ||||
| #   Gradle start up script for POSIX generated by Gradle. | ||||
| # | ||||
| #   Important for running: | ||||
| # | ||||
| #   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is | ||||
| #       noncompliant, but you have some other compliant shell such as ksh or | ||||
| #       bash, then to run this script, type that shell name before the whole | ||||
| #       command line, like: | ||||
| # | ||||
| #           ksh Gradle | ||||
| # | ||||
| #       Busybox and similar reduced shells will NOT work, because this script | ||||
| #       requires all of these POSIX shell features: | ||||
| #         * functions; | ||||
| #         * expansions «$var», «${var}», «${var:-default}», «${var+SET}», | ||||
| #           «${var#prefix}», «${var%suffix}», and «$( cmd )»; | ||||
| #         * compound commands having a testable exit status, especially «case»; | ||||
| #         * various built-in commands including «command», «set», and «ulimit». | ||||
| # | ||||
| #   Important for patching: | ||||
| # | ||||
| #   (2) This script targets any POSIX shell, so it avoids extensions provided | ||||
| #       by Bash, Ksh, etc; in particular arrays are avoided. | ||||
| # | ||||
| #       The "traditional" practice of packing multiple parameters into a | ||||
| #       space-separated string is a well documented source of bugs and security | ||||
| #       problems, so this is (mostly) avoided, by progressively accumulating | ||||
| #       options in "$@", and eventually passing that to Java. | ||||
| # | ||||
| #       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, | ||||
| #       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; | ||||
| #       see the in-line comments for details. | ||||
| # | ||||
| #       There are tweaks for specific operating systems such as AIX, CygWin, | ||||
| #       Darwin, MinGW, and NonStop. | ||||
| # | ||||
| #   (3) This script is generated from the Groovy template | ||||
| #       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||
| #       within the Gradle project. | ||||
| # | ||||
| #       You can find Gradle at https://github.com/gradle/gradle/. | ||||
| # | ||||
| ############################################################################## | ||||
| 
 | ||||
| # Attempt to set APP_HOME | ||||
| 
 | ||||
| # Resolve links: $0 may be a link | ||||
| app_path=$0 | ||||
| 
 | ||||
| # Need this for daisy-chained symlinks. | ||||
| while | ||||
|     APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path | ||||
|     [ -h "$app_path" ] | ||||
| do | ||||
|     ls=$( ls -ld "$app_path" ) | ||||
|     link=${ls#*' -> '} | ||||
|     case $link in             #( | ||||
|       /*)   app_path=$link ;; #( | ||||
|       *)    app_path=$APP_HOME$link ;; | ||||
|     esac | ||||
| done | ||||
| 
 | ||||
| # This is normally unused | ||||
| # shellcheck disable=SC2034 | ||||
| APP_BASE_NAME=${0##*/} | ||||
| # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||||
| APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | ||||
| 
 | ||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||
| MAX_FD=maximum | ||||
| 
 | ||||
| warn () { | ||||
|     echo "$*" | ||||
| } >&2 | ||||
| 
 | ||||
| die () { | ||||
|     echo | ||||
|     echo "$*" | ||||
|     echo | ||||
|     exit 1 | ||||
| } >&2 | ||||
| 
 | ||||
| # OS specific support (must be 'true' or 'false'). | ||||
| cygwin=false | ||||
| msys=false | ||||
| darwin=false | ||||
| nonstop=false | ||||
| case "$( uname )" in                #( | ||||
|   CYGWIN* )         cygwin=true  ;; #( | ||||
|   Darwin* )         darwin=true  ;; #( | ||||
|   MSYS* | MINGW* )  msys=true    ;; #( | ||||
|   NONSTOP* )        nonstop=true ;; | ||||
| esac | ||||
| 
 | ||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||
| 
 | ||||
| 
 | ||||
| # Determine the Java command to use to start the JVM. | ||||
| if [ -n "$JAVA_HOME" ] ; then | ||||
|     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||
|         # IBM's JDK on AIX uses strange locations for the executables | ||||
|         JAVACMD=$JAVA_HOME/jre/sh/java | ||||
|     else | ||||
|         JAVACMD=$JAVA_HOME/bin/java | ||||
|     fi | ||||
|     if [ ! -x "$JAVACMD" ] ; then | ||||
|         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
|     fi | ||||
| else | ||||
|     JAVACMD=java | ||||
|     if ! command -v java >/dev/null 2>&1 | ||||
|     then | ||||
|         die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| # Increase the maximum file descriptors if we can. | ||||
| if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then | ||||
|     case $MAX_FD in #( | ||||
|       max*) | ||||
|         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. | ||||
|         # shellcheck disable=SC2039,SC3045 | ||||
|         MAX_FD=$( ulimit -H -n ) || | ||||
|             warn "Could not query maximum file descriptor limit" | ||||
|     esac | ||||
|     case $MAX_FD in  #( | ||||
|       '' | soft) :;; #( | ||||
|       *) | ||||
|         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. | ||||
|         # shellcheck disable=SC2039,SC3045 | ||||
|         ulimit -n "$MAX_FD" || | ||||
|             warn "Could not set maximum file descriptor limit to $MAX_FD" | ||||
|     esac | ||||
| fi | ||||
| 
 | ||||
| # Collect all arguments for the java command, stacking in reverse order: | ||||
| #   * args from the command line | ||||
| #   * the main class name | ||||
| #   * -classpath | ||||
| #   * -D...appname settings | ||||
| #   * --module-path (only if needed) | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. | ||||
| 
 | ||||
| # For Cygwin or MSYS, switch paths to Windows format before running java | ||||
| if "$cygwin" || "$msys" ; then | ||||
|     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||||
|     CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | ||||
| 
 | ||||
|     JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||||
| 
 | ||||
|     # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||||
|     for arg do | ||||
|         if | ||||
|             case $arg in                                #( | ||||
|               -*)   false ;;                            # don't mess with options #( | ||||
|               /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath | ||||
|                     [ -e "$t" ] ;;                      #( | ||||
|               *)    false ;; | ||||
|             esac | ||||
|         then | ||||
|             arg=$( cygpath --path --ignore --mixed "$arg" ) | ||||
|         fi | ||||
|         # Roll the args list around exactly as many times as the number of | ||||
|         # args, so each arg winds up back in the position where it started, but | ||||
|         # possibly modified. | ||||
|         # | ||||
|         # NB: a `for` loop captures its iteration list before it begins, so | ||||
|         # changing the positional parameters here affects neither the number of | ||||
|         # iterations, nor the values presented in `arg`. | ||||
|         shift                   # remove old arg | ||||
|         set -- "$@" "$arg"      # push replacement arg | ||||
|     done | ||||
| fi | ||||
| 
 | ||||
| 
 | ||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
| 
 | ||||
| # Collect all arguments for the java command: | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||
| #     and any embedded shellness will be escaped. | ||||
| #   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | ||||
| #     treated as '${Hostname}' itself on the command line. | ||||
| 
 | ||||
| set -- \ | ||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||
|         -classpath "$CLASSPATH" \ | ||||
|         org.gradle.wrapper.GradleWrapperMain \ | ||||
|         "$@" | ||||
| 
 | ||||
| # Stop when "xargs" is not available. | ||||
| if ! command -v xargs >/dev/null 2>&1 | ||||
| then | ||||
|     die "xargs is not available" | ||||
| fi | ||||
| 
 | ||||
| # Use "xargs" to parse quoted args. | ||||
| # | ||||
| # With -n1 it outputs one arg per line, with the quotes and backslashes removed. | ||||
| # | ||||
| # In Bash we could simply go: | ||||
| # | ||||
| #   readarray ARGS < <( xargs -n1 <<<"$var" ) && | ||||
| #   set -- "${ARGS[@]}" "$@" | ||||
| # | ||||
| # but POSIX shell has neither arrays nor command substitution, so instead we | ||||
| # post-process each arg (as a line of input to sed) to backslash-escape any | ||||
| # character that might be a shell metacharacter, then use eval to reverse | ||||
| # that process (while maintaining the separation between arguments), and wrap | ||||
| # the whole thing up as a single "set" statement. | ||||
| # | ||||
| # This will of course break if any of these variables contains a newline or | ||||
| # an unmatched quote. | ||||
| # | ||||
| 
 | ||||
| eval "set -- $( | ||||
|         printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | | ||||
|         xargs -n1 | | ||||
|         sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | | ||||
|         tr '\n' ' ' | ||||
|     )" '"$@"' | ||||
| 
 | ||||
| exec "$JAVACMD" "$@" | ||||
							
								
								
									
										94
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| @rem | ||||
| @rem Copyright 2015 the original author or authors. | ||||
| @rem | ||||
| @rem Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| @rem you may not use this file except in compliance with the License. | ||||
| @rem You may obtain a copy of the License at | ||||
| @rem | ||||
| @rem      https://www.apache.org/licenses/LICENSE-2.0 | ||||
| @rem | ||||
| @rem Unless required by applicable law or agreed to in writing, software | ||||
| @rem distributed under the License is distributed on an "AS IS" BASIS, | ||||
| @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| @rem See the License for the specific language governing permissions and | ||||
| @rem limitations under the License. | ||||
| @rem | ||||
| @rem SPDX-License-Identifier: Apache-2.0 | ||||
| @rem | ||||
| 
 | ||||
| @if "%DEBUG%"=="" @echo off | ||||
| @rem ########################################################################## | ||||
| @rem | ||||
| @rem  Gradle startup script for Windows | ||||
| @rem | ||||
| @rem ########################################################################## | ||||
| 
 | ||||
| @rem Set local scope for the variables with windows NT shell | ||||
| if "%OS%"=="Windows_NT" setlocal | ||||
| 
 | ||||
| set DIRNAME=%~dp0 | ||||
| if "%DIRNAME%"=="" set DIRNAME=. | ||||
| @rem This is normally unused | ||||
| set APP_BASE_NAME=%~n0 | ||||
| set APP_HOME=%DIRNAME% | ||||
| 
 | ||||
| @rem Resolve any "." and ".." in APP_HOME to make it shorter. | ||||
| for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | ||||
| 
 | ||||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | ||||
| 
 | ||||
| @rem Find java.exe | ||||
| if defined JAVA_HOME goto findJavaFromJavaHome | ||||
| 
 | ||||
| set JAVA_EXE=java.exe | ||||
| %JAVA_EXE% -version >NUL 2>&1 | ||||
| if %ERRORLEVEL% equ 0 goto execute | ||||
| 
 | ||||
| echo. 1>&2 | ||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 | ||||
| echo. 1>&2 | ||||
| echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||||
| echo location of your Java installation. 1>&2 | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :findJavaFromJavaHome | ||||
| set JAVA_HOME=%JAVA_HOME:"=% | ||||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||||
| 
 | ||||
| if exist "%JAVA_EXE%" goto execute | ||||
| 
 | ||||
| echo. 1>&2 | ||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 | ||||
| echo. 1>&2 | ||||
| echo Please set the JAVA_HOME variable in your environment to match the 1>&2 | ||||
| echo location of your Java installation. 1>&2 | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :execute | ||||
| @rem Setup the command line | ||||
| 
 | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
| 
 | ||||
| 
 | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||
| 
 | ||||
| :end | ||||
| @rem End local scope for the variables with windows NT shell | ||||
| if %ERRORLEVEL% equ 0 goto mainEnd | ||||
| 
 | ||||
| :fail | ||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||
| rem the _cmd.exe /c_ return code! | ||||
| set EXIT_CODE=%ERRORLEVEL% | ||||
| if %EXIT_CODE% equ 0 set EXIT_CODE=1 | ||||
| if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% | ||||
| exit /b %EXIT_CODE% | ||||
| 
 | ||||
| :mainEnd | ||||
| if "%OS%"=="Windows_NT" endlocal | ||||
| 
 | ||||
| :omega | ||||
							
								
								
									
										1
									
								
								settings.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								settings.gradle
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| rootProject.name = 'di' | ||||
| @ -5,6 +5,8 @@ import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import java.lang.reflect.*; | ||||
| import java.util.*; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import static groowt.util.di.ObjectFactoryUtil.toTypes; | ||||
| 
 | ||||
| @ -19,6 +21,89 @@ public abstract class AbstractInjectingObjectFactory implements ObjectFactory { | ||||
|             Class<?>[] paramTypes | ||||
|     ) {} | ||||
| 
 | ||||
|     protected record Resolved(Class<?> type, Object object) {} | ||||
| 
 | ||||
|     private static final class DeferredSetters { | ||||
| 
 | ||||
|         private final Class<?> forType; | ||||
|         private final List<Consumer<Object>> actions = new ArrayList<>(); | ||||
| 
 | ||||
|         public DeferredSetters(Class<?> forType) { | ||||
|             this.forType = forType; | ||||
|         } | ||||
| 
 | ||||
|         public Class<?> getForType() { | ||||
|             return this.forType; | ||||
|         } | ||||
| 
 | ||||
|         public List<Consumer<Object>> getActions() { | ||||
|             return this.actions; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected static class CreateContext { | ||||
| 
 | ||||
|         private final Deque<Class<?>> constructionStack = new LinkedList<>(); | ||||
|         private final Deque<DeferredSetters> deferredSettersStack = new LinkedList<>(); | ||||
|         private final List<Resolved> allResolved = new ArrayList<>(); | ||||
| 
 | ||||
|         public CreateContext(Class<?> targetType) { | ||||
|             this.pushConstruction(targetType); | ||||
|         } | ||||
| 
 | ||||
|         public void checkForCircularDependency(Class<?> typeToConstruct) { | ||||
|             if (constructionStack.contains(typeToConstruct)) { | ||||
|                 throw new IllegalStateException( | ||||
|                         "Detected a circular constructor dependency for " + typeToConstruct.getName() | ||||
|                                 + " . Please use setter methods instead. Current construction stack: " | ||||
|                                 + constructionStack.stream() | ||||
|                                         .map(Class::getName) | ||||
|                                         .collect(Collectors.joining(", ")) | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void pushConstruction(Class<?> type) { | ||||
|             this.constructionStack.push(type); | ||||
|             this.deferredSettersStack.push(new DeferredSetters(type)); | ||||
|         } | ||||
| 
 | ||||
|         public void popConstruction() { | ||||
|             this.constructionStack.pop(); | ||||
|             this.deferredSettersStack.pop(); | ||||
|         } | ||||
| 
 | ||||
|         public boolean containsConstruction(Class<?> type) { | ||||
|             return this.constructionStack.contains(type); | ||||
|         } | ||||
| 
 | ||||
|         public void addDeferredSetterAction(Class<?> forType, Consumer<Object> action) { | ||||
|             boolean found = false; | ||||
|             for (final DeferredSetters deferredSetters : this.deferredSettersStack) { | ||||
|                 if (deferredSetters.getForType().equals(forType)) { | ||||
|                     found = true; | ||||
|                     deferredSetters.getActions().add(action); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (!found) { | ||||
|                 throw new IllegalArgumentException( | ||||
|                         "There is no construction for type " + forType.getName() + " which should be deferred." | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public List<Consumer<Object>> getDeferredSetterActions() { | ||||
|             return this.deferredSettersStack.getFirst().getActions(); | ||||
|         } | ||||
| 
 | ||||
|         public List<Resolved> getAllResolved() { | ||||
|             return this.allResolved; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private final Map<Class<?>, Constructor<?>[]> cachedAllConstructors = new HashMap<>(); | ||||
|     private final Collection<CachedInjectConstructor<?>> cachedInjectConstructors = new ArrayList<>(); | ||||
|     private final Collection<CachedNonInjectConstructor<?>> cachedNonInjectConstructors = new ArrayList<>(); | ||||
| @ -81,14 +166,27 @@ public abstract class AbstractInjectingObjectFactory implements ObjectFactory { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private static boolean areArgsAssignable(Class<?>[] paramTypes, Object[] givenArgs) { | ||||
|         if (paramTypes.length != givenArgs.length) { | ||||
|             return false; | ||||
|         } | ||||
|         for (int i = 0; i < paramTypes.length; i++) { | ||||
|             if (!paramTypes[i].isInstance(givenArgs[i])) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @implNote If overridden, please cache any found non-inject constructors using {@link #putCachedNonInjectConstructor}. | ||||
|      * @implNote If overridden, please cache any found non-inject constructors using | ||||
|      *   {@link #putCachedNonInjectConstructor}. | ||||
|      * | ||||
|      * @param clazz the {@link Class} in which to search for a constructor which does not have an <code>{@literal @}Inject</code> | ||||
|      *              annotation | ||||
|      * @param clazz the {@link Class} in which to search for a constructor which does | ||||
|      *              not have an <code>{@literal @}Inject</code> annotation. | ||||
|      * @param constructorArgs the given constructor args | ||||
|      * @return the found non-inject constructor appropriate for the given constructor args, or {@code null} if no | ||||
|      * such constructor exists | ||||
|      * @return the found non-inject constructor appropriate for the given constructor args, | ||||
|      * or {@code null} if no such constructor exists | ||||
|      * @param <T> the type | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
| @ -101,7 +199,7 @@ public abstract class AbstractInjectingObjectFactory implements ObjectFactory { | ||||
| 
 | ||||
|         final Constructor<?>[] constructors = this.cachedAllConstructors.computeIfAbsent(clazz, Class::getConstructors); | ||||
|         for (Constructor<?> constructor : constructors) { | ||||
|             if (Arrays.equals(constructor.getParameterTypes(), types)) { | ||||
|             if (areArgsAssignable(constructor.getParameterTypes(), constructorArgs)) { | ||||
|                 final Constructor<T> found = (Constructor<T>) constructor; | ||||
|                 this.putCachedNonInjectConstructor(new CachedNonInjectConstructor<>(clazz, found, types)); | ||||
|                 return found; | ||||
| @ -156,42 +254,104 @@ public abstract class AbstractInjectingObjectFactory implements ObjectFactory { | ||||
|     protected Parameter getCachedInjectParameter(Method setter) { | ||||
|         return this.cachedSetterParameters.computeIfAbsent(setter, s -> { | ||||
|             if (s.getParameterCount() != 1) { | ||||
|                 throw new IllegalArgumentException("Setter " + s.getName() + " has a parameter count other than one (1)!"); | ||||
|                 throw new IllegalArgumentException( | ||||
|                         "Setter " + s.getName() + " has a parameter count other than one (1)!" | ||||
|                 ); | ||||
|             } | ||||
|             return s.getParameters()[0]; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     protected void injectSetter(Object target, Method setter) { | ||||
|         try { | ||||
|             setter.invoke(target, this.getSetterInjectArg(target.getClass(), setter, this.getCachedInjectParameter(setter))); | ||||
|         } catch (InvocationTargetException | IllegalAccessException e) { | ||||
|             throw new RuntimeException(e); | ||||
|     private @Nullable Object findInContext(CreateContext context, Class<?> type) { | ||||
|         for (final Resolved resolved : context.getAllResolved()) { | ||||
|             if (type.isAssignableFrom(resolved.type)) { | ||||
|                 return resolved.object; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     protected void injectSetter(CreateContext context, Object target, Method setter) { | ||||
|         final Parameter injectParam = this.getCachedInjectParameter(setter); | ||||
|         final Class<?> typeToInject = injectParam.getType(); | ||||
|         final @Nullable Object fromContext = this.findInContext(context, typeToInject); | ||||
| 
 | ||||
|         final Consumer<Object> setterAction = arg -> { | ||||
|             try { | ||||
|                 setter.invoke(target, arg); | ||||
|             } catch (InvocationTargetException | IllegalAccessException e) { | ||||
|                 throw new RuntimeException(e); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         if (context.containsConstruction(typeToInject)) { | ||||
|             context.addDeferredSetterAction(typeToInject, setterAction); | ||||
|         } else { | ||||
|             final Object arg; | ||||
|             if (fromContext != null) { | ||||
|                 arg = fromContext; | ||||
|             } else { | ||||
|                 arg = this.getSetterInjectArg(context, target.getClass(), setter, injectParam); | ||||
|                 context.getAllResolved().add(new Resolved(typeToInject, arg)); | ||||
|             } | ||||
|             setterAction.accept(arg); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected void injectSetters(Object target) { | ||||
|         this.getCachedSettersFor(target).forEach(setter -> this.injectSetter(target, setter)); | ||||
|     protected void injectSetters(CreateContext context, Object target) { | ||||
|         this.getCachedSettersFor(target).forEach(setter -> this.injectSetter(context, target, setter)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritDoc} | ||||
|      */ | ||||
|     @Override | ||||
|     public <T> T createInstance(Class<T> clazz, Object... constructorArgs) { | ||||
|         final Constructor<T> constructor = this.findConstructor(clazz, constructorArgs); | ||||
|         final Object[] allArgs = this.createArgs(constructor, constructorArgs); | ||||
|     public <T> T createInstance(Class<T> type, Object... constructorArgs) { | ||||
|         final Constructor<T> constructor = this.findConstructor(type, constructorArgs); | ||||
|         final CreateContext context = new CreateContext(type); | ||||
|         final Object[] allArgs = this.createArgs(context, constructor, constructorArgs); | ||||
|         try { | ||||
|             final T instance = constructor.newInstance(allArgs); | ||||
|             this.injectSetters(instance); | ||||
|             context.getAllResolved().add(new Resolved(type, instance)); | ||||
|             this.injectSetters(context, instance); | ||||
|             final List<Consumer<Object>> deferredSetterActions = context.getDeferredSetterActions(); | ||||
|             context.popConstruction(); | ||||
|             deferredSetterActions.forEach(setterAction -> setterAction.accept(instance)); | ||||
|             return instance; | ||||
|         } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { | ||||
|             throw new RuntimeException(e); // In the future, we might have an option to ignore exceptions | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected abstract Object[] createArgs(Constructor<?> constructor, Object[] constructorArgs); | ||||
|     protected <T> T createInstance(CreateContext context, Class<T> type, Object... givenArgs) { | ||||
|         final Constructor<T> constructor = this.findConstructor(type, givenArgs); | ||||
|         context.checkForCircularDependency(type); | ||||
|         context.pushConstruction(type); | ||||
|         final Object[] allArgs = this.createArgs(context, constructor, givenArgs); | ||||
|         try { | ||||
|             final T instance = constructor.newInstance(allArgs); | ||||
|             context.getAllResolved().add(new Resolved(type, instance)); | ||||
|             this.injectSetters(context, instance); | ||||
|             final List<Consumer<Object>> deferredSetterActions = context.getDeferredSetterActions(); | ||||
|             context.popConstruction(); | ||||
|             deferredSetterActions.forEach(setterAction -> setterAction.accept(instance)); | ||||
|             return instance; | ||||
|         } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected abstract Object getSetterInjectArg(Class<?> targetType, Method setter, Parameter toInject); | ||||
|     protected abstract Object[] createArgs( | ||||
|             CreateContext context, | ||||
|             Constructor<?> constructor, | ||||
|             Object[] constructorArgs | ||||
|     ); | ||||
| 
 | ||||
|     protected abstract Object getSetterInjectArg( | ||||
|             CreateContext context, | ||||
|             Class<?> targetType, | ||||
|             Method setter, | ||||
|             Parameter toInject | ||||
|     ); | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -14,7 +14,8 @@ import java.util.function.Supplier; | ||||
| 
 | ||||
| import static groowt.util.di.RegistryObjectFactoryUtil.orElseSupply; | ||||
| 
 | ||||
| public abstract class AbstractRegistryObjectFactory extends AbstractInjectingObjectFactory implements RegistryObjectFactory { | ||||
| public abstract class AbstractRegistryObjectFactory extends AbstractInjectingObjectFactory | ||||
|         implements RegistryObjectFactory { | ||||
| 
 | ||||
|     public static abstract class AbstractBuilder<T extends DefaultRegistryObjectFactory> implements Builder<T> { | ||||
| 
 | ||||
| @ -75,8 +76,8 @@ public abstract class AbstractRegistryObjectFactory extends AbstractInjectingObj | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void configureRegistry(Consumer<? super Registry> use) { | ||||
|         use.accept(this.registry); | ||||
|     public Registry getRegistry() { | ||||
|         return this.registry; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -27,6 +27,10 @@ public final class BindingUtil { | ||||
|         return bc -> {}; | ||||
|     } | ||||
| 
 | ||||
|     public static <T> KeyHolder<NamedRegistryExtension, String, T> named(String name, Class<T> type) { | ||||
|         return new SimpleKeyHolder<>(NamedRegistryExtension.class, type, name); | ||||
|     } | ||||
| 
 | ||||
|     private BindingUtil() {} | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -3,31 +3,58 @@ package groowt.util.di; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.*; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| public class DefaultRegistry implements Registry { | ||||
| 
 | ||||
|     protected record ClassKeyBinding<T>(Class<T> key, Binding<T> binding) {} | ||||
|     protected static class BindingContainer { | ||||
| 
 | ||||
|     protected final Collection<ClassKeyBinding<?>> classBindings = new ArrayList<>(); | ||||
|         private final Map<Class<?>, Binding<?>> bindings = new HashMap<>(); | ||||
| 
 | ||||
|         @SuppressWarnings("unchecked") | ||||
|         public <T> @Nullable Binding<T> get(Class<T> key) { | ||||
|             for (final var entry : bindings.entrySet()) { | ||||
|                 if (entry.getKey().isAssignableFrom(key)) { | ||||
|                     return (Binding<T>) entry.getValue(); | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         public <T> void put(Class<T> key, Binding<T> binding) { | ||||
|             this.bindings.put(key, binding); | ||||
|         } | ||||
| 
 | ||||
|         public void remove(Class<?> key) { | ||||
|             this.bindings.remove(key); | ||||
|         } | ||||
| 
 | ||||
|         public <T> void removeIf(Class<T> key, Predicate<? super Binding<T>> filter) { | ||||
|             if (filter.test(this.get(key))) { | ||||
|                 this.bindings.remove(key); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void clear() { | ||||
|             this.bindings.clear(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected final BindingContainer bindingContainer = new BindingContainer(); | ||||
|     protected final Collection<RegistryExtension> extensions = new ArrayList<>(); | ||||
| 
 | ||||
|     @Override | ||||
|     public void removeBinding(Class<?> key) { | ||||
|         this.classBindings.removeIf(classKeyBinding -> classKeyBinding.key().equals(key)); | ||||
|         this.bindingContainer.remove(key); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) { | ||||
|         this.classBindings.removeIf(classKeyBinding -> | ||||
|                 classKeyBinding.key().equals(key) && filter.test((Binding<T>) classKeyBinding.binding()) | ||||
|         ); | ||||
|         this.bindingContainer.removeIf(key, filter); | ||||
|     } | ||||
| 
 | ||||
|     private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) { | ||||
| @ -117,18 +144,12 @@ public class DefaultRegistry implements Registry { | ||||
|     public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) { | ||||
|         final var configurator = new SimpleBindingConfigurator<>(key); | ||||
|         configure.accept(configurator); | ||||
|         this.classBindings.add(new ClassKeyBinding<>(key, configurator.getBinding())); | ||||
|         this.bindingContainer.put(key, configurator.getBinding()); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public @Nullable <T> Binding<T> getBinding(Class<T> key) { | ||||
|         for (final var classKeyBinding : this.classBindings) { | ||||
|             if (key.isAssignableFrom(classKeyBinding.key())) { | ||||
|                 return (Binding<T>) classKeyBinding.binding(); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     public <T> @Nullable Binding<T> getBinding(Class<T> key) { | ||||
|         return this.bindingContainer.get(key); | ||||
|     } | ||||
| 
 | ||||
|     private KeyBinder<?> findKeyBinder(Class<?> keyClass) { | ||||
| @ -189,7 +210,7 @@ public class DefaultRegistry implements Registry { | ||||
| 
 | ||||
|     @Override | ||||
|     public void clearAllBindings() { | ||||
|         this.classBindings.clear(); | ||||
|         this.bindingContainer.clear(); | ||||
|         for (final var extension : this.extensions) { | ||||
|             if (extension instanceof KeyBinder<?> keyBinder) { | ||||
|                 keyBinder.clearAllBindings(); | ||||
|  | ||||
| @ -3,8 +3,6 @@ package groowt.util.di; | ||||
| import groowt.util.di.filters.FilterHandler; | ||||
| import groowt.util.di.filters.IterableFilterHandler; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.Constructor; | ||||
| @ -20,7 +18,8 @@ import static groowt.util.di.RegistryObjectFactoryUtil.*; | ||||
| 
 | ||||
| public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory { | ||||
| 
 | ||||
|     public static final class Builder extends AbstractRegistryObjectFactory.AbstractBuilder<DefaultRegistryObjectFactory> { | ||||
|     public static final class Builder | ||||
|             extends AbstractRegistryObjectFactory.AbstractBuilder<DefaultRegistryObjectFactory> { | ||||
| 
 | ||||
|         /** | ||||
|          * Creates a {@code Builder} initialized with a {@link DefaultRegistry}, which is in-turn configured with a | ||||
| @ -74,7 +73,6 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|     } | ||||
| 
 | ||||
|     private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; | ||||
|     private static final Logger logger = LoggerFactory.getLogger(DefaultRegistryObjectFactory.class); // leave it for the future! | ||||
| 
 | ||||
|     private final Collection<FilterHandler<?, ?>> filterHandlers; | ||||
|     private final Collection<IterableFilterHandler<?, ?>> iterableFilterHandlers; | ||||
| @ -107,7 +105,9 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|     @SuppressWarnings("unchecked") | ||||
|     protected final @Nullable Object tryQualifiers(Parameter parameter) { | ||||
|         final Class<?> paramType = parameter.getType(); | ||||
|         final List<Annotation> qualifiers = RegistryObjectFactoryUtil.getQualifierAnnotations(parameter.getAnnotations()); | ||||
|         final List<Annotation> qualifiers = RegistryObjectFactoryUtil.getQualifierAnnotations( | ||||
|                 parameter.getAnnotations() | ||||
|         ); | ||||
|         if (qualifiers.size() > 1) { | ||||
|             throw new RuntimeException("Parameter " + parameter + " cannot have more than one Qualifier annotation."); | ||||
|         } else if (qualifiers.size() == 1) { | ||||
| @ -115,7 +115,9 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|             @SuppressWarnings("rawtypes") | ||||
|             final QualifierHandler handler = this.getInSelfOrParent( | ||||
|                     f -> f.findQualifierHandler(qualifier.annotationType()), | ||||
|                     () -> new RuntimeException("There is no configured QualifierHandler for " + qualifier.annotationType().getName()) | ||||
|                     () -> new RuntimeException("There is no configured QualifierHandler for " | ||||
|                             + qualifier.annotationType().getName() | ||||
|                     ) | ||||
|             ); | ||||
|             final Binding<?> binding = handler.handle(qualifier, paramType); | ||||
|             if (binding != null) { | ||||
| @ -165,21 +167,23 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected final Object resolveInjectedArg(Parameter parameter) { | ||||
|     protected final Object resolveInjectedArg(CreateContext context, Parameter parameter) { | ||||
|         final Object qualifierProvidedArg = this.tryQualifiers(parameter); | ||||
|         if (qualifierProvidedArg != null) { | ||||
|             this.checkFilters(parameter, qualifierProvidedArg); | ||||
|             context.getAllResolved().add(new Resolved(parameter.getType(), qualifierProvidedArg)); | ||||
|             return qualifierProvidedArg; | ||||
|         } else { | ||||
|             final Object created = this.get(parameter.getType()); | ||||
|             final Object created = this.get(context, parameter.getType()); | ||||
|             this.checkFilters(parameter, created); | ||||
|             context.getAllResolved().add(new Resolved(parameter.getType(), created)); | ||||
|             return created; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected final void resolveInjectedArgs(Object[] dest, Parameter[] params) { | ||||
|     protected final void resolveInjectedArgs(CreateContext context, Object[] dest, Parameter[] params) { | ||||
|         for (int i = 0; i < params.length; i++) { | ||||
|             dest[i] = this.resolveInjectedArg(params[i]); | ||||
|             dest[i] = this.resolveInjectedArg(context, params[i]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -194,7 +198,7 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
| 
 | ||||
|     // TODO: when there is a null arg, we lose the type. Therefore this algorithm breaks. Fix this. | ||||
|     @Override | ||||
|     protected Object[] createArgs(Constructor<?> constructor, Object[] givenArgs) { | ||||
|     protected Object[] createArgs(CreateContext context, Constructor<?> constructor, Object[] givenArgs) { | ||||
|         final Class<?>[] paramTypes = constructor.getParameterTypes(); | ||||
| 
 | ||||
|         // check no arg | ||||
| @ -218,7 +222,7 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
| 
 | ||||
|         if (givenArgs.length == 0) { | ||||
|             // if no given args, then they are all injected | ||||
|             this.resolveInjectedArgs(resolvedArgs, allParams); | ||||
|             this.resolveInjectedArgs(context, resolvedArgs, allParams); | ||||
|         } else if (givenArgs.length == paramTypes.length) { | ||||
|             // all are given | ||||
|             this.resolveGivenArgs(resolvedArgs, allParams, givenArgs, 0); | ||||
| @ -234,17 +238,23 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|             final Parameter[] givenParams = new Parameter[allParams.length - firstGivenIndex]; | ||||
| 
 | ||||
|             System.arraycopy(allParams, 0, injectedParams, 0, injectedParams.length); | ||||
|             System.arraycopy(allParams, firstGivenIndex, givenParams, 0, allParams.length - firstGivenIndex); | ||||
|             System.arraycopy( | ||||
|                     allParams, firstGivenIndex, givenParams, 0, allParams.length - firstGivenIndex | ||||
|             ); | ||||
| 
 | ||||
|             this.resolveInjectedArgs(resolvedArgs, injectedParams); | ||||
|             this.resolveInjectedArgs(context, resolvedArgs, injectedParams); | ||||
|             this.resolveGivenArgs(resolvedArgs, givenParams, givenArgs, firstGivenIndex); | ||||
|         } | ||||
| 
 | ||||
|         return resolvedArgs; | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private <T> T handleBinding(Binding<T> binding, Object[] constructorArgs) { | ||||
|         return this.handleBinding(binding, null, constructorArgs); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     private <T> T handleBinding(Binding<T> binding, @Nullable CreateContext context, Object[] constructorArgs) { | ||||
|         return switch (binding) { | ||||
|             case ClassBinding<T>(Class<T> ignored, Class<? extends T> to) -> { | ||||
|                 final Annotation scopeAnnotation = getScopeAnnotation(to); | ||||
| @ -253,12 +263,20 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|                     @SuppressWarnings("rawtypes") | ||||
|                     final ScopeHandler scopeHandler = this.getInSelfOrParent( | ||||
|                             f -> f.findScopeHandler(scopeClass), | ||||
|                             () -> new RuntimeException("There is no configured ScopeHandler for " + scopeClass.getName()) | ||||
|                             () -> new RuntimeException( | ||||
|                                     "There is no configured ScopeHandler for " + scopeClass.getName() | ||||
|                             ) | ||||
|                     ); | ||||
|                     final Binding<T> scopedBinding = scopeHandler.onScopedDependencyRequest( | ||||
|                             scopeAnnotation, to, this | ||||
|                     ); | ||||
|                     final Binding<T> scopedBinding = scopeHandler.onScopedDependencyRequest(scopeAnnotation, to, this); | ||||
|                     yield this.handleBinding(scopedBinding, constructorArgs); | ||||
|                 } else { | ||||
|                     yield this.createInstance(to, constructorArgs); | ||||
|                     if (context != null) { | ||||
|                         yield this.createInstance(context, to, constructorArgs); | ||||
|                     } else { | ||||
|                         yield this.createInstance(to, constructorArgs); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             case ProviderBinding<T> providerBinding -> providerBinding.provider().get(); | ||||
| @ -276,8 +294,8 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected Object getSetterInjectArg(Class<?> targetType, Method setter, Parameter toInject) { | ||||
|         return this.resolveInjectedArg(toInject); | ||||
|     protected Object getSetterInjectArg(CreateContext context, Class<?> targetType, Method setter, Parameter toInject) { | ||||
|         return this.resolveInjectedArg(context, toInject); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -293,7 +311,24 @@ public class DefaultRegistryObjectFactory extends AbstractRegistryObjectFactory | ||||
|         if (parentResult != null) { | ||||
|             return parentResult; | ||||
|         } else { | ||||
|             throw new RuntimeException("No bindings for " + clazz + " with args " + Arrays.toString(constructorArgs) + "."); | ||||
|             throw new RuntimeException( | ||||
|                     "No bindings for " + clazz + " with args " + Arrays.toString(constructorArgs) + "." | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected <T> T get(CreateContext context, Class<T> type) { | ||||
|         final Binding<T> binding = this.searchRegistry(type); | ||||
|         if (binding != null) { | ||||
|             return this.handleBinding(binding, context, EMPTY_OBJECT_ARRAY); | ||||
|         } | ||||
|         final T parentResult = this.tryParent(type, EMPTY_OBJECT_ARRAY); | ||||
|         if (parentResult != null) { | ||||
|             return parentResult; | ||||
|         } else { | ||||
|             throw new RuntimeException( | ||||
|                     "No bindings for " + type + " with args " + Arrays.toString(EMPTY_OBJECT_ARRAY) + "." | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,9 +1,3 @@ | ||||
| package groowt.util.di; | ||||
| 
 | ||||
| public interface NamedRegistryExtension extends RegistryExtension, KeyBinder<String>, QualifierHandlerContainer { | ||||
| 
 | ||||
|     static <T> KeyHolder<NamedRegistryExtension, String, T> named(String name, Class<T> type) { | ||||
|         return new SimpleKeyHolder<>(NamedRegistryExtension.class, type, name); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| public interface NamedRegistryExtension extends RegistryExtension, KeyBinder<String>, QualifierHandlerContainer {} | ||||
|  | ||||
| @ -6,6 +6,7 @@ import java.util.function.Consumer; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| public interface Registry extends ExtensionContainer, QualifierHandlerContainer, ScopeHandlerContainer { | ||||
| 
 | ||||
|     <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure); | ||||
|     @Nullable <T> Binding<T> getBinding(Class<T> key); | ||||
|     void removeBinding(Class<?> key); | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| package groowt.util.di; | ||||
| 
 | ||||
| import groovy.lang.Closure; | ||||
| import groovy.lang.DelegatesTo; | ||||
| import groowt.util.di.filters.FilterHandler; | ||||
| import groowt.util.di.filters.IterableFilterHandler; | ||||
| import jakarta.inject.Provider; | ||||
| @ -16,13 +18,38 @@ import java.util.function.Consumer; | ||||
| public interface RegistryObjectFactory extends ObjectFactory { | ||||
| 
 | ||||
|     interface Builder<T extends RegistryObjectFactory> { | ||||
| 
 | ||||
|         void configureRegistry(Consumer<? super Registry> configure); | ||||
| 
 | ||||
|         default void configureRegistry(@DelegatesTo(Registry.class) Closure<?> configureClosure) { | ||||
|             this.configureRegistry(registry -> { | ||||
|                 configureClosure.setDelegate(registry); | ||||
|                 configureClosure.call(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         void addFilterHandler(FilterHandler<?, ?> handler); | ||||
| 
 | ||||
|         void addIterableFilterHandler(IterableFilterHandler<?, ?> handler); | ||||
| 
 | ||||
|         T build(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     void configureRegistry(Consumer<? super Registry> use); | ||||
|     Registry getRegistry(); | ||||
| 
 | ||||
|     default void configureRegistry(Consumer<? super Registry> use) { | ||||
|         use.accept(this.getRegistry()); | ||||
|     } | ||||
| 
 | ||||
|     default void configureRegistry( | ||||
|             @DelegatesTo(value = Registry.class) | ||||
|             Closure<?> configureClosure | ||||
|     ) { | ||||
|         final Registry registry = this.getRegistry(); | ||||
|         configureClosure.setDelegate(registry); | ||||
|         configureClosure.call(); | ||||
|     } | ||||
| 
 | ||||
|     <A extends Annotation> @Nullable ScopeHandler<A> findScopeHandler(Class<A> scopeType); | ||||
| 
 | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| package groowt.util.di; | ||||
| 
 | ||||
| import jakarta.inject.Provider; | ||||
| import jakarta.inject.Singleton; | ||||
| 
 | ||||
| import static groowt.util.di.BindingUtil.toSingleton; | ||||
| import static groowt.util.di.BindingUtil.toLazySingleton; | ||||
| 
 | ||||
| public final class SingletonScopeHandler implements ScopeHandler<Singleton> { | ||||
| 
 | ||||
| @ -19,12 +20,22 @@ public final class SingletonScopeHandler implements ScopeHandler<Singleton> { | ||||
|             RegistryObjectFactory objectFactory | ||||
|     ) { | ||||
|         final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass); | ||||
|         if (potentialBinding != null) { | ||||
|             return potentialBinding; | ||||
|         } else { | ||||
|             this.owner.bind(dependencyClass, toSingleton(objectFactory.createInstance(dependencyClass))); | ||||
|             return this.owner.getBinding(dependencyClass); | ||||
|         } | ||||
|         return switch (potentialBinding) { | ||||
|             case ClassBinding<T>(Class<T> from, Class<? extends T> to) -> { | ||||
|                 this.owner.bind(from, toLazySingleton(() -> objectFactory.createInstance(to))); | ||||
|                 yield this.owner.getBinding(from); | ||||
|             } | ||||
|             case ProviderBinding<T>(Class<T> from, Provider<? extends T> provider) -> { | ||||
|                 this.owner.bind(from, toLazySingleton(provider::get)); | ||||
|                 yield this.owner.getBinding(from); | ||||
|             } | ||||
|             case SingletonBinding<T> singletonBinding -> singletonBinding; | ||||
|             case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding; | ||||
|             case null -> { | ||||
|                 this.owner.bind(dependencyClass, toLazySingleton(() -> objectFactory.createInstance(dependencyClass))); | ||||
|                 yield this.owner.getBinding(dependencyClass); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -2,10 +2,10 @@ package groowt.util.di; | ||||
| 
 | ||||
| import jakarta.inject.Inject; | ||||
| import jakarta.inject.Named; | ||||
| import jakarta.inject.Singleton; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import static groowt.util.di.BindingUtil.*; | ||||
| import static groowt.util.di.NamedRegistryExtension.named; | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| 
 | ||||
| public class DefaultRegistryObjectFactoryTests { | ||||
| @ -167,4 +167,109 @@ public class DefaultRegistryObjectFactoryTests { | ||||
|         assertEquals("Hello, World!", greeter.greet()); | ||||
|     } | ||||
| 
 | ||||
|     public static final class GreeterDependency { | ||||
| 
 | ||||
|         private GreeterDependencyUser greeter; | ||||
| 
 | ||||
|         @Inject | ||||
|         public void setGreeter(GreeterDependencyUser greeter) { | ||||
|             this.greeter = greeter; | ||||
|         } | ||||
| 
 | ||||
|         public String filterGreeting() { | ||||
|             return this.greeter.getGreeting().toUpperCase(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static final class GreeterDependencyUser implements Greeter { | ||||
| 
 | ||||
|         private final GreeterDependency greeterDependency; | ||||
| 
 | ||||
|         @Inject | ||||
|         public GreeterDependencyUser(GreeterDependency greeterDependency) { | ||||
|             this.greeterDependency = greeterDependency; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public String greet() { | ||||
|             return this.greeterDependency.filterGreeting(); | ||||
|         } | ||||
| 
 | ||||
|         public String getGreeting() { | ||||
|             return "hello, world!"; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void injectedDeferred() { | ||||
|         final var b = DefaultRegistryObjectFactory.Builder.withDefaults(); | ||||
|         b.configureRegistry(r -> { | ||||
|             r.bind(GreeterDependencyUser.class, toSelf()); | ||||
|             r.bind(GreeterDependency.class, toSelf()); | ||||
|         }); | ||||
|         final var f = b.build(); | ||||
|         final var g = f.get(GreeterDependencyUser.class); | ||||
|         assertEquals("HELLO, WORLD!", g.greet()); | ||||
|     } | ||||
| 
 | ||||
|     public static final class NoInjectGreeter implements Greeter { | ||||
| 
 | ||||
|         private final String greeting; | ||||
| 
 | ||||
|         public NoInjectGreeter(String greeting) { | ||||
|             this.greeting = greeting; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public String greet() { | ||||
|             return this.greeting; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void noInjectFoundViaGet() { | ||||
|         final var b = DefaultRegistryObjectFactory.Builder.withDefaults(); | ||||
|         b.configureRegistry(r -> { | ||||
|             r.bind(NoInjectGreeter.class, toSelf()); | ||||
|         }); | ||||
|         final var f = b.build(); | ||||
|         final var g = f.get(NoInjectGreeter.class, "Given Greeting"); | ||||
|         assertEquals("Given Greeting", g.greet()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void noInjectFindViaCreate() { | ||||
|         final var b = DefaultRegistryObjectFactory.Builder.withDefaults(); | ||||
|         b.configureRegistry(r -> { | ||||
|             r.bind(NoInjectGreeter.class, toSelf()); | ||||
|         }); | ||||
|         final var f = b.build(); | ||||
|         final var g = f.createInstance(NoInjectGreeter.class, "Given Greeting"); | ||||
|         assertEquals("Given Greeting", g.greet()); | ||||
|     } | ||||
| 
 | ||||
|     @Singleton | ||||
|     public static final class SingletonGreeter implements Greeter { | ||||
| 
 | ||||
|         @Override | ||||
|         public String greet() { | ||||
|             return "Hello, World!"; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void singletonDoesNotOverflow() { | ||||
|         final var b = DefaultRegistryObjectFactory.Builder.withDefaults(); | ||||
|         b.configureRegistry(r -> { | ||||
|             r.bind(SingletonGreeter.class, toSelf()); | ||||
|         }); | ||||
|         final var f = b.build(); | ||||
|         final var g = f.get(SingletonGreeter.class); | ||||
|         assertEquals("Hello, World!", g.greet()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user