001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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.producer;
015
016import static java.lang.invoke.MethodHandles.Lookup;
017
018import java.lang.reflect.InvocationHandler;
019import java.lang.reflect.Method;
020
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024
025import java.util.concurrent.ConcurrentHashMap;
026
027import java.util.function.Supplier;
028
029import javax.lang.model.element.ExecutableElement;
030import javax.lang.model.element.TypeElement;
031
032import javax.lang.model.type.TypeMirror;
033
034import org.microbean.construct.Domain;
035
036import org.microbean.interceptor.InterceptionFunction;
037import org.microbean.interceptor.InterceptorMethod;
038
039import org.microbean.producer.InterceptionProxier.Specification;
040
041import org.microbean.proxy.AbstractReflectiveProxier;
042import org.microbean.proxy.Proxy;
043
044import static java.lang.System.identityHashCode;
045
046import static java.lang.invoke.MethodHandles.lookup;
047
048import static java.lang.reflect.InvocationHandler.invokeDefault;
049
050import static java.lang.reflect.Proxy.newProxyInstance;
051
052import static org.microbean.interceptor.Interceptions.ofInvocation;
053
054/**
055 * An {@link AbstractReflectiveProxier} implementation that uses the proxy features of the Java Development Kit.
056 *
057 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
058 */
059public final class ReflectiveInterceptionProxier extends AbstractReflectiveProxier<Specification> {
060
061  private static final Lookup lookup = lookup();
062
063  /**
064   * Creates a new {@link ReflectiveInterceptionProxier}.
065   *
066   * @param domain a {@link Domain}; must not be {@code null}
067   */
068  public ReflectiveInterceptionProxier(final Domain domain) {
069    super(domain);
070  }
071
072  @Override // AbstractReflectiveProxier<Specification>
073  @SuppressWarnings("unchecked")
074  protected final <R> Proxy<R> proxy(final Specification ps,
075                                     final Class<?>[] interfaces,
076                                     final Supplier<? extends R> instanceSupplier) {
077    final Map<Method, ExecutableElement> ees = new ConcurrentHashMap<>();
078    final Map<Method, InterceptionFunction> fs = new ConcurrentHashMap<>();
079    final R instance = Objects.requireNonNull(instanceSupplier.get(), "instanceSupplier.get()");
080    return
081      (Proxy<R>)newProxyInstance(this.classLoader(), interfaces, new InvocationHandler() {
082          @Override // InvocationHandler
083          public final Object invoke(final Object p, final Method m, final Object[] a) throws Throwable {
084            return switch (m) {
085            case null -> throw new NullPointerException("m");
086            case Method x when equalsMethod(m) -> p == a[0];
087            case Method x when hashCodeMethod(m) -> identityHashCode(p);
088            default -> {
089              final List<InterceptorMethod> interceptorMethods =
090                ps.interceptorMethods(ees.computeIfAbsent(m, ReflectiveInterceptionProxier.this::executableElement));
091              if (interceptorMethods.isEmpty()) {
092                yield m.isDefault() ? invokeDefault(instance, m, a) : m.invoke(instance, a);
093              }
094              yield fs.computeIfAbsent(m, m0 -> {
095                  try {
096                    return ofInvocation(interceptorMethods, lookup, m0, () -> instance, null);
097                  } catch (final IllegalAccessException e) {
098                    throw new IllegalArgumentException("m0: " + m0, e);
099                  }
100                })
101                .apply(a);
102            }
103            };
104          }
105        });
106  }
107
108}