Skip to content

Commit

Permalink
@hook annotation (venasolutions#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
prdoyle authored Oct 14, 2023
2 parents 5566f0b + e80d1e7 commit 1e42dba
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 41 deletions.
2 changes: 1 addition & 1 deletion bosk-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
34 changes: 26 additions & 8 deletions bosk-core/src/main/java/io/vena/bosk/HookRegistrar.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 <T> 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<Object> scope = bosk.rootReference().then(Object.class, path);
List<Function<Reference<?>, Object>> argumentFunctions = new ArrayList<>(method.getParameterCount());
argumentFunctions.add(ref -> receiverObject); // The "this" pointer
Expand All @@ -30,12 +41,12 @@ static <T> 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;
Expand All @@ -53,7 +64,14 @@ static <T> 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);
}
21 changes: 21 additions & 0 deletions bosk-core/src/main/java/io/vena/bosk/annotations/Hook.java
Original file line number Diff line number Diff line change
@@ -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();
}
3 changes: 2 additions & 1 deletion bosk-core/src/test/java/io/vena/bosk/HooksTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String> ref, BindingEnvironment env) {
hookCalls.add(asList(ref, env, ref.valueIfExists()));
}
Expand Down
2 changes: 1 addition & 1 deletion bosk-gson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions bosk-jackson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion bosk-mongo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions bosk-spring-boot-3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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")
}
Expand Down
10 changes: 5 additions & 5 deletions bosk-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
26 changes: 13 additions & 13 deletions buildSrc/src/main/groovy/bosk.development.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'

}

Expand Down
4 changes: 2 additions & 2 deletions example-hello/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
27 changes: 27 additions & 0 deletions example-hello/src/main/java/io/vena/hello/HelloHooks.java
Original file line number Diff line number Diff line change
@@ -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<Target> ref) {
if (ref.exists()) {
LOGGER.info("Target: {}", ref.value());
} else {
LOGGER.info("Target removed: {}", ref);
}
}

private static final Logger LOGGER = LoggerFactory.getLogger(HelloHooks.class);
}
8 changes: 4 additions & 4 deletions lib-testing/build.gradle
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
6 changes: 6 additions & 0 deletions spotbugsExclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
<Match>
<Bug code="DP"/> <!-- doPrivileged is deprecated for removal -->
</Match>
<Match>
<Bug code="CT"/> <!-- Finalizers are deprecated for removal -->
</Match>
<Match>
<Bug code="PI"/> <!-- Some of our type names match those of the Java Standard Library -->
</Match>
<Match>
<Class name="io.vena.bosk.Path"/>
<Method name="empty"/>
Expand Down

0 comments on commit 1e42dba

Please sign in to comment.