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}