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<R>}, 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<R>}; 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<R>} instance needs to be created, creates a new instance of a {@link ClientProxy 314 * ClientProxy<R>} 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<R>} 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<R>} 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}