001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2023–2025 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; 018 019import java.lang.reflect.Method; 020 021import java.util.function.Supplier; 022 023import jakarta.interceptor.InvocationContext; 024 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 lookup a {@link Lookup}; must not be {@code null} 058 * 059 * @param staticMethod a {@code static} {@link Method}; must not be {@code null}; must accept exactly one {@link 060 * InvocationContext}-typed argument 061 * 062 * @return a new {@link InterceptorMethod}; never {@code null} 063 * 064 * @exception NullPointerException if {@code lookup} or {@code staticMethod} is {@code null} 065 * 066 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 067 * 068 * @see #of(Lookup, Method, Supplier) 069 * 070 * @see #of(MethodHandle, Supplier) 071 */ 072 public static InterceptorMethod of(final Lookup lookup, final Method staticMethod) 073 throws IllegalAccessException { 074 return of(lookup, staticMethod, null); 075 } 076 077 /** 078 * Returns a new {@link InterceptorMethod} that adapts the supplied {@link Method} and the supplied {@link Supplier} 079 * of its receiver. 080 * 081 * @param lookup a {@link Lookup}; must not be {@code null} 082 * 083 * @param m a {@link Method}; must not be {@code null}; must accept exactly one {@link InvocationContext}-typed 084 * argument 085 * 086 * @param targetSupplier a {@link Supplier} of the supplied {@link Method}'s receiver; often memoized; may be {@code 087 * null} in which case the supplied {@link Method} must be {@code static} 088 * 089 * @return a new {@link InterceptorMethod}; never {@code null} 090 * 091 * @exception NullPointerException if {@code lookup} or {@code m} is {@code null} 092 * 093 * @exception IllegalAccessException if {@linkplain Lookup#unreflect(Method) unreflecting} fails 094 * 095 * @see #of(MethodHandle, Supplier) 096 */ 097 public static InterceptorMethod of(final Lookup lookup, final Method m, final Supplier<?> targetSupplier) 098 throws IllegalAccessException { 099 return of(privateLookupIn(m.getDeclaringClass(), lookup).unreflect(m), targetSupplier); 100 } 101 102 /** 103 * Returns a new {@link InterceptorMethod} that adapts the supplied receiverless or {@linkplain 104 * MethodHandle#bindTo(Object) bound} {@link MethodHandle}. 105 * 106 * @param receiverlessOrBoundMethodHandle a {@link MethodHandle}; must not be {@code null}; must either not require a 107 * receiver or must be already {@linkplain MethodHandle#bindTo(Object) bound} to one; must accept exactly one {@link 108 * InvocationContext}-typed argument 109 * 110 * @return a new {@link InterceptorMethod}; never {@code null} 111 * 112 * @exception NullPointerException if {@code receiverlessOrBoundMethodHandle} is {@code null} 113 * 114 * @see #of(MethodHandle, Supplier) 115 */ 116 public static InterceptorMethod of(final MethodHandle receiverlessOrBoundMethodHandle) { 117 return of(receiverlessOrBoundMethodHandle, null); 118 } 119 120 /** 121 * Returns a new {@link InterceptorMethod} that adapts the supplied {@link MethodHandle} and the supplied {@link 122 * Supplier} of its receiver. 123 * 124 * @param mh a {@link MethodHandle}; must not be {@code null}; must either accept two arguments where the first 125 * argument's type is a valid receiver type and the second argument's type is {@link InvocationContext}, or one 126 * argument whose type is {@link InvocationContext} 127 * 128 * @param receiverSupplier a {@link Supplier} of the supplied {@link MethodHandle}'s receiver; often memoized; may be 129 * {@code null} in which case the supplied {@link MethodHandle} must either not require a receiver or must be already 130 * {@linkplain MethodHandle#bindTo(Object) bound} to one 131 * 132 * @return a new {@link InterceptorMethod}; never {@code null} 133 * 134 * @exception NullPointerException if {@code m} is {@code null} 135 */ 136 public static InterceptorMethod of(final MethodHandle mh, final Supplier<?> receiverSupplier) { 137 final MethodHandle unboundInterceptorMethod; 138 final Object returnType = mh.type().returnType(); 139 if (returnType == void.class || returnType == Void.class) { 140 if (receiverSupplier == null) { 141 return ic -> invokeExactReturnNull(mh, ic); 142 } 143 unboundInterceptorMethod = mh.asType(mh.type().changeParameterType(0, Object.class)); 144 return ic -> invokeExactReturnNull(unboundInterceptorMethod, receiverSupplier, ic); 145 } 146 unboundInterceptorMethod = mh.asType(mh.type().changeParameterType(0, Object.class)); 147 return 148 receiverSupplier == null ? 149 ic -> invokeExact(unboundInterceptorMethod, ic) : 150 ic -> invokeExact(unboundInterceptorMethod, receiverSupplier, ic); 151 } 152 153 154 /* 155 * Private static methods. 156 */ 157 158 159 private static Object invokeExact(final MethodHandle mh, final InvocationContext ic) { 160 try { 161 return mh.invokeExact(ic); 162 } catch (final RuntimeException | Error e) { 163 throw e; 164 } catch (final InterruptedException e) { 165 Thread.currentThread().interrupt(); 166 throw new InterceptorException(e.getMessage(), e); 167 } catch (final Throwable e) { 168 throw new InterceptorException(e.getMessage(), e); 169 } 170 } 171 172 private static Object invokeExact(final MethodHandle mh, final Supplier<?> receiverSupplier, final InvocationContext ic) { 173 try { 174 return mh.invokeExact(receiverSupplier.get(), ic); 175 } catch (final RuntimeException | Error e) { 176 throw e; 177 } catch (final InterruptedException e) { 178 Thread.currentThread().interrupt(); 179 throw new InterceptorException(e.getMessage(), e); 180 } catch (final Throwable e) { 181 throw new InterceptorException(e.getMessage(), e); 182 } 183 } 184 185 private static Void invokeExactReturnNull(final MethodHandle mh, final InvocationContext ic) { 186 try { 187 mh.invokeExact(ic); 188 } catch (final RuntimeException | Error e) { 189 throw e; 190 } catch (final InterruptedException e) { 191 Thread.currentThread().interrupt(); 192 throw new InterceptorException(e.getMessage(), e); 193 } catch (final Throwable e) { 194 throw new InterceptorException(e.getMessage(), e); 195 } 196 return null; 197 } 198 199 private static Void invokeExactReturnNull(final MethodHandle mh, final Supplier<?> receiverSupplier, final InvocationContext ic) { 200 try { 201 mh.invokeExact(receiverSupplier.get(), ic); 202 } catch (final RuntimeException | Error e) { 203 throw e; 204 } catch (final InterruptedException e) { 205 Thread.currentThread().interrupt(); 206 throw new InterceptorException(e.getMessage(), e); 207 } catch (final Throwable e) { 208 throw new InterceptorException(e.getMessage(), e); 209 } 210 return null; 211 } 212 213}