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.System.Logger;
017
018import java.lang.invoke.MethodHandle;
019import java.lang.invoke.MethodHandles.Lookup;
020
021import java.util.List;
022import java.util.Objects;
023
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ConcurrentMap;
026
027import java.util.function.Supplier;
028
029import javax.lang.model.element.TypeElement;
030
031import org.microbean.bean.BeanTypeList;
032import org.microbean.bean.Creation;
033import org.microbean.bean.Id;
034
035import org.microbean.construct.Domain;
036
037import static java.lang.System.getLogger;
038import static java.lang.System.Logger.Level.DEBUG;
039
040import static java.lang.invoke.MethodType.methodType;
041
042/**
043 * A skeletal implementation of the {@link ClientProxier} interface.
044 *
045 * @param <T> the type of descriptor this implementation uses to represent (normally unloaded) generated classes
046 *
047 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
048 */
049// <T>: the type of descriptor the underlying mechanism uses to represent (normally unloaded) generated classes;
050// ByteBuddy uses DynamicType.Unloaded<?>; Red Hat projects use ClassFile or something like it, ReflectiveClientProxier
051// uses Class<?>, and so on
052public abstract class AbstractClientProxier<T> implements ClientProxier {
053
054
055  /*
056   * Static fields.
057   */
058
059
060  private static final Logger LOGGER = getLogger(AbstractClientProxier.class.getName());
061
062  private static final ConcurrentMap<Key, ClientProxy<?>> clientProxyInstances = new ConcurrentHashMap<>();
063
064
065  /*
066   * Instance fields.
067   */
068
069
070  // Not sure the cache is worth it if we're storing clientProxyInstances
071  private final ClassValue<MethodHandle> clientProxyClassConstructorMethodHandles;
072
073  private final Domain domain;
074
075
076  /*
077   * Constructors.
078   */
079
080
081  /**
082   * Creates a new {@link AbstractClientProxier}.
083   *
084   * @param domain a {@link Domain}; must not be {@code null}
085   *
086   * @exception NullPointerException if {@code domain} is {@code null}
087   */
088  protected AbstractClientProxier(final Domain domain) {
089    super();
090    this.domain = Objects.requireNonNull(domain, "domain");
091    this.clientProxyClassConstructorMethodHandles = new ClassValue<>() {
092        @Override
093        protected final MethodHandle computeValue(final Class<?> c) {
094          this.getClass().getModule().addReads(c.getModule());
095          try {
096            // The class the constructor creates will not be ClientProxy.class (that's an interface), but any invoker of
097            // the corresponding MethodHandle won't know that, so adapt the handle's return type to be ClientProxy.class
098            // (the erasure of one of the supertypes of c) instead of c. Now callers can call
099            // (ClientProxy<?>)h.invokeExact(someSupplier) on it. See #instantiate(Class, Supplier) and
100            // MethodHandle#invokeExact().
101            return
102              lookup(c).findConstructor(c, methodType(void.class, Supplier.class))
103              .asType(methodType(ClientProxy.class, Supplier.class));
104          } catch (final RuntimeException | Error e) {
105            throw e;
106          } catch (final Throwable e) {
107            throw new ReferenceException(e.getMessage(), e);
108          }
109        }
110      };
111  }
112
113
114  /*
115   * Instance methods.
116   */
117
118
119  /**
120   * Given a {@link TypeElement} that {@linkplain javax.lang.model.element.ElementKind#isDeclaredType() is a declared
121   * type}, returns a {@link ClassLoader} suitable for eventually transforming it into a {@link Class}.
122   *
123   * <p>The default implementation of this method returns the {@linkplain Thread#getContextClassLoader() context
124   * classloader}.</p>
125   *
126   * @param e a {@link TypeElement}; must not be {@code null}; must be a {@linkplain
127   * javax.lang.model.element.ElementKind#isDeclaredType() declared type}
128   *
129   * @return a non-{@code null} {@link ClassLoader}
130   *
131   * @exception NullPointerException if {@code e} is {@code null}
132   *
133   * @exception IllegalArgumentException if {@code e} {@linkplain javax.lang.model.element.ElementKind#isDeclaredType()
134   * is not a declared type}
135   */
136  protected ClassLoader classLoader(final TypeElement e) {
137    if (!e.getKind().isDeclaredType()) {
138      throw new IllegalArgumentException("e: " + e);
139    }
140    return Thread.currentThread().getContextClassLoader();
141  }
142
143  /**
144   * Returns a <dfn>contextual reference</dfn> of type {@code R}, which is also a {@link ClientProxy
145   * ClientProxy&lt;R&gt;}, given a {@link Supplier} of <dfn>contextual instances</dfn> of the appropriate type.
146   *
147   * <p>This method never returns {@code null}.</p>
148   *
149   * @param <R> the type of the contextual reference
150   *
151   * @param id an {@link Id} qualifying the contextual instance that will be proxied; must not be {@code null}
152   *
153   * @param instanceSupplier a {@link Supplier} of contextual instances of type {@code R}; must not be {@code null}
154   *
155   * @return a contextual reference that is also a {@link ClientProxy ClientProxy&lt;R&gt;}; never {@code null}
156   *
157   * @exception NullPointerException if any argument is {@code null}, or if the {@link #instantiate(ProxySpecification,
158   * Supplier)} method, invoked as part of the implementation of this method, returns {@code null}
159   *
160   * @exception IllegalArgumentException if the supplied {@link Id} is not proxiable for any reason
161   *
162   * @exception ReferenceException if the {@link #instantiate(ProxySpecification, Supplier)} method throws a checked
163   * {@link Exception}
164   *
165   * @see #instantiate(ProxySpecification, Supplier)
166   *
167   * @see ClientProxy
168   */
169  // By the time we get here, proxying is absolutely called for. (If we can't return an R, something went wrong, it's
170  // not that the inputs were unsuitable.)
171  @Override // ClientProxier
172  public final <R> R clientProxy(final Id id, final Supplier<? extends R> instanceSupplier) {
173    return this.clientProxy(id.types(), id.attributes(), instanceSupplier);
174  }
175
176  // Called only by #clientProxy(Id, Supplier).
177  final <R> R clientProxy(final BeanTypeList types, final Object attributes, final Supplier<? extends R> instanceSupplier) {
178    @SuppressWarnings("unchecked")
179    final ClientProxy<R> cp =
180      (ClientProxy<R>)clientProxyInstances.computeIfAbsent(new Key(new ProxySpecification(this.domain(), types), attributes),
181                                                           k -> {
182                                                             try {
183                                                               return this.instantiate(k.proxySpecification(), instanceSupplier);
184                                                             } catch (final RuntimeException | Error e) {
185                                                               throw e;
186                                                             } catch (final Throwable e) {
187                                                               throw new ReferenceException(e.getMessage(), e);
188                                                             }
189                                                           });
190    return cp.$cast();
191  }
192
193  // Called only by default implementation of #instantiate(ProxySpecification, Supplier)
194  private final Class<?> clientProxyClass(final ProxySpecification ps) throws ClassNotFoundException {
195    final String name = ps.name();
196    final ClassLoader cl = this.classLoader((TypeElement)ps.superclass().asElement());
197    Class<?> c;
198    try {
199      c = cl.loadClass(name);
200    } catch (ClassNotFoundException primaryLoadException) {
201      try {
202        c = this.clientProxyClass(this.generate(ps), cl);
203        if (c == null) {
204          primaryLoadException = new ClassNotFoundException(name + "; clientProxyClass(generate(" + ps + "), " + cl + " == null");
205          throw primaryLoadException;
206        }
207        if (LOGGER.isLoggable(DEBUG)) {
208          LOGGER.log(DEBUG, "Proxy class generated because it was not initially found", primaryLoadException);
209        }
210      } catch (final ClassNotFoundException | RuntimeException generationException) {
211        try {
212          // We try again; generation may have failed because of a race condition defining a class in the
213          // classloader. This will probably fail 99.9% of the time.
214          c = cl.loadClass(name);
215        } catch (final ClassNotFoundException secondaryLoadException) {
216          generationException.addSuppressed(primaryLoadException);
217          secondaryLoadException.addSuppressed(generationException);
218          throw secondaryLoadException;
219        } catch (final Error e) {
220          e.addSuppressed(generationException);
221          throw e;
222        } catch (final Throwable wtf) {
223          generationException.addSuppressed(wtf);
224          throw generationException;
225        }
226      } catch (final Error e) {
227        e.addSuppressed(primaryLoadException);
228        throw e;
229      } catch (final Throwable wtf) {
230        primaryLoadException.addSuppressed(wtf);
231        throw primaryLoadException;
232      }
233    }
234    return c;
235  }
236
237  /**
238   * Called by the default implementation of the {@link #instantiate(ProxySpecification, Supplier)} method, converts the
239   * supplied class definition into a {@link Class}, using the supplied {@link ClassLoader}, and returns it (optional
240   * operation).
241   *
242   * <p>If an override of the {@link #instantiate(ProxySpecification, Supplier)} method does something entirely
243   * different, this method may never be called.</p>
244   *
245   * <p><strong>The default implementation of this method throws an {@link UnsupportedOperationException}.</strong></p>
246   *
247   * <p>In general, implementations of this method should not call the {@link #instantiate(ProxySpecification,
248   * Supplier)} method, or undefined behavior may result.</p>
249   *
250   * @param t a class definition; must not be {@code null}
251   *
252   * @param cl a {@link ClassLoader}; must not be {@code null}
253   *
254   * @return a non-{@code null} {@link Class}
255   *
256   * @exception NullPointerException if any argument is {@code null}
257   *
258   * @exception UnsupportedOperationException if class generation is not supported by this {@link AbstractClientProxier}
259   *
260   * @exception ClassNotFoundException if an invocation of {@link ClassLoader#loadClass(String)} fails
261   *
262   * @see #instantiate(ProxySpecification, Supplier)
263   */
264  // Turns a T into a Class (loads it) using a ClassLoader.
265  protected Class<?> clientProxyClass(final T t, final ClassLoader cl) throws ClassNotFoundException {
266    throw new UnsupportedOperationException();
267  }
268
269  /**
270   * Returns the {@link Domain} {@linkplain #AbstractClientProxier(Domain) supplied at construction time}.
271   *
272   * @return a non-{@code null} {@link Domain}
273   *
274   * @see #AbstractClientProxier(Domain)
275   */
276  protected final Domain domain() {
277    return this.domain;
278  }
279
280  /**
281   * Called indirectly by the default implementation of the {@link #instantiate(ProxySpecification, Supplier)} method,
282   * creates a generated class definition from the information present in the supplied {@link ProxySpecification}, and
283   * returns it for eventual supplying to an inovcation of the {@link #clientProxyClass(Object, ClassLoader)} method
284   * (optional operation).
285   *
286   * <p>If an override of the {@link #instantiate(ProxySpecification, Supplier)} method does something entirely
287   * different, this method may never be called.</p>
288   *
289   * <p><strong>The default implementation of this method throws an {@link UnsupportedOperationException}.</strong></p>
290   *
291   * <p>Implementations of this method must not call the {@link #instantiate(ProxySpecification, Supplier)} method, or
292   * undefined behavior may result.</p>
293   *
294   * @param ps a {@link ProxySpecification}; must not be {@code null}
295   *
296   * @return a non-{@code null} generated class definition
297   *
298   * @exception NullPointerException if {@code ps} is {@code null}
299   *
300   * @exception UnsupportedOperationException if class generation is unsupported by this {@link AbstractClientProxier}
301   * implementation
302   *
303   * @exception Throwable if creation of the generated class definition fails
304   *
305   * @see #clientProxyClass(Object, ClassLoader)
306   */
307  protected T generate(final ProxySpecification ps) throws Throwable {
308    throw new UnsupportedOperationException();
309  }
310
311  /**
312   * Called indirectly by the {@link #clientProxy(Id, Supplier)} method when a new {@link ClientProxy
313   * ClientProxy&lt;R&gt;} instance needs to be created, creates a new instance of a {@link ClientProxy
314   * ClientProxy&lt;R&gt;} that proxies contextual instances supplied by the supplied {@code instanceSupplier}, and
315   * returns it.
316   *
317   * <p>The default implementation of this method eventually calls the {@link #instantiate(Class, Supplier)} method with
318   * the return value of an indirect invocation of the {@link #clientProxyClass(Object, ClassLoader)} method and the
319   * supplied {@link Supplier} and returns the result.</p>
320   *
321   * <p>Overrides of this method that do something entirely different are not obligated to call the {@link
322   * #clientProxyClass(Object, ClassLoader)} method or the {@link #instantiate(Class, Supplier)} method. In such a case,
323   * <strong>those methods will be effectively orphaned</strong>.</p>
324   *
325   * @param <R> the type of contextual instance being proxied
326   *
327   * @param ps a {@link ProxySpecification}; must not be {@code null}
328   *
329   * @param instanceSupplier a {@link Supplier} of contextual instances of the {@code <R>} type; must not be {@code null}
330   *
331   * @return a non-{@code null} {@link ClientProxy ClientProxy&lt;R&gt;}
332   *
333   * @exception NullPointerException if any argument is {@code null}
334   *
335   * @exception ReferenceException if instantiation fails
336   *
337   * @exception Throwable if instantiation fails
338   *
339   * @see #clientProxyClass(Object, ClassLoader)
340   *
341   * @see #instantiate(Class, Supplier)
342   */
343  protected <R> ClientProxy<R> instantiate(final ProxySpecification ps, final Supplier<? extends R> instanceSupplier)
344    throws Throwable {
345    return this.instantiate(this.clientProxyClass(ps), instanceSupplier);
346  }
347
348  /**
349   * Called by the {@link #instantiate(ProxySpecification, Supplier)} method, creates a new instance of the supplied
350   * {@code clientProxyClass} that proxies contextual instances supplied by the supplied {@code instanceSupplier}.
351   *
352   * <p>If a subclass overrides the {@link #instantiate(ProxySpecification, Supplier)} method to do something entirely
353   * different, <strong>this method may not be called</strong>.</p>
354   *
355   * <p>Overrides of this method must not call the {@link #instantiate(ProxySpecification, Supplier)} method or
356   * undefined behavior may result.</p>
357   *
358   * @param <R> the type of contextual instance being proxied
359   *
360   * @param clientProxyClass a {@link Class} {@linkplain Class#isAssignableFrom(Class) assignable to} {@link ClientProxy
361   * ClientProxy.class} and {@linkplain ClientProxy#$cast() castable to} the {@code R} type; must not be {@code null}
362   *
363   * @param instanceSupplier a {@link Supplier} of contextual instances of the {@code R} type; must not be {@code
364   * null}
365   *
366   * @return a non-{@code null} {@link ClientProxy ClientProxy&lt;R&gt;}
367   *
368   * @see #instantiate(ProxySpecification, Supplier)
369   *
370   * @exception NullPointerException if any argument is {@code null}
371   *
372   * @exception ReferenceException if instantiation fails
373   *
374   * @exception Throwable if instantiation fails
375   */
376  protected <R> ClientProxy<R> instantiate(final Class<?> clientProxyClass, final Supplier<? extends R> instanceSupplier)
377    throws Throwable {
378    return (ClientProxy<R>)this.clientProxyClassConstructorMethodHandles
379      .get(clientProxyClass)
380      .invokeExact(instanceSupplier);
381  }
382
383  /**
384   * Returns a {@link Lookup} suitable for the supplied {@link Class}.
385   *
386   * @param c a {@link Class}; must not be {@code null}
387   *
388   * @return a {@link Lookup}; never {@code null}
389   *
390   * @exception NullPointerException if {@code c} is {@code null}
391   *
392   * @see java.lang.invoke.MethodHandles#lookup()
393   *
394   * @see Lookup#in(Class)
395   *
396   * @see java.lang.invoke.MethodHandles#privateLookupIn(Class, Lookup)
397   */
398  protected abstract Lookup lookup(final Class<?> c);
399
400
401  /*
402   * Inner and nested classes.
403   */
404
405
406  private static final record Key(ProxySpecification proxySpecification, Object attributes) {}
407
408}