From f2ff95731b4d357fbd356fdda8cbdc2179defb2e Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sat, 14 Oct 2023 13:23:55 -0400 Subject: [PATCH 1/3] Add @Hook annotation. Even though it's the same as @ReferencePath, it seems confusing to use the same annotation for two such different purposes. --- .../main/java/io/vena/bosk/HookRegistrar.java | 34 ++++++++++++++----- .../java/io/vena/bosk/annotations/Hook.java | 21 ++++++++++++ .../src/test/java/io/vena/bosk/HooksTest.java | 3 +- 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 bosk-core/src/main/java/io/vena/bosk/annotations/Hook.java diff --git a/bosk-core/src/main/java/io/vena/bosk/HookRegistrar.java b/bosk-core/src/main/java/io/vena/bosk/HookRegistrar.java index 60cb9efe..febd5484 100644 --- a/bosk-core/src/main/java/io/vena/bosk/HookRegistrar.java +++ b/bosk-core/src/main/java/io/vena/bosk/HookRegistrar.java @@ -1,6 +1,6 @@ package io.vena.bosk; -import io.vena.bosk.annotations.ReferencePath; +import io.vena.bosk.annotations.Hook; import io.vena.bosk.exceptions.InvalidTypeException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -10,18 +10,29 @@ import java.util.List; import java.util.function.Function; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isStatic; @RequiredArgsConstructor class HookRegistrar { - @SuppressWarnings({"unchecked","rawtypes"}) static void registerHooks(T receiverObject, Bosk bosk) throws InvalidTypeException { Class receiverClass = receiverObject.getClass(); + int hookCounter = 0; for (Method method: receiverClass.getDeclaredMethods()) { // TODO: Inherited methods - ReferencePath referencePath = method.getAnnotation(ReferencePath.class); - if (referencePath == null) { + Hook hookAnnotation = method.getAnnotation(Hook.class); + if (hookAnnotation == null) { continue; } - Path path = Path.parseParameterized(referencePath.value()); + if (isStatic(method.getModifiers())) { + throw new IllegalArgumentException("Hook method cannot be static: " + method); + } else if (isPrivate(method.getModifiers())) { + throw new IllegalArgumentException("Hook method cannot be private: " + method); + } + method.setAccessible(true); + Path path = Path.parseParameterized(hookAnnotation.value()); Reference scope = bosk.rootReference().then(Object.class, path); List, Object>> argumentFunctions = new ArrayList<>(method.getParameterCount()); argumentFunctions.add(ref -> receiverObject); // The "this" pointer @@ -30,12 +41,12 @@ static void registerHooks(T receiverObject, Bosk bosk) throws InvalidType if (ReferenceUtils.parameterType(p.getParameterizedType(), Reference.class, 0).equals(scope.targetType())) { argumentFunctions.add(ref -> ref); } else { - throw new InvalidTypeException("Expected reference to " + scope.targetType() + ": " + method.getName() + " parameter " + p.getName()); + throw new IllegalArgumentException("Expected reference to " + scope.targetType() + ": " + method.getName() + " parameter " + p.getName()); } } else if (p.getType().isAssignableFrom(BindingEnvironment.class)) { argumentFunctions.add(ref -> scope.parametersFrom(ref.path())); } else { - throw new InvalidTypeException("Unsupported parameter type " + p.getType() + ": " + method.getName() + " parameter " + p.getName()); + throw new IllegalArgumentException("Unsupported parameter type " + p.getType() + ": " + method.getName() + " parameter " + p.getName()); } } MethodHandle hook; @@ -53,7 +64,14 @@ static void registerHooks(T receiverObject, Bosk bosk) throws InvalidType throw new IllegalStateException("Unable to call hook \"" + method.getName() + "\"", e); } }); + hookCounter++; + } + if (hookCounter == 0) { + LOGGER.warn("Found no hook methods in {}; may be misconfigured", receiverObject.getClass().getSimpleName()); + } else { + LOGGER.info("Registered {} hook{} in {}", hookCounter, (hookCounter >= 2)? "s":"", receiverObject.getClass().getSimpleName()); } - } + + private static final Logger LOGGER = LoggerFactory.getLogger(HookRegistrar.class); } diff --git a/bosk-core/src/main/java/io/vena/bosk/annotations/Hook.java b/bosk-core/src/main/java/io/vena/bosk/annotations/Hook.java new file mode 100644 index 00000000..126f42bd --- /dev/null +++ b/bosk-core/src/main/java/io/vena/bosk/annotations/Hook.java @@ -0,0 +1,21 @@ +package io.vena.bosk.annotations; + +import io.vena.bosk.Bosk; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Marks a method to be registered as a hook + * for an object passed to {@link Bosk#registerHooks}. + */ +@Retention(RUNTIME) +@Target(METHOD) +public @interface Hook { + /** + * The scope of the hook for this method. + */ + String value(); +} diff --git a/bosk-core/src/test/java/io/vena/bosk/HooksTest.java b/bosk-core/src/test/java/io/vena/bosk/HooksTest.java index c76c0e3a..e122dc6c 100755 --- a/bosk-core/src/test/java/io/vena/bosk/HooksTest.java +++ b/bosk-core/src/test/java/io/vena/bosk/HooksTest.java @@ -1,6 +1,7 @@ package io.vena.bosk; import io.vena.bosk.HookRecorder.Event; +import io.vena.bosk.annotations.Hook; import io.vena.bosk.annotations.ReferencePath; import io.vena.bosk.exceptions.InvalidTypeException; import java.io.IOException; @@ -495,7 +496,7 @@ public HookReceiver(Bosk bosk) throws InvalidTypeException { bosk.registerHooks(this); } - @ReferencePath("/entities/parent/children/-child-/string") + @Hook("/entities/parent/children/-child-/string") void childStringChanged(Reference ref, BindingEnvironment env) { hookCalls.add(asList(ref, env, ref.valueIfExists())); } From b019305a95adfbf5f0057d04899e1249e4bdeaa3 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sat, 14 Oct 2023 13:25:55 -0400 Subject: [PATCH 2/3] Add hooks to example-hello --- .../main/java/io/vena/hello/HelloHooks.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 example-hello/src/main/java/io/vena/hello/HelloHooks.java diff --git a/example-hello/src/main/java/io/vena/hello/HelloHooks.java b/example-hello/src/main/java/io/vena/hello/HelloHooks.java new file mode 100755 index 00000000..a3fb1978 --- /dev/null +++ b/example-hello/src/main/java/io/vena/hello/HelloHooks.java @@ -0,0 +1,27 @@ +package io.vena.hello; + +import io.vena.bosk.Reference; +import io.vena.bosk.annotations.Hook; +import io.vena.bosk.exceptions.InvalidTypeException; +import io.vena.hello.state.Target; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class HelloHooks { + public HelloHooks(HelloBosk bosk) throws InvalidTypeException { + bosk.registerHooks(this); + } + + @Hook("/targets/-target-") + void targetChanged(Reference ref) { + if (ref.exists()) { + LOGGER.info("Target: {}", ref.value()); + } else { + LOGGER.info("Target removed: {}", ref); + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(HelloHooks.class); +} From e80d1e7f6da1389a899d6966a7a832ecf18c8bcc Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sat, 14 Oct 2023 13:48:01 -0400 Subject: [PATCH 3/3] Bump dependencies --- bosk-core/build.gradle | 2 +- bosk-gson/build.gradle | 2 +- bosk-jackson/build.gradle | 4 +-- bosk-mongo/build.gradle | 2 +- bosk-spring-boot-3/build.gradle | 6 ++--- bosk-testing/build.gradle | 10 +++---- .../src/main/groovy/bosk.development.gradle | 26 +++++++++---------- example-hello/build.gradle | 4 +-- lib-testing/build.gradle | 8 +++--- spotbugsExclude.xml | 6 +++++ 10 files changed, 38 insertions(+), 32 deletions(-) diff --git a/bosk-core/build.gradle b/bosk-core/build.gradle index 35b94e1f..e84a858a 100644 --- a/bosk-core/build.gradle +++ b/bosk-core/build.gradle @@ -3,7 +3,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' id 'info.solidsoft.pitest' version '1.7.4' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { diff --git a/bosk-gson/build.gradle b/bosk-gson/build.gradle index 087c77c1..29c6325f 100644 --- a/bosk-gson/build.gradle +++ b/bosk-gson/build.gradle @@ -2,7 +2,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { diff --git a/bosk-jackson/build.gradle b/bosk-jackson/build.gradle index 22302cf7..5a536ea2 100644 --- a/bosk-jackson/build.gradle +++ b/bosk-jackson/build.gradle @@ -2,7 +2,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { @@ -11,7 +11,7 @@ java { } dependencies { - api 'com.fasterxml.jackson.core:jackson-databind:2.15.1' + api 'com.fasterxml.jackson.core:jackson-databind:2.15.3' api project(":bosk-core") testImplementation project(":lib-testing") diff --git a/bosk-mongo/build.gradle b/bosk-mongo/build.gradle index baef2ec9..93e73c06 100644 --- a/bosk-mongo/build.gradle +++ b/bosk-mongo/build.gradle @@ -3,7 +3,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' id 'info.solidsoft.pitest' version '1.7.4' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } base { diff --git a/bosk-spring-boot-3/build.gradle b/bosk-spring-boot-3/build.gradle index fbf57cc8..595f9251 100644 --- a/bosk-spring-boot-3/build.gradle +++ b/bosk-spring-boot-3/build.gradle @@ -2,7 +2,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { @@ -12,8 +12,8 @@ java { dependencies { api project(":bosk-jackson") - implementation 'org.springframework.boot:spring-boot-starter-web:3.1.1' - annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:3.1.1" + implementation 'org.springframework.boot:spring-boot-starter-web:3.1.4' + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:3.1.4" testImplementation project(":bosk-testing") testImplementation project(":lib-testing") } diff --git a/bosk-testing/build.gradle b/bosk-testing/build.gradle index dc371f34..442a893d 100644 --- a/bosk-testing/build.gradle +++ b/bosk-testing/build.gradle @@ -2,7 +2,7 @@ plugins { id 'bosk.development' id 'bosk.maven-publish' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { @@ -18,10 +18,10 @@ dependencies { // but we need them as main dependencies. // It's gross that we need to repeat the version info here. // There must be a clean way to do this in Gradle?? - implementation 'org.mongodb:mongodb-driver-sync:4.1.2' - implementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' - implementation 'org.junit.jupiter:junit-jupiter-params:5.9.2' - runtimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + implementation 'org.mongodb:mongodb-driver-sync:4.11.0' + implementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + implementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' + runtimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' } // SpotBugs warnings on test code are not useful and often counterproductive diff --git a/buildSrc/src/main/groovy/bosk.development.gradle b/buildSrc/src/main/groovy/bosk.development.gradle index 70cb986f..399a10bf 100644 --- a/buildSrc/src/main/groovy/bosk.development.gradle +++ b/buildSrc/src/main/groovy/bosk.development.gradle @@ -49,27 +49,27 @@ dependencies { // Developer aids dependencies { - annotationProcessor "org.projectlombok:lombok:1.18.24" - compileOnly "org.projectlombok:lombok:1.18.24" - testAnnotationProcessor "org.projectlombok:lombok:1.18.24" - testCompileOnly "org.projectlombok:lombok:1.18.24" + annotationProcessor "org.projectlombok:lombok:1.18.30" + compileOnly "org.projectlombok:lombok:1.18.30" + testAnnotationProcessor "org.projectlombok:lombok:1.18.30" + testCompileOnly "org.projectlombok:lombok:1.18.30" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.1" - testImplementation "org.junit.jupiter:junit-jupiter-params:5.9.1" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.1" + testImplementation "org.junit.jupiter:junit-jupiter-api:5.10.0" + testImplementation "org.junit.jupiter:junit-jupiter-params:5.10.0" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.10.0" - testImplementation "org.testcontainers:testcontainers:1.17.6" - testImplementation "org.testcontainers:junit-jupiter:1.17.6" - testImplementation "org.testcontainers:toxiproxy:1.17.6" + testImplementation "org.testcontainers:testcontainers:1.19.1" + testImplementation "org.testcontainers:junit-jupiter:1.19.1" + testImplementation "org.testcontainers:toxiproxy:1.19.1" testImplementation "org.hamcrest:hamcrest:2.2" testImplementation "org.hamcrest:hamcrest-library:2.2" implementation "org.slf4j:slf4j-api:1.7.36" - testImplementation "ch.qos.logback:logback-classic:1.4.5" + testImplementation "ch.qos.logback:logback-classic:1.4.11" - testImplementation 'org.openjdk.jmh:jmh-core:1.36' - testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.36' + testImplementation 'org.openjdk.jmh:jmh-core:1.37' + testAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' } diff --git a/example-hello/build.gradle b/example-hello/build.gradle index 06676c6d..bb4f3f4e 100644 --- a/example-hello/build.gradle +++ b/example-hello/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.1.1' - id 'io.spring.dependency-management' version '1.1.0' + id 'org.springframework.boot' version '3.1.4' + id 'io.spring.dependency-management' version '1.1.3' } group = 'io.vena' diff --git a/lib-testing/build.gradle b/lib-testing/build.gradle index 62dda17c..817608aa 100644 --- a/lib-testing/build.gradle +++ b/lib-testing/build.gradle @@ -1,6 +1,6 @@ plugins { id 'bosk.development' - id 'com.github.spotbugs' version '5.1.3' + id 'com.github.spotbugs' version '5.1.5' } java { @@ -22,9 +22,9 @@ dependencies { // It's gross that we need to repeat the version info here. // There must be a clean way to do this in Gradle?? implementation 'org.mongodb:mongodb-driver-sync:4.1.2' - implementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' - implementation 'org.junit.jupiter:junit-jupiter-params:5.9.2' - runtimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + implementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + implementation 'org.junit.jupiter:junit-jupiter-params:5.10.0' + runtimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' } spotbugsMain.enabled = false // Spotbugs is counterproductive on test code diff --git a/spotbugsExclude.xml b/spotbugsExclude.xml index 0b19823a..148866e4 100644 --- a/spotbugsExclude.xml +++ b/spotbugsExclude.xml @@ -19,6 +19,12 @@ + + + + + +