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.reference; 015 016import java.lang.invoke.MethodHandles; 017import java.lang.invoke.MethodHandles.Lookup; 018 019import java.lang.reflect.InvocationHandler; 020import java.lang.reflect.Method; 021 022import java.util.List; 023import java.util.Objects; 024 025import java.util.function.Function; 026import java.util.function.Supplier; 027 028import javax.lang.model.type.DeclaredType; 029import javax.lang.model.type.TypeMirror; 030 031import javax.lang.model.element.TypeElement; 032 033import org.microbean.construct.Domain; 034 035import static java.lang.reflect.Proxy.newProxyInstance; 036 037/** 038 * An {@link AbstractClientProxier} implementation that uses {@link java.lang.reflect.Proxy} machinery. 039 * 040 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 041 */ 042public class ReflectiveClientProxier extends AbstractClientProxier<Class<?>> { 043 044 private static final Lookup lookup = MethodHandles.publicLookup(); 045 046 private final Function<? super List<? extends Class<?>>, ? extends ClassLoader> clf; 047 048 /** 049 * Creates a new {@link ReflectiveClientProxier}. 050 * 051 * @param domain a {@link Domain}; must not be {@code null} 052 * 053 * @exception NullPointerException if {@code domain} is {@code null} 054 * 055 * @see #ReflectiveClientProxier(Domain, Function) 056 */ 057 public ReflectiveClientProxier(final Domain domain) { 058 this(domain, i -> Thread.currentThread().getContextClassLoader()); 059 } 060 061 /** 062 * Creates a new {@link ReflectiveClientProxier}. 063 * 064 * @param domain a {@link Domain}; must not be {@code null} 065 * 066 * @param clf a {@link Function} that returns a {@link ClassLoader} appropriate for a {@link List} of {@link Class} 067 * instances; must not be {@code null} 068 * 069 * @exception NullPointerException if any argument is {@code null} 070 */ 071 public ReflectiveClientProxier(final Domain domain, 072 final Function<? super List<? extends Class<?>>, ? extends ClassLoader> clf) { 073 super(domain); 074 this.clf = Objects.requireNonNull(clf, "clf"); 075 } 076 077 @Override // AbstractClientProxier<Class<?>> 078 @SuppressWarnings("unchecked") 079 protected <R> ClientProxy<R> instantiate(final ProxySpecification ps, final Supplier<? extends R> instanceSupplier) { 080 final Domain domain = this.domain(); 081 if (!domain.javaLangObject(ps.superclass())) { 082 throw new IllegalArgumentException("ps: " + ps); 083 } 084 final List<? extends TypeMirror> interfaceTypeMirrors = ps.interfaces(); 085 final int size = interfaceTypeMirrors.size(); 086 final Class<?>[] interfaces = new Class<?>[size]; 087 try { 088 for (int i = 0; i < size; i++) { 089 final TypeElement e = (TypeElement)((DeclaredType)interfaceTypeMirrors.get(i)).asElement(); 090 final String binaryName = domain.toString(domain.binaryName(e)); 091 interfaces[i] = Class.forName(binaryName, false, this.classLoader(e)); 092 } 093 } catch (final ClassNotFoundException cnfe) { 094 throw new IllegalArgumentException("ps: " + ps, cnfe); 095 } 096 return (ClientProxy<R>)newProxyInstance(this.clf.apply(List.of(interfaces)), interfaces, new InvocationHandler() { 097 @Override // InvocationHandler 098 public final Object invoke(final Object p, final Method m, final Object[] a) throws Throwable { 099 return switch (m) { 100 case null -> throw new NullPointerException("m"); 101 case Method x when 102 x.getDeclaringClass() == Object.class && 103 x.getReturnType() == boolean.class && 104 x.getParameterCount() == 1 && 105 x.getParameterTypes()[0] == Object.class && 106 x.getName().equals("equals") -> p == a[0]; 107 case Method x when 108 x.getDeclaringClass() == Object.class && 109 x.getReturnType() == int.class && 110 x.getParameterCount() == 0 && 111 x.getName().equals("hashCode") -> System.identityHashCode(p); 112 default -> m.invoke(instanceSupplier.get(), a); 113 }; 114 } 115 }); 116 } 117 118 @Override // AbstractClientProxier<Void> 119 protected Lookup lookup(final Class<?> c) { 120 return lookup; 121 } 122 123}