001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.interceptor; 015 016import java.lang.System.Logger; 017 018import java.lang.annotation.Annotation; 019 020import java.lang.invoke.MethodHandle; 021import java.lang.invoke.MethodHandles.Lookup; 022import java.lang.invoke.MethodType; 023import java.lang.invoke.VarHandle; 024 025import java.lang.reflect.Constructor; 026import java.lang.reflect.Method; 027 028import java.util.Arrays; 029import java.util.Collection; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Optional; 035import java.util.Set; 036 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.ConcurrentMap; 039 040import java.util.function.BiFunction; 041import java.util.function.Consumer; 042import java.util.function.Supplier; 043 044import jakarta.interceptor.InvocationContext; 045 046import static java.lang.System.getLogger; 047import static java.lang.System.Logger.Level.DEBUG; 048 049import static java.lang.invoke.MethodHandles.lookup; 050import static java.lang.invoke.MethodHandles.privateLookupIn; 051 052/** 053 * A utility class that makes {@link InterceptionFunction}s and {@link Runnable}s that intercept lifecycle events, 054 * constructions, and invocations of methods in accordance with the Jakarta Interceptors specification. 055 * 056 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a> 057 */ 058public final class Interceptions { 059 060 061 /* 062 * Static fields. 063 */ 064 065 066 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 067 068 private static final Property<Object[]> EMPTY_OBJECT_ARRAY_PROPERTY = new Property<>(Interceptions::emptyObjectArray, false); 069 070 private static final Logger LOGGER = getLogger(Interceptions.class.getName()); 071 072 private static final Optional<Object[]> OPTIONAL_EMPTY_OBJECT_ARRAY = Optional.of(EMPTY_OBJECT_ARRAY); 073 074 private static final Lookup lookup = lookup(); 075 076 077 /* 078 * Instance fields. 079 */ 080 081 082 private final ConcurrentMap<String, Object> data; 083 084 private final Supplier<? extends Constructor<?>> constructorBootstrap; 085 086 private final Supplier<? extends Method> methodBootstrap; 087 088 private final Supplier<?> timerBootstrap; 089 090 private final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap; 091 092 093 /* 094 * Constructors. 095 */ 096 097 098 private Interceptions(final Supplier<? extends Constructor<?>> constructorBootstrap, 099 final Supplier<? extends Method> methodBootstrap, 100 final Supplier<?> timerBootstrap, 101 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) { 102 super(); 103 this.data = new ConcurrentHashMap<>(); 104 this.constructorBootstrap = constructorBootstrap == null ? Interceptions::returnNull : constructorBootstrap; 105 this.methodBootstrap = methodBootstrap == null ? Interceptions::returnNull : methodBootstrap; 106 this.timerBootstrap = timerBootstrap == null ? Interceptions::returnNull : timerBootstrap; 107 this.interceptorBindingsBootstrap = interceptorBindingsBootstrap == null ? Set::of : interceptorBindingsBootstrap; 108 } 109 110 111 /* 112 * Static methods. 113 */ 114 115 116 /** 117 * Returns a {@link Runnable} whose {@link Runnable#run() run()} method will invoke all supplied {@link 118 * InterceptorMethod}s in encounter order. 119 * 120 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} in which case the returned 121 * {@link Runnable}'s {@link Runnable#run() run()} method will do nothing 122 * 123 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 124 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 125 * null} 126 * 127 * @return a {@link Runnable}; never {@code null} 128 */ 129 // Methodless lifecycle event interception. For example, post-construct interceptions by external interceptors only. 130 public static final Runnable ofLifecycleEvent(final Collection<? extends InterceptorMethod> interceptorMethods, 131 final Supplier<?> targetBootstrap) { 132 return ofLifecycleEvent(interceptorMethods, targetBootstrap, Set::of); 133 } 134 135 /** 136 * Returns a {@link Runnable} whose {@link Runnable#run() run()} method will invoke all supplied {@link 137 * InterceptorMethod}s in encounter order. 138 * 139 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} in which case the returned 140 * {@link Runnable}'s {@link Runnable#run() run()} method will do nothing 141 * 142 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 143 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 144 * null} 145 * 146 * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called 147 * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be 148 * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>} 149 * 150 * @return a {@link Runnable}; never {@code null} 151 */ 152 // Methodless lifecycle event interception. For example, post-construct interceptions by external interceptors only. 153 public static final Runnable ofLifecycleEvent(final Collection<? extends InterceptorMethod> interceptorMethods, 154 final Supplier<?> targetBootstrap, 155 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) { 156 return () -> { 157 ff(interceptorMethods, 158 null, 159 targetBootstrap, 160 null, // argumentsValidator 161 false, // setTarget 162 null, // cb 163 null, // mb // TODO: may have to be the last im in the chain if it's defined on the target class (!); weird requirement 164 null, // tb 165 interceptorBindingsBootstrap) 166 .apply(EMPTY_OBJECT_ARRAY); 167 }; 168 } 169 170 /** 171 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 172 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 173 * Constructor}'s {@link Constructor#newInstance(Object...) newInstance(Object...)} method. 174 * 175 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 176 * 177 * @param constructor the {@link Constructor} to invoke; may be {@code null} (rather uselessly) 178 * 179 * @return an {@link InterceptionFunction}; never {@code null} 180 * 181 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 182 */ 183 // Around-construct 184 public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods, 185 final Constructor<?> constructor) 186 throws IllegalAccessException { 187 return ofConstruction(interceptorMethods, constructor, Set::of); 188 } 189 190 /** 191 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 192 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 193 * Constructor}'s {@link Constructor#newInstance(Object...) newInstance(Object...)} method. 194 * 195 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 196 * 197 * @param constructor the {@link Constructor} to invoke; may be {@code null} (rather uselessly) 198 * 199 * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called 200 * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be 201 * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>} 202 * 203 * @return an {@link InterceptionFunction}; never {@code null} 204 * 205 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 206 */ 207 // Around-construct 208 public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods, 209 final Constructor<?> constructor, 210 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) 211 throws IllegalAccessException { 212 return 213 ff(interceptorMethods, 214 terminalBiFunctionOf(constructor), 215 null, // targetBootstrap 216 a -> validate(constructor.getParameterTypes(), a), 217 true, // setTarget 218 () -> constructor, 219 null, // mb 220 null, // tb 221 interceptorBindingsBootstrap); 222 } 223 224 /** 225 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 226 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 227 * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object, Object[])} method with {@code null} (the return 228 * value of {@link InvocationContext#getTarget()}, which will always be {@code null} in this scenario) and the return 229 * value of an invocation of {@link InvocationContext#getParameters()}. 230 * 231 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 232 * 233 * @param terminalBiFunction a {@link BiFunction} serving as a notional constructor that takes {@code null}, always, 234 * as its first argument, and the return value of an invocation of {@link InvocationContext#getParameters()} as its 235 * second argument, and returns a new instance; may be {@code null} (rather uselessly) 236 * 237 * @return an {@link InterceptionFunction}; never {@code null} 238 */ 239 // Around-construct 240 public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods, 241 final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction) { 242 return ofConstruction(interceptorMethods, terminalBiFunction, Set::of); 243 } 244 245 /** 246 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 247 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 248 * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with {@code null} (the return 249 * value of {@link InvocationContext#getTarget()}, which will always be {@code null} in this scenario) and the return 250 * value of an invocation of {@link InvocationContext#getParameters()}. 251 * 252 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 253 * 254 * @param terminalBiFunction a {@link BiFunction} serving as a notional constructor that takes {@code null}, always, 255 * as its first argument, and the return value of an invocation of {@link InvocationContext#getParameters()} as its 256 * second argument, and returns a new instance; may be {@code null} (rather uselessly) 257 * 258 * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called 259 * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be 260 * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>} 261 * 262 * @return an {@link InterceptionFunction}; never {@code null} 263 */ 264 // Around-construct 265 public static final InterceptionFunction ofConstruction(final Collection<? extends InterceptorMethod> interceptorMethods, 266 final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction, 267 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) { 268 return 269 ff(interceptorMethods, 270 terminalBiFunction, 271 null, // targetBootstrap 272 null, // argumentsValidator 273 true, // setTarget 274 null, // cb 275 null, // mb 276 null, // tb 277 interceptorBindingsBootstrap); 278 } 279 280 /** 281 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 282 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 283 * Method}'s {@link Method#invoke(Object, Object...) invoke(Object, Object...)} method with the return value of {@link 284 * InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}. 285 * 286 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 287 * 288 * @param method a {@link Method} encapsulating the invocation to be intercepted whose {@link Method#invoke(Object, 289 * Object...) invoke(Object, Object...)} method takes the return value of {@link InvocationContext#getTarget()} as 290 * its first argument, and the return value of {@link InvocationContext#getParameters()} spread out appropriately as 291 * its trailing arguments; may be {@code null} (rather uselessly) 292 * 293 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 294 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 295 * null} 296 * 297 * @return an {@link InterceptionFunction}; never {@code null} 298 * 299 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 300 */ 301 // Around-invoke or similar. 302 public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods, 303 final Method method, // not nullable 304 final Supplier<?> targetBootstrap) 305 throws IllegalAccessException { 306 return ofInvocation(interceptorMethods, method, targetBootstrap, Set::of); 307 } 308 309 /** 310 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 311 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 312 * Method}'s {@link Method#invoke(Object, Object...) invoke(Object, Object...)} method with the return value of {@link 313 * InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}. 314 * 315 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 316 * 317 * @param method a {@link Method} encapsulating the invocation to be intercepted whose {@link Method#invoke(Object, 318 * Object...) invoke(Object, Object...)} method takes the return value of {@link InvocationContext#getTarget()} as 319 * its first argument, and the return value of {@link InvocationContext#getParameters()} spread out appropriately as 320 * its trailing arguments; may be {@code null} (rather uselessly) 321 * 322 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 323 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 324 * null} 325 * 326 * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called 327 * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be 328 * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>} 329 * 330 * @return an {@link InterceptionFunction}; never {@code null} 331 * 332 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 333 */ 334 // Around-invoke or similar. 335 public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods, 336 final Method method, // not nullable 337 final Supplier<?> targetBootstrap, 338 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) 339 throws IllegalAccessException { 340 return 341 ff(interceptorMethods, 342 method == null ? null : terminalBiFunctionOf(method), 343 targetBootstrap, 344 method == null ? null : a -> validate(method.getParameterTypes(), a), 345 false, // setTarget 346 null, // cb, 347 method == null ? null : () -> method, 348 null, // tb 349 interceptorBindingsBootstrap); 350 } 351 352 /** 353 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 354 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 355 * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with the return value of 356 * {@link InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}. 357 * 358 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 359 * 360 * @param terminalBiFunction a {@link BiFunction} encapsulating the invocation to be intercepted that takes the return 361 * value of {@link InvocationContext#getTarget()} as its first argument, and the return value of {@link 362 * InvocationContext#getParameters()} as its second argument; may be {@code null} (rather uselessly) 363 * 364 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 365 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 366 * null} 367 * 368 * @return an {@link InterceptionFunction}; never {@code null} 369 */ 370 // Around-invoke or similar. 371 public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods, 372 final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction, 373 final Supplier<?> targetBootstrap) { 374 return ofInvocation(interceptorMethods, terminalBiFunction, targetBootstrap, Set::of); 375 } 376 377 /** 378 * Returns an {@link InterceptionFunction} whose {@link InterceptionFunction#apply(Object...) apply(Object...)} method 379 * will invoke all supplied {@link InterceptorMethod}s in encounter order before invoking the supplied {@link 380 * BiFunction}'s {@link BiFunction#apply(Object, Object) apply(Object Object[])} method with the return value of 381 * {@link InvocationContext#getTarget()}, and with the return value of {@link InvocationContext#getParameters()}. 382 * 383 * @param interceptorMethods the {@link InterceptorMethod}s to invoke; may be {@code null} (rather uselessly) 384 * 385 * @param terminalBiFunction a {@link BiFunction} encapsulating the invocation to be intercepted that takes the return 386 * value of {@link InvocationContext#getTarget()} as its first argument, and the return value of {@link 387 * InvocationContext#getParameters()} as its second argument; may be {@code null} (rather uselessly) 388 * 389 * @param targetBootstrap a {@link Supplier} that will be called for the initial value to be returned by the first 390 * invocation of {@link InvocationContext#getTarget()}; may be {@code null} in which case the value too will be {@code 391 * null} 392 * 393 * @param interceptorBindingsBootstrap a {@link Supplier} of a {@link Set} of {@link Annotation}s that will be called 394 * for the value to be returned by the first invocation of {@link InvocationContext#getInterceptorBindings()}; may be 395 * {@code null} in which case the value will be an {@linkplain Set#of() empty, immutable <code>Set</code>} 396 * 397 * @return an {@link InterceptionFunction}; never {@code null} 398 */ 399 // Around-invoke or similar. 400 public static final InterceptionFunction ofInvocation(final Collection<? extends InterceptorMethod> interceptorMethods, 401 final BiFunction<? super Object, ? super Object[], ?> terminalBiFunction, 402 final Supplier<?> targetBootstrap, 403 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) { 404 return 405 ff(interceptorMethods, 406 terminalBiFunction, 407 targetBootstrap, 408 null, // argumentsValidator 409 false, // setTarget 410 null, // cb, 411 null, // mb, 412 null, // tb, 413 interceptorBindingsBootstrap); 414 } 415 416 // ff for function factory :-) 417 private static InterceptionFunction ff(final Collection<? extends InterceptorMethod> interceptorMethods, 418 final BiFunction<? super Object, ? super Object[], ?> tbf, 419 final Supplier<?> targetBootstrap, 420 final Consumer<? super Object[]> argumentsValidator, 421 final boolean setTarget, 422 final Supplier<? extends Constructor<?>> cb, 423 final Supplier<? extends Method> mb, 424 final Supplier<?> tb, 425 final Supplier<? extends Set<Annotation>> interceptorBindingsBootstrap) { 426 final Interceptions i; 427 final List<? extends InterceptorMethod> ims; 428 if (tbf == null) { 429 if (interceptorMethods == null || interceptorMethods.isEmpty()) { 430 return Interceptions::returnNull; 431 } 432 ims = List.copyOf(interceptorMethods); 433 // We can get away with hoisting this Property out of the function scope because we know it will never change. 434 final Property<Object> target = new Property<Object>(targetBootstrap, false); 435 i = new Interceptions(cb, mb, tb, interceptorBindingsBootstrap); 436 return a -> 437 i.new State(target) 438 .new Context(ims.iterator()) 439 .proceed(); 440 } else if (interceptorMethods == null || interceptorMethods.isEmpty()) { 441 final Property<Object> target = new Property<Object>(targetBootstrap, false); 442 return argumentsValidator == null ? Interceptions::returnNull : a -> { 443 argumentsValidator.accept(a); 444 return tbf.apply(target.get(), a); 445 }; 446 } else { 447 ims = List.copyOf(interceptorMethods); 448 i = new Interceptions(cb, mb, tb, interceptorBindingsBootstrap); 449 if (setTarget) { 450 return a -> { 451 final State s = i.new State(new Property<>(targetBootstrap, true), 452 new Property<>(a == null ? Interceptions::emptyObjectArray : () -> a, 453 argumentsValidator, 454 true)); 455 final State.Context c = s 456 .new Context(ims.iterator(), 457 (t, a2) -> { 458 s.target(tbf.apply(t, a2)); 459 return s.target(); 460 }); 461 final Object v = c.proceed(); 462 final Object t = c.getTarget(); 463 if (v != t && LOGGER.isLoggable(DEBUG)) { 464 LOGGER.log(DEBUG, "around-construct proceed() return value: " + v + "; returning getTarget() return value: " + t); 465 } 466 return t; 467 }; 468 } 469 // We can get away with hoisting this Property out of the function scope because we know it will never change. 470 final Property<Object> target = new Property<Object>(targetBootstrap, false); 471 return a -> { 472 return i.new State(target, new Property<Object[]>(a == null ? Interceptions::emptyObjectArray : () -> a, 473 argumentsValidator, 474 true)) 475 .new Context(ims.iterator(), tbf) 476 .proceed(); 477 }; 478 } 479 } 480 481 /** 482 * Creates and returns a {@link BiFunction} encapsulating the supplied {@link Constructor}. 483 * 484 * @param c a {@link Constructor}; must not be {@code null} 485 * 486 * @return a {@link BiFunction} encapsulating the supplied {@link Constructor}; never {@code null} 487 * 488 * @exception NullPointerException if {@code c} is {@code null} 489 * 490 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 491 */ 492 public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(final Constructor<?> c) throws IllegalAccessException { 493 return terminalBiFunctionOf(privateLookupIn(c.getDeclaringClass(), lookup).unreflectConstructor(c)); 494 } 495 496 /** 497 * Creates and returns a {@link BiFunction} encapsulating the supplied {@link Method}. 498 * 499 * @param m a {@link Method}; must not be {@code null} 500 * 501 * @return a {@link BiFunction} encapsulating the supplied {@link Method}; never {@code null} 502 * 503 * @exception NullPointerException if {@code m} is {@code null} 504 * 505 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 506 */ 507 public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(final Method m) throws IllegalAccessException { 508 return terminalBiFunctionOf(privateLookupIn(m.getDeclaringClass(), lookup).unreflect(m)); 509 } 510 511 /** 512 * Creates and returns a {@link BiFunction} encapsulating the supplied {@link MethodHandle}. 513 * 514 * @param mh a {@link MethodHandle}; must not be {@code null} 515 * 516 * @return a {@link BiFunction} encapsulating the supplied {@link MethodHandle}; never {@code null} 517 * 518 * @exception NullPointerException if {@code mh} is {@code null} 519 */ 520 public static final BiFunction<Object, Object[], Object> terminalBiFunctionOf(MethodHandle mh) { 521 mh = mh.asType(mh.type().changeReturnType(Object.class)); 522 final MethodType mt = mh.type(); 523 final int pc = mt.parameterCount(); 524 final MethodHandle terminalBiFunction; 525 if (pc == 0) { 526 terminalBiFunction = mh; 527 return (t, a) -> { 528 try { 529 return terminalBiFunction.invokeExact(); 530 } catch (final RuntimeException | Error e) { 531 throw e; 532 } catch (final InterruptedException e) { 533 Thread.currentThread().interrupt(); 534 throw new IllegalStateException(e.getMessage(), e); 535 } catch (final Throwable e) { 536 throw new IllegalStateException(e.getMessage(), e); 537 } 538 }; 539 } else if (pc == 1) { 540 if (mt.parameterType(0) == Object[].class) { 541 terminalBiFunction = mh; // no need to spread 542 return (t, a) -> { 543 try { 544 return terminalBiFunction.invokeExact(a); 545 } catch (final RuntimeException | Error e) { 546 throw e; 547 } catch (final InterruptedException e) { 548 Thread.currentThread().interrupt(); 549 throw new IllegalStateException(e.getMessage(), e); 550 } catch (final Throwable e) { 551 throw new IllegalStateException(e.getMessage(), e); 552 } 553 }; 554 } 555 terminalBiFunction = mh.asType(mt.changeParameterType(0, Object.class)); 556 return (t, a) -> { 557 try { 558 return terminalBiFunction.invokeExact(t); 559 } catch (final RuntimeException | Error e) { 560 throw e; 561 } catch (final InterruptedException e) { 562 Thread.currentThread().interrupt(); 563 throw new IllegalStateException(e.getMessage(), e); 564 } catch (final Throwable e) { 565 throw new IllegalStateException(e.getMessage(), e); 566 } 567 }; 568 } 569 terminalBiFunction = mh.asType((mt.changeParameterType(0, Object.class))).asSpreader(Object[].class, pc - 1); 570 return (t, a) -> { 571 try { 572 return terminalBiFunction.invokeExact(t, a); 573 } catch (final RuntimeException | Error e) { 574 throw e; 575 } catch (final InterruptedException e) { 576 Thread.currentThread().interrupt(); 577 throw new IllegalStateException(e.getMessage(), e); 578 } catch (final Throwable e) { 579 throw new IllegalStateException(e.getMessage(), e); 580 } 581 }; 582 } 583 584 /** 585 * A convenience method that ensures that every element of the supplied {@code arguments} array can be assigned to a 586 * reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array. 587 * 588 * <p>Boxing, unboxing and widening conversions are taken into consideration.</p> 589 * 590 * <p>This method implements the logic implied, but nowhere actually specified, by the contract of {@link 591 * InvocationContext#setParameters(Object[])}.</p> 592 * 593 * @param parameterTypes an array of {@link Class} instances; may be {@code null}; must not contain {@code null} 594 * elements or {@code void.class}; must have a length equal to that of the supplied {@code arguments} array 595 * 596 * @param arguments an array of {@link Object}s; may be {@code null}; must have a length equal to that of the supplied 597 * {@code parameterTypes} array 598 * 599 * @exception IllegalArgumentException if validation fails, i.e. if the length of {@code parameterTypes} is not equal 600 * to the length of {@code arguments}, or if an element of {@code parameterTypes} is {@code null} or {@code 601 * void.class}, or if not every element of the supplied {@code arguments} array can be assigned to a reference bearing 602 * the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array 603 */ 604 public static final void validate(final Class<?>[] parameterTypes, final Object[] arguments) { 605 final int parameterTypesLength = parameterTypes == null ? 0 : parameterTypes.length; 606 final int argumentsLength = arguments == null ? 0 : arguments.length; 607 if (argumentsLength != parameterTypesLength) { 608 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 609 "; arguments: " + Arrays.toString(arguments)); 610 } 611 for (int i = 0; i < argumentsLength; i++) { 612 final Class<?> parameterType = parameterTypes[i]; 613 if (parameterType == null || parameterType == void.class) { 614 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 615 "; arguments: " + Arrays.toString(arguments) + 616 "; parameter type: " + parameterType); 617 } 618 final Object argument = arguments[i]; 619 if (argument == null) { 620 if (parameterType.isPrimitive() || parameterType != Void.class) { 621 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 622 "; arguments: " + Arrays.toString(arguments) + 623 "; parameter type: " + parameterType.getName() + 624 "; argument: null"); 625 } 626 } else { 627 final Class<?> argumentType = argument.getClass(); 628 if (parameterType != argumentType) { 629 if (parameterType == boolean.class) { 630 if (argumentType != Boolean.class) { 631 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 632 "; arguments: " + Arrays.toString(arguments) + 633 "; parameter type: boolean" + 634 "; argument type: " + argumentType.getName()); 635 } 636 } else if (parameterType == Boolean.class) { 637 if (argumentType != boolean.class) { 638 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 639 "; arguments: " + Arrays.toString(arguments) + 640 "; parameter type: java.lang.Boolean" + 641 "; argument type: " + argumentType.getName()); 642 } 643 } else if (parameterType == byte.class) { 644 if (argumentType != Byte.class) { 645 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 646 "; arguments: " + Arrays.toString(arguments) + 647 "; parameter type: byte" + 648 "; argument type: " + argumentType.getName()); 649 } 650 } else if (parameterType == Byte.class) { 651 if (argumentType != byte.class) { 652 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 653 "; arguments: " + Arrays.toString(arguments) + 654 "; parameter type: java.lang.Byte" + 655 "; argument type: " + argumentType.getName()); 656 } 657 } else if (parameterType == char.class) { 658 if (argumentType != Character.class) { 659 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 660 "; arguments: " + Arrays.toString(arguments) + 661 "; parameter type: char" + 662 "; argument type: " + argumentType.getName()); 663 } 664 } else if (parameterType == Character.class) { 665 if (argumentType != char.class) { 666 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 667 "; arguments: " + Arrays.toString(arguments) + 668 "; parameter type: java.lang.Character" + 669 "; argument type: " + argumentType.getName()); 670 } 671 } else if (parameterType == double.class) { 672 if (argumentType != byte.class && 673 argumentType != char.class && 674 argumentType != Double.class && 675 argumentType != float.class && 676 argumentType != int.class && 677 argumentType != long.class && 678 argumentType != short.class) { 679 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 680 "; arguments: " + Arrays.toString(arguments) + 681 "; parameter type: double" + 682 "; argument type: " + argumentType.getName()); 683 } 684 } else if (parameterType == Double.class) { 685 if (argumentType != double.class) { 686 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 687 "; arguments: " + Arrays.toString(arguments) + 688 "; parameter type: java.lang.Double" + 689 "; argument type: " + argumentType.getName()); 690 } 691 } else if (parameterType == float.class) { 692 if (argumentType != byte.class && 693 argumentType != char.class && 694 argumentType != Float.class && 695 argumentType != int.class && 696 argumentType != long.class && 697 argumentType != short.class) { 698 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 699 "; arguments: " + Arrays.toString(arguments) + 700 "; parameter type: float" + 701 "; argument type: " + argumentType.getName()); 702 } 703 } else if (parameterType == Float.class) { 704 if (argumentType != float.class) { 705 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 706 "; arguments: " + Arrays.toString(arguments) + 707 "; parameter type: java.lang.Float" + 708 "; argument type: " + argumentType.getName()); 709 } 710 } else if (parameterType == int.class) { 711 if (argumentType != byte.class && 712 argumentType != char.class && 713 argumentType != Integer.class && 714 argumentType != short.class) { 715 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 716 "; arguments: " + Arrays.toString(arguments) + 717 "; parameter type: int; argument type: " + argumentType.getName()); 718 } 719 } else if (parameterType == Integer.class) { 720 if (argumentType != int.class) { 721 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 722 "; arguments: " + Arrays.toString(arguments) + 723 "; parameter type: java.lang.Integer" + 724 "; argument type: " + argumentType.getName()); 725 } 726 } else if (parameterType == long.class) { 727 if (argumentType != byte.class && 728 argumentType != char.class && 729 argumentType != int.class && 730 argumentType != Long.class && 731 argumentType != short.class) { 732 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 733 "; arguments: " + Arrays.toString(arguments) + 734 "; parameter type: long" + 735 "; argument type: " + argumentType.getName()); 736 } 737 } else if (parameterType == Long.class) { 738 if (argumentType != long.class) { 739 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 740 "; arguments: " + Arrays.toString(arguments) + 741 "; parameter type: java.lang.Long" + 742 "; argument type: " + argumentType.getName()); 743 } 744 } else if (parameterType == short.class) { 745 if (argumentType != byte.class && 746 argumentType != Short.class) { 747 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 748 "; arguments: " + Arrays.toString(arguments) + 749 "; parameter type: byte" + 750 "; argument type: " + argumentType.getName()); 751 } 752 } else if (parameterType == Short.class) { 753 if (argumentType != short.class) { 754 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 755 "; arguments: " + Arrays.toString(arguments) + 756 "; parameter type: java.lang.Short" + 757 "; argument type: " + argumentType.getName()); 758 } 759 } else if (parameterType == Void.class || !parameterType.isAssignableFrom(argumentType)) { 760 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 761 "; arguments: " + Arrays.toString(arguments) + 762 "; parameter type: " + parameterType.getName() + 763 "; argument type: " + argumentType.getName()); 764 } 765 } 766 } 767 } 768 } 769 770 private static final Object[] emptyObjectArray() { 771 return EMPTY_OBJECT_ARRAY; 772 } 773 774 private static final <T> T returnNull() { 775 return null; 776 } 777 778 private static final <T, U> T returnNull(final U ignored) { 779 return null; 780 } 781 782 private static final <T> void throwIllegalStateException(final T ignored) { 783 throw new IllegalStateException(); 784 785 } 786 787 788 /* 789 * Inner and nested classes. 790 */ 791 792 793 private final class State { 794 795 796 /* 797 * Instance fields. 798 */ 799 800 801 private final Property<Object> target; 802 803 private final Property<Object[]> arguments; 804 805 806 /* 807 * Constructors. 808 */ 809 810 811 private State(final Property<Object> target) { 812 this(target, null); 813 } 814 815 private State(final Property<Object> target, 816 final Property<Object[]> arguments) { 817 super(); 818 this.target = Objects.requireNonNull(target, "target"); 819 this.arguments = arguments == null ? EMPTY_OBJECT_ARRAY_PROPERTY : arguments; 820 } 821 822 823 /* 824 * Instance methods. 825 */ 826 827 828 private final Object target() { 829 return this.target.get(); 830 } 831 832 private final void target(final Object target) { 833 this.target.accept(target); 834 } 835 836 private final Object[] arguments() { 837 final Object[] a = this.arguments.get(); 838 return a == null ? EMPTY_OBJECT_ARRAY : a.clone(); 839 } 840 841 private final void arguments(final Object[] a) { 842 this.arguments.accept(a == null ? EMPTY_OBJECT_ARRAY : a.clone()); 843 } 844 845 846 /* 847 * Inner and nested classes. 848 */ 849 850 851 private final class Context implements InvocationContext { 852 853 854 /* 855 * Instance fields. 856 */ 857 858 859 private final Supplier<?> proceeder; 860 861 862 /* 863 * Constructors. 864 */ 865 866 867 private Context(final Iterator<? extends InterceptorMethod> iterator) { 868 this(iterator, (Supplier<?>)Interceptions::returnNull); 869 } 870 871 private Context(final Iterator<? extends InterceptorMethod> iterator, 872 final BiFunction<? super Object, ? super Object[], ?> tbf) { 873 this(iterator, () -> tbf.apply(target(), arguments())); 874 } 875 876 private Context(final Iterator<? extends InterceptorMethod> iterator, 877 final Supplier<?> f) { 878 super(); 879 if (iterator == null || !iterator.hasNext()) { 880 this.proceeder = f == null ? Interceptions::returnNull : f; 881 } else { 882 final InterceptorMethod im = Objects.requireNonNull(iterator.next()); 883 final Context c = new Context(iterator, f); 884 this.proceeder = () -> { 885 try { 886 return im.intercept(c); 887 } catch (final RuntimeException e) { 888 throw e; 889 } catch (final InterruptedException e) { 890 Thread.currentThread().interrupt(); 891 throw new InterceptorException(e.getMessage(), e); 892 } catch (final Exception e) { 893 throw new InterceptorException(e.getMessage(), e); 894 } 895 }; 896 } 897 } 898 899 900 /* 901 * Instance methods. 902 */ 903 904 905 @Override // InvocationContext 906 public final Constructor<?> getConstructor() { 907 return constructorBootstrap.get(); 908 } 909 910 @Override // InvocationContext 911 public final Map<String, Object> getContextData() { 912 return data; 913 } 914 915 @Override // InvocationContext 916 public final Set<Annotation> getInterceptorBindings() { 917 return interceptorBindingsBootstrap.get(); 918 } 919 920 @Override // InvocationContext 921 public final Method getMethod() { 922 return methodBootstrap.get(); 923 } 924 925 @Override // InvocationContext 926 public final Object[] getParameters() { 927 return arguments(); 928 } 929 930 @Override // InvocationContext 931 public final Object getTarget() { 932 return target(); 933 } 934 935 @Override // InvocationContext 936 public final Object getTimer() { 937 return timerBootstrap.get(); 938 } 939 940 @Override // InvocationContext 941 public final Object proceed() { 942 return this.proceeder.get(); 943 } 944 945 @Override // InvocationContext 946 public final void setParameters(final Object[] arguments) { 947 arguments(arguments); 948 } 949 950 } 951 952 } 953 954 private static final class Property<T> implements Consumer<T>, Supplier<T> { 955 956 957 /* 958 * Static fields. 959 */ 960 961 962 private static final VarHandle VALUE; 963 964 static { 965 try { 966 // Bizarrely, we cannot use the lookup static field belonging to the Interceptions class, at least in JDK 967 // 11 (NullPointerException (!)). Compiler bug? 968 VALUE = lookup().findVarHandle(Property.class, "value", Optional.class); 969 } catch (final IllegalAccessException | NoSuchFieldException e) { 970 throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e); 971 } 972 } 973 974 975 /* 976 * Instance fields. 977 */ 978 979 980 // set only through/using VALUE 981 private volatile Optional<T> value; 982 983 private final Supplier<T> reader; 984 985 private final Consumer<? super T> writer; 986 987 988 /* 989 * Constructors. 990 */ 991 992 993 private Property(final Supplier<? extends T> bootstrap, final boolean mutable) { 994 this(bootstrap, null, mutable); 995 } 996 997 private Property(final Supplier<? extends T> bootstrap, final Consumer<? super T> validator, final boolean mutable) { 998 super(); 999 if (bootstrap == null) { 1000 if (mutable) { 1001 VALUE.set(this, Optional.empty()); // no need for volatile semantics here 1002 this.reader = () -> ((Optional<T>)VALUE.getVolatile(this)).orElse(null); 1003 if (validator == null) { 1004 this.writer = v -> { 1005 VALUE.setVolatile(this, Optional.ofNullable(v)); 1006 }; 1007 } else { 1008 validator.accept(null); // make sure the Optional.empty() assignment above was OK 1009 this.writer = v -> { 1010 validator.accept(v); 1011 VALUE.setVolatile(this, Optional.ofNullable(v)); 1012 }; 1013 } 1014 } else { 1015 this.reader = Interceptions::returnNull; 1016 this.writer = Interceptions::throwIllegalStateException; 1017 } 1018 } else if (mutable) { 1019 if (validator == null) { 1020 this.reader = () -> { 1021 Optional<T> o = (Optional<T>)VALUE.getVolatile(this); 1022 if (o == null) { 1023 o = Optional.ofNullable(bootstrap.get()); 1024 if (!VALUE.compareAndSet(this, null, o)) { 1025 o = (Optional<T>)VALUE.getVolatile(this); 1026 } 1027 } 1028 return o.orElse(null); 1029 }; 1030 this.writer = v -> { 1031 VALUE.setVolatile(this, Optional.ofNullable(v)); 1032 }; 1033 } else { 1034 this.reader = () -> { 1035 Optional<T> o = (Optional<T>)VALUE.getVolatile(this); 1036 if (o == null) { 1037 final T v = bootstrap.get(); 1038 validator.accept(v); 1039 o = Optional.ofNullable(v); 1040 if (!VALUE.compareAndSet(this, null, o)) { 1041 o = (Optional<T>)VALUE.getVolatile(this); 1042 } 1043 } 1044 return o.orElse(null); 1045 }; 1046 this.writer = v -> { 1047 validator.accept(v); 1048 VALUE.setVolatile(this, Optional.ofNullable(v)); 1049 }; 1050 } 1051 } else { // !mutable 1052 this.writer = Interceptions::throwIllegalStateException; 1053 if (validator == null) { 1054 this.reader = () -> { 1055 Optional<T> o = (Optional<T>)VALUE.getVolatile(this); 1056 if (o == null) { 1057 o = Optional.ofNullable(bootstrap.get()); 1058 if (!VALUE.compareAndSet(this, null, o)) { 1059 o = (Optional<T>)VALUE.getVolatile(this); 1060 } 1061 } 1062 return o.orElse(null); 1063 }; 1064 } else { 1065 this.reader = () -> { 1066 Optional<T> o = (Optional<T>)VALUE.getVolatile(this); 1067 if (o == null) { 1068 final T v = bootstrap.get(); 1069 validator.accept(v); 1070 o = Optional.ofNullable(v); 1071 if (!VALUE.compareAndSet(this, null, o)) { 1072 o = (Optional<T>)VALUE.getVolatile(this); 1073 } 1074 } 1075 return o.orElse(null); 1076 }; 1077 } 1078 } 1079 } 1080 1081 1082 /* 1083 * Instance methods. 1084 */ 1085 1086 1087 @Override // Supplier<T> 1088 public final T get() { 1089 return this.reader.get(); 1090 } 1091 1092 @Override // Consumer<T> 1093 public final void accept(final T value) { 1094 this.writer.accept(value); 1095 } 1096 1097 @Override 1098 public final String toString() { 1099 return String.valueOf(this.get()); 1100 } 1101 1102 } 1103 1104}