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