Skip to content

Commit

Permalink
Corrects Chain API to accept a supplier of arguments rather than the …
Browse files Browse the repository at this point in the history
…arguments directly as argument creation may be prohibitively expensive (#11)

Signed-off-by: Laird Nelson <[email protected]>
  • Loading branch information
ljnelson authored Feb 10, 2024
1 parent c1d5ab4 commit f6c2160
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Maven dependency:
<groupId>org.microbean</groupId>
<artifactId>microbean-interceptor</artifactId>
<!-- Always check https://search.maven.org/artifact/org.microbean/microbean-interceptor for up-to-date available versions. -->
<version>0.2.2</version>
<version>0.2.3</version>
</dependency>
```

Expand Down
90 changes: 70 additions & 20 deletions src/main/java/org/microbean/interceptor/Chain.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2022–2023 microBean™.
* Copyright © 2022–2024 microBean™.
*
* 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
Expand All @@ -16,6 +16,7 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
Expand All @@ -41,16 +42,41 @@
import static org.microbean.interceptor.LowLevelOperation.invokeUnchecked;

/**
* A {@link Callable} {@link InvocationContext} implementation.
* A {@link Callable} {@link InvocationContext} implementation representing the interception of a constructor, method,
* or lifecycle event.
*
* @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a>
*
* @see #proceed()
*/
public class Chain implements Callable<Object>, InvocationContext {


/*
* Static fields.
*/


private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

private static final Lookup lookup = lookup();

private static final VarHandle ARGUMENTS;

static {
try {
ARGUMENTS = lookup.findVarHandle(Chain.class, "arguments", Object[].class);
} catch (final IllegalAccessException | NoSuchFieldException e) {
throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e);
}
}


/*
* Instance fields.
*/


private final ConcurrentMap<String, Object> contextData;

private final Supplier<? extends Constructor<?>> constructorSupplier;
Expand All @@ -65,8 +91,16 @@ public class Chain implements Callable<Object>, InvocationContext {

private final Supplier<?> proceedImplementation;

private final Supplier<? extends Object[]> argumentsSupplier;

private volatile Object[] arguments;


/*
* Constructors.
*/


/**
* Creates a new {@link Chain} primarily for testing purposes.
*
Expand Down Expand Up @@ -97,6 +131,7 @@ public Chain() {
this.targetReference = new AtomicReference<>();
this.targetSupplier = Chain::returnNull;
this.proceedImplementation = Chain::returnNull;
this.argumentsSupplier = Chain::emptyObjectArray;
this.arguments = EMPTY_OBJECT_ARRAY;
}

Expand Down Expand Up @@ -129,7 +164,7 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
Chain::returnNull, // constructor supplier
Chain::returnNull, // method supplier
targetSupplier,
EMPTY_OBJECT_ARRAY,
Chain::emptyObjectArray, // arguments supplier
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand Down Expand Up @@ -164,7 +199,7 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
() -> terminalConstructor,
Chain::returnNull, // method supplier
Chain::returnNull, // targetSupplier (initial target supplier)
EMPTY_OBJECT_ARRAY,
Chain::emptyObjectArray, // arguments supplier
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand All @@ -186,21 +221,22 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
*
* @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null}
*
* @param arguments the arguments to supply to the {@link Constructor}; may be {@code null}
* @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Constructor}; may be {@code
* null}
*
* @exception NullPointerException if {@code terminalConstructor} is {@code null}
*/
public Chain(final List<? extends InterceptorMethod> interceptorMethods,
final Constructor<?> terminalConstructor,
final Object[] arguments) {
final Supplier<? extends Object[]> argumentsSupplier) {
this(interceptorMethods,
terminalFunctionOf(terminalConstructor),
true, // set target
new ConcurrentHashMap<>(),
() -> terminalConstructor,
Chain::returnNull, // method supplier
Chain::returnNull, // targetSupplier (initial target supplier)
arguments,
argumentsSupplier,
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand Down Expand Up @@ -236,7 +272,7 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
Chain::returnNull, // constructor supplier
() -> terminalMethod,
targetSupplier,
EMPTY_OBJECT_ARRAY,
Chain::emptyObjectArray, // arguments supplier
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand All @@ -260,22 +296,22 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
*
* @param terminalMethod the {@link Method} to intercept; must not be {@code null}
*
* @param arguments the arguments to supply to the {@link Method}; may be {@code null}
* @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Method}; may be {@code null}
*
* @exception NullPointerException if {@code terminalMethod} is {@code null}
*/
public Chain(final List<? extends InterceptorMethod> interceptorMethods,
final Supplier<?> targetSupplier,
final Method terminalMethod,
final Object[] arguments) {
final Supplier<? extends Object[]> argumentsSupplier) {
this(interceptorMethods,
terminalFunctionOf(terminalMethod, targetSupplier),
false, // don't set target
new ConcurrentHashMap<>(),
Chain::returnNull, // constructor supplier
() -> terminalMethod,
targetSupplier,
arguments,
argumentsSupplier,
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand Down Expand Up @@ -303,21 +339,22 @@ public Chain(final List<? extends InterceptorMethod> interceptorMethods,
*
* @param setTarget whether the supplied {@code terminalFunction} is effectively a constructor
*
* @param arguments the arguments to supply to the terminal {@link Function}; may be {@code null}
* @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be
* {@code null}
*/
public Chain(final List<? extends InterceptorMethod> interceptorMethods,
final Supplier<?> targetSupplier,
final Function<? super Object[], ?> terminalFunction,
final boolean setTarget, // is the terminal function effectively a constructor?
final Object[] arguments) {
final Supplier<? extends Object[]> argumentsSupplier) {
this(interceptorMethods,
terminalFunction,
setTarget,
new ConcurrentHashMap<>(),
Chain::returnNull, // constructor supplier
Chain::returnNull, // method supplier
targetSupplier,
arguments,
argumentsSupplier,
Chain::returnNull, // timer supplier
new AtomicReference<>());
}
Expand All @@ -329,14 +366,14 @@ private Chain(List<? extends InterceptorMethod> interceptorMethods,
final Supplier<? extends Constructor<?>> constructorSupplier,
final Supplier<? extends Method> methodSupplier,
final Supplier<?> targetSupplier,
final Object[] arguments,
final Supplier<? extends Object[]> argumentsSupplier,
final Supplier<?> timerSupplier,
final AtomicReference<Object> targetReference) {
super();
this.contextData = contextData == null ? new ConcurrentHashMap<>() : contextData;
this.constructorSupplier = constructorSupplier == null ? Chain::returnNull : constructorSupplier;
this.methodSupplier = methodSupplier == null ? Chain::returnNull : methodSupplier;
this.arguments = arguments == null ? EMPTY_OBJECT_ARRAY : arguments;
this.argumentsSupplier = argumentsSupplier == null ? Chain::emptyObjectArray : argumentsSupplier;
this.timerSupplier = timerSupplier == null ? Chain::returnNull : timerSupplier;
this.targetReference = targetReference == null ? new AtomicReference<>() : targetReference;
this.targetSupplier = targetSupplier == null ? Chain::returnNull : targetSupplier;
Expand All @@ -361,7 +398,7 @@ private Chain(List<? extends InterceptorMethod> interceptorMethods,
this::getConstructor,
this::getMethod,
this.targetSupplier,
this.arguments,
this.argumentsSupplier,
this::getTimer,
this.targetReference));
} catch (final RuntimeException | Error e) {
Expand All @@ -376,6 +413,12 @@ private Chain(List<? extends InterceptorMethod> interceptorMethods,
}
}


/*
* Instance methods.
*/


/**
* Returns the {@link Constructor} being intercepted, if available, or {@code null}.
*
Expand Down Expand Up @@ -415,7 +458,10 @@ public final Method getMethod() {
@Override
public final Object[] getParameters() {
// Cloning etc. is not necessary; this whole API is stupid
return this.arguments; // volatile read
if (this.arguments == null) { // volatile read
ARGUMENTS.compareAndSet(this, null, this.argumentsSupplier.get()); // volatile write
}
return this.arguments; // volatile read but at this point it doesn't really matter
}

/**
Expand Down Expand Up @@ -471,8 +517,8 @@ public final Object call() throws Exception {
}

/**
* Applies the next interception in this {@link Chain}, or calls the terminal function and returns the result, which
* may be {@code null}.
* Applies the next interception in this {@link Chain}, or calls the terminal function, and returns the result of the
* interception, which may be {@code null}.
*
* @return the result of proceeding, which may be {@code null}
*
Expand Down Expand Up @@ -630,4 +676,8 @@ private static final <T> T returnNull(final Object[] ignored) {
return null;
}

private static final Object[] emptyObjectArray() {
return EMPTY_OBJECT_ARRAY;
}

}
12 changes: 6 additions & 6 deletions src/test/java/org/microbean/interceptor/TestChain.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2022–2023 microBean™.
* Copyright © 2022–2024 microBean™.
*
* 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
Expand Down Expand Up @@ -127,7 +127,7 @@ final void testAroundInvokeOnFrobnicate() throws Exception {
@Test
final void testUninterceptedAdd() throws Exception {
final Method add = Frobnicator.class.getDeclaredMethod("add", int.class, int.class);
final Chain chain = new Chain(List.of(), Frobnicator::new, add, new Object[] { 1, 2 });
final Chain chain = new Chain(List.of(), Frobnicator::new, add, () -> new Object[] { 1, 2 });
assertSame(add, chain.getMethod());
assertEquals(Integer.valueOf(3), chain.call());
assertFalse(construct);
Expand All @@ -142,7 +142,7 @@ final void testAroundInvokeOnAdd() throws Exception {
final List<InterceptorMethod> ims =
List.of(InterceptorMethod.of(this.getClass().getDeclaredMethod("aroundInvoke", InvocationContext.class),
this::returnThis));
final Chain chain = new Chain(ims, Frobnicator::new, add, new Object[] { 1, 2 });
final Chain chain = new Chain(ims, Frobnicator::new, add, () -> new Object[] { 1, 2 });
assertNotNull(chain.getTarget());
assertSame(add, chain.getMethod());
final Object result = chain.call();
Expand All @@ -159,7 +159,7 @@ final void testAroundInvokeOnRuminate() throws Exception {
final List<InterceptorMethod> ims =
List.of(InterceptorMethod.of(this.getClass().getDeclaredMethod("aroundInvoke", InvocationContext.class),
this::returnThis));
final Chain chain = new Chain(ims, Frobnicator::new, ruminate, new Object[] { 1, 2 });
final Chain chain = new Chain(ims, Frobnicator::new, ruminate, () -> new Object[] { 1, 2 });
assertNotNull(chain.getTarget());
assertSame(ruminate, chain.getMethod());
final Object result = chain.call();
Expand All @@ -176,7 +176,7 @@ final void testAroundInvokeOnVoidLambdaize() throws Exception {
final List<InterceptorMethod> ims =
List.of(InterceptorMethod.of(this.getClass().getDeclaredMethod("aroundInvoke", InvocationContext.class),
this::returnThis));
final Chain chain = new Chain(ims, Frobnicator::new, voidLambdaize, new Object[] { 1, 2 });
final Chain chain = new Chain(ims, Frobnicator::new, voidLambdaize, () -> new Object[] { 1, 2 });
assertNotNull(chain.getTarget());
assertSame(voidLambdaize, chain.getMethod());
final Object result = chain.call();
Expand All @@ -193,7 +193,7 @@ final void testAroundInvokeOnLambdaize() throws Exception {
final List<InterceptorMethod> ims =
List.of(InterceptorMethod.of(this.getClass().getDeclaredMethod("aroundInvoke", InvocationContext.class),
this::returnThis));
final Chain chain = new Chain(ims, Frobnicator::new, lambdaize, new Object[] { 1, 2 });
final Chain chain = new Chain(ims, Frobnicator::new, lambdaize, () -> new Object[] { 1, 2 });
assertNotNull(chain.getTarget());
assertSame(lambdaize, chain.getMethod());
final Object result = chain.call();
Expand Down

0 comments on commit f6c2160

Please sign in to comment.