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