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