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}