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.proxy;
015
016import java.lang.invoke.MethodHandles;
017import java.lang.invoke.MethodHandles.Lookup;
018
019import java.lang.System.Logger;
020
021import java.util.Objects;
022
023import java.util.function.Supplier;
024
025import org.microbean.construct.Domain;
026
027import static java.lang.System.getLogger;
028
029import static java.lang.System.Logger.Level.DEBUG;
030
031/**
032 * An {@link AbstractProxier} built using some kind of <dfn>toolkit</dfn>, such as <a
033 * href="https://bytebuddy.net/#/">Byte Buddy</a> and the like.
034 *
035 * @param <PS> the {@link ProxySpecification} type
036 *
037 * @param <T> the toolkit representation of an unloaded {@link Class}
038 *
039 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
040 */
041public abstract non-sealed class AbstractToolkitProxier<PS extends ProxySpecification, T> extends AbstractProxier<PS> {
042
043  private static final Logger LOGGER = getLogger(AbstractToolkitProxier.class.getName());
044
045  private final Lookup lookup;
046
047  /**
048   * Creates a new {@link AbstractToolkitProxier}.
049   *
050   * @param domain a {@link Domain}; must not be {@code null}
051   *
052   * @param lookup a {@link Lookup}; must not be {@code null}
053   *
054   * @exception NullPointerException if any argument is {@code null}
055   *
056   * @see #lookup(Class)
057   */
058  protected AbstractToolkitProxier(final Domain domain, final Lookup lookup) {
059    super(domain);
060    // see #lookup(Class) below
061    this.lookup = Objects.requireNonNull(lookup, "lookup");
062  }
063
064  /**
065   * Creates a generated class definition from the information present in the supplied {@link ProxySpecification}, and
066   * returns it for eventual supplying to an inovcation of the {@link #proxyClass(Object, ClassLoader)} method.
067   *
068   * @param ps a {@link ProxySpecification}; must not be {@code null}
069   *
070   * @return a non-{@code null} generated class definition
071   *
072   * @exception NullPointerException if {@code ps} is {@code null}
073   *
074   * @exception Throwable if creation of the generated class definition fails
075   *
076   * @see #proxyClass(Object, ClassLoader)
077   */
078  protected abstract T generate(final PS ps) throws Throwable;
079
080  /**
081   * Returns a {@link Lookup} suitable for the supplied {@link Class}.
082   *
083   * @param c a {@link Class}; must not be {@code null}
084   *
085   * @return a {@link Lookup} suitable for the supplied {@link Class}
086   8
087   * @see Lookup#in(Class)
088   */
089  protected Lookup lookup(final Class<?> c) {
090    return this.lookup.in(c);
091  }
092
093  /**
094   * Loads and returns the proxy class corresponding to the supplied proxy specification, {@linkplain
095   * #generate(ProxySpecification) generating it} first if necessary.
096   *
097   * @param ps a proxy specification; must not be {@code null}
098   *
099   * @return a non-{@code null} {@link Class} that is guaranteed to be {@linkplain Class#isAssignableFrom(Class)
100   * assignable to} {@link Proxy Proxy.class}
101   *
102   * @exception ClassNotFoundException if the class could not be found
103   *
104   * @see #generate(ProxySpecification)
105   *
106   * @see #proxyClass(Object, ClassLoader)
107   */
108  protected final Class<?> proxyClass(final PS ps) throws ClassNotFoundException {
109    final String name = ps.name();
110    final ClassLoader cl = this.classLoader();
111    Class<?> pc;
112    try {
113      pc = cl.loadClass(name);
114    } catch (ClassNotFoundException primaryLoadException) {
115      try {
116        pc = this.proxyClass(this.generate(ps), cl);
117        if (pc == null) {
118          primaryLoadException = new ClassNotFoundException(name + "; proxyClass(generate(" + ps + "), " + cl + " == null");
119          throw primaryLoadException;
120        }
121        if (LOGGER.isLoggable(DEBUG)) {
122          LOGGER.log(DEBUG, "Proxy class generated because it was not initially found", primaryLoadException);
123        }
124      } catch (final ClassNotFoundException | RuntimeException generationException) {
125        try {
126          // We try again; generation may have failed because of a race condition defining a class in the
127          // classloader. This will probably fail 99.9% of the time.
128          pc = cl.loadClass(name);
129        } catch (final ClassNotFoundException secondaryLoadException) {
130          generationException.addSuppressed(primaryLoadException);
131          secondaryLoadException.addSuppressed(generationException);
132          throw secondaryLoadException;
133        } catch (final Error e) {
134          e.addSuppressed(generationException);
135          throw e;
136        } catch (final Throwable wtf) {
137          generationException.addSuppressed(wtf);
138          throw generationException;
139        }
140      } catch (final Error e) {
141        e.addSuppressed(primaryLoadException);
142        throw e;
143      } catch (final Throwable wtf) {
144        primaryLoadException.addSuppressed(wtf);
145        throw primaryLoadException;
146      }
147    }
148    return pc;
149  }
150
151  /**
152   * Called by the {@link #proxyClass(ProxySpecification)} method, converts the supplied class definition into a {@link
153   * Class}, using the supplied {@link ClassLoader}, and returns it.
154   *
155   * <p>Overrides of this method must not call the {@link #proxyClass(ProxySpecification)} method or undefined behavior
156   * may result.</p>
157   *
158   * @param t a class definition; must not be {@code null}
159   *
160   * @param cl a {@link ClassLoader}; must not be {@code null}
161   *
162   * @return a non-{@code null} {@link Class}
163   *
164   * @exception NullPointerException if any argument is {@code null}
165   *
166   * @exception ClassNotFoundException if an invocation of {@link ClassLoader#loadClass(String)} fails
167   *
168   * @see #proxyClass(ProxySpecification)
169   */
170  // Turns a T into a Class (loads it) using a ClassLoader.
171  protected abstract Class<?> proxyClass(final T t, final ClassLoader cl) throws ClassNotFoundException;
172
173}