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