001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2023–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; 017 018import java.lang.reflect.Method; 019 020import java.util.function.Supplier; 021 022import jakarta.interceptor.InvocationContext; 023 024import static java.lang.invoke.MethodHandles.lookup; 025import static java.lang.invoke.MethodHandles.privateLookupIn; 026 027/** 028 * A representation of a Jakarta Interceptors <em>interceptor method</em>. 029 * 030 * @author <a href="https://about.me/lairdnelson/" target="_top">Laird Nelson</a> 031 */ 032@FunctionalInterface 033public interface InterceptorMethod { 034 035 036 /** 037 * Performs interception and returns any result. 038 * 039 * @param ic an {@link InvocationContext}; must not be {@code null} 040 * 041 * @return the result of interception, often computed by invoking the {@link InvocationContext#proceed()} method; the 042 * return value may be {@code null} 043 * 044 * @exception Exception if any error occurs 045 */ 046 public Object intercept(final InvocationContext ic) throws Exception; 047 048 049 /* 050 * Public static methods. 051 */ 052 053 054 /** 055 * Returns a new {@link InterceptorMethod} that adapts the supplied {@code static} {@link Method}. 056 * 057 * @param staticMethod a {@code static} {@link Method}; must not be {@code null}; must accept exactly one {@link 058 * InvocationContext}-typed argument 059 * 060 * @return a new {@link InterceptorMethod}; never {@code null} 061 * 062 * @exception NullPointerException if {@code staticMethod} is {@code null} 063 */ 064 public static InterceptorMethod of(final Method staticMethod) { 065 return of(staticMethod, null); 066 } 067 068 /** 069 * Returns a new {@link InterceptorMethod} that adapts the supplied {@link Method} and the supplied {@link Supplier} 070 * of its receiver. 071 * 072 * @param m a {@link Method}; must not be {@code null}; must accept exactly one {@link InvocationContext}-typed 073 * argument 074 * 075 * @param targetSupplier a {@link Supplier} of the supplied {@link Method}'s receiver; often memoized; may be {@code 076 * null} in which case the supplied {@link Method} must be {@code static} 077 * 078 * @return a new {@link InterceptorMethod}; never {@code null} 079 * 080 * @exception NullPointerException if {@code m} is {@code null} 081 * 082 * @exception InterceptorException if {@linkplain java.lang.invoke.MethodHandles.Lookup#unreflect(Method) 083 * unreflecting} fails 084 */ 085 public static InterceptorMethod of(final Method m, final Supplier<?> targetSupplier) { 086 try { 087 return of(privateLookupIn(m.getDeclaringClass(), lookup()).unreflect(m), targetSupplier); 088 } catch (final IllegalAccessException e) { 089 throw new InterceptorException(e.getMessage(), e); 090 } 091 } 092 093 /** 094 * Returns a new {@link InterceptorMethod} that adapts the supplied receiverless or {@linkplain 095 * MethodHandle#bindTo(Object) bound} {@link MethodHandle}. 096 * 097 * @param receiverlessOrBoundMethodHandle a {@link MethodHandle}; must not be {@code null}; must either not require a 098 * receiver or must be already {@linkplain MethodHandle#bindTo(Object) bound} to one; must accept exactly one {@link 099 * InvocationContext}-typed argument 100 * 101 * @return a new {@link InterceptorMethod}; never {@code null} 102 * 103 * @exception NullPointerException if {@code receiverlessOrBoundMethodHandle} is {@code null} 104 */ 105 public static InterceptorMethod of(final MethodHandle receiverlessOrBoundMethodHandle) { 106 return of(receiverlessOrBoundMethodHandle, null); 107 } 108 109 /** 110 * Returns a new {@link InterceptorMethod} that adapts the supplied {@link MethodHandle} and the supplied {@link 111 * Supplier} of its receiver. 112 * 113 * @param mh a {@link MethodHandle}; must not be {@code null}; must either accept two arguments where the first 114 * argument's type is a valid receiver type and the second argument's type is {@link InvocationContext}, or one 115 * argument whose type is {@link InvocationContext} 116 * 117 * @param receiverSupplier a {@link Supplier} of the supplied {@link MethodHandle}'s receiver; often memoized; may be 118 * {@code null} in which case the supplied {@link MethodHandle} must either not require a receiver or must be already 119 * {@linkplain MethodHandle#bindTo(Object) bound} to one 120 * 121 * @return a new {@link InterceptorMethod}; never {@code null} 122 * 123 * @exception NullPointerException if {@code m} is {@code null} 124 */ 125 public static InterceptorMethod of(final MethodHandle mh, final Supplier<?> receiverSupplier) { 126 final Object returnType = mh.type().returnType(); 127 if (returnType == void.class || returnType == Void.class) { 128 if (receiverSupplier == null) { 129 return ic -> invokeExact(mh, ic); 130 } 131 final MethodHandle unboundInterceptorMethod = mh.asType(mh.type().changeParameterType(0, Object.class)); 132 return ic -> { 133 try { 134 unboundInterceptorMethod.invokeExact(receiverSupplier.get(), ic); 135 } catch (final RuntimeException | Error e) { 136 throw e; 137 } catch (final InterruptedException e) { 138 Thread.currentThread().interrupt(); 139 throw new InterceptorException(e.getMessage(), e); 140 } catch (final Throwable e) { 141 throw new InterceptorException(e.getMessage(), e); 142 } 143 return null; 144 }; 145 } else if (receiverSupplier == null) { 146 return ic -> invokeExact(mh, ic); 147 } 148 final MethodHandle unboundInterceptorMethod = mh.asType(mh.type().changeParameterType(0, Object.class)); 149 return ic -> invokeExact(unboundInterceptorMethod, receiverSupplier, ic); 150 } 151 152 153 /* 154 * Private static methods. 155 */ 156 157 158 private static Object invokeExact(final MethodHandle mh, final InvocationContext ic) { 159 try { 160 return mh.invokeExact(ic); 161 } catch (final RuntimeException | Error e) { 162 throw e; 163 } catch (final InterruptedException e) { 164 Thread.currentThread().interrupt(); 165 throw new InterceptorException(e.getMessage(), e); 166 } catch (final Throwable e) { 167 throw new InterceptorException(e.getMessage(), e); 168 } 169 } 170 171 private static Object invokeExact(final MethodHandle mh, final Supplier<?> receiverSupplier, final InvocationContext ic) { 172 try { 173 return mh.invokeExact(receiverSupplier.get(), ic); 174 } catch (final RuntimeException | Error e) { 175 throw e; 176 } catch (final InterruptedException e) { 177 Thread.currentThread().interrupt(); 178 throw new InterceptorException(e.getMessage(), e); 179 } catch (final Throwable e) { 180 throw new InterceptorException(e.getMessage(), e); 181 } 182 } 183 184}