001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022–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.invoke.MethodHandle; 017import java.lang.invoke.MethodHandles.Lookup; 018import java.lang.invoke.MethodType; 019import java.lang.invoke.VarHandle; 020 021import java.lang.reflect.Constructor; 022import java.lang.reflect.Method; 023 024import java.util.Arrays; 025import java.util.List; 026import java.util.Map; 027import java.util.Objects; 028 029import java.util.concurrent.Callable; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.concurrent.ConcurrentMap; 032 033import java.util.function.Consumer; 034import java.util.function.Function; 035import java.util.function.Supplier; 036 037import jakarta.interceptor.InvocationContext; 038 039import static java.lang.invoke.MethodHandles.lookup; 040import static java.lang.invoke.MethodHandles.privateLookupIn; 041 042import static org.microbean.interceptor.LowLevelOperation.invokeUnchecked; 043 044/** 045 * A {@link Callable} {@link InvocationContext} implementation representing the interception of a constructor, method, 046 * or lifecycle event. 047 * 048 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a> 049 * 050 * @see #proceed() 051 * 052 * @deprecated See {@link Interceptions}. 053 */ 054@Deprecated(forRemoval = true) 055public class Chain implements Callable<Object>, InvocationContext { 056 057 058 /* 059 * Static fields. 060 */ 061 062 063 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 064 065 private static final Lookup lookup = lookup(); 066 067 private static final VarHandle ARGUMENTS; 068 069 private static final VarHandle TARGET; 070 071 static { 072 try { 073 ARGUMENTS = lookup.findVarHandle(Chain.class, "arguments", Object[].class); 074 TARGET = lookup.findVarHandle(Chain.class, "target", Object.class); 075 } catch (final IllegalAccessException | NoSuchFieldException e) { 076 throw (ExceptionInInitializerError)new ExceptionInInitializerError(e.getMessage()).initCause(e); 077 } 078 } 079 080 081 /* 082 * Instance fields. 083 */ 084 085 086 private final ConcurrentMap<String, Object> contextData; 087 088 private final Supplier<? extends Constructor<?>> constructorSupplier; 089 090 private final Supplier<? extends Method> methodSupplier; 091 092 private final Supplier<?> timerSupplier; 093 094 private final Supplier<?> targetSupplier; 095 096 private final Supplier<? extends Object[]> argumentsSupplier; 097 098 private final Consumer<? super Object[]> setParametersImplementation; 099 100 private final Callable<?> proceedImplementation; 101 102 private final Chain chain; 103 104 private volatile Object[] arguments; 105 106 private volatile Object target; 107 108 109 /* 110 * Constructors. 111 */ 112 113 /* 114 * Lifecycle method/callback. 115 */ 116 117 /** 118 * Creates a new {@link Chain} primarily for testing purposes. 119 * 120 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 121 * 122 * <ul> 123 * 124 * <li>{@link #call()}</li> 125 * 126 * <li>{@link #getConstructor()}</li> 127 * 128 * <li>{@link #getMethod()}</li> 129 * 130 * <li>{@link #getTimer()}</li> 131 * 132 * <li>{@link #getTarget()}</li> 133 * 134 * <li>{@link #proceed()}</li> 135 * 136 * </ul> 137 */ 138 public Chain() { 139 super(); 140 this.contextData = new ConcurrentHashMap<>(); 141 this.constructorSupplier = Chain::returnNull; 142 this.methodSupplier = Chain::returnNull; 143 this.timerSupplier = Chain::returnNull; 144 this.targetSupplier = Chain::returnNull; 145 this.chain = this; 146 this.proceedImplementation = Chain::returnNull; 147 this.argumentsSupplier = Chain::emptyObjectArray; 148 this.setParametersImplementation = Chain::throwIllegalStateException; 149 this.arguments = EMPTY_OBJECT_ARRAY; 150 } 151 152 /** 153 * Creates a new {@link Chain} for lifecycle method interceptions. 154 * 155 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 156 * 157 * <ul> 158 * 159 * <li>{@link #getConstructor()}</li> 160 * 161 * <li>{@link #getMethod()}</li> 162 * 163 * <li>{@link #getTimer()}</li> 164 * 165 * </ul> 166 * 167 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 168 * 169 * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} 170 */ 171 // For lifecycle events like PostConstruct, PreDestroy; no terminal function in these cases 172 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 173 final Supplier<?> targetSupplier) { 174 this(interceptorMethods, 175 null, // terminal function; null on purpose 176 false, // set target 177 new ConcurrentHashMap<>(), 178 Chain::returnNull, // constructor supplier 179 Chain::returnNull, // method supplier 180 targetSupplier, 181 Chain::emptyObjectArray, // arguments supplier 182 Chain::sink, // arguments validator 183 Chain::returnNull, // timer supplier 184 null); // chain 185 } 186 187 /* 188 * Around-construct. 189 */ 190 191 /** 192 * Creates a new {@link Chain} for around-construct interceptions. 193 * 194 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 195 * 196 * <ul> 197 * 198 * <li>{@link #getMethod()}</li> 199 * 200 * <li>{@link #getTarget()}</li> 201 * 202 * <li>{@link #getTimer()}</li> 203 * 204 * </ul> 205 * 206 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 207 * 208 * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null} 209 * 210 * @exception NullPointerException if {@code terminalConstructor} is {@code null} 211 * 212 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 213 */ 214 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 215 final Constructor<?> terminalConstructor) 216 throws IllegalAccessException { 217 this(interceptorMethods, 218 terminalFunctionOf(terminalConstructor), 219 true, // set target 220 new ConcurrentHashMap<>(), 221 () -> terminalConstructor, 222 Chain::returnNull, // method supplier 223 Chain::returnNull, // targetSupplier (initial target supplier) 224 Chain::emptyObjectArray, // arguments supplier 225 args -> validate(terminalConstructor.getParameterTypes(), args), 226 Chain::returnNull, // timer supplier 227 null); // chain 228 } 229 230 /** 231 * Creates a new {@link Chain} for around-construct interceptions. 232 * 233 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 234 * 235 * <ul> 236 * 237 * <li>{@link #getMethod()}</li> 238 * 239 * <li>{@link #getTimer()}</li> 240 * 241 * </ul> 242 * 243 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 244 * 245 * @param terminalConstructor the {@link Constructor} being intercepted; must not be {@code null} 246 * 247 * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Constructor}; may be {@code 248 * null} 249 * 250 * @exception NullPointerException if {@code terminalConstructor} is {@code null} 251 * 252 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 253 */ 254 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 255 final Constructor<?> terminalConstructor, 256 final Supplier<? extends Object[]> argumentsSupplier) 257 throws IllegalAccessException { 258 this(interceptorMethods, 259 terminalFunctionOf(terminalConstructor), 260 true, // set target 261 new ConcurrentHashMap<>(), 262 () -> terminalConstructor, 263 Chain::returnNull, // method supplier 264 Chain::returnNull, // targetSupplier (initial target supplier) 265 argumentsSupplier, 266 args -> validate(terminalConstructor.getParameterTypes(), args), 267 Chain::returnNull, // timer supplier 268 null); // chain 269 } 270 271 /** 272 * Creates a new {@link Chain} for around-construct interceptions. 273 * 274 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 275 * 276 * <ul> 277 * 278 * <li>{@link #getConstructor()}</li> 279 * 280 * <li>{@link #getMethod()}</li> 281 * 282 * <li>{@link #getTimer()}</li> 283 * 284 * </ul> 285 * 286 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 287 * 288 * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null} 289 * 290 * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be 291 * {@code null} 292 * 293 * @exception NullPointerException if {@code terminalFunction} is {@code null} 294 */ 295 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 296 final Function<? super Object[], ?> terminalFunction, 297 final Supplier<? extends Object[]> argumentsSupplier) { 298 this(interceptorMethods, 299 Objects.requireNonNull(terminalFunction, "terminalFunction"), 300 true, // set target 301 new ConcurrentHashMap<>(), 302 Chain::returnNull, // constructor supplier 303 Chain::returnNull, // method supplier 304 Chain::returnNull, // target supplier (won't ever be consulted/invoked; target is set by terminalFunction directly) 305 argumentsSupplier, 306 Chain::sink, // arguments validator 307 Chain::returnNull, // timer supplier 308 null); // chain 309 } 310 311 /* 312 * Around-invoke. 313 */ 314 315 /** 316 * Creates a new {@link Chain} for around-invoke interceptions. 317 * 318 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 319 * 320 * <ul> 321 * 322 * <li>{@link #getConstructor()}</li> 323 * 324 * <li>{@link #getTimer()}</li> 325 * 326 * </ul> 327 * 328 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 329 * 330 * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} 331 * 332 * @param terminalMethod the {@link Method} to intercept; must not be {@code null} 333 * 334 * @exception NullPointerException if {@code terminalMethod} is {@code null} 335 * 336 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 337 */ 338 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 339 final Supplier<?> targetSupplier, 340 final Method terminalMethod) 341 throws IllegalAccessException { 342 this(interceptorMethods, 343 terminalFunctionOf(terminalMethod, targetSupplier), 344 false, // don't set target 345 new ConcurrentHashMap<>(), 346 Chain::returnNull, // constructor supplier 347 () -> terminalMethod, 348 targetSupplier, 349 Chain::emptyObjectArray, // arguments supplier 350 args -> validate(terminalMethod.getParameterTypes(), args), 351 Chain::returnNull, // timer supplier 352 null); // chain 353 } 354 355 /** 356 * Creates a new {@link Chain} for around-invoke interceptions. 357 * 358 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 359 * 360 * <ul> 361 * 362 * <li>{@link #getConstructor()}</li> 363 * 364 * <li>{@link #getTimer()}</li> 365 * 366 * </ul> 367 * 368 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 369 * 370 * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} 371 * 372 * @param terminalMethod the {@link Method} to intercept; must not be {@code null} 373 * 374 * @param argumentsSupplier a {@link Supplier} supplying the arguments for the {@link Method}; may be {@code null} 375 * 376 * @exception NullPointerException if {@code terminalMethod} is {@code null} 377 * 378 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 379 */ 380 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 381 final Supplier<?> targetSupplier, 382 final Method terminalMethod, 383 final Supplier<? extends Object[]> argumentsSupplier) 384 throws IllegalAccessException { 385 this(interceptorMethods, 386 terminalFunctionOf(terminalMethod, targetSupplier), 387 false, // don't set target 388 new ConcurrentHashMap<>(), 389 Chain::returnNull, // constructor supplier 390 () -> terminalMethod, 391 targetSupplier, 392 argumentsSupplier, 393 args -> validate(terminalMethod.getParameterTypes(), args), 394 Chain::returnNull, // timer supplier 395 null); // chain 396 } 397 398 /* 399 * Around-construct or around-invoke (but not lifecycle method). 400 */ 401 402 /** 403 * Creates a new {@link Chain} for around-construct or around-invoke interceptions. 404 * 405 * <p>The resulting {@link Chain} will return {@code null} from the following methods:</p> 406 * 407 * <ul> 408 * 409 * <li>{@link #getConstructor()}</li> 410 * 411 * <li>{@link #getMethod()}</li> 412 * 413 * <li>{@link #getTimer()}</li> 414 * 415 * </ul> 416 * 417 * @param interceptorMethods a {@link List} of {@link InterceptorMethod}s; may, rather uselessly, be {@code null} 418 * 419 * @param targetSupplier a {@link Supplier} of the target instance; may, rather uselessly, be {@code null} 420 * 421 * @param terminalFunction the terminal {@link Function} to intercept; must not be {@code null} 422 * 423 * @param setTarget whether the supplied {@code terminalFunction} is effectively a constructor 424 * 425 * @param argumentsSupplier a {@link Supplier} supplying the arguments for the terminal {@link Function}; may be 426 * {@code null} 427 * 428 * @exception NullPointerException if {@code terminalFunction} is {@code null} 429 */ 430 public Chain(final List<? extends InterceptorMethod> interceptorMethods, 431 final Supplier<?> targetSupplier, 432 final Function<? super Object[], ?> terminalFunction, 433 final boolean setTarget, // is the terminal function effectively a constructor? 434 final Supplier<? extends Object[]> argumentsSupplier) { 435 this(interceptorMethods, 436 Objects.requireNonNull(terminalFunction, "terminalFunction"), 437 setTarget, 438 new ConcurrentHashMap<>(), 439 Chain::returnNull, // constructor supplier 440 Chain::returnNull, // method supplier 441 targetSupplier, 442 argumentsSupplier, 443 Chain::sink, // arguments validator 444 Chain::returnNull, // timer supplier 445 null); // chain 446 } 447 448 /* 449 * Kitchen sink constructor. 450 */ 451 452 // Everything is nullable. 453 private Chain(List<? extends InterceptorMethod> interceptorMethods, 454 final Function<? super Object[], ?> terminalFunction, 455 final boolean setTarget, 456 final ConcurrentMap<String, Object> contextData, 457 final Supplier<? extends Constructor<?>> constructorSupplier, 458 final Supplier<? extends Method> methodSupplier, 459 final Supplier<?> targetSupplier, 460 final Supplier<? extends Object[]> argumentsSupplier, 461 final Consumer<? super Object[]> argumentsValidator, 462 final Supplier<?> timerSupplier, 463 final Chain parent) { 464 super(); 465 this.contextData = contextData == null ? new ConcurrentHashMap<>() : contextData; 466 this.constructorSupplier = constructorSupplier == null ? Chain::returnNull : constructorSupplier; 467 this.methodSupplier = methodSupplier == null ? Chain::returnNull : methodSupplier; 468 this.argumentsSupplier = argumentsSupplier == null ? Chain::emptyObjectArray : argumentsSupplier; 469 this.timerSupplier = timerSupplier == null ? Chain::returnNull : timerSupplier; 470 this.targetSupplier = targetSupplier == null ? Chain::returnNull : targetSupplier; 471 this.chain = parent == null ? this : parent; 472 if (interceptorMethods == null || interceptorMethods.isEmpty()) { 473 Objects.requireNonNull(terminalFunction, "terminalFunction"); 474 this.setParametersImplementation = 475 argumentsValidator == null ? 476 args -> this.setArguments(Chain::sink, args) : 477 args -> this.setArguments(argumentsValidator, args); 478 if (setTarget) { 479 this.proceedImplementation = () -> { 480 final Object t = terminalFunction.apply(this.getParameters()); 481 TARGET.setVolatile(this.chain, t); // volatile write 482 return t; 483 }; 484 } else { 485 this.proceedImplementation = () -> terminalFunction.apply(this.getParameters()); 486 } 487 } else { 488 if (terminalFunction == null) { 489 this.setParametersImplementation = Chain::throwIllegalStateException; 490 } else if (argumentsValidator == null) { 491 this.setParametersImplementation = args -> this.setArguments(Chain::sink, args); 492 } else { 493 this.setParametersImplementation = args -> this.setArguments(argumentsValidator, args); 494 } 495 interceptorMethods = List.copyOf(interceptorMethods); 496 final int size = interceptorMethods.size(); 497 final InterceptorMethod im = interceptorMethods.get(0); 498 // TODO: OK to create Chain here, or do we need to create a new one inside the proceedImplementation? Consider 499 // setParameters(), setTarget(Object). See also: https://www.eclipse.org/lists/interceptors-dev/msg00056.html 500 final Chain chain = new Chain(size == 1 ? List.of() : interceptorMethods.subList(1, size), 501 terminalFunction, 502 setTarget, 503 this.contextData, 504 this.constructorSupplier, 505 this.methodSupplier, 506 this.targetSupplier, 507 this.argumentsSupplier, 508 argumentsValidator, 509 this.timerSupplier, 510 this.chain); 511 512 this.proceedImplementation = () -> im.intercept(chain); 513 } 514 } 515 516 517 /* 518 * Instance methods. 519 */ 520 521 522 /** 523 * Returns the {@link Constructor} being intercepted, if available, or {@code null}. 524 * 525 * @return the {@link Constructor} being intercepted, if available, or {@code null} 526 */ 527 @Override 528 public final Constructor<?> getConstructor() { 529 return this.constructorSupplier.get(); 530 } 531 532 /** 533 * Returns the context data {@link Map} shared by the current invocation. 534 * 535 * @return the context data {@link Map} shared by the current invocation; never {@code null} 536 * 537 * @see InvocationContext#getContextData() 538 */ 539 @Override 540 public final Map<String, Object> getContextData() { 541 return this.contextData; 542 } 543 544 /** 545 * Returns the {@link Method} being intercepted, if available, or {@code null}. 546 * 547 * @return the {@link Method} being intercepted, if available, or {@code null} 548 */ 549 // OK to return null in many cases; see 550 // https://jakarta.ee/specifications/interceptors/2.1/jakarta-interceptors-spec-2.1#invocation_context 551 @Override 552 public final Method getMethod() { 553 return this.methodSupplier.get(); 554 } 555 556 /** 557 * Returns any arguments in effect for the current interception. 558 * 559 * <p>For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as 560 * "parameters".</p> 561 * 562 * @return any arguments in effect for the current interception; never {@code null} 563 * 564 * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link 565 * jakarta.interceptor.AroundConstruct AroundConstruct} callback 566 * 567 * @see #setParameters(Object[]) 568 */ 569 @Override 570 public final Object[] getParameters() { 571 // Cloning etc. is not necessary; this whole API is stupid 572 final Object[] arguments = this.chain.arguments; // volatile read 573 if (arguments == null) { 574 try { 575 this.setParameters(this.argumentsSupplier.get()); 576 } catch (final IllegalArgumentException e) { 577 throw new IllegalStateException(e.getMessage(), e); 578 } 579 return this.chain.arguments; // volatile read 580 } 581 return arguments; 582 } 583 584 /** 585 * Returns the target instance, if available, or {@code null}. 586 * 587 * @return the target instance, if available, or {@code null} 588 */ 589 @Override 590 public final Object getTarget() { 591 Object target = this.chain.target; // volatile read 592 if (target == null) { 593 target = this.targetSupplier.get(); 594 if (target != null && TARGET.compareAndSet(this.chain, null, target)) { // volatile write 595 target = this.chain.target; // volatile read 596 } 597 } 598 return target; 599 } 600 601 /** 602 * Sets the target instance to be the supplied {@code target}. 603 * 604 * @param target the target instance; must not be {@code null} 605 * 606 * @exception NullPointerException if {@code target} is {@code null} 607 * 608 * @see #getTarget() 609 * 610 * @deprecated This really shouldn't be needed as part of the public API. 611 */ 612 @Deprecated // should not be needed? 613 public final void setTarget(final Object target) { 614 TARGET.setVolatile(this.chain, Objects.requireNonNull(target, "target")); // volatile write 615 } 616 617 /** 618 * Returns the timer, if available, or {@code null}. 619 * 620 * @return the timer, if available, or {@code null} 621 */ 622 @Override 623 public final Object getTimer() { 624 return this.timerSupplier.get(); 625 } 626 627 /** 628 * Invokes the {@link #proceed()} method and returns its result. 629 * 630 * @return the result of invoking the {@link #proceed()} method, which may be {@code null} 631 * 632 * @exception Exception if an error occurs 633 * 634 * @see #proceed() 635 */ 636 @Override // Callable<Object> 637 public final Object call() throws Exception { 638 return this.proceed(); 639 } 640 641 /** 642 * Applies the next interception in this {@link Chain}, or calls the terminal function, and returns the result of the 643 * interception, which may be {@code null}. 644 * 645 * <p>Overrides must not call {@link #call()} or an infinite loop will result.</p> 646 * 647 * @return the result of proceeding, which may be {@code null} 648 * 649 * @exception Exception if an error occurs 650 */ 651 @Override 652 public Object proceed() throws Exception { 653 return this.proceedImplementation.call(); 654 } 655 656 /** 657 * Sets arguments to be in effect for the current interception. 658 * 659 * <p>For historical reasons, the Jakarta Interceptors specification refers, incorrectly, to arguments as 660 * "parameters".</p> 661 * 662 * @param arguments the arguments; may be {@code null} 663 * 664 * @exception IllegalArgumentException if the arguments are invalid 665 * 666 * @exception IllegalStateException if invoked within a lifecycle callback method that is not an {@link 667 * jakarta.interceptor.AroundConstruct AroundConstruct} callback 668 * 669 * @see #getParameters() 670 */ 671 @Override 672 public final void setParameters(final Object[] arguments) { 673 this.setParametersImplementation.accept(arguments); 674 } 675 676 private final void setArguments(final Consumer<? super Object[]> argumentsValidator, final Object[] arguments) { 677 if (arguments == null) { 678 argumentsValidator.accept(EMPTY_OBJECT_ARRAY); 679 this.chain.arguments = EMPTY_OBJECT_ARRAY; // volatile write 680 } else { 681 // Cloning etc. is not necessary; this whole API is stupid 682 argumentsValidator.accept(arguments); 683 this.chain.arguments = arguments; // volatile write 684 } 685 } 686 687 688 /* 689 * Static methods. 690 */ 691 692 693 /** 694 * Creates and returns a {@link Function} encapsulating the supplied {@link Constructor}. 695 * 696 * @param c a {@link Constructor}; must not be {@code null} 697 * 698 * @return a {@link Function} encapsulating the supplied {@link Constructor}; never {@code null} 699 * 700 * @exception NullPointerException if {@code c} is {@code null} 701 * 702 * @exception IllegalAccessException if {@linkplain Lookup#unreflectConstructor(Constructor) unreflecting} fails 703 */ 704 public static final Function<Object[], Object> terminalFunctionOf(final Constructor<?> c) throws IllegalAccessException { 705 return terminalFunctionOf(privateLookupIn(c.getDeclaringClass(), Chain.lookup).unreflectConstructor(c), null); 706 } 707 708 /** 709 * Creates and returns a {@link Function} encapsulating the supplied {@code static} {@link Method}. 710 * 711 * @param staticMethod a {@code static} {@link Method}; must not be {@code null} 712 * 713 * @return a {@link Function} encapsulating the supplied {@code static} {@link Method}; never {@code null} 714 * 715 * @exception NullPointerException if {@code staticMethod} is {@code null} 716 * 717 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 718 */ 719 public static final Function<Object[], Object> terminalFunctionOf(final Method staticMethod) throws IllegalAccessException { 720 return terminalFunctionOf(staticMethod, null); 721 } 722 723 /** 724 * Creates and returns a {@link Function} encapsulating the supplied {@link Method}. 725 * 726 * @param m a {@link Method}; must not be {@code null}. If {@code m} is a virtual method, then the supplied {@code 727 * receiverSupplier} will be used to supply its receiver 728 * 729 * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link Method}; may be {@code null} in 730 * which case the supplied {@link Method} must be {@code static} 731 * 732 * @return a {@link Function} encapsulating the supplied {@link Method}; never {@code null} 733 * 734 * @exception NullPointerException if {@code m} is {@code null} 735 * 736 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 737 */ 738 public static final Function<Object[], Object> terminalFunctionOf(final Method m, final Supplier<?> receiverSupplier) 739 throws IllegalAccessException { 740 return terminalFunctionOf(privateLookupIn(m.getDeclaringClass(), Chain.lookup).unreflect(m), receiverSupplier); 741 } 742 743 /** 744 * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}. 745 * 746 * @param receiverlessMethodHandle a {@link MethodHandle}; must not be {@code null}; must be receiverless or 747 * {@linkplain MethodHandle#bindTo(Object) bound} to a receiver already. 748 * 749 * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null} 750 * 751 * @exception NullPointerException if {@code receiverlessMethodHandle} is {@code null} 752 */ 753 public static final Function<Object[], Object> terminalFunctionOf(final MethodHandle receiverlessMethodHandle) { 754 return terminalFunctionOf(receiverlessMethodHandle, null); 755 } 756 757 /** 758 * Creates and returns a {@link Function} encapsulating the supplied {@link MethodHandle}. 759 * 760 * @param mh a {@link MethodHandle}; must not be {@code null}. If {@code mh} is a {@link MethodHandle} requiring a 761 * receiver, then the supplied {@code receiverSupplier}, if non-{@code null}, will be used to supply its receiver 762 * 763 * @param receiverSupplier a {@link Supplier} of the receiver for the supplied {@link MethodHandle}; may be {@code 764 * null} in which case the supplied {@link MethodHandle} must be receiverless 765 * 766 * @return a {@link Function} encapsulating the supplied {@link MethodHandle}; never {@code null} 767 * 768 * @exception NullPointerException if {@code mh} is {@code null} 769 */ 770 public static final Function<Object[], Object> terminalFunctionOf(MethodHandle mh, final Supplier<?> receiverSupplier) { 771 mh = mh.asType(mh.type().changeReturnType(Object.class)); 772 MethodType mt = mh.type(); 773 final int pc = mt.parameterCount(); 774 775 final MethodHandle terminalFunction; 776 777 if (receiverSupplier == null) { 778 // Static 779 switch (pc) { 780 case 0: 781 terminalFunction = mh; 782 return ps -> invokeUnchecked(() -> terminalFunction.invokeExact()); 783 default: 784 terminalFunction = mh.asSpreader(Object[].class, pc); 785 return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(ps)); 786 } 787 } 788 789 // Virtual 790 mh = mh.asType(mt.changeParameterType(0, Object.class)); 791 mt = mh.type(); 792 793 switch (pc) { 794 case 1: 795 terminalFunction = mh; 796 return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get())); 797 default: 798 terminalFunction = mh.asSpreader(Object[].class, pc - 1); 799 return ps -> invokeUnchecked(() -> terminalFunction.invokeExact(receiverSupplier.get(), ps)); 800 } 801 } 802 803 private static final <T> T returnNull() { 804 return null; 805 } 806 807 private static final <X, Y> X returnNull(final Y ignored) { 808 return null; 809 } 810 811 private static final Object[] emptyObjectArray() { 812 return EMPTY_OBJECT_ARRAY; 813 } 814 815 private static final <T> void sink(T ignored) { 816 817 } 818 819 private static final <X> void throwIllegalStateException(final X ignored) { 820 throw new IllegalStateException(); 821 } 822 823 /** 824 * A convenience method that ensures that every element of the supplied {@code arguments} array can be assigned to a 825 * reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array. 826 * 827 * <p>Boxing, unboxing and widening conversions are taken into consideration.</p> 828 * 829 * <p>This method implements the logic implied, but nowhere actually specified, by the contract of {@link 830 * InvocationContext#setParameters(Object[])}.</p> 831 * 832 * @param parameterTypes an array of {@link Class} instances; may be {@code null}; must not contain {@code null} 833 * elements or {@code void.class}; must have a length equal to that of the supplied {@code arguments} array 834 * 835 * @param arguments an array of {@link Object}s; may be {@code null}; must have a length equal to that of the supplied 836 * {@code parameterTypes} array 837 * 838 * @exception IllegalArgumentException if not every element of the supplied {@code arguments} array can be assigned to 839 * a reference bearing the corresponding {@link Class} drawn from the supplied {@code parameterTypes} array 840 * 841 * @see #setParameters(Object[]) 842 */ 843 public static final void validate(final Class<?>[] parameterTypes, final Object[] arguments) { 844 final int parameterTypesLength = parameterTypes == null ? 0 : parameterTypes.length; 845 final int argumentsLength = arguments == null ? 0 : arguments.length; 846 if (argumentsLength != parameterTypesLength) { 847 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 848 "; arguments: " + Arrays.toString(arguments)); 849 } 850 for (int i = 0; i < argumentsLength; i++) { 851 final Class<?> parameterType = parameterTypes[i]; 852 if (parameterType == null || parameterType == void.class) { 853 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 854 "; arguments: " + Arrays.toString(arguments) + 855 "; parameter type: " + parameterType); 856 } 857 final Object argument = arguments[i]; 858 if (argument == null) { 859 if (parameterType.isPrimitive() || parameterType != Void.class) { 860 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 861 "; arguments: " + Arrays.toString(arguments) + 862 "; parameter type: " + parameterType.getName() + 863 "; argument: null"); 864 } 865 } else { 866 final Class<?> argumentType = argument.getClass(); 867 if (parameterType != argumentType) { 868 if (parameterType == boolean.class) { 869 if (argumentType != Boolean.class) { 870 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 871 "; arguments: " + Arrays.toString(arguments) + 872 "; parameter type: boolean" + 873 "; argument type: " + argumentType.getName()); 874 } 875 } else if (parameterType == Boolean.class) { 876 if (argumentType != boolean.class) { 877 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 878 "; arguments: " + Arrays.toString(arguments) + 879 "; parameter type: java.lang.Boolean" + 880 "; argument type: " + argumentType.getName()); 881 } 882 } else if (parameterType == byte.class) { 883 if (argumentType != Byte.class) { 884 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 885 "; arguments: " + Arrays.toString(arguments) + 886 "; parameter type: byte" + 887 "; argument type: " + argumentType.getName()); 888 } 889 } else if (parameterType == Byte.class) { 890 if (argumentType != byte.class) { 891 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 892 "; arguments: " + Arrays.toString(arguments) + 893 "; parameter type: java.lang.Byte" + 894 "; argument type: " + argumentType.getName()); 895 } 896 } else if (parameterType == char.class) { 897 if (argumentType != Character.class) { 898 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 899 "; arguments: " + Arrays.toString(arguments) + 900 "; parameter type: char" + 901 "; argument type: " + argumentType.getName()); 902 } 903 } else if (parameterType == Character.class) { 904 if (argumentType != char.class) { 905 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 906 "; arguments: " + Arrays.toString(arguments) + 907 "; parameter type: java.lang.Character" + 908 "; argument type: " + argumentType.getName()); 909 } 910 } else if (parameterType == double.class) { 911 if (argumentType != byte.class && 912 argumentType != char.class && 913 argumentType != Double.class && 914 argumentType != float.class && 915 argumentType != int.class && 916 argumentType != long.class && 917 argumentType != short.class) { 918 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 919 "; arguments: " + Arrays.toString(arguments) + 920 "; parameter type: double" + 921 "; argument type: " + argumentType.getName()); 922 } 923 } else if (parameterType == Double.class) { 924 if (argumentType != double.class) { 925 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 926 "; arguments: " + Arrays.toString(arguments) + 927 "; parameter type: java.lang.Double" + 928 "; argument type: " + argumentType.getName()); 929 } 930 } else if (parameterType == float.class) { 931 if (argumentType != byte.class && 932 argumentType != char.class && 933 argumentType != Float.class && 934 argumentType != int.class && 935 argumentType != long.class && 936 argumentType != short.class) { 937 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 938 "; arguments: " + Arrays.toString(arguments) + 939 "; parameter type: float" + 940 "; argument type: " + argumentType.getName()); 941 } 942 } else if (parameterType == Float.class) { 943 if (argumentType != float.class) { 944 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 945 "; arguments: " + Arrays.toString(arguments) + 946 "; parameter type: java.lang.Float" + 947 "; argument type: " + argumentType.getName()); 948 } 949 } else if (parameterType == int.class) { 950 if (argumentType != byte.class && 951 argumentType != char.class && 952 argumentType != Integer.class && 953 argumentType != short.class) { 954 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 955 "; arguments: " + Arrays.toString(arguments) + 956 "; parameter type: int; argument type: " + argumentType.getName()); 957 } 958 } else if (parameterType == Integer.class) { 959 if (argumentType != int.class) { 960 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 961 "; arguments: " + Arrays.toString(arguments) + 962 "; parameter type: java.lang.Integer" + 963 "; argument type: " + argumentType.getName()); 964 } 965 } else if (parameterType == long.class) { 966 if (argumentType != byte.class && 967 argumentType != char.class && 968 argumentType != int.class && 969 argumentType != Long.class && 970 argumentType != short.class) { 971 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 972 "; arguments: " + Arrays.toString(arguments) + 973 "; parameter type: long" + 974 "; argument type: " + argumentType.getName()); 975 } 976 } else if (parameterType == Long.class) { 977 if (argumentType != long.class) { 978 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 979 "; arguments: " + Arrays.toString(arguments) + 980 "; parameter type: java.lang.Long" + 981 "; argument type: " + argumentType.getName()); 982 } 983 } else if (parameterType == short.class) { 984 if (argumentType != byte.class && 985 argumentType != Short.class) { 986 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 987 "; arguments: " + Arrays.toString(arguments) + 988 "; parameter type: byte" + 989 "; argument type: " + argumentType.getName()); 990 } 991 } else if (parameterType == Short.class) { 992 if (argumentType != short.class) { 993 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 994 "; arguments: " + Arrays.toString(arguments) + 995 "; parameter type: java.lang.Short" + 996 "; argument type: " + argumentType.getName()); 997 } 998 } else if (parameterType == Void.class || !parameterType.isAssignableFrom(argumentType)) { 999 throw new IllegalArgumentException("parameter types: " + Arrays.toString(parameterTypes) + 1000 "; arguments: " + Arrays.toString(arguments) + 1001 "; parameter type: " + parameterType.getName() + 1002 "; argument type: " + argumentType.getName()); 1003 } 1004 } 1005 } 1006 } 1007 } 1008 1009}