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}