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}