From 22b500a4a412e42c5fe5939056e2b177c52fa4e3 Mon Sep 17 00:00:00 2001
From: QwQ-dev
Date: Mon, 23 Dec 2024 18:46:33 +0800
Subject: [PATCH] feat: VarHandleReflectionInjector.
---
annotation/README.md | 3 +-
cache/README.md | 8 +-
.../cache/factory/CacheServiceFactory.java | 23 +++-
commons/README.md | 51 +++++++++
commons/build.gradle.kts | 2 +
.../library/commons/CommonsLauncher.java | 16 +++
.../commons/factory/InjectorFactory.java | 25 +++++
.../injector/ObjectInjectorInterface.java | 16 +++
.../injector/StaticInjectorInterface.java | 16 +++
.../injector/VarHandleReflectionInjector.java | 100 ++++++++++++++++++
.../annotation/VarHandleAutoInjection.java | 42 ++++++++
.../factory/SimplixBuilderFactory.java | 9 +-
mongodb/README.md | 8 +-
.../MongoDBConnectionConfigFactory.java | 7 +-
14 files changed, 313 insertions(+), 13 deletions(-)
create mode 100644 commons/README.md
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/CommonsLauncher.java
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/factory/InjectorFactory.java
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/injector/ObjectInjectorInterface.java
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/injector/StaticInjectorInterface.java
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java
create mode 100644 commons/src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java
diff --git a/annotation/README.md b/annotation/README.md
index ec832b8..b5517ab 100644
--- a/annotation/README.md
+++ b/annotation/README.md
@@ -71,5 +71,6 @@ We only need to get `AnnotationProcessingService` through dependency injection a
Let's focus on `annotationProcessingService.processAnnotations(basePackage, false, this.getClassLoader())`.
The first parameter is the package we need to scan.
-The second is whether the processed class should be injected into the singleton mode by the Fairy framework. If it is false, it will be created through parameterless reflection.
+The second is whether the processed class should be injected into the singleton mode by the Fairy framework. If it is
+false, it will be created through parameterless reflection.
The third is the classloader that needs to be scanned.
\ No newline at end of file
diff --git a/cache/README.md b/cache/README.md
index 800456c..58012db 100644
--- a/cache/README.md
+++ b/cache/README.md
@@ -1,9 +1,11 @@
### cache
-The module is a caching solution that supports `Caffeine`'s `Cache` and `AsyncCache`,
-as well as an in-memory `Redis` database implementation, offering functional programming support and automatic lock handling.
+The module is a caching solution that supports `Caffeine`'s `Cache` and `AsyncCache`,
+as well as an in-memory `Redis` database implementation, offering functional programming support and automatic lock
+handling.
-The original purpose of this module was to design a L1 cache for the `mongodb` module, but now it is **general-purpose**.
+The original purpose of this module was to design a L1 cache for the `mongodb` module, but now it is **general-purpose
+**.
### usage
diff --git a/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java b/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java
index a948e0e..09a6cb9 100644
--- a/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java
+++ b/cache/src/main/java/me/qwqdev/library/cache/factory/CacheServiceFactory.java
@@ -22,22 +22,28 @@
@UtilityClass
public final class CacheServiceFactory {
/**
- * Creates a Redis cache service with the specified configuration.
+ * Creates a {@link RedisCacheService} with the specified configuration.
*
* @param config the Redis configuration
- * @return a new Redis cache service instance
+ * @return a new {@link RedisCacheService} instance
+ * @see RedisCacheService
+ * @see RedisCacheServiceInterface
+ * @see Config
*/
public static RedisCacheServiceInterface createRedisCache(Config config) {
return new RedisCacheService(config);
}
/**
- * Creates a Caffeine synchronous cache service with custom configuration.
+ * Creates a {@link CaffeineCacheService} with custom configuration.
*
* @param cache the Caffeine cache
* @param the cache key type
* @param the cache value type
- * @return a new Caffeine cache service instance
+ * @return a new {@link CaffeineCacheService} instance
+ * @see CacheServiceInterface
+ * @see Cache
+ * @see CaffeineCacheService
*/
public static CacheServiceInterface, V> createCaffeineCache(Cache cache) {
return new CaffeineCacheService<>(cache);
@@ -49,6 +55,9 @@ public static CacheServiceInterface, V> createCaffeineCache(C
* @param the cache key type
* @param the cache value type
* @return a new Caffeine cache service instance
+ * @see CacheServiceInterface
+ * @see Cache
+ * @see CaffeineCacheService
*/
public static CacheServiceInterface, V> createCaffeineCache() {
return new CaffeineCacheService<>();
@@ -61,6 +70,9 @@ public static CacheServiceInterface, V> createCaffeineCache()
* @param the cache key type
* @param the cache value type
* @return a new async Caffeine cache service instance
+ * @see CacheServiceInterface
+ * @see AsyncCache
+ * @see CaffeineAsyncCacheService
*/
public static CacheServiceInterface, V> createCaffeineAsyncCache(AsyncCache asyncCache) {
return new CaffeineAsyncCacheService<>(asyncCache);
@@ -72,6 +84,9 @@ public static CacheServiceInterface, V> createCaffeineAs
* @param the cache key type
* @param the cache value type
* @return a new async Caffeine cache service instance
+ * @see CacheServiceInterface
+ * @see AsyncCache
+ * @see CaffeineAsyncCacheService
*/
public static CacheServiceInterface, V> createCaffeineAsyncCache() {
return new CaffeineAsyncCacheService<>();
diff --git a/commons/README.md b/commons/README.md
new file mode 100644
index 0000000..d52bc6c
--- /dev/null
+++ b/commons/README.md
@@ -0,0 +1,51 @@
+### commons
+
+This is a module full of good stuff that is useful in every way, so it's a bit of a mixed bag, but I'll always update
+this document when there's new content.
+
+### usage
+
+```kotlin
+// Dependencies
+dependencies {
+ // commons module
+ compileOnly("me.qwqdev.library:commons:1.0-SNAPSHOT")
+}
+```
+
+### [VarHandleReflectionInjector](src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java)
+
+This is an `injector`, and its main use is to be used
+with [VarHandleAutoInjection](src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java).
+
+Yes, just like its name, we don't have to write a bunch of ugly code to assign `VarHandle`, let it all disappear, Amen.
+
+```java
+public class Example {
+ public static void main(String[] args) {
+ Test test = new Test();
+
+ // we can use TField_HANDLE
+ Test.TField_HANDLE.set(test, 2);
+
+ // prints 2
+ System.out.println(test.getTField());
+ }
+}
+```
+
+```java
+
+@Getter
+@Setter
+public class Test {
+ private volatile int tField = 100;
+
+ @VarHandleAutoInjection(fieldName = "tField")
+ public static VarHandle TField_HANDLE;
+
+ static {
+ new VarHandleReflectionInjector().inject(Test.class);
+ }
+}
+```
\ No newline at end of file
diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts
index f1bb3e8..8f37427 100644
--- a/commons/build.gradle.kts
+++ b/commons/build.gradle.kts
@@ -24,4 +24,6 @@ fairy {
// Dependencies
dependencies {
+ // Annotation module
+ compileOnly(project(":annotation"))
}
\ No newline at end of file
diff --git a/commons/src/main/java/me/qwqdev/library/commons/CommonsLauncher.java b/commons/src/main/java/me/qwqdev/library/commons/CommonsLauncher.java
new file mode 100644
index 0000000..dc5b43d
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/CommonsLauncher.java
@@ -0,0 +1,16 @@
+package me.qwqdev.library.commons;
+
+import io.fairyproject.FairyLaunch;
+import io.fairyproject.container.InjectableComponent;
+import io.fairyproject.plugin.Plugin;
+
+/**
+ * The type Commons launcher.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 18:32
+ */
+@FairyLaunch
+@InjectableComponent
+public class CommonsLauncher extends Plugin {
+}
diff --git a/commons/src/main/java/me/qwqdev/library/commons/factory/InjectorFactory.java b/commons/src/main/java/me/qwqdev/library/commons/factory/InjectorFactory.java
new file mode 100644
index 0000000..746ed18
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/factory/InjectorFactory.java
@@ -0,0 +1,25 @@
+package me.qwqdev.library.commons.factory;
+
+import lombok.experimental.UtilityClass;
+import me.qwqdev.library.commons.injector.StaticInjectorInterface;
+import me.qwqdev.library.commons.injector.VarHandleReflectionInjector;
+
+/**
+ * Factory for creating injector instances.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 18:38
+ */
+@UtilityClass
+public final class InjectorFactory {
+ /**
+ * Create a {@link VarHandleReflectionInjector} instance.
+ *
+ * @return a {@link VarHandleReflectionInjector} instance
+ * @see StaticInjectorInterface
+ * @see VarHandleReflectionInjector
+ */
+ public static StaticInjectorInterface createVarHandleReflectionInjector() {
+ return new VarHandleReflectionInjector();
+ }
+}
diff --git a/commons/src/main/java/me/qwqdev/library/commons/injector/ObjectInjectorInterface.java b/commons/src/main/java/me/qwqdev/library/commons/injector/ObjectInjectorInterface.java
new file mode 100644
index 0000000..cf43b67
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/injector/ObjectInjectorInterface.java
@@ -0,0 +1,16 @@
+package me.qwqdev.library.commons.injector;
+
+/**
+ * Interface for object injectors.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 16:44
+ */
+public interface ObjectInjectorInterface {
+ /**
+ * Process the given object.
+ *
+ * @param object given object
+ */
+ void inject(Object object);
+}
diff --git a/commons/src/main/java/me/qwqdev/library/commons/injector/StaticInjectorInterface.java b/commons/src/main/java/me/qwqdev/library/commons/injector/StaticInjectorInterface.java
new file mode 100644
index 0000000..acfc093
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/injector/StaticInjectorInterface.java
@@ -0,0 +1,16 @@
+package me.qwqdev.library.commons.injector;
+
+/**
+ * Interface for static injectors.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 17:07
+ */
+public interface StaticInjectorInterface {
+ /**
+ * Process the given class.
+ *
+ * @param clazz given class
+ */
+ void inject(Class> clazz);
+}
diff --git a/commons/src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java b/commons/src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java
new file mode 100644
index 0000000..8a81c7d
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java
@@ -0,0 +1,100 @@
+package me.qwqdev.library.commons.injector;
+
+import me.qwqdev.library.commons.injector.annotation.VarHandleAutoInjection;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for injecting {@link VarHandle} instances into static fields annotated with {@link VarHandleAutoInjection}.
+ * This class automatically injects {@link VarHandle} instances into the static fields of a class based on the annotations
+ * present on those fields.
+ *
+ * The injection is done via reflection, which allows this class to work without explicitly hardcoding the
+ * dependencies. However, this approach introduces some overhead and may not be suitable for high-performance use cases.
+ *
+ *
+ * The injection process works in two ways:
+ *
+ * - Default: Uses {@link MethodHandles#lookup()} to locate the {@link VarHandle} for the specified static field.
+ * This is the most common use case when static methods aren't needed.
+ * - Static Method: If specified in the {@link VarHandleAutoInjection} annotation, a static method can be used
+ * to retrieve the {@link VarHandle} for the field. This allows for more customized handling of the {@link VarHandle}
+ * injection.
+ *
+ *
+ *
+ * If using a static method for injection, the class containing the static method must be loadable by the current
+ * thread's context {@link ClassLoader}. The method should be accessible and match the required signature for retrieving
+ * the {@link VarHandle}.
+ *
+ * Due to the use of reflection, the fields will be made accessible even if they are private, ensuring that injection
+ * can occur regardless of visibility modifiers.
+ *
+ * This class implements the {@link StaticInjectorInterface} interface, which defines the contract for injecting
+ * dependencies (in this case, {@link VarHandle} instances) into static fields of a class.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 16:15
+ */
+public class VarHandleReflectionInjector implements StaticInjectorInterface {
+ /**
+ * {@inheritDoc}
+ *
+ * Injects {@link VarHandle} instances into static fields of the given class that are annotated with
+ * {@link VarHandleAutoInjection}. This method uses reflection to find the appropriate {@link VarHandle}
+ * for each annotated field and assigns it to the field.
+ *
+ *
The method performs the injection by either using the default method of
+ * {@link MethodHandles#privateLookupIn(Class, MethodHandles.Lookup)} to
+ * locate the {@link VarHandle} for the specified static field or by using a static method if defined in the
+ * annotation {@link VarHandleAutoInjection}.
+ *
+ *
The specified static method must contain two parameters: {@link String} and {@link Class}
+ *
+ *
If a static method is specified in the annotation, it will be called to retrieve the {@link VarHandle}
+ * for the field. The class containing the static method must be loadable by the current thread's context
+ * {@link ClassLoader}.
+ *
+ * @param clazz the class into which {@link VarHandle} instances will be injected into its static fields
+ * @throws IllegalStateException if the injection fails due to reflection issues or invalid annotations
+ */
+ @Override
+ public void inject(Class> clazz) {
+ Field[] fields = clazz.getDeclaredFields();
+
+ for (Field field : fields) {
+ VarHandleAutoInjection annotation = field.getAnnotation(VarHandleAutoInjection.class);
+
+ if (annotation == null) {
+ continue;
+ }
+
+ String targetFieldName = annotation.fieldName();
+ String staticMethodName = annotation.staticMethodName();
+ String staticMethodPackage = annotation.staticMethodPackage();
+ boolean useStaticMethod = !staticMethodName.isEmpty() && !staticMethodPackage.isEmpty();
+
+ try {
+ VarHandle varHandle;
+
+ if (useStaticMethod) {
+ Class> staticClass = Class.forName(staticMethodPackage);
+ Method method = staticClass.getDeclaredMethod(staticMethodName, String.class, Class.class);
+ method.setAccessible(true);
+ varHandle = (VarHandle) method.invoke(null, targetFieldName, field.getType());
+ } else {
+ varHandle = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())
+ .unreflectVarHandle(clazz.getDeclaredField(targetFieldName));
+ }
+
+ field.setAccessible(true);
+ field.set(null, varHandle);
+ } catch (Exception exception) {
+ throw new IllegalStateException("Failed to inject VarHandle for field: " + field.getName(), exception);
+ }
+ }
+ }
+}
diff --git a/commons/src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java b/commons/src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java
new file mode 100644
index 0000000..bc2423b
--- /dev/null
+++ b/commons/src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java
@@ -0,0 +1,42 @@
+package me.qwqdev.library.commons.injector.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.invoke.VarHandle;
+
+/**
+ * Annotation to mark a field for automatic {@link VarHandle} injection.
+ *
+ * @author qwq-dev
+ * @since 2024-12-23 16:15
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface VarHandleAutoInjection {
+ /**
+ * The name of the target field for which the {@link VarHandle} is being generated.
+ *
+ * @return the field name
+ */
+ String fieldName();
+
+ /**
+ * The name of the static method used to generate the {@link VarHandle}.
+ *
+ * The specified static method must contain two parameters: {@link String} and {@link Class}
+ *
+ * @return the static method name, or an empty string if not specified
+ */
+ String staticMethodName() default "";
+
+ /**
+ * The fully qualified name of the class containing the static method used to generate the {@link VarHandle}.
+ *
+ *
The class must be loadable by the current thread's context {@link ClassLoader}.
+ *
+ * @return the fully qualified class name, or an empty string if not specified
+ */
+ String staticMethodPackage() default "";
+}
diff --git a/configuration/src/main/java/me/qwqdev/library/configuration/factory/SimplixBuilderFactory.java b/configuration/src/main/java/me/qwqdev/library/configuration/factory/SimplixBuilderFactory.java
index 228d760..873a802 100644
--- a/configuration/src/main/java/me/qwqdev/library/configuration/factory/SimplixBuilderFactory.java
+++ b/configuration/src/main/java/me/qwqdev/library/configuration/factory/SimplixBuilderFactory.java
@@ -20,7 +20,7 @@
* @since 2024-12-18 13:48
*/
@UtilityClass
-public class SimplixBuilderFactory {
+public final class SimplixBuilderFactory {
/**
* Creates a {@link SimplixBuilder} instance from a file.
*
@@ -28,6 +28,8 @@ public class SimplixBuilderFactory {
*
* @param file the file to create the {@link SimplixBuilder} from
* @return a configured {@link SimplixBuilder} instance
+ * @see File
+ * @see SimplixBuilder
*/
public static SimplixBuilder createSimplixBuilderFromFile(File file) {
return configureSimplixBuilder(SimplixBuilder.fromFile(file));
@@ -40,6 +42,8 @@ public static SimplixBuilder createSimplixBuilderFromFile(File file) {
*
* @param file the directory to create the {@link SimplixBuilder} from
* @return a configured {@link SimplixBuilder} instance
+ * @see File
+ * @see SimplixBuilder
*/
public static SimplixBuilder createSimplixBuilderFromDirectory(File file) {
return configureSimplixBuilder(SimplixBuilder.fromDirectory(file));
@@ -52,6 +56,8 @@ public static SimplixBuilder createSimplixBuilderFromDirectory(File file) {
*
* @param path the path to create the {@link SimplixBuilder} from
* @return a configured {@link SimplixBuilder} instance
+ * @see Path
+ * @see SimplixBuilder
*/
public static SimplixBuilder createSimplixBuilderFromPath(Path path) {
return configureSimplixBuilder(SimplixBuilder.fromPath(path));
@@ -65,6 +71,7 @@ public static SimplixBuilder createSimplixBuilderFromPath(Path path) {
* @param name the name of the file
* @param path the path to the file
* @return a configured {@link SimplixBuilder} instance
+ * @see SimplixBuilder
*/
public static SimplixBuilder createSimplixBuilder(String name, String path) {
return configureSimplixBuilder(SimplixBuilder.fromPath(name, path));
diff --git a/mongodb/README.md b/mongodb/README.md
index eca85ca..944e7d9 100644
--- a/mongodb/README.md
+++ b/mongodb/README.md
@@ -1,8 +1,10 @@
### mongodb
-This module only encapsulates a more convenient MongoConfig based on [Morphia](https://morphia.dev/landing/index.html), and its implementation is very simple.
+This module only encapsulates a more convenient MongoConfig based on [Morphia](https://morphia.dev/landing/index.html),
+and its implementation is very simple.
-We recommend using Datastore directly for any CRUD operations, using [Morphia](https://morphia.dev/landing/index.html) for index creation or using the [aggregation](https://morphia.dev/morphia/2.4/aggregations.html).
+We recommend using Datastore directly for any CRUD operations, using [Morphia](https://morphia.dev/landing/index.html)
+for index creation or using the [aggregation](https://morphia.dev/morphia/2.4/aggregations.html).
### cache
@@ -57,7 +59,7 @@ public class Example {
Filters.eq("name", "Alice"), // name eq
Filters.gt("age", 35) // age > 35, it's time to lay off employees lol
))
-
+
// It should be noted that iterators are not thread-safe
.iterator(); // get iterator
diff --git a/mongodb/src/main/java/me/qwqdev/library/mongodb/factory/MongoDBConnectionConfigFactory.java b/mongodb/src/main/java/me/qwqdev/library/mongodb/factory/MongoDBConnectionConfigFactory.java
index b6ecb54..2c0c4ed 100644
--- a/mongodb/src/main/java/me/qwqdev/library/mongodb/factory/MongoDBConnectionConfigFactory.java
+++ b/mongodb/src/main/java/me/qwqdev/library/mongodb/factory/MongoDBConnectionConfigFactory.java
@@ -13,7 +13,7 @@
* @since 2024-12-20 12:18
*/
@UtilityClass
-public class MongoDBConnectionConfigFactory {
+public final class MongoDBConnectionConfigFactory {
/**
* Creates a default {@link MongoDBConnectionConfig} instance using the standard UUID representation.
*
@@ -21,6 +21,7 @@ public class MongoDBConnectionConfigFactory {
* @param mongoURL the MongoDB connection URL, e.g., "mongodb://localhost:27017"
* @return a configured {@link MongoDBConnectionConfig} instance
* @throws IllegalArgumentException if either the database name or MongoDB URL is null or empty
+ * @see MongoDBConnectionConfig
*/
public static MongoDBConnectionConfig create(String databaseName, String mongoURL) {
return new MongoDBConnectionConfig(databaseName, mongoURL);
@@ -34,6 +35,8 @@ public static MongoDBConnectionConfig create(String databaseName, String mongoUR
* @param uuidRepresentation the UUID representation to be used in MongoDB documents
* @return a configured {@link MongoDBConnectionConfig} instance
* @throws IllegalArgumentException if either the database name or MongoDB URL is null or empty
+ * @see MongoDBConnectionConfig
+ * @see UuidRepresentation
*/
public static MongoDBConnectionConfig create(String databaseName, String mongoURL, UuidRepresentation uuidRepresentation) {
return new MongoDBConnectionConfig(databaseName, mongoURL, uuidRepresentation);
@@ -45,6 +48,8 @@ public static MongoDBConnectionConfig create(String databaseName, String mongoUR
* @param mongoClientSettings the custom {@link MongoClientSettings} to configure the MongoClient
* @return a configured {@link MongoDBConnectionConfig} instance
* @throws IllegalArgumentException if {@link MongoClientSettings} is null
+ * @see MongoDBConnectionConfig
+ * @see MongoClientSettings
*/
public static MongoDBConnectionConfig create(MongoClientSettings mongoClientSettings) {
return new MongoDBConnectionConfig(mongoClientSettings);