001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2025–2026 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.clientproxy.bytebuddy; 015 016import java.lang.invoke.MethodHandles; 017import java.lang.invoke.MethodHandles.Lookup; 018 019import java.util.Map; 020import java.util.Objects; 021 022import java.util.concurrent.ConcurrentHashMap; 023 024import java.util.function.Supplier; 025 026import net.bytebuddy.dynamic.DynamicType; 027 028import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; 029 030import net.bytebuddy.pool.TypePool; 031 032import org.microbean.bean.Id; 033 034import org.microbean.construct.Domain; 035 036import org.microbean.proxy.AbstractToolkitProxier; 037import org.microbean.proxy.Proxy; 038import org.microbean.proxy.ProxySpecification; 039 040import org.microbean.reference.ClientProxier; 041import org.microbean.reference.ReferenceException; 042 043import static java.lang.invoke.MethodType.methodType; 044 045import static java.util.Objects.requireNonNull; 046 047/** 048 * An {@link AbstractToolkitProxier} and {@link ClientProxier} that uses <a href="https://bytebuddy.net/#/">Byte 049 * Buddy</a> to {@linkplain #generate(ProxySpecification) generate} {@linkplain org.microbean.proxy.Proxy client 050 * proxies}. 051 * 052 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 053 * 054 * @see BBClientProxyClassGenerator 055 */ 056public final class BBClientProxier extends AbstractToolkitProxier<ProxySpecification, DynamicType.Unloaded<?>> implements ClientProxier { 057 058 private static final Map<ProxySpecification, Object> clientProxyInstances = new ConcurrentHashMap<>(); 059 060 private final TypeDefinitions tds; 061 062 private final BBClientProxyClassGenerator g; 063 064 /** 065 * Creates a new {@link BBClientProxier}. 066 * 067 * @param domain a non-{@code null} {@link Domain} 068 * 069 * @exception NullPointerException if any argument is {@code null} 070 * 071 * @see TypeElementTypePool#TypeElementTypePool(Domain) 072 * 073 * @see #BBClientProxier(Domain, TypePool) 074 */ 075 public BBClientProxier(final Domain domain) { 076 this(domain, new TypeElementTypePool(domain)); 077 } 078 079 /** 080 * Creates a new {@link BBClientProxier}. 081 * 082 * @param domain a non-{@code null} {@link Domain} 083 * 084 * @param typePool a non-{@code null} {@link TypePool} 085 * 086 * @exception NullPointerException if any argument is {@code null} 087 * 088 * @see TypeDefinitions#TypeDefinitions(TypePool) 089 * 090 * @see BBClientProxyClassGenerator#BBClientProxyClassGenerator(TypePool) 091 * 092 * @see #BBClientProxier(Domain, TypeDefinitions, BBClientProxyClassGenerator) 093 */ 094 public BBClientProxier(final Domain domain, 095 final TypePool typePool) { 096 this(domain, new TypeDefinitions(typePool), new BBClientProxyClassGenerator(typePool)); 097 } 098 099 /** 100 * Creates a new {@link BBClientProxier}. 101 * 102 * @param domain a non-{@code null} {@link Domain}; must not be {@code null} 103 * 104 * @param tds a non-{@code null} {@link TypeDefinitions} 105 * 106 * @param g a non-{@code nulll} {@link BBClientProxyClassGenerator} 107 * 108 * @exception NullPointerException if any argument is {@code null} 109 */ 110 public BBClientProxier(final Domain domain, 111 final TypeDefinitions tds, 112 final BBClientProxyClassGenerator g) { 113 super(domain, MethodHandles.lookup()); 114 this.tds = requireNonNull(tds, "tds"); 115 this.g = requireNonNull(g, "g"); 116 } 117 118 @Override // ClientProxier 119 public <R> R clientProxy(final Id id, final Supplier<? extends R> instanceSupplier) { 120 return this.proxy(new ProxySpecification(this.domain(), id), instanceSupplier).$cast(); 121 } 122 123 @Override // AbstractClientProxier<DynamicType.Unloaded<?>> 124 protected final DynamicType.Unloaded<?> generate(final ProxySpecification ps) { 125 return 126 this.g.generate(ps.name(), 127 this.tds.typeDescription(ps.superclass()), 128 ps.interfaces().stream().map(this.tds::typeDescriptionGeneric).toList()); 129 } 130 131 @Override // AbstractToolkitProxier<ProxySpecification, DynamicType.Unloaded<?>> 132 @SuppressWarnings("unchecked") 133 public final <R> Proxy<R> proxy(final ProxySpecification ps, final Supplier<? extends R> instanceSupplier) { 134 return (Proxy<R>)clientProxyInstances.computeIfAbsent(ps, ps0 -> { 135 try { 136 final Class<?> proxyClass = this.proxyClass(ps); 137 this.getClass().getModule().addReads(proxyClass.getModule()); 138 return 139 this.lookup(proxyClass).findConstructor(proxyClass, methodType(void.class, Supplier.class)) 140 .asType(methodType(Object.class, Supplier.class)) 141 .invokeExact(instanceSupplier); 142 } catch (final RuntimeException | Error e) { 143 throw e; 144 } catch (final Throwable e) { 145 throw new ReferenceException(e.getMessage(), e); 146 } 147 }); 148 } 149 150 @Override // AbstractClientProxier<DynamicType.Unloaded<?>> 151 protected final Class<?> proxyClass(final DynamicType.Unloaded<?> dtu, final ClassLoader cl) 152 throws ClassNotFoundException { 153 // getTypeName() invoked on a TypeDescription will be its binary name (required by Class#forName(String)): 154 // https://javadoc.io/static/net.bytebuddy/byte-buddy/1.17.3/net/bytebuddy/description/type/TypeDefinition.html#getTypeName-- 155 final String binaryName = dtu.getTypeDescription().getSuperClass().asErasure().getTypeName(); 156 final Class<?> superclass = Class.forName(binaryName, false, cl); 157 return dtu.load(superclass.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(lookup(superclass))).getLoaded(); 158 } 159 160}