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.Id;
033import org.microbean.bean.Request;
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 r the {@link Request} necessitating this invocation; 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 Request} 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 Request<R> r, final Supplier<? extends R> instanceSupplier) {
173    return this.clientProxy(r.beanReduction().bean().id(), instanceSupplier);
174  }
175
176  // Called only by #clientProxy(Request, Supplier).
177  private final <R> R clientProxy(final Id id, final Supplier<? extends R> instanceSupplier) {
178    return this.clientProxy(id.types(), id.attributes(), instanceSupplier);
179  }
180
181  // Called only by #clientProxy(Id, Supplier).
182  final <R> R clientProxy(final BeanTypeList types, final Object attributes, final Supplier<? extends R> instanceSupplier) {
183    @SuppressWarnings("unchecked")
184    final ClientProxy<R> cp =
185      (ClientProxy<R>)clientProxyInstances.computeIfAbsent(new Key(new ProxySpecification(this.domain(), types), attributes),
186                                                           k -> {
187                                                             try {
188                                                               return this.instantiate(k.proxySpecification(), instanceSupplier);
189                                                             } catch (final RuntimeException | Error e) {
190                                                               throw e;
191                                                             } catch (final Throwable e) {
192                                                               throw new ReferenceException(e.getMessage(), e);
193                                                             }
194                                                           });
195    return cp.$cast();
196  }
197
198  // Called only by default implementation of #instantiate(ProxySpecification, Supplier)
199  private final Class<?> clientProxyClass(final ProxySpecification ps) throws ClassNotFoundException {
200    final String name = ps.name();
201    final ClassLoader cl = this.classLoader((TypeElement)ps.superclass().asElement());
202    Class<?> c;
203    try {
204      c = cl.loadClass(name);
205    } catch (ClassNotFoundException primaryLoadException) {
206      try {
207        c = this.clientProxyClass(this.generate(ps), cl);
208        if (c == null) {
209          primaryLoadException = new ClassNotFoundException(name + "; clientProxyClass(generate(" + ps + "), " + cl + " == null");
210          throw primaryLoadException;
211        }
212        if (LOGGER.isLoggable(DEBUG)) {
213          LOGGER.log(DEBUG, "Proxy class generated because it was not initially found", primaryLoadException);
214        }
215      } catch (final ClassNotFoundException | RuntimeException generationException) {
216        try {
217          // We try again; generation may have failed because of a race condition defining a class in the
218          // classloader. This will probably fail 99.9% of the time.
219          c = cl.loadClass(name);
220        } catch (final ClassNotFoundException secondaryLoadException) {
221          generationException.addSuppressed(primaryLoadException);
222          secondaryLoadException.addSuppressed(generationException);
223          throw secondaryLoadException;
224        } catch (final Error e) {
225          e.addSuppressed(generationException);
226          throw e;
227        } catch (final Throwable wtf) {
228          generationException.addSuppressed(wtf);
229          throw generationException;
230        }
231      } catch (final Error e) {
232        e.addSuppressed(primaryLoadException);
233        throw e;
234      } catch (final Throwable wtf) {
235        primaryLoadException.addSuppressed(wtf);
236        throw primaryLoadException;
237      }
238    }
239    return c;
240  }
241
242  /**
243   * Called by the default implementation of the {@link #instantiate(ProxySpecification, Supplier)} method, converts the
244   * supplied class definition into a {@link Class}, using the supplied {@link ClassLoader}, and returns it (optional
245   * operation).
246   *
247   * <p>If an override of the {@link #instantiate(ProxySpecification, Supplier)} method does something entirely
248   * different, this method may never be called.</p>
249   *
250   * <p><strong>The default implementation of this method throws an {@link UnsupportedOperationException}.</strong></p>
251   *
252   * <p>In general, implementations of this method should not call the {@link #instantiate(ProxySpecification,
253   * Supplier)} method, or undefined behavior may result.</p>
254   *
255   * @param t a class definition; must not be {@code null}
256   *
257   * @param cl a {@link ClassLoader}; must not be {@code null}
258   *
259   * @return a non-{@code null} {@link Class}
260   *
261   * @exception NullPointerException if any argument is {@code null}
262   *
263   * @exception UnsupportedOperationException if class generation is not supported by this {@link AbstractClientProxier}
264   *
265   * @exception ClassNotFoundException if an invocation of {@link ClassLoader#loadClass(String)} fails
266   *
267   * @see #instantiate(ProxySpecification, Supplier)
268   */
269  // Turns a T into a Class (loads it) using a ClassLoader.
270  protected Class<?> clientProxyClass(final T t, final ClassLoader cl) throws ClassNotFoundException {
271    throw new UnsupportedOperationException();
272  }
273
274  /**
275   * Returns the {@link Domain} {@linkplain #AbstractClientProxier(Domain) supplied at construction time}.
276   *
277   * @return a non-{@code null} {@link Domain}
278   *
279   * @see #AbstractClientProxier(Domain)
280   */
281  protected final Domain domain() {
282    return this.domain;
283  }
284
285  /**
286   * Called indirectly by the default implementation of the {@link #instantiate(ProxySpecification, Supplier)} method,
287   * creates a generated class definition from the information present in the supplied {@link ProxySpecification}, and
288   * returns it for eventual supplying to an inovcation of the {@link #clientProxyClass(Object, ClassLoader)} method
289   * (optional operation).
290   *
291   * <p>If an override of the {@link #instantiate(ProxySpecification, Supplier)} method does something entirely
292   * different, this method may never be called.</p>
293   *
294   * <p><strong>The default implementation of this method throws an {@link UnsupportedOperationException}.</strong></p>
295   *
296   * <p>Implementations of this method must not call the {@link #instantiate(ProxySpecification, Supplier)} method, or
297   * undefined behavior may result.</p>
298   *
299   * @param ps a {@link ProxySpecification}; must not be {@code null}
300   *
301   * @return a non-{@code null} generated class definition
302   *
303   * @exception NullPointerException if {@code ps} is {@code null}
304   *
305   * @exception UnsupportedOperationException if class generation is unsupported by this {@link AbstractClientProxier}
306   * implementation
307   *
308   * @exception Throwable if creation of the generated class definition fails
309   *
310   * @see #clientProxyClass(Object, ClassLoader)
311   */
312  protected T generate(final ProxySpecification ps) throws Throwable {
313    throw new UnsupportedOperationException();
314  }
315
316  /**
317   * Called indirectly by the {@link #clientProxy(Request, Supplier)} method when a new {@link ClientProxy
318   * ClientProxy&lt;R&gt;} instance needs to be created, creates a new instance of a {@link ClientProxy
319   * ClientProxy&lt;R&gt;} that proxies contextual instances supplied by the supplied {@code instanceSupplier}, and
320   * returns it.
321   *
322   * <p>The default implementation of this method eventually calls the {@link #instantiate(Class, Supplier)} method with
323   * the return value of an indirect invocation of the {@link #clientProxyClass(Object, ClassLoader)} method and the
324   * supplied {@link Supplier} and returns the result.</p>
325   *
326   * <p>Overrides of this method that do something entirely different are not obligated to call the {@link
327   * #clientProxyClass(Object, ClassLoader)} method or the {@link #instantiate(Class, Supplier)} method. In such a case,
328   * <strong>those methods will be effectively orphaned</strong>.</p>
329   *
330   * @param <R> the type of contextual instance being proxied
331   *
332   * @param ps a {@link ProxySpecification}; must not be {@code null}
333   *
334   * @param instanceSupplier a {@link Supplier} of contextual instances of the {@code <R>} type; must not be {@code null}
335   *
336   * @return a non-{@code null} {@link ClientProxy ClientProxy&lt;R&gt;}
337   *
338   * @exception NullPointerException if any argument is {@code null}
339   *
340   * @exception ReferenceException if instantiation fails
341   *
342   * @exception Throwable if instantiation fails
343   *
344   * @see #clientProxyClass(Object, ClassLoader)
345   *
346   * @see #instantiate(Class, Supplier)
347   */
348  protected <R> ClientProxy<R> instantiate(final ProxySpecification ps, final Supplier<? extends R> instanceSupplier)
349    throws Throwable {
350    return this.instantiate(this.clientProxyClass(ps), instanceSupplier);
351  }
352
353  /**
354   * Called by the {@link #instantiate(ProxySpecification, Supplier)} method, creates a new instance of the supplied
355   * {@code clientProxyClass} that proxies contextual instances supplied by the supplied {@code instanceSupplier}.
356   *
357   * <p>If a subclass overrides the {@link #instantiate(ProxySpecification, Supplier)} method to do something entirely
358   * different, <strong>this method may not be called</strong>.</p>
359   *
360   * <p>Overrides of this method must not call the {@link #instantiate(ProxySpecification, Supplier)} method or
361   * undefined behavior may result.</p>
362   *
363   * @param <R> the type of contextual instance being proxied
364   *
365   * @param clientProxyClass a {@link Class} {@linkplain Class#isAssignableFrom(Class) assignable to} {@link ClientProxy
366   * ClientProxy.class} and {@linkplain ClientProxy#$cast() castable to} the {@code R} type; must not be {@code null}
367   *
368   * @param instanceSupplier a {@link Supplier} of contextual instances of the {@code R} type; must not be {@code
369   * null}
370   *
371   * @return a non-{@code null} {@link ClientProxy ClientProxy&lt;R&gt;}
372   *
373   * @see #instantiate(ProxySpecification, Supplier)
374   *
375   * @exception NullPointerException if any argument is {@code null}
376   *
377   * @exception ReferenceException if instantiation fails
378   *
379   * @exception Throwable if instantiation fails
380   */
381  protected <R> ClientProxy<R> instantiate(final Class<?> clientProxyClass, final Supplier<? extends R> instanceSupplier)
382    throws Throwable {
383    return (ClientProxy<R>)this.clientProxyClassConstructorMethodHandles
384      .get(clientProxyClass)
385      .invokeExact(instanceSupplier);
386  }
387
388  /**
389   * Returns a {@link Lookup} suitable for the supplied {@link Class}.
390   *
391   * @param c a {@link Class}; must not be {@code null}
392   *
393   * @return a {@link Lookup}; never {@code null}
394   *
395   * @exception NullPointerException if {@code c} is {@code null}
396   *
397   * @see java.lang.invoke.MethodHandles#lookup()
398   *
399   * @see Lookup#in(Class)
400   *
401   * @see java.lang.invoke.MethodHandles#privateLookupIn(Class, Lookup)
402   */
403  protected abstract Lookup lookup(final Class<?> c);
404
405
406  /*
407   * Inner and nested classes.
408   */
409
410
411  private static final record Key(ProxySpecification proxySpecification, Object attributes) {}
412
413}