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}