diff --git a/README.md b/README.md index d8e8472..c1ea34e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # microBean™ Client Proxy: Byte Buddy [![Maven -Central](https://img.shields.io/maven-central/v/org.microbean/microbean-clientproxy-bytebuddy.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.microbean/microbean-clientproxy-bytebuddy) +Central](https://img.shields.io/maven-central/v/org.microbean/microbean-clientproxy-bytebuddy.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/org.microbean/microbean-clientproxy-bytebuddy) + +![0% AI](https://img.shields.io/badge/%F0%9F%A4%96_AI-0%25_%F0%9F%8C%BC-brightgreen) The microBean™ Client Proxy: Byte Buddy project provides classes and interfaces assisting with implementing client @@ -22,15 +24,16 @@ microBean™ Client Proxy: Byte Buddy requires a Java runtime of version 19 or h # Installation -microBean™ Client Proxy: Byte Buddy is available on [Maven Central](https://search.maven.org/). Include microBean™ -Client Proxy: Byte Buddy as a Maven dependency: +microBean™ Client Proxy: Byte Buddy is available on [Maven +Central](https://central.sonatype.com/artifact/org.microbean/microbean-clientproxy-bytebuddy). Include microBean™ Client +Proxy: Byte Buddy as a Maven dependency: ```xml org.microbean microbean-clientproxy-bytebuddy - - 0.0.2 + + 0.0.3 ``` diff --git a/pom.xml b/pom.xml index 9cd8fef..2718e14 100644 --- a/pom.xml +++ b/pom.xml @@ -130,49 +130,49 @@ net.bytebuddy byte-buddy - 1.18.3 + 1.18.5 org.microbean microbean-assign - 0.0.11 + 0.0.14 org.microbean microbean-bean - 0.0.22 + 0.0.23 org.microbean microbean-construct - 0.0.18 + 0.0.24 org.microbean microbean-producer - 0.0.3 + 0.0.4 org.microbean microbean-proxy - 0.0.5 + 0.0.6 org.microbean microbean-reference - 0.0.5 + 0.0.6 org.microbean microbean-scopelet - 0.0.11 + 0.0.12 @@ -363,7 +363,7 @@ com.puppycrawl.tools checkstyle - 12.3.0 + 13.2.0 @@ -384,7 +384,7 @@ maven-compiler-plugin - 3.14.1 + 3.15.0 -Xlint:all @@ -394,7 +394,7 @@ maven-dependency-plugin - 3.9.0 + 3.10.0 maven-deploy-plugin @@ -484,9 +484,6 @@ maven-surefire-plugin 3.5.4 - - -XX:StartFlightRecording=duration=10s,filename=${project.build.directory}/recording.jfr - maven-toolchains-plugin @@ -500,7 +497,7 @@ org.codehaus.mojo versions-maven-plugin - 2.20.1 + 2.21.0 io.smallrye @@ -510,7 +507,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + 0.10.0 true central.sonatype.com diff --git a/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxier.java b/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxier.java index 157c668..69b0110 100644 --- a/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxier.java +++ b/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxier.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * Copyright © 2025–2026 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -42,6 +42,8 @@ import static java.lang.invoke.MethodType.methodType; +import static java.util.Objects.requireNonNull; + /** * An {@link AbstractToolkitProxier} and {@link ClientProxier} that uses Byte * Buddy to {@linkplain #generate(ProxySpecification) generate} {@linkplain org.microbean.proxy.Proxy client @@ -54,7 +56,7 @@ public final class BBClientProxier extends AbstractToolkitProxier> implements ClientProxier { private static final Map clientProxyInstances = new ConcurrentHashMap<>(); - + private final TypeDefinitions tds; private final BBClientProxyClassGenerator g; @@ -62,7 +64,7 @@ public final class BBClientProxier extends AbstractToolkitProxier R clientProxy(final Id id, final Supplier instanceSupplier) { return this.proxy(new ProxySpecification(this.domain(), id), instanceSupplier).$cast(); } - + @Override // AbstractClientProxier> protected final DynamicType.Unloaded generate(final ProxySpecification ps) { return @@ -145,7 +147,6 @@ public final Proxy proxy(final ProxySpecification ps, final Supplier> protected final Class proxyClass(final DynamicType.Unloaded dtu, final ClassLoader cl) throws ClassNotFoundException { diff --git a/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxyClassGenerator.java b/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxyClassGenerator.java index 0959e55..34a7d67 100644 --- a/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxyClassGenerator.java +++ b/src/main/java/org/microbean/clientproxy/bytebuddy/BBClientProxyClassGenerator.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * Copyright © 2025–2026 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,7 +35,6 @@ import net.bytebuddy.implementation.HashCodeMethod; import net.bytebuddy.implementation.EqualsMethod; import net.bytebuddy.implementation.FieldAccessor; -import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.implementation.bytecode.assign.Assigner; @@ -45,13 +44,11 @@ import static java.util.Objects.requireNonNull; -import static net.bytebuddy.description.modifier.Ownership.STATIC; import static net.bytebuddy.description.modifier.SyntheticState.SYNTHETIC; import static net.bytebuddy.description.modifier.Visibility.PRIVATE; import static net.bytebuddy.description.modifier.Visibility.PUBLIC; import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType; -import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.typeVariable; import static net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.NO_CONSTRUCTORS; @@ -59,20 +56,16 @@ import static net.bytebuddy.implementation.MethodCall.invokeSelf; import static net.bytebuddy.matcher.ElementMatchers.any; -import static net.bytebuddy.matcher.ElementMatchers.hasParameters; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy; import static net.bytebuddy.matcher.ElementMatchers.isEquals; import static net.bytebuddy.matcher.ElementMatchers.isFinal; import static net.bytebuddy.matcher.ElementMatchers.isHashCode; import static net.bytebuddy.matcher.ElementMatchers.isPackagePrivate; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.isToString; import static net.bytebuddy.matcher.ElementMatchers.isVirtual; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments; @@ -89,7 +82,7 @@ public final class BBClientProxyClassGenerator { /** * Creates a new {@link BBClientProxyClassGenerator}. * - * @param typePool a {@link TypePool} (normally a {@link TypeElementTypePool}); must not be {@code null} + * @param typePool a non-{@code null} {@link TypePool} (normally a {@link TypeElementTypePool}) * * @exception NullPointerException if {@code typePool} is {@code null} */ @@ -101,14 +94,14 @@ public BBClientProxyClassGenerator(final TypePool typePool) { /** * Creates and returns a new {@link DynamicType.Unloaded} representing a client proxy class. * - * @param name the name of the client proxy class; must not be {@code null}; must be a valid Java class binary * name * - * @param superclass a {@link TypeDefinition} representing a superclass; must not be {@code null} + * @param superclass a non-{@code null} {@link TypeDefinition} representing a superclass * - * @param interfaces a {@link Collection} of {@link TypeDefinition}s representing interfaces the client proxy class - * will implement; must not be {@code null} + * @param interfaces a non-{@code null} {@link Collection} of {@link TypeDefinition}s representing interfaces the + * client proxy class will implement * * @return a new, non-{@code null} {@link DynamicType.Unloaded} representing a client proxy class * diff --git a/src/test/java/org/microbean/clientproxy/bytebuddy/TestBBClientProxier.java b/src/test/java/org/microbean/clientproxy/bytebuddy/TestBBClientProxier.java index 90bcc02..92dd813 100644 --- a/src/test/java/org/microbean/clientproxy/bytebuddy/TestBBClientProxier.java +++ b/src/test/java/org/microbean/clientproxy/bytebuddy/TestBBClientProxier.java @@ -1,6 +1,6 @@ /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- * - * Copyright © 2025 microBean™. + * Copyright © 2025–2026 microBean™. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -13,35 +13,31 @@ */ package org.microbean.clientproxy.bytebuddy; -import java.lang.constant.Constable; -import java.lang.constant.ConstantDesc; -import java.lang.constant.DynamicConstantDesc; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodHandleDesc; - +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import javax.lang.model.AnnotatedConstruct; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.microbean.attributes.Attributes; - -import org.microbean.assign.AttributedType; +import org.microbean.assign.Annotated; import org.microbean.assign.Selectable; import org.microbean.assign.Selectables; import org.microbean.bean.Bean; import org.microbean.bean.BeanQualifiersMatcher; -import org.microbean.bean.BeanTypeList; import org.microbean.bean.BeanTypeMatcher; import org.microbean.bean.BeanTypes; import org.microbean.bean.Beans; -import org.microbean.bean.Constant; import org.microbean.bean.Id; import org.microbean.bean.IdMatcher; import org.microbean.bean.Qualifiers; @@ -49,41 +45,41 @@ import org.microbean.construct.DefaultDomain; import org.microbean.construct.Domain; +import org.microbean.construct.element.SyntheticAnnotationMirror; +import org.microbean.construct.element.SyntheticAnnotationTypeElement; +import org.microbean.construct.element.SyntheticAnnotationValue; +import org.microbean.construct.element.SyntheticLocalVariableElement; + +import org.microbean.producer.InterceptorBindings; import org.microbean.producer.InterceptorBindingsMatcher; import org.microbean.proxy.Proxy; -import org.microbean.reference.Instances; import org.microbean.reference.Request; -import org.microbean.scopelet.MapBackedScopelet; import org.microbean.scopelet.NoneScopelet; import org.microbean.scopelet.ScopedInstances; -import org.microbean.scopelet.Scopelet; import org.microbean.scopelet.Scopes; import org.microbean.scopelet.SingletonScopelet; -import static java.lang.constant.ConstantDescs.BSM_INVOKE; +import static javax.lang.model.element.ElementKind.ENUM_CONSTANT; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.microbean.bean.Selectables.ambiguityReducing; -import static org.microbean.bean.Selectables.typesafeFiltering; final class TestBBClientProxier { private Domain domain; - private Qualifiers qualifiers; + private Qualifiers bq; private Request r; - private Instances instances; + private ScopedInstances scopedInstances; private Bean gorpBean; @@ -93,13 +89,20 @@ private TestBBClientProxier() { @BeforeEach final void setup() { - this.domain = new DefaultDomain(); - + final Domain domain = new DefaultDomain(); final BeanTypes beanTypes = new BeanTypes(domain); - - this.qualifiers = new Qualifiers(); - - final Scopes scopes = new Scopes(qualifiers); + final org.microbean.assign.Qualifiers aq = new org.microbean.assign.Qualifiers(domain); + final Qualifiers bq = new Qualifiers(domain, aq); // TODO: add predicate representing @Nonbinding + final org.microbean.scopelet.Qualifiers sq = new org.microbean.scopelet.Qualifiers(domain, aq); + final Scopes scopes = new Scopes(domain, aq, sq); + final AnnotationMirror anyQualifier = bq.anyQualifier(); + final AnnotationMirror defaultQualifier = bq.defaultQualifier(); + + // Set up the singleton scope annotation. + final SyntheticAnnotationMirror anyQualifierWithSingletonParentScope = new SyntheticAnnotationMirror(anyQualifier); + SyntheticAnnotationTypeElement.class.cast(anyQualifierWithSingletonParentScope.getAnnotationType().asElement()) + .getAnnotationMirrors() + .add(scopes.singletonScope()); // TODO: all this scopelet tunneling stuff is a specific instance of a general case that is already handle-able. If // you conceive of a Factory that can "create" (cache and return) all kinds of objects, and can use this tunneling @@ -108,79 +111,97 @@ final void setup() { // Rule: your parent scope goes on the Any qualifier. All beans have the Any qualifier (normally) so this is a // convenient way to "tunnel" a qualifier/scope without screwing up typesafe resolution and without requiring // something stupid like BeanAttributes in CDI. You just add meta-annotations. - final Attributes anyQualifierWithSingletonParentScope = Attributes.of("Any", qualifiers.qualifier(), scopes.singleton()); final Bean domainBean = new Bean<>(new Id(beanTypes.beanTypes(domain.declaredType(DefaultDomain.class.getCanonicalName())), - List.of(anyQualifierWithSingletonParentScope, qualifiers.defaultQualifier())), + List.of(anyQualifierWithSingletonParentScope, defaultQualifier)), c -> domain); final Bean beanTypesBean = new Bean<>(new Id(beanTypes.beanTypes(domain.declaredType(BeanTypes.class.getCanonicalName())), - List.of(anyQualifierWithSingletonParentScope, qualifiers.defaultQualifier())), + List.of(anyQualifierWithSingletonParentScope, defaultQualifier)), c -> beanTypes); - // TODO: we should do this for all the matchers, too, e.g. BeanTypeMatcher, BeanQualifiersMatcher, - // EventTypesMatcher, etc. etc. - // - // BBClientProxier too - // - // Then all these handcrafted beans could say what they need as dependencies and assign them - - final Attributes application = - Attributes.of("Application", - scopes.normal(), - Map.of(), - Map.of("Application", - List.of(qualifiers.qualifier(), - scopes.scope(), - scopes.singleton()))); - - // Set up scopes. + // TODO: other system level beans + + // Set up the application scope. To do this we need to do what Scopes does for other scopes. + final List as = domain.typeElement("java.lang.annotation.Documented").getAnnotationMirrors(); + assert as.size() == 3; // @Documented, @Retention, @Target, in that order, all annotated in turn with each other + final AnnotationMirror documentedAnnotation = as.get(0); + final AnnotationMirror retentionAnnotation = as.get(1); + final List documentedRetentionTarget = + List.of(documentedAnnotation, // @Documented + retentionAnnotation, // @Retention(TargetType.RUNTIME) (happens fortuitously to be RUNTIME) + as.get(2)); // @Target(ANNOTATION_TYPE) + final List savs = new ArrayList<>(4); + for (final Element e : domain.typeElement("java.lang.annotation.ElementType").getEnclosedElements()) { + if (e.getKind() == ENUM_CONSTANT && e instanceof VariableElement ve) { + final Name n = e.getSimpleName(); + if (n.contentEquals("TYPE") || n.contentEquals("METHOD") || n.contentEquals("FIELD")) { + savs.add(new SyntheticAnnotationValue(ve)); + } + } + } + final SyntheticAnnotationMirror applicationScope = + new SyntheticAnnotationMirror(new SyntheticAnnotationTypeElement(List.of(documentedAnnotation, + retentionAnnotation, + new SyntheticAnnotationMirror(domain.typeElement("java.lang.annotation.Target"), + Map.of("value", savs)), + scopes.metaNormalScope(), + aq.metaQualifier(), + scopes.singletonScope()), // parent scope meta-annotation + "Application")); + final SyntheticAnnotationMirror anyQualifierWithApplicationParentScope = new SyntheticAnnotationMirror(anyQualifier); + SyntheticAnnotationTypeElement.class.cast(anyQualifierWithApplicationParentScope.getAnnotationType().asElement()) + .getAnnotationMirrors() + .add(applicationScope); + + // Set up scopelets. final Bean applicationAndSingletonScopeletBean = new Bean<>(new Id(beanTypes.beanTypes(domain.declaredType(SingletonScopelet.class.getCanonicalName())), - // Rule: application and scopes.singleton() function here as qualifiers - List.of(anyQualifierWithSingletonParentScope, application, scopes.singleton())), + // Rule: application and scopes.singletonScope() function here as qualifiers + List.of(anyQualifierWithSingletonParentScope, applicationScope, scopes.singletonScope())), new SingletonScopelet()); final Bean noneScopeletBean = new Bean<>(new Id(beanTypes.beanTypes(domain.declaredType(NoneScopelet.class.getCanonicalName())), - // Rule: NONE_ID functions here as a qualifier - List.of(anyQualifierWithSingletonParentScope, scopes.none())), + // Rule: scopes.none() functions here as a qualifier + List.of(anyQualifierWithSingletonParentScope, scopes.noneScope())), new NoneScopelet()); // Set up the user-supplied bean. Note that it is in application scope. - this.gorpBean = + final Bean gorpBean = new Bean<>(new Id(new BeanTypes(domain).beanTypes(domain.declaredType(Gorp.class.getCanonicalName())), - List.of(Attributes.of("Any", qualifiers.qualifier(), application), qualifiers.defaultQualifier())), + List.of(anyQualifierWithApplicationParentScope, defaultQualifier)), c -> new Gorp()); // In a production version of all this we need to allow for supplying a preinitialized selection cache. We will // supply an empty one. - final Map>> selectionCache = new ConcurrentHashMap<>(); + final Map, List>> selectionCache = new ConcurrentHashMap<>(); + final ScopedInstances scopedInstances = new ScopedInstances(domain, bq, sq, scopes, null); // See https://jakarta.ee/specifications/cdi/4.1/jakarta-cdi-spec-4.1#unsatisfied_and_ambig_dependencies - Selectable> selectable = - ScopedInstances.selectableOf(domain, - Selectables.>caching(ambiguityReducing(typesafeFiltering(List.of(domainBean, - beanTypesBean, - noneScopeletBean, - applicationAndSingletonScopeletBean, - this.gorpBean), - new IdMatcher(new BeanTypeMatcher(domain), - new BeanQualifiersMatcher(qualifiers), - new InterceptorBindingsMatcher())), - org.microbean.bean.Ranked::alternate, - org.microbean.bean.Ranked::rank - ), - selectionCache::computeIfAbsent)); - - this.instances = new ScopedInstances(domain, qualifiers, scopes); - this.r = - new Request(this.domain, - selectable, - this.instances, - new BBClientProxier(domain)); + final Beans beans = new Beans(domain); + Selectable, Bean> selectable = + scopedInstances.selectableOf(Selectables., Bean>caching(ambiguityReducing(beans.typesafeFilteringSelectable(List.of(domainBean, + beanTypesBean, + noneScopeletBean, + applicationAndSingletonScopeletBean, + gorpBean), + new IdMatcher(new BeanTypeMatcher(domain), + new BeanQualifiersMatcher(aq, bq), + new InterceptorBindingsMatcher(new InterceptorBindings(domain)))), + beans::alternate, + beans::rank), + selectionCache::computeIfAbsent)); + + final Request r = new Request<>(domain, selectable, scopedInstances, new BBClientProxier(domain)); + + this.domain = domain; + this.bq = bq; + this.scopedInstances = scopedInstances; + this.gorpBean = gorpBean; + this.r = r; } private static final boolean alternate(final Bean b) { @@ -190,12 +211,13 @@ private static final boolean alternate(final Bean b) { @Test final void testGorpIsProxiable() { - assertTrue(this.instances.proxiable(this.gorpBean.id())); + assertTrue(this.scopedInstances.proxiable(this.gorpBean.id())); } @Test final void testClientProxySunnyDay() { - final Gorp g = this.r.reference(new AttributedType(this.domain.declaredType(Gorp.class.getCanonicalName()), qualifiers.defaultQualifiers())); + final AnnotatedConstruct ac = new SyntheticLocalVariableElement(this.bq.defaultQualifiers(), this.domain.declaredType(Gorp.class.getCanonicalName())); + final Gorp g = this.r.reference(Annotated.of(ac)); assertTrue(g instanceof Proxy, String.valueOf(g)); assertTrue(g.getClass().isSynthetic()); @SuppressWarnings("unchecked")