diff --git a/ext/README.md b/ext/README.md new file mode 100644 index 0000000000000..f98cb92af170f --- /dev/null +++ b/ext/README.md @@ -0,0 +1,3 @@ +# External projects + +This directory contains independent standalone projects that can eventually split off from Shamrock. diff --git a/ext/arc/.gitignore b/ext/arc/.gitignore new file mode 100644 index 0000000000000..68cde206569d1 --- /dev/null +++ b/ext/arc/.gitignore @@ -0,0 +1,4 @@ +.project +.settings +.classpath +target \ No newline at end of file diff --git a/ext/arc/README.md b/ext/arc/README.md new file mode 100644 index 0000000000000..40e3ce31486ab --- /dev/null +++ b/ext/arc/README.md @@ -0,0 +1,109 @@ +# Weld Arc - "CDI lite" Experiments + +This repo contains a working prototype of something that could be called "CDI lite". +The goal is to verify whether it's feasible to implement a "reasonable" subset of CDI features with minimal runtime. +The target envs are JVM, GraalVM (both jvm and aot modes). + +**NOTE:** Jandex built from master is needed to build this project: https://github.com/wildfly/jandex + +## Goals + +* Instant startup and low memory footprint (compared to Weld) +* As little reflection in runtime as possible +* Acceptable build time increase +* Stick with CDI API and CDI idioms where possible + +## Architecture + +Currently, we generate metadata classes bytecode (aka "factory" classes) using [Gizmo](https://github.com/protean-project/shamrock/tree/master/gizmo). +One of the drawbacks is that debugging is not that simple. +On the other hand, the logic in generated classes should be quite straightforward and the developer can always decompile the classes. + +## Features and Limitations + +:heavy_check_mark: - basic support implemented +:white_check_mark: - not implemented yet +:x: - not supported, no plan to support ATM + +* Programming model + * :heavy_check_mark: Class beans + * :heavy_check_mark: `@PostConstruct` and `@PreDestroy` callbacks + * :white_check_mark: Lifecycle callbacks on superclasses + * :heavy_check_mark: Producer methods and fields + * :heavy_check_mark: Private members support + * :white_check_mark: Disposers + * :white_check_mark: Stereotypes +* Dependency injection + * :heavy_check_mark: Field, constructor and initializer injection + * :heavy_check_mark: Private injection fields + * :heavy_check_mark: Private constructors + * :white_check_mark: Private initializers + * :heavy_check_mark: Type-safe resolution + * :heavy_check_mark: Proper type-safe resolution rules at runtime; i.e. `ArcContainer.instance(Class, Annotation...)` +* Scopes and Contexts: + * :heavy_check_mark: `@Dependent` + * :heavy_check_mark: `@Singleton` + * :heavy_check_mark: `@RequestScoped` + * :heavy_check_mark: `@ApplicationScoped` + * :x: other built-in scopes such as `@SessionScoped` + * :x: Custom scopes +* :heavy_check_mark: Client proxies +* Interceptors + * :heavy_check_mark: `@AroundInvoke` + * :heavy_check_mark: Lifecycle (`@PostConstruct`, `@PreDestroy`, `@AroundConstruct`) + * :white_check_mark: Transitive interceptor bindings + * :x: Interceptor methods on superclasses +* :white_check_mark: Events/observers +* :x: Decorators +* :x: Portable extensions +* :x: EL support +* :x: `BeanManager` +* :x: Specialization + +## Modules + +* `processor` - generates "bean" sources from a Jandex index +* `runtime` - classes needed at runtime +* `maven-plugin` - uses `processor` to generate sources and then compiles the sources +* `example` - a simple example; `target/generated-sources/java` contains the generated sources + +## How to build + +```bash +mvn clean install +``` + +## How to run the example + +```bash +time java -jar example/target/arc-example-shaded.jar +``` +And the results on my laptop: + +``` +real 0m0,133s +user 0m0,175s +sys 0m0,024s +``` + +### Native image + +First build the image: + +```bash +/opt/java/graalvm-ee-1.0.0-rc4/bin/native-image --verbose -jar target/arc-example-shaded.jar +``` + +Then run the example: + +```bash +time ./arc-example-shaded +``` + +And the results on my laptop: + +``` +real 0m0,005s +user 0m0,000s +sys 0m0,005s +``` diff --git a/ext/arc/example/pom.xml b/ext/arc/example/pom.xml new file mode 100644 index 0000000000000..8e1c69948c3db --- /dev/null +++ b/ext/arc/example/pom.xml @@ -0,0 +1,96 @@ + + 4.0.0 + + + org.jboss.protean.arc + arc-parent + 1.0.0.Alpha1-SNAPSHOT + ../ + + + arc-example + + + 1.3.1 + + + + + + io.smallrye + smallrye-config + ${version.smallrye-config} + + + javax.el + javax.el-api + + + + + + org.jboss.protean.arc + arc-runtime + + + + junit + junit + + + + + + + + org.jboss.protean.arc + arc-maven-plugin + 1.0.0-SNAPSHOT + + + + run + + + + + + io.smallrye:smallrye-config + org.eclipse.microprofile.config:microprofile-config-api + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + default + package + + shade + + + + + + org.jboss.weld.arc.example.Main + + + + + true + arc-example-shaded + + + + + + + + diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Bar.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Bar.java new file mode 100644 index 0000000000000..6d7a486cce061 --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Bar.java @@ -0,0 +1,44 @@ +package org.jboss.protean.arc.example; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@Dependent +public class Bar { + + private String name; + + @ConfigProperty(name = "arc.example.surname", defaultValue = "Foo") + @Inject + String surname; + + @PostConstruct + void init() { + this.name = "Lu"; + } + + @PreDestroy + void destroy() { + System.out.println("Destroy bar..."); + } + + public String getName() { + return name + " " + surname; + } + + @ApplicationScoped + @Produces + List listOfNumbers() { + return Collections.emptyList(); + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Baz.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Baz.java new file mode 100644 index 0000000000000..d51689fecb9ca --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Baz.java @@ -0,0 +1,32 @@ +package org.jboss.protean.arc.example; + +import java.util.Arrays; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; + +@ApplicationScoped +public class Baz { + + @MyQualifier(alpha = "1", bravo = "1") + @Inject + Foo foo; + + @Inject + void setBar(Bar bar) { + } + + public String pingFoo() { + return foo.ping(); + } + + @MyQualifier(bravo = "1") + @Produces + public List listProducer(InjectionPoint injectionPoint) { + return Arrays.asList(injectionPoint.getType().toString()); + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/BazListProducerClient.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/BazListProducerClient.java new file mode 100644 index 0000000000000..e73098df64166 --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/BazListProducerClient.java @@ -0,0 +1,22 @@ +package org.jboss.protean.arc.example; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class BazListProducerClient { + + private List list; + + @Inject + public BazListProducerClient(@MyQualifier(alpha = "bang", bravo = "1") List list) { + this.list = list; + } + + public List getList() { + return list; + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Foo.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Foo.java new file mode 100644 index 0000000000000..c18f8985e321c --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Foo.java @@ -0,0 +1,31 @@ +package org.jboss.protean.arc.example; + +import java.io.Serializable; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.inject.Singleton; + +@MyQualifier(alpha = "1", bravo = "1") +@Singleton +public class Foo implements Serializable { + + private Bar bar; + + @Inject + Instance barInstance; + + @Inject + Foo(Bar bar) { + this.bar = bar; + } + + String ping() { + return bar.getName(); + } + + String lazyPing() { + return barInstance.isResolvable() ? barInstance.get().getName() : "NOK"; + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/FooRequest.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/FooRequest.java new file mode 100644 index 0000000000000..f21c0d3bb575a --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/FooRequest.java @@ -0,0 +1,34 @@ +package org.jboss.protean.arc.example; + +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +@RequestScoped +public class FooRequest { + + private Bar bar; + + private String id; + + @PostConstruct + void init() { + this.id = UUID.randomUUID().toString(); + } + + @Inject + void setBar(Bar bar) { + this.bar = bar; + } + + String getId() { + return id; + } + + String ping() { + return bar.getName(); + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Main.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Main.java new file mode 100644 index 0000000000000..810a05f6ca0ff --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/Main.java @@ -0,0 +1,22 @@ +package org.jboss.protean.arc.example; + +import org.jboss.protean.arc.Arc; + +/** + * + * @author Martin Kouba + */ +public class Main { + + static { + // This is needed for graal ahead-of-time compilation + // ArcContainer collects all beans using a service provider + Arc.container(); + } + + public static void main(String[] args) { + Baz baz = Arc.container().instance(Baz.class).get(); + System.out.println(baz.pingFoo()); + } + +} diff --git a/ext/arc/example/src/main/java/org/jboss/protean/arc/example/MyQualifier.java b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/MyQualifier.java new file mode 100644 index 0000000000000..11dfed1baa965 --- /dev/null +++ b/ext/arc/example/src/main/java/org/jboss/protean/arc/example/MyQualifier.java @@ -0,0 +1,42 @@ +package org.jboss.protean.arc.example; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; +import javax.inject.Qualifier; + +@Qualifier +@Inherited +@Target({ TYPE, METHOD, FIELD, PARAMETER }) +@Retention(RUNTIME) +public @interface MyQualifier { + + @Nonbinding + String alpha() default ""; + + String bravo() default ""; + + static class OneLiteral extends AnnotationLiteral implements MyQualifier { + + @Override + public String alpha() { + return "1"; + } + + @Override + public String bravo() { + return "1"; + } + + } + +} \ No newline at end of file diff --git a/ext/arc/example/src/test/java/org/jboss/protean/arc/example/InjectionTest.java b/ext/arc/example/src/test/java/org/jboss/protean/arc/example/InjectionTest.java new file mode 100644 index 0000000000000..58d05d81a7c17 --- /dev/null +++ b/ext/arc/example/src/test/java/org/jboss/protean/arc/example/InjectionTest.java @@ -0,0 +1,86 @@ +package org.jboss.protean.arc.example; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.util.TypeLiteral; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.InstanceHandle; +import org.jboss.protean.arc.example.Baz; +import org.jboss.protean.arc.example.BazListProducerClient; +import org.jboss.protean.arc.example.Foo; +import org.jboss.protean.arc.example.FooRequest; +import org.jboss.protean.arc.example.MyQualifier; +import org.junit.Test; + +public class InjectionTest { + + @SuppressWarnings("serial") + @Test + public void testInjection() { + ArcContainer arc = Arc.container(); + + Baz baz = arc.instance(Baz.class).get(); + assertEquals("Lu Foo", baz.pingFoo()); + assertEquals(baz, arc.instance(Baz.class).get()); + + InstanceHandle foo = arc.instance(Foo.class, new MyQualifier.OneLiteral()); + assertEquals("Lu Foo", foo.get().ping()); + assertEquals("Lu Foo", foo.get().lazyPing()); + assertEquals(foo.get(), arc.instance(Foo.class, new MyQualifier.OneLiteral()).get()); + foo.release(); + } + + @Test + public void testRequestContext() { + ArcContainer arc = Arc.container(); + + try { + arc.instance(FooRequest.class).get().getId(); + fail(); + } catch (ContextNotActiveException expected) { + } + + arc.requestContext().activate(); + + FooRequest foo1 = arc.instance(FooRequest.class).get(); + assertEquals("Lu Foo", foo1.ping()); + FooRequest foo2 = arc.instance(FooRequest.class).get(); + assertEquals(foo1.getId(), foo2.getId()); + arc.requestContext().deactivate(); + + try { + arc.instance(FooRequest.class).get().getId(); + fail(); + } catch (ContextNotActiveException expected) { + } + } + + @Test + public void testProducerMethodWithNormalScope() { + // Bar#listOfNumbers + InstanceHandle> list = Arc.container().instance(new TypeLiteral>() { + }); + assertTrue(list.isAvailable()); + assertEquals(0, list.get().size()); + } + + @Test + public void testInjectionPointMetadata() { + ArcContainer arc = Arc.container(); + + // Empty injection point + assertEquals(Object.class.toString(), arc.instance(new TypeLiteral>() { + }, new MyQualifier.OneLiteral()).get().get(0)); + + BazListProducerClient client = arc.instance(BazListProducerClient.class).get(); + List list = client.getList(); + assertEquals("java.util.List", list.get(0)); + } +} diff --git a/ext/arc/maven-plugin/pom.xml b/ext/arc/maven-plugin/pom.xml new file mode 100644 index 0000000000000..623ee4c5d166e --- /dev/null +++ b/ext/arc/maven-plugin/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + + + org.jboss.protean.arc + arc-parent + 1.0.0.Alpha1-SNAPSHOT + ../ + + + arc-maven-plugin + maven-plugin + + + 3.5.2 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${version.maven-plugin-plugin} + + + default-descriptor + process-classes + + + + + + + + + + org.jboss.protean.arc + arc-processor + + + + org.apache.maven + maven-plugin-api + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + + + + org.apache.maven + maven-core + + + + junit + junit + + + + + diff --git a/ext/arc/maven-plugin/src/main/java/org/jboss/protean/arc/maven/BeanMojo.java b/ext/arc/maven-plugin/src/main/java/org/jboss/protean/arc/maven/BeanMojo.java new file mode 100644 index 0000000000000..73a175416edfc --- /dev/null +++ b/ext/arc/maven-plugin/src/main/java/org/jboss/protean/arc/maven/BeanMojo.java @@ -0,0 +1,180 @@ +package org.jboss.protean.arc.maven; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.function.Predicate; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.Indexer; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.ResourceOutput; + +/** + * + * @author Martin Kouba + */ +@Mojo(name = "run", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class BeanMojo extends AbstractMojo { + + private static final String CLASS_FILE_EXTENSION = ".class"; + + @Parameter(readonly = true, required = true, defaultValue = "${project.build.directory}/generated-sources") + private File targetDirectory; + + @Parameter(readonly = true, required = true, defaultValue = "${project.build.outputDirectory}") + private File outputDirectory; + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + protected MavenProject project; + + @Parameter + private String[] additionalBeanDefiningAnnotations; + + @Parameter + private String[] dependenciesToScan; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + + Index index; + try { + index = createIndex(); + } catch (IOException e) { + throw new MojoFailureException("Failed to create index", e); + } + + BeanProcessor beanProcessor = BeanProcessor.builder().setIndex(index).setAdditionalBeanDefiningAnnotations(getAdditionalBeanDefiningAnnotations()) + .setOutput(new ResourceOutput() { + + @Override + public void writeResource(Resource resource) throws IOException { + switch (resource.getType()) { + case JAVA_CLASS: + resource.writeTo(outputDirectory); + break; + case SERVICE_PROVIDER: + resource.writeTo(new File(outputDirectory, "/META-INF/services/")); + default: + break; + } + } + }).build(); + try { + beanProcessor.process(); + } catch (IOException e) { + throw new MojoExecutionException("Error generating resources", e); + } + } + + private Index createIndex() throws IOException { + Indexer indexer = new Indexer(); + + // Index dependencies + if (dependenciesToScan != null && dependenciesToScan.length > 0) { + List dependenciesToIndex = project.getArtifacts().stream().filter(this::isDependencyToScan).map(Artifact::getFile) + .collect(Collectors.toList()); + for (File dependency : dependenciesToIndex) { + index(indexer, dependency, this::isClassJarEntry); + } + } + + // Index output directory, i.e. target/classes + Files.walkFileTree(outputDirectory.toPath(), new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".class")) { + try (InputStream stream = Files.newInputStream(file)) { + indexer.index(stream); + } + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + }); + + return indexer.complete(); + } + + private void index(Indexer indexer, File jarFile, Predicate predicate) throws IOException { + JarFile jar = new JarFile(jarFile); + try { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (predicate.test(entry)) { + try (InputStream stream = jar.getInputStream(entry)) { + indexer.index(stream); + } + } + } + } finally { + if (jar != null) { + jar.close(); + } + } + } + + private Collection getAdditionalBeanDefiningAnnotations() { + List beanDefiningAnnotations = Collections.emptyList(); + if (additionalBeanDefiningAnnotations != null && additionalBeanDefiningAnnotations.length > 0) { + beanDefiningAnnotations = new ArrayList<>(); + for (String beanDefiningAnnotation : additionalBeanDefiningAnnotations) { + beanDefiningAnnotations.add(DotName.createSimple(beanDefiningAnnotation)); + } + } + return beanDefiningAnnotations; + } + + private boolean isDependencyToScan(Artifact artifact) { + String id = artifact.getGroupId() + ":" + artifact.getArtifactId(); + for (String dependency : dependenciesToScan) { + if (dependency.equals(id)) { + return true; + } + } + return false; + } + + private boolean isClassJarEntry(JarEntry entry) { + return entry.getName().endsWith(CLASS_FILE_EXTENSION); + } + +} \ No newline at end of file diff --git a/ext/arc/pom.xml b/ext/arc/pom.xml new file mode 100644 index 0000000000000..68545339026a0 --- /dev/null +++ b/ext/arc/pom.xml @@ -0,0 +1,119 @@ + + 4.0.0 + org.jboss.protean.arc + arc-parent + pom + 1.0.0.Alpha1-SNAPSHOT + + + + Apache License, Version 2.0 + repo + http://www.apache.org/licenses/LICENSE-2.0.html + + + + + UTF-8 + 1.8 + 1.8 + + 2.0.SP1 + 2.0.6.Final-SNAPSHOT + 4.12 + 3.5.2 + 3.3.2.Final + 1.2 + 1.0.0.Alpha1-SNAPSHOT + + + + runtime + processor + maven-plugin + tests + example + + + + + + + + org.jboss.protean.arc + arc-runtime + ${project.version} + + + + org.jboss.protean.arc + arc-processor + ${project.version} + + + + javax.enterprise + cdi-api + ${version.cdi} + + + + org.jboss + jandex + ${version.jandex} + + + + org.jboss.protean.gizmo + gizmo + ${version.gizmo} + + + + junit + junit + ${version.junit4} + test + + + + org.apache.maven + maven-plugin-api + ${version.maven} + provided + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${version.maven} + provided + + + + org.apache.maven + maven-core + ${version.maven} + provided + + + + org.jboss.logging + jboss-logging + ${version.jboss-logging} + + + + javax.annotation + javax.annotation-api + ${version.javax-annotation} + + + + + + + + diff --git a/ext/arc/processor/pom.xml b/ext/arc/processor/pom.xml new file mode 100644 index 0000000000000..0e46741411754 --- /dev/null +++ b/ext/arc/processor/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + + org.jboss.protean.arc + arc-parent + 1.0.0.Alpha1-SNAPSHOT + ../ + + + arc-processor + + + + + org.jboss.protean.arc + arc-runtime + + + + javax.enterprise + cdi-api + + + + org.jboss.logging + jboss-logging + + + + org.jboss + jandex + + + + org.jboss.protean.gizmo + gizmo + + + + javax.annotation + javax.annotation-api + + + + junit + junit + + + + + diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AbstractGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AbstractGenerator.java new file mode 100644 index 0000000000000..7f0456ca1e020 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AbstractGenerator.java @@ -0,0 +1,14 @@ +package org.jboss.protean.arc.processor; + +abstract class AbstractGenerator { + + protected String providerName(String name) { + // TODO we can do better + return Character.toLowerCase(name.charAt(0)) + name.substring(1); + } + + protected String getBaseName(BeanInfo bean, String beanClassName) { + String name = Types.getSimpleName(beanClassName); + return name.substring(0, name.indexOf(BeanGenerator.BEAN_SUFFIX)); + } +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralGenerator.java new file mode 100644 index 0000000000000..ea883f919e320 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralGenerator.java @@ -0,0 +1,144 @@ +package org.jboss.protean.arc.processor; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.enterprise.util.AnnotationLiteral; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.logging.Logger; +import org.jboss.protean.arc.ComputingCache; +import org.jboss.protean.arc.processor.AnnotationLiteralProcessor.CacheKey; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.ClassOutput; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +public class AnnotationLiteralGenerator extends AbstractGenerator { + + static final String ANNOTATION_LITERAL_SUFFIX = "_AnnotationLiteral"; + + private static final Logger LOGGER = Logger.getLogger(AnnotationLiteralGenerator.class); + + /** + * + * @param beanDeployment + * @param annotationLiterals + * @return a collection of resources + */ + Collection generate(String name, BeanDeployment beanDeployment, ComputingCache annotationLiteralsCache) { + ResourceClassOutput classOutput = new ResourceClassOutput(); + + annotationLiteralsCache.forEachEntry( + (key, literalName) -> createAnnotationLiteral(classOutput, beanDeployment.getIndex().getClassByName(key.name), key.values, literalName)); + + return classOutput.getResources(); + } + + static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String literalName) { + createAnnotationLiteral(classOutput, annotationClass, annotationInstance.values(), literalName); + } + + static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, List values, String literalName) { + + Map annotationValues = values.stream().collect(Collectors.toMap(AnnotationValue::name, Function.identity())); + + // Ljavax/enterprise/util/AnnotationLiteral;Lcom/foo/MyQualifier; + String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral;L%1$s;", annotationClass.name().toString().replace(".", "/")); + String generatedName = literalName.replace(".", "/"); + + ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(AnnotationLiteral.class) + .interfaces(annotationClass.name().toString()).signature(signature).build(); + + for (MethodInfo method : annotationClass.methods()) { + MethodCreator valueMethod = annotationLiteral.getMethodCreator(MethodDescriptor.of(method)); + AnnotationValue value = annotationValues.get(method.name()); + if (value == null) { + value = method.defaultValue(); + } + ResultHandle retValue = null; + if (value != null) { + switch (value.kind()) { + case BOOLEAN: + retValue = value != null ? valueMethod.load(value.asBoolean()) : valueMethod.load(false); + break; + case STRING: + retValue = value != null ? valueMethod.load(value.asString()) : valueMethod.loadNull(); + break; + case BYTE: + retValue = value != null ? valueMethod.load(value.asByte()) : valueMethod.load(0); + break; + case SHORT: + retValue = value != null ? valueMethod.load(value.asShort()) : valueMethod.load(0); + break; + case LONG: + retValue = value != null ? valueMethod.load(value.asLong()) : valueMethod.load(0L); + break; + case INTEGER: + retValue = value != null ? valueMethod.load(value.asInt()) : valueMethod.load(0); + break; + case FLOAT: + retValue = value != null ? valueMethod.load(value.asFloat()) : valueMethod.load(0.0f); + break; + case DOUBLE: + retValue = value != null ? valueMethod.load(value.asDouble()) : valueMethod.load(0.0d); + break; + case CHARACTER: + retValue = value != null ? valueMethod.load(value.asChar()) : valueMethod.load('\u0000'); + break; + case CLASS: + retValue = value != null ? valueMethod.loadClass(value.asClass().toString()) : valueMethod.loadNull(); + break; + case ARRAY: + // Always return an empty array + // Array members must be Nonbinding + retValue = value != null ? valueMethod.newArray(componentType(method), valueMethod.load(0)) : valueMethod.loadNull(); + break; + case ENUM: + retValue = value != null + ? valueMethod.readStaticField(FieldDescriptor.of(value.asEnumType().toString(), value.asEnum(), value.asEnumType().toString())) + : valueMethod.loadNull(); + break; + case NESTED: + default: + throw new UnsupportedOperationException(); + } + } + valueMethod.returnValue(retValue); + } + annotationLiteral.close(); + LOGGER.debugf("Annotation literal generated: " + literalName); + } + + static String componentType(MethodInfo method) { + ArrayType arrayType = method.returnType().asArrayType(); + return arrayType.component().name().toString(); + } + + static String generatedSharedName(String prefix, String simpleName, AtomicInteger index) { + // com.foo.MyQualifier -> org.jboss.protean.arc.setup.Default_MyQualifier1_AnnotationLiteral + return BeanProviderGenerator.SETUP_PACKAGE + "." + prefix + "_" + simpleName + index.incrementAndGet() + + AnnotationLiteralGenerator.ANNOTATION_LITERAL_SUFFIX; + } + + static String generatedLocalName(String targetPackage, String simpleName, AtomicInteger index) { + // com.foo.MyQualifier -> com.bar.MyQualifier1_AnnotationLiteral + return targetPackage + "." + simpleName + index.incrementAndGet() + AnnotationLiteralGenerator.ANNOTATION_LITERAL_SUFFIX; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralProcessor.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralProcessor.java new file mode 100644 index 0000000000000..f6139b09a3f7e --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/AnnotationLiteralProcessor.java @@ -0,0 +1,102 @@ +package org.jboss.protean.arc.processor; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.protean.arc.ComputingCache; +import org.jboss.protean.gizmo.ClassOutput; + +/** + * + * @author Martin Kouba + */ +public class AnnotationLiteralProcessor { + + private final AtomicInteger index; + + private final ComputingCache cache; + + public AnnotationLiteralProcessor(String name, boolean shared) { + this.index = new AtomicInteger(1); + this.cache = shared ? new ComputingCache<>(key -> AnnotationLiteralGenerator.generatedSharedName(name, DotNames.simpleName(key.name), index)) : null; + } + + boolean hasLiteralsToGenerate() { + return cache != null && !cache.isEmpty(); + } + + ComputingCache getCache() { + return cache; + } + + /** + * + * @param classOutput + * @param annotationClass + * @return an annotation literal class name + */ + String process(ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String targetPackage) { + if (cache != null) { + return cache.getValue(new CacheKey(annotationInstance.name(), annotationInstance.values())); + } + String literalName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, DotNames.simpleName(annotationClass.name()), index); + AnnotationLiteralGenerator.createAnnotationLiteral(classOutput, annotationClass, annotationInstance, literalName); + return literalName; + } + + static class CacheKey { + + final DotName name; + + final List values; + + public CacheKey(DotName name, List values) { + this.name = name; + this.values = values; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((values == null) ? 0 : values.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof CacheKey)) { + return false; + } + CacheKey other = (CacheKey) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (values == null) { + if (other.values != null) { + return false; + } + } else if (!values.equals(other.values)) { + return false; + } + return true; + } + + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Annotations.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Annotations.java new file mode 100644 index 0000000000000..c27ad3ef06423 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Annotations.java @@ -0,0 +1,26 @@ +package org.jboss.protean.arc.processor; + +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.MethodInfo; + +public final class Annotations { + + private Annotations() { + } + + static Object convertAnnotationValue(AnnotationValue value, MethodInfo method) { + if (value.kind().equals(org.jboss.jandex.AnnotationValue.Kind.ARRAY)) { + // Array members must be Nonbinding + return "new " + method.returnType().asArrayType().component().name() + "[]{}"; + } else if (value.kind().equals(org.jboss.jandex.AnnotationValue.Kind.BOOLEAN)) { + return value.asBoolean(); + } else if (value.kind().equals(org.jboss.jandex.AnnotationValue.Kind.STRING)) { + return "\"" + value.asString() + "\""; + } else if (value.kind().equals(org.jboss.jandex.AnnotationValue.Kind.ENUM)) { + return Types.convertNested(method.returnType().name()) + "." + value.asEnum(); + } else { + return value.toString(); + } + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java new file mode 100644 index 0000000000000..7496cc3458bcd --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java @@ -0,0 +1,209 @@ +package org.jboss.protean.arc.processor; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassInfo.NestingType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.logging.Logger; +import org.jboss.logging.Logger.Level; + +/** + * + * @author Martin Kouba + */ +public class BeanDeployment { + + private static final Logger LOGGER = Logger.getLogger(BeanDeployment.class); + + private final IndexView index; + + private final Map qualifiers; + + private final Map interceptorBindings; + + private final List beans; + + private final List interceptors; + + private final BeanResolver beanResolver; + + private final InterceptorResolver interceptorResolver; + + public BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations) { + long start = System.currentTimeMillis(); + this.index = index; + this.qualifiers = findQualifiers(index); + // TODO interceptor bindings are transitive!!! + this.interceptorBindings = findInterceptorBindings(index); + this.interceptors = findInterceptors(); + this.beans = findBeans(initBeanDefiningAnnotations(additionalBeanDefiningAnnotations)); + this.beanResolver = new BeanResolver(this); + this.interceptorResolver = new InterceptorResolver(this); + // TODO observers + LOGGER.infof("Build deployment created in %s ms", System.currentTimeMillis() - start); + } + + Collection getBeans() { + return beans; + } + + Collection getInterceptors() { + return interceptors; + } + + IndexView getIndex() { + return index; + } + + BeanResolver getBeanResolver() { + return beanResolver; + } + + InterceptorResolver getInterceptorResolver() { + return interceptorResolver; + } + + ClassInfo getQualifier(DotName name) { + return qualifiers.get(name); + } + + ClassInfo getInterceptorBinding(DotName name) { + return interceptorBindings.get(name); + } + + void init() { + long start = System.currentTimeMillis(); + for (BeanInfo bean : beans) { + bean.init(); + } + for (InterceptorInfo interceptor : interceptors) { + interceptor.init(); + } + LOGGER.infof("Bean deployment initialized in %s ms", System.currentTimeMillis() - start); + } + + static Map findQualifiers(IndexView index) { + Map qualifiers = new HashMap<>(); + for (AnnotationInstance qualifier : index.getAnnotations(DotNames.QUALIFIER)) { + qualifiers.put(qualifier.target().asClass().name(), qualifier.target().asClass()); + } + return qualifiers; + } + + static Map findInterceptorBindings(IndexView index) { + Map bindings = new HashMap<>(); + for (AnnotationInstance binding : index.getAnnotations(DotNames.INTERCEPTOR_BINDING)) { + bindings.put(binding.target().asClass().name(), binding.target().asClass()); + } + return bindings; + } + + private List findBeans(List beanDefiningAnnotations) { + + Set beanClasses = new HashSet<>(); + Set producerMethods = new HashSet<>(); + Set producerFields = new HashSet<>(); + + for (DotName beanDefiningAnnotation : beanDefiningAnnotations) { + for (AnnotationInstance annotation : index.getAnnotations(beanDefiningAnnotation)) { + if (Kind.CLASS.equals(annotation.target().kind())) { + + ClassInfo beanClass = annotation.target().asClass(); + + if (beanClass.annotations().containsKey(DotNames.INTERCEPTOR)) { + // Skip interceptors + continue; + } + if (beanClass.nestingType().equals(NestingType.ANONYMOUS) || beanClass.nestingType().equals(NestingType.LOCAL) + || (beanClass.nestingType().equals(NestingType.INNER) && !Modifier.isStatic(beanClass.flags()))) { + // Skip annonymous, local and inner classes + continue; + } + beanClasses.add(beanClass); + + for (MethodInfo method : beanClass.methods()) { + if (method.hasAnnotation(DotNames.PRODUCES)) { + producerMethods.add(method); + } + } + for (FieldInfo field : beanClass.fields()) { + if (field.annotations().stream().anyMatch(a -> a.name().equals(DotNames.PRODUCES))) { + producerFields.add(field); + } + } + } + } + } + + // Build metadata for typesafe resolution + List beans = new ArrayList<>(); + Map beanClassToBean = new HashMap<>(); + for (ClassInfo beanClass : beanClasses) { + BeanInfo classBean = Beans.createClassBean(beanClass, this); + beans.add(classBean); + beanClassToBean.put(beanClass, classBean); + } + for (MethodInfo producerMethod : producerMethods) { + BeanInfo declaringBean = beanClassToBean.get(producerMethod.declaringClass()); + if (declaringBean != null) { + beans.add(Beans.createProducerMethod(producerMethod, declaringBean, this)); + } + } + for (FieldInfo producerField : producerFields) { + BeanInfo declaringBean = beanClassToBean.get(producerField.declaringClass()); + if (declaringBean != null) { + beans.add(Beans.createProducerField(producerField, declaringBean, this)); + } + } + if (LOGGER.isDebugEnabled()) { + for (BeanInfo bean : beans) { + LOGGER.logf(Level.DEBUG, "Created %s", bean); + } + } + return beans; + } + + private List findInterceptors() { + Set interceptorClasses = new HashSet<>(); + for (AnnotationInstance annotation : index.getAnnotations(DotNames.INTERCEPTOR)) { + if (Kind.CLASS.equals(annotation.target().kind())) { + interceptorClasses.add(annotation.target().asClass()); + } + } + List interceptors = new ArrayList<>(); + for (ClassInfo interceptorClass : interceptorClasses) { + interceptors.add(Interceptors.createInterceptor(interceptorClass, this)); + } + if (LOGGER.isDebugEnabled()) { + for (InterceptorInfo interceptor : interceptors) { + LOGGER.logf(Level.DEBUG, "Created %s", interceptor); + } + } + return interceptors; + } + + private List initBeanDefiningAnnotations(Collection additionalBeanDefiningAnnotationss) { + List beanDefiningAnnotations = new ArrayList<>(); + for (ScopeInfo scope : ScopeInfo.values()) { + beanDefiningAnnotations.add(scope.getDotName()); + } + if (additionalBeanDefiningAnnotationss != null) { + beanDefiningAnnotations.addAll(additionalBeanDefiningAnnotationss); + } + return beanDefiningAnnotations; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java new file mode 100644 index 0000000000000..390c610006c53 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanGenerator.java @@ -0,0 +1,884 @@ +package org.jboss.protean.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_BRIDGE; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.enterprise.context.spi.Context; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.CreationalContextImpl; +import org.jboss.protean.arc.CurrentInjectionPointProvider; +import org.jboss.protean.arc.InitializedInterceptor; +import org.jboss.protean.arc.InjectableBean; +import org.jboss.protean.arc.InjectableInterceptor; +import org.jboss.protean.arc.InjectableReferenceProvider; +import org.jboss.protean.arc.InvocationContextImpl; +import org.jboss.protean.arc.LazyValue; +import org.jboss.protean.arc.Subclass; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType; +import org.jboss.protean.gizmo.BytecodeCreator; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.ClassOutput; +import org.jboss.protean.gizmo.ExceptionTable; +import org.jboss.protean.gizmo.FieldCreator; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.FunctionCreator; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +public class BeanGenerator extends AbstractGenerator { + + static final String BEAN_SUFFIX = "_Bean"; + + static final String PRODUCER_METHOD_SUFFIX = "_ProducerMethod"; + + static final String PRODUCER_FIELD_SUFFIX = "_ProducerField"; + + private static final Logger LOGGER = Logger.getLogger(BeanGenerator.class); + + private static final AtomicInteger PRODUCER_INDEX = new AtomicInteger(); + + /** + * + * @param bean + * @param annotationLiterals + * @return a collection of resources + */ + Collection generate(BeanInfo bean, AnnotationLiteralProcessor annotationLiterals) { + if (Kind.CLASS.equals(bean.getTarget().kind())) { + return generateClassBean(bean, bean.getTarget().asClass(), annotationLiterals); + } else if (Kind.METHOD.equals(bean.getTarget().kind())) { + return generateProducerMethodBean(bean, bean.getTarget().asMethod(), annotationLiterals); + } else if (Kind.FIELD.equals(bean.getTarget().kind())) { + return generateProducerFieldBean(bean, bean.getTarget().asField(), annotationLiterals); + } + throw new IllegalArgumentException("Unsupported bean type"); + } + + Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, AnnotationLiteralProcessor annotationLiterals) { + + String baseName; + if (beanClass.enclosingClass() != null) { + baseName = DotNames.simpleName(beanClass.enclosingClass()) + "_" + DotNames.simpleName(beanClass.name()); + } else { + baseName = DotNames.simpleName(beanClass.name()); + } + Type providerType = bean.getProviderType(); + ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String generatedName = DotNames.packageName(providerType.name()).replace(".", "/") + "/" + baseName + BEAN_SUFFIX; + + ResourceClassOutput classOutput = new ResourceClassOutput(name -> name.equals(generatedName) ? SpecialType.BEAN : null); + + // Foo_Bean implements InjectableBean + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + + // Fields + FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator qualifiers = null; + if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { + qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + if (bean.getScope().isNormal()) { + // For normal scopes a client proxy is generated too + beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + + Map providerToInjectionPoint = new HashMap<>(); + Map injectionPointToProviderField = new HashMap<>(); + Map interceptorToProviderField = new HashMap<>(); + initMaps(bean, providerToInjectionPoint, injectionPointToProviderField, interceptorToProviderField); + + createProviderFields(beanCreator, bean, injectionPointToProviderField, interceptorToProviderField); + createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, annotationLiterals); + + if (!bean.hasDefaultDestroy()) { + createDestroy(bean, beanCreator, providerTypeName); + } + createCreate(beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField); + createGet(bean, beanCreator, providerTypeName); + + createGetTypes(beanCreator, beanTypes.getFieldDescriptor()); + if (!bean.getScope().isDefault()) { + createGetScope(bean, beanCreator); + } + if (qualifiers != null) { + createGetQualifiers(bean, beanCreator, qualifiers.getFieldDescriptor()); + } + + beanCreator.close(); + return classOutput.getResources(); + } + + Collection generateProducerMethodBean(BeanInfo bean, MethodInfo producerMethod, AnnotationLiteralProcessor annotationLiterals) { + + ClassInfo declaringClass = producerMethod.declaringClass(); + String declaringClassBase; + if (declaringClass.enclosingClass() != null) { + declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + DotNames.simpleName(declaringClass.name()); + } else { + declaringClassBase = DotNames.simpleName(declaringClass.name()); + } + + Type providerType = bean.getProviderType(); + String baseName = declaringClassBase + PRODUCER_METHOD_SUFFIX + PRODUCER_INDEX.incrementAndGet(); + ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String generatedName = DotNames.packageName(declaringClass.name()).replace(".", "/") + "/" + baseName + BEAN_SUFFIX; + + ResourceClassOutput classOutput = new ResourceClassOutput(name -> name.equals(generatedName) ? SpecialType.BEAN : null); + + // Foo_Bean implements InjectableBean + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + + // Fields + FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator qualifiers = null; + if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { + qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + if (bean.getScope().isNormal()) { + // For normal scopes a client proxy is generated too + beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + + Map providerToInjectionPoint = new HashMap<>(); + Map injectionPointToProviderField = new HashMap<>(); + // Producer methods are not intercepted + initMaps(bean, providerToInjectionPoint, injectionPointToProviderField, null); + + createProviderFields(beanCreator, bean, injectionPointToProviderField, Collections.emptyMap()); + createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, Collections.emptyMap(), annotationLiterals); + + if (!bean.hasDefaultDestroy()) { + createDestroy(bean, beanCreator, providerTypeName); + } + createCreate(beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, Collections.emptyMap()); + createGet(bean, beanCreator, providerTypeName); + + createGetTypes(beanCreator, beanTypes.getFieldDescriptor()); + if (!bean.getScope().isDefault()) { + createGetScope(bean, beanCreator); + } + if (qualifiers != null) { + createGetQualifiers(bean, beanCreator, qualifiers.getFieldDescriptor()); + } + + beanCreator.close(); + return classOutput.getResources(); + } + + Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producerField, AnnotationLiteralProcessor annotationLiterals) { + + ClassInfo declaringClass = producerField.declaringClass(); + String declaringClassBase; + if (declaringClass.enclosingClass() != null) { + declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + DotNames.simpleName(declaringClass.name()); + } else { + declaringClassBase = DotNames.simpleName(declaringClass.name()); + } + + Type providerType = bean.getProviderType(); + String baseName = declaringClassBase + PRODUCER_METHOD_SUFFIX + PRODUCER_INDEX.incrementAndGet(); + ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String generatedName = DotNames.packageName(declaringClass.name()).replace(".", "/") + "/" + baseName + BEAN_SUFFIX; + + ResourceClassOutput classOutput = new ResourceClassOutput(name -> name.equals(generatedName) ? SpecialType.BEAN : null); + + // Foo_Bean implements InjectableBean + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + + // Fields + FieldCreator beanTypes = beanCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator qualifiers = null; + if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { + qualifiers = beanCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + if (bean.getScope().isNormal()) { + // For normal scopes a client proxy is generated too + beanCreator.getFieldCreator("proxy", LazyValue.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + + createProviderFields(beanCreator, bean, Collections.emptyMap(), Collections.emptyMap()); + createConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), Collections.emptyMap(), annotationLiterals); + + if (!bean.hasDefaultDestroy()) { + createDestroy(bean, beanCreator, providerTypeName); + } + createCreate(beanCreator, bean, providerTypeName, baseName, Collections.emptyMap(), Collections.emptyMap()); + createGet(bean, beanCreator, providerTypeName); + + createGetTypes(beanCreator, beanTypes.getFieldDescriptor()); + if (!bean.getScope().isDefault()) { + createGetScope(bean, beanCreator); + } + if (qualifiers != null) { + createGetQualifiers(bean, beanCreator, qualifiers.getFieldDescriptor()); + } + + beanCreator.close(); + return classOutput.getResources(); + } + + protected void initMaps(BeanInfo bean, Map providerToInjectionPoint, Map injectionPointToProvider, + Map interceptorToProvider) { + int providerIdx = 1; + for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { + String name = providerName(DotNames.simpleName(injectionPoint.requiredType.name())) + "Provider" + providerIdx++; + providerToInjectionPoint.put(name, injectionPoint); + injectionPointToProvider.put(injectionPoint, name); + } + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + String name = providerName(DotNames.simpleName(interceptor.getProviderType().name())) + "Provider" + providerIdx++; + interceptorToProvider.put(interceptor, name); + } + } + + protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, Map injectionPointToProvider, + Map interceptorToProvider) { + // Declaring bean provider + if (bean.isProducerMethod() || bean.isProducerField()) { + beanCreator.getFieldCreator("declaringProvider", InjectableReferenceProvider.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + // Injection points + for (String provider : injectionPointToProvider.values()) { + beanCreator.getFieldCreator(provider, InjectableReferenceProvider.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + // Interceptors + for (String interceptorProvider : interceptorToProvider.values()) { + beanCreator.getFieldCreator(interceptorProvider, InjectableInterceptor.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + } + } + + protected void createConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, + Map injectionPointToProviderField, Map interceptorToProviderField, + AnnotationLiteralProcessor annotationLiterals) { + initConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, annotationLiterals) + .returnValue(null); + } + + protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, + Map injectionPointToProviderField, Map interceptorToProviderField, + AnnotationLiteralProcessor annotationLiterals) { + + // First collect all param types + List parameterTypes = new ArrayList<>(); + if (bean.isProducerMethod() || bean.isProducerField()) { + parameterTypes.add(InjectableReferenceProvider.class.getName()); + } + for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { + if (BuiltinBean.resolve(injectionPoint) == null) { + parameterTypes.add(InjectableReferenceProvider.class.getName()); + } + } + for (int i = 0; i < interceptorToProviderField.size(); i++) { + parameterTypes.add(InjectableInterceptor.class.getName()); + } + + MethodCreator constructor = beanCreator.getMethodCreator("", "V", parameterTypes.toArray(new String[0])); + // Invoke super() + constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); + + // Declaring bean provider + int paramIdx = 0; + if (bean.isProducerMethod() || bean.isProducerField()) { + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "declaringProvider", InjectableReferenceProvider.class.getName()), + constructor.getThis(), constructor.getMethodParam(0)); + paramIdx++; + } + + for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { + // Injection points + BuiltinBean builtinBean = null; + if (injectionPoint.getResolvedBean() == null) { + builtinBean = BuiltinBean.resolve(injectionPoint); + } + if (builtinBean != null) { + builtinBean.getGenerator().generate(classOutput, bean, injectionPoint, beanCreator, constructor, + injectionPointToProviderField.get(injectionPoint), annotationLiterals); + } else { + if (injectionPoint.getResolvedBean().getAllInjectionPoints().stream() + .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(ip.requiredType.name()))) { + // IMPL NOTE: Injection point resolves to a dependent bean that injects InjectionPoint metadata and so we need to wrap the injectable + // reference provider + ResultHandle requiredQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance qualifierAnnotation : injectionPoint.requiredQualifiers) { + BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); + if (qualifier != null) { + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, qualifier.getLiteralInstance(constructor)); + } else { + // Create annotation literal first + ClassInfo qualifierClass = bean.getDeployment().getQualifier(qualifierAnnotation.name()); + String annotationLiteralName = annotationLiterals.process(classOutput, qualifierClass, qualifierAnnotation, + Types.getPackageName(beanCreator.getClassName())); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, + constructor.newInstance(MethodDescriptor.ofConstructor(annotationLiteralName))); + } + } + ResultHandle wrapHandle = constructor.newInstance( + MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableReferenceProvider.class, java.lang.reflect.Type.class, + Set.class), + constructor.getMethodParam(paramIdx++), Types.getTypeHandle(constructor, injectionPoint.requiredType), requiredQualifiersHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), + InjectableReferenceProvider.class.getName()), constructor.getThis(), wrapHandle); + } else { + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), + InjectableReferenceProvider.class.getName()), constructor.getThis(), constructor.getMethodParam(paramIdx++)); + } + } + } + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + constructor.getThis(), constructor.getMethodParam(paramIdx++)); + } + + // Bean types + ResultHandle typesHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (org.jboss.jandex.Type type : bean.getTypes()) { + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, typesHandle, Types.getTypeHandle(constructor, type)); + } + ResultHandle unmodifiableTypesHandle = constructor + .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), typesHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "beanTypes", Set.class.getName()), constructor.getThis(), + unmodifiableTypesHandle); + + // Qualifiers + if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { + + ResultHandle qualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + + for (AnnotationInstance qualifierAnnotation : bean.getQualifiers()) { + BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); + if (qualifier != null) { + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, qualifier.getLiteralInstance(constructor)); + } else { + // Create annotation literal first + ClassInfo qualifierClass = bean.getDeployment().getQualifier(qualifierAnnotation.name()); + String annotationLiteralName = annotationLiterals.process(classOutput, qualifierClass, qualifierAnnotation, + Types.getPackageName(beanCreator.getClassName())); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, + constructor.newInstance(MethodDescriptor.ofConstructor(annotationLiteralName))); + } + } + ResultHandle unmodifiableQualifiersHandle = constructor + .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), qualifiersHandle); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "qualifiers", Set.class.getName()), constructor.getThis(), + unmodifiableQualifiersHandle); + } + + if (bean.getScope().isNormal()) { + // this.proxy = new LazyValue(() -> new Bar_ClientProxy(this)) + String proxyTypeName = ClientProxyGenerator.getProxyPackageName(bean) + "." + baseName + ClientProxyGenerator.CLIENT_PROXY_SUFFIX; + FunctionCreator func = constructor.createFunction(Supplier.class); + BytecodeCreator funcBytecode = func.getBytecode(); + funcBytecode + .returnValue(funcBytecode.newInstance(MethodDescriptor.ofConstructor(proxyTypeName, beanCreator.getClassName()), constructor.getThis())); + + ResultHandle proxyHandle = constructor.newInstance(MethodDescriptor.ofConstructor(LazyValue.class, Supplier.class), func.getInstance()); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "proxy", LazyValue.class.getName()), constructor.getThis(), + proxyHandle); + } + + return constructor; + } + + protected void createDestroy(BeanInfo bean, ClassCreator beanCreator, String providerTypeName) { + + MethodCreator destroy = beanCreator.getMethodCreator("destroy", void.class, providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + + if (bean.isClassBean() && !bean.isInterceptor()) { + // PreDestroy interceptors + if (!bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty()) { + destroy.invokeInterfaceMethod(MethodDescriptor.ofMethod(Subclass.class, "destroy", void.class), destroy.getMethodParam(0)); + } + + // TODO callbacks may be defined on superclasses + Optional preDestroy = bean.getTarget().asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.PRE_DESTROY)).findFirst(); + if (preDestroy.isPresent()) { + // instance.superCoolDestroyCallback() + destroy.invokeVirtualMethod(MethodDescriptor.of(preDestroy.get()), destroy.getMethodParam(0)); + } + } + // ctx.release() + destroy.invokeInterfaceMethod(MethodDescriptor.ofMethod(CreationalContext.class, "release", void.class), destroy.getMethodParam(1)); + destroy.returnValue(null); + + // TODO! + MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC); + bridgeDestroy.returnValue(bridgeDestroy.invokeVirtualMethod(destroy.getMethodDescriptor(), bridgeDestroy.getThis(), bridgeDestroy.getMethodParam(0), + bridgeDestroy.getMethodParam(1))); + } + + /** + * + * @param beanCreator + * @param bean + * @param baseName + * @param injectionPointToProviderField + * @param interceptorToProviderField + * @see Contextual#create() + */ + protected void createCreate(ClassCreator beanCreator, BeanInfo bean, String providerTypeName, String baseName, + Map injectionPointToProviderField, Map interceptorToProviderField) { + + MethodCreator create = beanCreator.getMethodCreator("create", providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + ResultHandle instanceHandle = null; + + if (bean.isClassBean()) { + List methodInjections = bean.getInjections().stream().filter(i -> i.isMethod() && !i.isConstructor()).collect(Collectors.toList()); + List fieldInjections = bean.getInjections().stream().filter(i -> i.isField()).collect(Collectors.toList()); + + ResultHandle postConstructsHandle = null; + ResultHandle aroundConstructsHandle = null; + Map interceptorToWrap = new HashMap<>(); + + if (bean.hasLifecycleInterceptors()) { + // Note that we must share the interceptors instances with the intercepted subclass, if present + List postConstructs = bean.getLifecycleInterceptors(InterceptionType.POST_CONSTRUCT); + List aroundConstructs = bean.getLifecycleInterceptors(InterceptionType.AROUND_CONSTRUCT); + + // Wrap InjectableInterceptors using InitializedInterceptor + Set wraps = new HashSet<>(); + wraps.addAll(aroundConstructs); + wraps.addAll(postConstructs); + for (InterceptorInfo interceptor : wraps) { + ResultHandle interceptorProvider = create.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + create.getThis()); + ResultHandle interceptorInstaceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorProvider, + create.getMethodParam(0)); + ResultHandle wrapHandle = create.invokeStaticMethod(MethodDescriptor.ofMethod(InitializedInterceptor.class, "of", + InitializedInterceptor.class, Object.class, InjectableInterceptor.class), interceptorInstaceHandle, interceptorProvider); + interceptorToWrap.put(interceptor, wrapHandle); + } + + if (!postConstructs.isEmpty()) { + // postConstructs = new ArrayList() + postConstructsHandle = create.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (InterceptorInfo interceptor : postConstructs) { + ResultHandle interceptorHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), create.getThis()); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, + childCtxHandle); + ResultHandle interceptorInvocationHandle = create.invokeStaticMethod(MethodDescriptors.INTERCEPTOR_INVOCATION_POST_CONSTRUCT, + interceptorHandle, interceptorInstanceHandle); + // postConstructs.add(InterceptorInvocation.postConstruct(interceptor,interceptor.get(CreationalContextImpl.child(ctx)))) + create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, postConstructsHandle, interceptorInvocationHandle); + } + } + if (!aroundConstructs.isEmpty()) { + // aroundConstructs = new ArrayList() + aroundConstructsHandle = create.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (InterceptorInfo interceptor : aroundConstructs) { + ResultHandle interceptorHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), create.getThis()); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, + childCtxHandle); + ResultHandle interceptorInvocationHandle = create.invokeStaticMethod(MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT, + interceptorHandle, interceptorInstanceHandle); + // aroundConstructs.add(InterceptorInvocation.aroundConstruct(interceptor,interceptor.get(CreationalContextImpl.child(ctx)))) + create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, aroundConstructsHandle, interceptorInvocationHandle); + } + } + } + + // AroundConstruct lifecycle callback interceptors + if (!bean.getLifecycleInterceptors(InterceptionType.AROUND_CONSTRUCT).isEmpty()) { + Optional constructorInjection = bean.getConstructorInjection(); + ResultHandle constructorHandle; + if (constructorInjection.isPresent()) { + List paramTypes = constructorInjection.get().injectionPoints.stream().map(i -> i.requiredType.name().toString()) + .collect(Collectors.toList()); + ResultHandle[] paramsHandles = new ResultHandle[2]; + paramsHandles[0] = create.loadClass(providerTypeName); + ResultHandle paramsArray = create.newArray(Class.class, create.load(paramTypes.size())); + for (ListIterator iterator = paramTypes.listIterator(); iterator.hasNext();) { + create.writeArrayValue(paramsArray, create.load(iterator.nextIndex()), create.loadClass(iterator.next())); + } + paramsHandles[1] = paramsArray; + constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, paramsHandles); + } else { + // constructor = Reflections.findConstructor(Foo.class) + ResultHandle[] paramsHandles = new ResultHandle[2]; + paramsHandles[0] = create.loadClass(providerTypeName); + paramsHandles[1] = create.newArray(Class.class, create.load(0)); + constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, paramsHandles); + } + + List providerHandles = newProviderHandles(bean, beanCreator, create, injectionPointToProviderField, interceptorToProviderField, + interceptorToWrap); + + // Forwarding function + // Supplier forward = () -> new SimpleBean_Subclass(ctx,lifecycleInterceptorProvider1) + FunctionCreator func = create.createFunction(Supplier.class); + BytecodeCreator funcBytecode = func.getBytecode(); + ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerTypeName, baseName, providerHandles); + funcBytecode.returnValue(retHandle); + + // InvocationContextImpl.aroundConstruct(constructor,aroundConstructs,forward).proceed() + ResultHandle invocationContextHandle = create.invokeStaticMethod(MethodDescriptor.ofMethod(InvocationContextImpl.class, "aroundConstruct", + InvocationContextImpl.class, Constructor.class, List.class, Supplier.class), constructorHandle, aroundConstructsHandle, + func.getInstance()); + ExceptionTable tryCatch = create.addTryCatch(); + BytecodeCreator exceptionCatch = tryCatch.addCatchClause(Exception.class); + // throw new RuntimeException(e) + // TODO existing exception param + exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs", exceptionCatch.getMethodParam(0)); + instanceHandle = create.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), + invocationContextHandle); + tryCatch.complete(); + + } else { + instanceHandle = newInstanceHandle(bean, beanCreator, create, create, providerTypeName, baseName, + newProviderHandles(bean, beanCreator, create, injectionPointToProviderField, interceptorToProviderField, interceptorToWrap)); + } + + // Perform field and initializer injections + for (Injection fieldInjection : fieldInjections) { + InjectionPointInfo injectionPoint = fieldInjection.injectionPoints.get(0); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + if (Modifier.isPrivate(fieldInjection.target.asField().flags())) { + LOGGER.infof("@Inject %s#%s is private - Arc users are encouraged to avoid using private injection fields", + fieldInjection.target.asField().declaringClass().name(), fieldInjection.target.asField().name()); + create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_WRITE_FIELD, create.loadClass(providerTypeName), + create.load(fieldInjection.target.asField().name()), instanceHandle, referenceHandle); + } else { + create.writeInstanceField( + FieldDescriptor.of(providerTypeName, fieldInjection.target.asField().name(), injectionPoint.requiredType.name().toString()), + instanceHandle, referenceHandle); + } + } + for (Injection methodInjection : methodInjections) { + ResultHandle[] referenceHandles = new ResultHandle[methodInjection.injectionPoints.size()]; + int paramIdx = 0; + for (InjectionPointInfo injectionPoint : methodInjection.injectionPoints) { + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + referenceHandles[paramIdx++] = referenceHandle; + } + create.invokeVirtualMethod(MethodDescriptor.of(methodInjection.target.asMethod()), instanceHandle, referenceHandles); + } + + // PostConstruct lifecycle callback interceptors + if (!bean.getLifecycleInterceptors(InterceptionType.POST_CONSTRUCT).isEmpty()) { + + // InvocationContextImpl.postConstruct(instance,postConstructs).proceed() + ResultHandle invocationContextHandle = create.invokeStaticMethod( + MethodDescriptor.ofMethod(InvocationContextImpl.class, "postConstruct", InvocationContextImpl.class, Object.class, List.class), + instanceHandle, postConstructsHandle); + + ExceptionTable tryCatch = create.addTryCatch(); + BytecodeCreator exceptionCatch = tryCatch.addCatchClause(Exception.class); + // throw new RuntimeException(e) + // TODO existing exception param + exceptionCatch.throwException(RuntimeException.class, "Error invoking postConstructs", exceptionCatch.getMethodParam(0)); + create.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), invocationContextHandle); + tryCatch.complete(); + } + + // TODO callbacks may be defined on superclasses + if (!bean.isInterceptor()) { + Optional postConstruct = bean.getTarget().asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.POST_CONSTRUCT)) + .findFirst(); + if (postConstruct.isPresent()) { + create.invokeVirtualMethod(MethodDescriptor.ofMethod(providerTypeName, postConstruct.get().name(), void.class), instanceHandle); + } + } + + } else if (bean.isProducerMethod()) { + // TODO if the declaring bean is @Dependent we must destroy it afterwards + // instance = declaringProvider.get(new CreationalContextImpl<>()).produce() + ResultHandle declaringProviderHandle = create.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), "declaringProvider", InjectableReferenceProvider.class.getName()), create.getThis()); + ResultHandle ctxHandle = create.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); + ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ctxHandle); + + if (bean.getDeclaringBean().getScope().isNormal()) { + // We need to unwrap the client proxy + declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle); + } + + List injectionPoints = bean.getAllInjectionPoints(); + ResultHandle[] referenceHandles = new ResultHandle[injectionPoints.size()]; + int paramIdx = 0; + for (InjectionPointInfo injectionPoint : injectionPoints) { + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + referenceHandles[paramIdx++] = referenceHandle; + } + + MethodInfo producerMethod = bean.getTarget().asMethod(); + if (Modifier.isPrivate(producerMethod.flags())) { + LOGGER.infof("Producer %s#%s is private - Arc users are encouraged to avoid using private producers", producerMethod.declaringClass().name(), + producerMethod.name()); + ResultHandle paramTypesArray = create.newArray(Class.class, create.load(referenceHandles.length)); + ResultHandle argsArray = create.newArray(Object.class, create.load(referenceHandles.length)); + for (int i = 0; i < referenceHandles.length; i++) { + create.writeArrayValue(paramTypesArray, create.load(i), create.loadClass(producerMethod.parameters().get(i).name().toString())); + create.writeArrayValue(argsArray, create.load(i), referenceHandles[i]); + } + instanceHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + create.loadClass(producerMethod.declaringClass().name().toString()), create.load(producerMethod.name()), paramTypesArray, + declaringProviderInstanceHandle, argsArray); + } else { + instanceHandle = create.invokeVirtualMethod(MethodDescriptor.of(producerMethod), declaringProviderInstanceHandle, referenceHandles); + } + + } else if (bean.isProducerField()) { + // TODO if the declaring bean is @Dependent we must destroy it afterwards + // instance = declaringProvider.get(new CreationalContextImpl<>()).field + + FieldInfo producerField = bean.getTarget().asField(); + + ResultHandle declaringProviderHandle = create.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), "declaringProvider", InjectableReferenceProvider.class.getName()), create.getThis()); + ResultHandle ctxHandle = create.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); + ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ctxHandle); + + if (bean.getDeclaringBean().getScope().isNormal()) { + // We need to unwrap the client proxy + declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle); + } + + if (Modifier.isPrivate(producerField.flags())) { + LOGGER.infof("Producer %s#%s is private - Arc users are encouraged to avoid using private producers", producerField.declaringClass().name(), + producerField.name()); + instanceHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_READ_FIELD, + create.loadClass(producerField.declaringClass().name().toString()), create.load(producerField.name()), declaringProviderInstanceHandle); + } else { + instanceHandle = create.readInstanceField(FieldDescriptor.of(bean.getTarget().asField()), declaringProviderInstanceHandle); + } + } + + create.returnValue(instanceHandle); + + // Bridge method needed + MethodCreator bridgeCreate = beanCreator.getMethodCreator("create", Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC | ACC_BRIDGE); + bridgeCreate.returnValue(bridgeCreate.invokeVirtualMethod(create.getMethodDescriptor(), bridgeCreate.getThis(), bridgeCreate.getMethodParam(0))); + } + + private List newProviderHandles(BeanInfo bean, ClassCreator beanCreator, MethodCreator createMethod, + Map injectionPointToProviderField, Map interceptorToProviderField, + Map interceptorToWrap) { + + List providerHandles = new ArrayList<>(); + Optional constructorInjection = bean.getConstructorInjection(); + + if (constructorInjection.isPresent()) { + for (InjectionPointInfo injectionPoint : constructorInjection.get().injectionPoints) { + ResultHandle providerHandle = createMethod.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), createMethod.getThis()); + ResultHandle childCtx = createMethod.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, createMethod.getMethodParam(0)); + providerHandles.add(createMethod.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtx)); + } + } + if (bean.isSubclassRequired()) { + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + ResultHandle wrapped = interceptorToWrap.get(interceptor); + if (wrapped != null) { + providerHandles.add(wrapped); + } else { + providerHandles.add(createMethod.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + createMethod.getThis())); + } + } + } + return providerHandles; + } + + private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, BytecodeCreator creator, MethodCreator createMethod, + String providerTypeName, String baseName, List providerHandles) { + + Optional constructorInjection = bean.getConstructorInjection(); + MethodInfo constructor = constructorInjection.isPresent() ? constructorInjection.get().target.asMethod() : null; + List injectionPoints = constructorInjection.isPresent() ? constructorInjection.get().injectionPoints : Collections.emptyList(); + + if (bean.isSubclassRequired()) { + // new SimpleBean_Subclass(foo,ctx,lifecycleInterceptorProvider1) + + List interceptors = bean.getBoundInterceptors(); + List paramTypes = new ArrayList<>(); + List paramHandles = new ArrayList<>(); + + // 1. constructor injection points + for (int i = 0; i < injectionPoints.size(); i++) { + paramTypes.add(injectionPoints.get(i).requiredType.name().toString()); + paramHandles.add(providerHandles.get(i)); + } + + // 2. ctx + paramHandles.add(createMethod.getMethodParam(0)); + paramTypes.add(CreationalContext.class.getName()); + + // 3. interceptors (wrapped if needed) + for (int i = 0; i < interceptors.size(); i++) { + paramTypes.add(InjectableInterceptor.class.getName()); + paramHandles.add(providerHandles.get(injectionPoints.size() + i)); + } + + return creator.newInstance( + MethodDescriptor.ofConstructor(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), paramTypes.toArray(new String[0])), + paramHandles.toArray(new ResultHandle[0])); + + } else if (constructorInjection.isPresent()) { + if (Modifier.isPrivate(constructor.flags())) { + LOGGER.infof("Constructor %s is private - Arc users are encouraged to avoid using private interceptor methods", + constructor.declaringClass().name()); + ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(providerHandles.size())); + ResultHandle argsArray = creator.newArray(Object.class, creator.load(providerHandles.size())); + for (int i = 0; i < injectionPoints.size(); i++) { + creator.writeArrayValue(paramTypesArray, creator.load(i), creator.loadClass(injectionPoints.get(i).requiredType.name().toString())); + creator.writeArrayValue(argsArray, creator.load(i), providerHandles.get(i)); + } + return creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_NEW_INSTANCE, creator.loadClass(constructor.declaringClass().name().toString()), + paramTypesArray, argsArray); + } else { + // new SimpleBean(foo) + return creator.newInstance( + MethodDescriptor.ofConstructor(providerTypeName, + injectionPoints.stream().map(ip -> ip.requiredType.name().toString()).collect(Collectors.toList()).toArray(new String[0])), + providerHandles.toArray(new ResultHandle[0])); + } + } else { + MethodInfo noArgsConstructor = bean.getTarget().asClass().method(""); + if (Modifier.isPrivate(noArgsConstructor.flags())) { + LOGGER.infof("Constructor %s is private - Arc users are encouraged to avoid using private interceptor methods", + noArgsConstructor.declaringClass().name()); + ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(0)); + ResultHandle argsArray = creator.newArray(Object.class, creator.load(0)); + return creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_NEW_INSTANCE, + creator.loadClass(noArgsConstructor.declaringClass().name().toString()), paramTypesArray, argsArray); + } else { + // new SimpleBean() + return creator.newInstance(MethodDescriptor.ofConstructor(providerTypeName)); + } + } + } + + /** + * + * @param bean + * @param beanCreator + * @param providerTypeName + * @see InjectableReferenceProvider#get() + */ + protected void createGet(BeanInfo bean, ClassCreator beanCreator, String providerTypeName) { + + MethodCreator get = beanCreator.getMethodCreator("get", providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + + if (ScopeInfo.DEPENDENT.equals(bean.getScope())) { + // Foo instance = create(ctx) + ResultHandle instance = get.invokeVirtualMethod( + MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), get.getThis(), + get.getMethodParam(0)); + // CreationalContextImpl.addDependencyToParent(this,instance,ctx) + get.invokeStaticMethod(MethodDescriptor.ofMethod(CreationalContextImpl.class, "addDependencyToParent", void.class, InjectableBean.class, + Object.class, CreationalContext.class), get.getThis(), instance, get.getMethodParam(0)); + // return instance + get.returnValue(instance); + } else if (ScopeInfo.SINGLETON.equals(bean.getScope())) { + // return Arc.container().getContext(getScope()).get(this, new CreationalContextImpl<>()) + ResultHandle container = get.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class)); + ResultHandle creationalContext = get.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); + ResultHandle scope = get.loadClass(bean.getScope().getClazz()); + ResultHandle context = get.invokeInterfaceMethod(MethodDescriptor.ofMethod(ArcContainer.class, "getContext", Context.class, Class.class), container, + scope); + get.returnValue(get.invokeInterfaceMethod(MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, CreationalContext.class), + context, get.getThis(), creationalContext)); + } else if (bean.getScope().isNormal()) { + // return proxy.get() + ResultHandle proxy = get.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "proxy", LazyValue.class.getName()), get.getThis()); + get.returnValue(get.invokeVirtualMethod(MethodDescriptor.ofMethod(LazyValue.class, "get", Object.class), proxy)); + } + + // Bridge method needed + MethodCreator bridgeGet = beanCreator.getMethodCreator("get", Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC | ACC_BRIDGE); + bridgeGet.returnValue(bridgeGet.invokeVirtualMethod(get.getMethodDescriptor(), bridgeGet.getThis(), bridgeGet.getMethodParam(0))); + } + + /** + * + * @param beanCreator + * @see InjectableBean#getTypes() + */ + protected void createGetTypes(ClassCreator beanCreator, FieldDescriptor typesField) { + MethodCreator getScope = beanCreator.getMethodCreator("getTypes", Set.class).setModifiers(ACC_PUBLIC); + getScope.returnValue(getScope.readInstanceField(typesField, getScope.getThis())); + } + + /** + * + * @param bean + * @param beanCreator + * @see InjectableBean#getScope() + */ + protected void createGetScope(BeanInfo bean, ClassCreator beanCreator) { + MethodCreator getScope = beanCreator.getMethodCreator("getScope", Class.class).setModifiers(ACC_PUBLIC); + getScope.returnValue(getScope.loadClass(bean.getScope().getClazz())); + } + + /** + * + * @param bean + * @param beanCreator + * @param qualifiersField + * @see InjectableBean#getQualifiers() + */ + protected void createGetQualifiers(BeanInfo bean, ClassCreator beanCreator, FieldDescriptor qualifiersField) { + MethodCreator getQualifiers = beanCreator.getMethodCreator("getQualifiers", Set.class).setModifiers(ACC_PUBLIC); + getQualifiers.returnValue(getQualifiers.readInstanceField(qualifiersField, getQualifiers.getThis())); + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java new file mode 100644 index 0000000000000..05baa613a9948 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java @@ -0,0 +1,333 @@ +package org.jboss.protean.arc.processor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.inject.AmbiguousResolutionException; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.enterprise.inject.spi.InterceptionType; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.protean.arc.processor.Methods.MethodKey; + +/** + * + * @author Martin Kouba + */ +class BeanInfo { + + private final AnnotationTarget target; + + private final BeanDeployment beanDeployment; + + private final ScopeInfo scope; + + private final Set types; + + private final Set qualifiers; + + private final List injections; + + private final BeanInfo declaringBean; + + private Map> interceptedMethods; + + private Map> lifecycleInterceptors; + + /** + * + * @param target + * @param beanDeployment + * @param scope + * @param types + * @param qualifiers + * @param alternativePriority + * @param injections + * @param declaringBean + */ + BeanInfo(AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set types, Set qualifiers, + List injections, BeanInfo declaringBean) { + this.target = target; + this.beanDeployment = beanDeployment; + this.scope = scope != null ? scope : ScopeInfo.DEPENDENT; + this.types = types; + if (qualifiers.isEmpty()) { + qualifiers.add(BuiltinQualifier.DEFAULT.getInstance()); + } + qualifiers.add(BuiltinQualifier.ANY.getInstance()); + this.qualifiers = qualifiers; + this.injections = injections; + this.declaringBean = declaringBean; + } + + AnnotationTarget getTarget() { + return target; + } + + boolean isClassBean() { + return Kind.CLASS.equals(target.kind()); + } + + boolean isProducerMethod() { + return Kind.METHOD.equals(target.kind()); + } + + boolean isProducerField() { + return Kind.FIELD.equals(target.kind()); + } + + boolean isInterceptor() { + return false; + } + + BeanInfo getDeclaringBean() { + return declaringBean; + } + + BeanDeployment getDeployment() { + return beanDeployment; + } + + Type getProviderType() { + if (Kind.CLASS.equals(target.kind())) { + return Types.getProviderType(target.asClass()); + } else if (Kind.METHOD.equals(target.kind())) { + return target.asMethod().returnType(); + } else if (Kind.FIELD.equals(target.kind())) { + return target.asField().type(); + } + throw new IllegalStateException("Cannot infer provider type"); + } + + ScopeInfo getScope() { + return scope; + } + + Set getTypes() { + return types; + } + + Set getQualifiers() { + return qualifiers; + } + + boolean hasDefaultQualifiers() { + return qualifiers.size() == 2 && qualifiers.contains(BuiltinQualifier.DEFAULT.getInstance()) + && qualifiers.contains(BuiltinQualifier.DEFAULT.getInstance()); + } + + List getInjections() { + return injections; + } + + List getAllInjectionPoints() { + if (injections.isEmpty()) { + return Collections.emptyList(); + } + List injectionPoints = new ArrayList<>(); + for (Injection injection : injections) { + injectionPoints.addAll(injection.injectionPoints); + } + return injectionPoints; + } + + Optional getConstructorInjection() { + return injections.isEmpty() ? Optional.empty() : injections.stream().filter(i -> i.isConstructor()).findAny(); + } + + Map> getInterceptedMethods() { + return interceptedMethods; + } + + List getLifecycleInterceptors(InterceptionType interceptionType) { + return lifecycleInterceptors.containsKey(interceptionType) ? lifecycleInterceptors.get(interceptionType) : Collections.emptyList(); + } + + boolean hasLifecycleInterceptors() { + return !lifecycleInterceptors.isEmpty(); + } + + boolean isSubclassRequired() { + return !interceptedMethods.isEmpty() || lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY); + } + + boolean hasDefaultDestroy() { + // TODO disposer methods + return isInterceptor() || !isClassBean() || (getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty() + && target.asClass().methods().stream().noneMatch(m -> m.hasAnnotation(DotNames.PRE_DESTROY))); + } + + /** + * + * @return an ordered list of all interceptors associated with the bean + */ + List getBoundInterceptors() { + List bound = new ArrayList<>(); + for (List interceptors : lifecycleInterceptors.values()) { + bound.addAll(interceptors); + } + if (!interceptedMethods.isEmpty()) { + bound.addAll(interceptedMethods.values().stream().flatMap(list -> list.stream()).collect(Collectors.toList())); + } + return bound.isEmpty() ? Collections.emptyList() : bound.stream().distinct().sorted().collect(Collectors.toList()); + } + + boolean matches(InjectionPointInfo injectionPoint) { + // Bean has all the required qualifiers + for (AnnotationInstance requiredQualifier : injectionPoint.requiredQualifiers) { + if (!hasQualifier(requiredQualifier)) { + return false; + } + } + // Bean has a bean type that matches the required type + return matchesType(injectionPoint.requiredType); + } + + boolean matchesType(Type requiredType) { + for (Type beanType : types) { + if (beanDeployment.getBeanResolver().matches(requiredType, beanType)) { + return true; + } + } + return false; + } + + boolean hasQualifier(AnnotationInstance required) { + ClassInfo requiredInfo = beanDeployment.getQualifier(required.name()); + List binding = required.values().stream().filter(v -> !requiredInfo.method(v.name()).hasAnnotation(DotNames.NONBINDING)) + .collect(Collectors.toList()); + for (AnnotationInstance qualifier : qualifiers) { + if (required.name().equals(qualifier.name())) { + // Must have the same annotation member value for each member which is not annotated @Nonbinding + boolean matches = true; + for (AnnotationValue value : binding) { + if (!value.equals(qualifier.value(value.name()))) { + matches = false; + break; + } + } + if (matches) { + return true; + } + } + } + return false; + } + + void init() { + for (Injection injection : injections) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + resolveInjectionPoint(injectionPoint); + } + } + interceptedMethods = initInterceptedMethods(); + lifecycleInterceptors = initLifecycleInterceptors(); + } + + private void resolveInjectionPoint(InjectionPointInfo injectionPoint) { + if (BuiltinBean.resolvesTo(injectionPoint)) { + // Skip built-in beans + return; + } + List resolved = new ArrayList<>(); + for (BeanInfo bean : beanDeployment.getBeans()) { + if (bean.matches(injectionPoint)) { + resolved.add(bean); + } + } + if (resolved.isEmpty()) { + throw new UnsatisfiedResolutionException(injectionPoint + " on " + this); + } else if (resolved.size() > 1) { + throw new AmbiguousResolutionException( + injectionPoint + " on " + this + "\nBeans:\n" + resolved.stream().map(Object::toString).collect(Collectors.joining("\n"))); + } + injectionPoint.resolve(resolved.get(0)); + } + + protected String getType() { + if (Kind.METHOD.equals(target.kind())) { + return "PRODUCER METHOD"; + } else if (Kind.FIELD.equals(target.kind())) { + return "PRODUCER FIELD"; + } else { + return target.kind().toString(); + } + } + + private Map> initInterceptedMethods() { + if (!isInterceptor() && isClassBean()) { + Map> interceptedMethods = new HashMap<>(); + + Map> candidates = new HashMap<>(); + // TODO interceptor bindings are transitive!!! + List classLevelBindings = new ArrayList<>(); + addClassLevelBindings(target.asClass(), classLevelBindings); + Methods.addInterceptedMethodCandidates(beanDeployment, target.asClass(), candidates, classLevelBindings); + + for (Entry> entry : candidates.entrySet()) { + List interceptors = beanDeployment.getInterceptorResolver().resolve(InterceptionType.AROUND_INVOKE, entry.getValue()); + if (!interceptors.isEmpty()) { + interceptedMethods.put(entry.getKey().method, interceptors); + } + } + return interceptedMethods; + } else { + return Collections.emptyMap(); + } + } + + private Map> initLifecycleInterceptors() { + if (!isInterceptor() && isClassBean()) { + Map> lifecycleInterceptors = new HashMap<>(); + Set classLevelBindings = new HashSet<>(); + addClassLevelBindings(target.asClass(), classLevelBindings); + putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.POST_CONSTRUCT); + putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.PRE_DESTROY); + putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.AROUND_CONSTRUCT); + return lifecycleInterceptors; + } else { + return Collections.emptyMap(); + } + } + + private void putLifecycleInterceptors(Map> lifecycleInterceptors, Set classLevelBindings, + InterceptionType interceptionType) { + List interceptors = beanDeployment.getInterceptorResolver().resolve(interceptionType, classLevelBindings); + if (!interceptors.isEmpty()) { + lifecycleInterceptors.put(interceptionType, interceptors); + } + } + + private void addClassLevelBindings(ClassInfo classInfo, Collection bindings) { + classInfo.classAnnotations().stream() + .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null && bindings.stream().noneMatch(e -> e.name().equals(a.name()))) + .forEach(a -> bindings.add(a)); + if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotNames.OBJECT)) { + ClassInfo superClass = beanDeployment.getIndex().getClassByName(classInfo.superName()); + if (superClass != null) { + addClassLevelBindings(superClass, bindings); + } + } + } + + @Override + public String toString() { + return getType() + " bean [types=" + types + ", qualifiers=" + qualifiers + ", target=" + target + "]"; + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java new file mode 100644 index 0000000000000..89768bf5c6651 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProcessor.java @@ -0,0 +1,248 @@ +package org.jboss.protean.arc.processor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.inject.Named; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import org.jboss.logging.Logger; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType; + +/** + * + * @author Martin Kouba + */ +public class BeanProcessor { + + public static Builder builder() { + return new Builder(); + } + + static final String DEFAULT_NAME = "Default"; + + private static final Logger LOGGER = Logger.getLogger(BeanProcessor.class); + + private final String name; + + private final IndexView index; + + private final Collection additionalBeanDefiningAnnotations; + + private final ResourceOutput output; + + private final boolean sharedAnnotationLiterals; + + private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, ResourceOutput output, + boolean sharedAnnotationLiterals) { + Objects.requireNonNull(output); + this.name = name; + this.index = index; + this.additionalBeanDefiningAnnotations = additionalBeanDefiningAnnotations; + this.output = output; + this.sharedAnnotationLiterals = sharedAnnotationLiterals; + } + + public BeanDeployment process() throws IOException { + + BeanDeployment beanDeployment = new BeanDeployment(new IndexWrapper(index), additionalBeanDefiningAnnotations); + beanDeployment.init(); + + BeanGenerator beanGenerator = new BeanGenerator(); + ClientProxyGenerator clientProxyGenerator = new ClientProxyGenerator(); + InterceptorGenerator interceptorGenerator = new InterceptorGenerator(); + SubclassGenerator subclassGenerator = new SubclassGenerator(); + AnnotationLiteralGenerator annotationLiteralsGenerator = new AnnotationLiteralGenerator(); + + Map beanToGeneratedName = new HashMap<>(); + Map interceptorToGeneratedName = new HashMap<>(); + + AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(name, sharedAnnotationLiterals); + + long start = System.currentTimeMillis(); + List resources = new ArrayList<>(); + + // Generate interceptors + for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) { + for (Resource resource : interceptorGenerator.generate(interceptor, annotationLiterals)) { + resources.add(resource); + if (SpecialType.INTERCEPTOR_BEAN.equals(resource.getSpecialType())) { + interceptorToGeneratedName.put(interceptor, resource.getName()); + beanToGeneratedName.put(interceptor, resource.getName()); + } + } + } + + // Generate beans + for (BeanInfo bean : beanDeployment.getBeans()) { + for (Resource resource : beanGenerator.generate(bean, annotationLiterals)) { + resources.add(resource); + if (SpecialType.BEAN.equals(resource.getSpecialType())) { + if (bean.getScope().isNormal()) { + // Generate client proxy + resources.addAll(clientProxyGenerator.generate(bean, resource.getFullyQualifiedName())); + } + beanToGeneratedName.put(bean, resource.getName()); + if (bean.isSubclassRequired()) { + resources.addAll(subclassGenerator.generate(bean, resource.getFullyQualifiedName())); + } + } + } + } + + // Generate BeanProvider + resources.addAll(new BeanProviderGenerator().generate(name, beanDeployment, beanToGeneratedName)); + + // Generate AnnotationLiterals + if (annotationLiterals.hasLiteralsToGenerate()) { + resources.addAll(annotationLiteralsGenerator.generate(name, beanDeployment, annotationLiterals.getCache())); + } + + for (Resource resource : resources) { + output.writeResource(resource); + } + LOGGER.infof("%s resources written in %s ms", resources.size(), System.currentTimeMillis() - start); + return beanDeployment; + } + + private static IndexView addBuiltinQualifiersIfNeeded(IndexView index) { + if (index.getClassByName(DotNames.ANY) == null) { + Indexer indexer = new Indexer(); + index(indexer, Default.class.getName()); + index(indexer, Any.class.getName()); + index(indexer, Named.class.getName()); + return CompositeIndex.create(index, indexer.complete()); + } + return index; + } + + private static void index(Indexer indexer, String className) { + try (InputStream stream = BeanProcessor.class.getClassLoader().getResourceAsStream(className.replace('.', '/') + ".class")) { + indexer.index(stream); + } catch (IOException e) { + throw new IllegalStateException("Failed to index: " + className); + } + } + + public static class Builder { + + private String name = DEFAULT_NAME; + + private IndexView index; + + private Collection additionalBeanDefiningAnnotations = Collections.emptySet(); + + private ResourceOutput output; + + private boolean sharedAnnotationLiterals = true; + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setIndex(IndexView index) { + this.index = index; + return this; + } + + public Builder setAdditionalBeanDefiningAnnotations(Collection additionalBeanDefiningAnnotations) { + this.additionalBeanDefiningAnnotations = additionalBeanDefiningAnnotations; + return this; + } + + public Builder setOutput(ResourceOutput output) { + this.output = output; + return this; + } + + public Builder setSharedAnnotationLiterals(boolean sharedAnnotationLiterals) { + this.sharedAnnotationLiterals = sharedAnnotationLiterals; + return this; + } + + public BeanProcessor build() { + return new BeanProcessor(name, addBuiltinQualifiersIfNeeded(index), additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals); + } + + } + + /** + * This wrapper is used to index JDK classes on demand. + */ + public static class IndexWrapper implements IndexView { + + private final Map additionalClasses; + + private final IndexView index; + + public IndexWrapper(IndexView index) { + this.index = index; + this.additionalClasses = new ConcurrentHashMap<>(); + } + + @Override + public Collection getKnownClasses() { + return index.getKnownClasses(); + } + + @Override + public ClassInfo getClassByName(DotName className) { + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + return additionalClasses.computeIfAbsent(className, name -> { + LOGGER.debugf("Index: %s", className); + Indexer indexer = new Indexer(); + index(indexer, className.toString()); + Index index = indexer.complete(); + return index.getClassByName(name); + }); + } + return classInfo; + } + + @Override + public Collection getKnownDirectSubclasses(DotName className) { + return index.getKnownDirectSubclasses(className); + } + + @Override + public Collection getAllKnownSubclasses(DotName className) { + return index.getAllKnownSubclasses(className); + } + + @Override + public Collection getKnownDirectImplementors(DotName className) { + return index.getKnownDirectImplementors(className); + } + + @Override + public Collection getAllKnownImplementors(DotName interfaceName) { + return index.getAllKnownImplementors(interfaceName); + } + + @Override + public Collection getAnnotations(DotName annotationName) { + return index.getAnnotations(annotationName); + } + + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProviderGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProviderGenerator.java new file mode 100644 index 0000000000000..ea9c05654b800 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanProviderGenerator.java @@ -0,0 +1,169 @@ +package org.jboss.protean.arc.processor; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.BeanProvider; +import org.jboss.protean.arc.InjectableInterceptor; +import org.jboss.protean.arc.InjectableReferenceProvider; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; +import org.objectweb.asm.Type; + +/** + * + * @author Martin Kouba + */ +public class BeanProviderGenerator extends AbstractGenerator { + + static final String BEAN_PROVIDER_SUFFIX = "_BeanProvider"; + + static final String SETUP_PACKAGE = Arc.class.getPackage().getName() + ".setup"; + + /** + * + * @param name + * @param beanDeployment + * @param beanToGeneratedName + * @return a collection of resources + */ + Collection generate(String name, BeanDeployment beanDeployment, Map beanToGeneratedName) { + + ResourceClassOutput classOutput = new ResourceClassOutput(); + + String generatedName = SETUP_PACKAGE + "." + name + BEAN_PROVIDER_SUFFIX; + ClassCreator beanProvider = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(BeanProvider.class).build(); + + // BeanProvider#getBeans() + MethodCreator getBeans = beanProvider.getMethodCreator("getBeans", Collection.class); + // List> beans = new ArrayList<>(); + ResultHandle beansHandle = getBeans.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + + // Bar -> Foo, Baz + // Foo -> Baz + // Interceptor -> Baz + Map> beanToInjections = new HashMap<>(); + for (BeanInfo bean : beanDeployment.getBeans()) { + if (bean.isProducerMethod() || bean.isProducerField()) { + beanToInjections.computeIfAbsent(bean.getDeclaringBean(), d -> new ArrayList<>()).add(bean); + } + for (Injection injection : bean.getInjections()) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + if (!BuiltinBean.resolvesTo(injectionPoint)) { + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(bean); + } + } + } + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + beanToInjections.computeIfAbsent(interceptor, d -> new ArrayList<>()).add(bean); + } + } + // Also process interceptors injection points + for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) { + for (Injection injection : interceptor.getInjections()) { + for (InjectionPointInfo injectionPoint : injection.injectionPoints) { + if (!BuiltinBean.resolvesTo(injectionPoint)) { + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()).add(interceptor); + } + } + } + } + + Map beanToResultHandle = new HashMap<>(); + AtomicInteger localNameIdx = new AtomicInteger(); + List processed = new ArrayList<>(); + + // TODO handle circular dependencies + while (!beanToInjections.isEmpty()) { + for (Iterator>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) { + Entry> entry = iterator.next(); + BeanInfo bean = entry.getKey(); + if (!isDependency(bean, beanToInjections)) { + addBean(getBeans, beansHandle, bean, beanToGeneratedName, localNameIdx, beanToResultHandle); + iterator.remove(); + processed.add(bean); + } + } + } + // Finally process beans that are not dependencies + for (BeanInfo bean : beanDeployment.getBeans()) { + if (!processed.contains(bean)) { + addBean(getBeans, beansHandle, bean, beanToGeneratedName, localNameIdx, beanToResultHandle); + } + } + // return beans + getBeans.returnValue(beansHandle); + + // Finally write the bytecode + beanProvider.close(); + + List resources = new ArrayList<>(); + for (Resource resource : classOutput.getResources()) { + resources.add(resource); + // TODO proper name conversion + resources + .add(ResourceImpl.serviceProvider(BeanProvider.class.getName(), (resource.getName().replace("/", ".")).getBytes(Charset.forName("UTF-8")))); + } + return resources; + } + + private void addBean(MethodCreator getBeans, ResultHandle beansResultHandle, BeanInfo bean, Map beanToGeneratedName, + AtomicInteger localNameIdx, Map beanToResultHandle) { + + String beanType = beanToGeneratedName.get(bean); + + List injectionPoints = bean.getInjections().isEmpty() ? Collections.emptyList() + : bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream()).filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(Collectors.toList()); + List params = new ArrayList<>(); + List paramTypes = new ArrayList<>(); + + if (bean.isProducerMethod() || bean.isProducerField()) { + params.add(beanToResultHandle.get(bean.getDeclaringBean())); + paramTypes.add(Type.getDescriptor(InjectableReferenceProvider.class)); + } + for (InjectionPointInfo injetionPoint : injectionPoints) { + ResultHandle resultHandle = beanToResultHandle.get(injetionPoint.getResolvedBean()); + params.add(resultHandle); + paramTypes.add(Type.getDescriptor(InjectableReferenceProvider.class)); + } + if (!bean.getInterceptedMethods().isEmpty()) { + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + ResultHandle resultHandle = beanToResultHandle.get(interceptor); + params.add(resultHandle); + paramTypes.add(Type.getDescriptor(InjectableInterceptor.class)); + } + } + // Foo_Bean bean2 = new Foo_Bean(bean2) + ResultHandle beanInstance = getBeans.newInstance(MethodDescriptor.ofConstructor(beanType, paramTypes.toArray(new String[0])), + params.toArray(new ResultHandle[0])); + // beans.add(bean2) + getBeans.invokeInterfaceMethod(MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class), beansResultHandle, beanInstance); + beanToResultHandle.put(bean, beanInstance); + } + + private boolean isDependency(BeanInfo bean, Map> beanToInjections) { + for (Iterator>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) { + Entry> entry = iterator.next(); + if (entry.getKey().equals(bean)) { + continue; + } else if (entry.getValue().contains(bean)) { + return true; + } + } + return false; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanResolver.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanResolver.java new file mode 100644 index 0000000000000..232626a525fab --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanResolver.java @@ -0,0 +1,260 @@ +package org.jboss.protean.arc.processor; + +import static java.util.Collections.singletonList; +import static org.jboss.jandex.Type.Kind.ARRAY; +import static org.jboss.jandex.Type.Kind.CLASS; +import static org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE; +import static org.jboss.jandex.Type.Kind.TYPE_VARIABLE; +import static org.jboss.jandex.Type.Kind.WILDCARD_TYPE; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.WildcardType; + +/** + * + * @author Martin Kouba + */ +public class BeanResolver { + + private final BeanDeployment beanDeployment; + + private final ConcurrentMap> assignableFromMap; + + private final Function> assignableFromMapFunction; + + public BeanResolver(BeanDeployment beanDeployment) { + this.beanDeployment = beanDeployment; + this.assignableFromMap = new ConcurrentHashMap<>(); + this.assignableFromMapFunction = name -> { + Set assignables = new HashSet<>(); + assignables.addAll(beanDeployment.getIndex().getAllKnownSubclasses(name).stream().map(c -> c.name()).collect(Collectors.toSet())); + assignables.addAll(beanDeployment.getIndex().getAllKnownImplementors(name).stream().map(c -> c.name()).collect(Collectors.toSet())); + return assignables; + }; + } + + BeanInfo findMatchingBean(InjectionPointInfo injectionPoint) { + for (BeanInfo bean : beanDeployment.getBeans()) { + if (bean.matches(injectionPoint)) { + return bean; + } + } + return null; + } + + boolean matches(Type requiredType, Type beanType) { + + if (requiredType == beanType) { + return true; + } + + // TODO box types + + if (ARRAY.equals(requiredType.kind())) { + if (ARRAY.equals(beanType.kind())) { + // Array types are considered to match only if their element types are identical + return matches(requiredType.asArrayType().component(), beanType.asArrayType().component()); + } + } else if (CLASS.equals(requiredType.kind())) { + if (CLASS.equals(beanType.kind())) { + return requiredType.name().equals(beanType.name()); + } else if (PARAMETERIZED_TYPE.equals(beanType.kind())) { + // A parameterized bean type is considered assignable to a raw required type if the raw types + // are identical and all type parameters of the bean type are either unbounded type variables or + // java.lang.Object. + if (!requiredType.name().equals(beanType.asParameterizedType().name())) { + return false; + } + return containsUnboundedTypeVariablesOrObjects(beanType.asParameterizedType().arguments()); + } + } else if (PARAMETERIZED_TYPE.equals(requiredType.kind())) { + if (CLASS.equals(beanType.kind())) { + // A raw bean type is considered assignable to a parameterized required type if the raw types are + // identical and all type parameters of the required type are either unbounded type variables or + // java.lang.Object. + if (!beanType.name().equals(requiredType.asParameterizedType().name())) { + return false; + } + return containsUnboundedTypeVariablesOrObjects(requiredType.asParameterizedType().arguments()); + } else if (PARAMETERIZED_TYPE.equals(beanType.kind())) { + // A parameterized bean type is considered assignable to a parameterized required type if they have + // identical raw type and for each parameter. + if (!requiredType.name().equals(beanType.name())) { + return false; + } + List requiredTypeArguments = requiredType.asParameterizedType().arguments(); + List beanTypeArguments = beanType.asParameterizedType().arguments(); + if (requiredTypeArguments.size() != beanTypeArguments.size()) { + throw new IllegalArgumentException("Invalid argument combination " + requiredType + "; " + beanType); + } + for (int i = 0; i < requiredTypeArguments.size(); i++) { + if (!parametersMatch(requiredTypeArguments.get(i), beanTypeArguments.get(i))) { + return false; + } + } + return true; + } + } + return false; + } + + boolean parametersMatch(Type requiredParameter, Type beanParameter) { + if (isActualType(requiredParameter) && isActualType(beanParameter)) { + /* + * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the type is parameterized, the bean + * type parameter is assignable to the required type parameter according to these rules, or + */ + return matches(requiredParameter, beanParameter); + } + if (WILDCARD_TYPE.equals(requiredParameter.kind()) && isActualType(beanParameter)) { + /* + * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is assignable to the upper bound, if + * any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + */ + return parametersMatch(requiredParameter.asWildcardType(), beanParameter); + } + if (WILDCARD_TYPE.equals(requiredParameter.kind()) && TYPE_VARIABLE.equals(beanParameter.kind())) { + /* + * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the type variable is assignable to + * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + */ + return parametersMatch(requiredParameter.asWildcardType(), beanParameter.asTypeVariable()); + } + if (isActualType(requiredParameter) && TYPE_VARIABLE.equals(beanParameter.kind())) { + /* + * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is assignable to the upper bound, + * if any, of the type variable, or + */ + return parametersMatch(requiredParameter, beanParameter.asTypeVariable()); + } + if (TYPE_VARIABLE.equals(requiredParameter.kind()) && TYPE_VARIABLE.equals(beanParameter.kind())) { + /* + * the required type parameter and the bean type parameter are both type variables and the upper bound of the required type parameter is assignable + * to the upper bound, if any, of the bean type parameter + */ + return parametersMatch(requiredParameter.asTypeVariable(), beanParameter.asTypeVariable()); + } + return false; + } + + boolean parametersMatch(WildcardType requiredParameter, Type beanParameter) { + return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); + } + + boolean parametersMatch(WildcardType requiredParameter, TypeVariable beanParameter) { + List beanParameterBounds = getUppermostTypeVariableBounds(beanParameter); + if (!lowerBoundsOfWildcardMatch(beanParameterBounds, requiredParameter)) { + return false; + } + + List requiredUpperBounds = Collections.singletonList(requiredParameter.extendsBound()); + // upper bound of the type variable is assignable to OR assignable from the upper bound of the wildcard + return (boundsMatch(requiredUpperBounds, beanParameterBounds) || boundsMatch(beanParameterBounds, requiredUpperBounds)); + } + + boolean parametersMatch(Type requiredParameter, TypeVariable beanParameter) { + for (Type bound : getUppermostTypeVariableBounds(beanParameter)) { + if (!isAssignableFrom(bound, requiredParameter)) { + return false; + } + } + return true; + } + + boolean parametersMatch(TypeVariable requiredParameter, TypeVariable beanParameter) { + return boundsMatch(getUppermostTypeVariableBounds(beanParameter), getUppermostTypeVariableBounds(requiredParameter)); + } + + /** + * Returns true iff for each bound T, there is at least one bound from stricterBounds assignable to T. This reflects that + * stricterBounds are at least as strict as bounds are. + */ + boolean boundsMatch(List bounds, List stricterBounds) { + // getUppermostBounds to make sure that both arrays of bounds contain ONLY ACTUAL TYPES! otherwise, the CovariantTypes + // assignability rules do not reflect our needs + bounds = getUppermostBounds(bounds); + stricterBounds = getUppermostBounds(stricterBounds); + for (Type bound : bounds) { + for (Type stricterBound : stricterBounds) { + if (!isAssignableFrom(bound, stricterBound)) { + return false; + } + } + } + return true; + } + + boolean isAssignableFrom(Type type1, Type type2) { + return assignableFromMap.computeIfAbsent(type1.name(), assignableFromMapFunction).contains(type2.name()); + } + + boolean lowerBoundsOfWildcardMatch(Type parameter, WildcardType requiredParameter) { + return lowerBoundsOfWildcardMatch(singletonList(parameter), requiredParameter); + } + + boolean lowerBoundsOfWildcardMatch(List beanParameterBounds, WildcardType requiredParameter) { + if (requiredParameter.superBound() != null) { + if (!boundsMatch(beanParameterBounds, singletonList(requiredParameter.superBound()))) { + return false; + } + } + return true; + } + + boolean upperBoundsOfWildcardMatch(WildcardType requiredParameter, Type parameter) { + return boundsMatch(singletonList(requiredParameter.superBound()), singletonList(parameter)); + } + + /* + * TypeVariable bounds are treated specially - CDI assignability rules are applied. Standard Java covariant assignability rules are applied to all other + * types of bounds. This is not explicitly mentioned in the specification but is implied. + */ + List getUppermostTypeVariableBounds(TypeVariable bound) { + if (TYPE_VARIABLE.equals(bound.bounds().get(0).kind())) { + return getUppermostTypeVariableBounds(bound.bounds().get(0).asTypeVariable()); + } + return bound.bounds(); + } + + List getUppermostBounds(List bounds) { + // if a type variable (or wildcard) declares a bound which is a type variable, it can declare no other bound + if (TYPE_VARIABLE.equals(bounds.get(0).kind())) { + return getUppermostTypeVariableBounds(bounds.get(0).asTypeVariable()); + } + return bounds; + } + + static boolean isActualType(Type type) { + return CLASS.equals(type.kind()) || PARAMETERIZED_TYPE.equals(type.kind()) || ARRAY.equals(type.kind()); + } + + static boolean containsUnboundedTypeVariablesOrObjects(List types) { + for (Type type : types) { + if (ClassType.OBJECT_TYPE.equals(type)) { + continue; + } + if (Kind.TYPE_VARIABLE.equals(type.kind())) { + List bounds = type.asTypeVariable().bounds(); + if (bounds.isEmpty() || bounds.size() == 1 && ClassType.OBJECT_TYPE.equals(bounds.get(0))) { + continue; + } + } + return false; + } + return true; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java new file mode 100644 index 0000000000000..77df539f97544 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Beans.java @@ -0,0 +1,80 @@ +package org.jboss.protean.arc.processor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +final class Beans { + + private Beans() { + } + + /** + * + * @param beanClass + * @param beanDeployment + * @return a new bean info + */ + static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployment) { + Set qualifiers = new HashSet<>(); + ScopeInfo scope = null; + Set types = Types.getTypeClosure(beanClass, Collections.emptyMap(), beanDeployment); + for (AnnotationInstance annotation : beanClass.classAnnotations()) { + if (beanDeployment.getQualifier(annotation.name()) != null) { + qualifiers.add(annotation); + } else if (scope == null) { + scope = ScopeInfo.from(annotation.name()); + } + } + return new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, Injection.init(beanClass, beanDeployment), null); + } + + /** + * + * @param producerMethod + * @param declaringBean + * @param beanDeployment + * @return a new bean info + */ + static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declaringBean, BeanDeployment beanDeployment) { + Set qualifiers = new HashSet<>(); + ScopeInfo scope = null; + Set types = Types.getTypeClosure(producerMethod, beanDeployment); + for (AnnotationInstance annotation : producerMethod.annotations()) { + if (beanDeployment.getQualifier(annotation.name()) != null) { + qualifiers.add(annotation); + } else if (scope == null) { + scope = ScopeInfo.from(annotation.name()); + } + } + return new BeanInfo(producerMethod, beanDeployment, scope, types, qualifiers, Injection.init(producerMethod, beanDeployment), declaringBean); + } + + /** + * + * @param producerField + * @param declaringBean + * @param beanDeployment + * @return a new bean info + */ + static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringBean, BeanDeployment beanDeployment) { + Set qualifiers = new HashSet<>(); + ScopeInfo scope = null; + Set types = Types.getTypeClosure(producerField, beanDeployment); + for (AnnotationInstance annotation : producerField.annotations()) { + if (beanDeployment.getQualifier(annotation.name()) != null) { + qualifiers.add(annotation); + } else if (scope == null) { + scope = ScopeInfo.from(annotation.name()); + } + } + return new BeanInfo(producerField, beanDeployment, scope, types, qualifiers, Collections.emptyList(), declaringBean); + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java new file mode 100644 index 0000000000000..1fb6fde5773d6 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinBean.java @@ -0,0 +1,118 @@ +package org.jboss.protean.arc.processor; + +import java.util.HashSet; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.protean.arc.BeanManagerProvider; +import org.jboss.protean.arc.BeanMetadataProvider; +import org.jboss.protean.arc.InjectableReferenceProvider; +import org.jboss.protean.arc.InjectionPointProvider; +import org.jboss.protean.arc.InstanceProvider; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.ClassOutput; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +enum BuiltinBean { + + INSTANCE(DotNames.INSTANCE, new Generator() { + void generate(ClassOutput classOutput, BeanInfo bean, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, MethodCreator constructor, + String providerName, AnnotationLiteralProcessor annotationLiterals) { + + ResultHandle qualifiers = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + if (!injectionPoint.requiredQualifiers.isEmpty()) { + // Set instanceProvider1Qualifiers = new HashSet<>() + // instanceProvider1Qualifiers.add(javax.enterprise.inject.Default.Literal.INSTANCE) + + for (AnnotationInstance qualifierAnnotation : injectionPoint.requiredQualifiers) { + BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); + if (qualifier != null) { + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class), qualifiers, + qualifier.getLiteralInstance(constructor)); + } else { + // Create annotation literal first + ClassInfo qualifierClass = bean.getDeployment().getQualifier(qualifierAnnotation.name()); + String annotationLiteralName = annotationLiterals.process(classOutput, qualifierClass, qualifierAnnotation, + Types.getPackageName(clazzCreator.getClassName())); + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class), qualifiers, + constructor.newInstance(MethodDescriptor.ofConstructor(annotationLiteralName))); + } + } + } + ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.requiredType); + ResultHandle instanceProvider = constructor.newInstance( + MethodDescriptor.ofConstructor(InstanceProvider.class, java.lang.reflect.Type.class, Set.class), parameterizedType, qualifiers); + constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.getThis(), instanceProvider); + + } + }), INJECTION_POINT(DotNames.INJECTION_POINT, new Generator() { + void generate(ClassOutput classOutput, BeanInfo bean, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, MethodCreator constructor, + String providerName, AnnotationLiteralProcessor annotationLiterals) { + // this.injectionPointProvider1 = new InjectionPointProvider(); + constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(InjectionPointProvider.class))); + } + }), BEAN(DotNames.BEAN, new Generator() { + void generate(ClassOutput classOutput, BeanInfo bean, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, MethodCreator constructor, + String providerName, AnnotationLiteralProcessor annotationLiterals) { + // this.beanProvider1 = new BeanMetadataProvider<>(); + constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(BeanMetadataProvider.class))); + } + }), BEAN_MANAGER(DotNames.BEAN_MANAGER, new Generator() { + void generate(ClassOutput classOutput, BeanInfo bean, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, MethodCreator constructor, + String providerName, AnnotationLiteralProcessor annotationLiterals) { + // TODO dummy provider + constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(BeanManagerProvider.class))); + } + }); + + private final DotName rawTypeDotName; + + private final Generator generator; + + BuiltinBean(DotName rawTypeDotName, Generator generator) { + this.rawTypeDotName = rawTypeDotName; + this.generator = generator; + } + + public DotName getRawTypeDotName() { + return rawTypeDotName; + } + + public Generator getGenerator() { + return generator; + } + + static boolean resolvesTo(InjectionPointInfo injectionPoint) { + return resolve(injectionPoint) != null; + } + + static BuiltinBean resolve(InjectionPointInfo injectionPoint) { + for (BuiltinBean bean : values()) { + if (bean.getRawTypeDotName().equals(injectionPoint.requiredType.name())) { + return bean; + } + } + return null; + } + + abstract static class Generator { + + abstract void generate(ClassOutput classOutput, BeanInfo bean, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, MethodCreator constructor, + String providerName, AnnotationLiteralProcessor annotationLiterals); + + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinQualifier.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinQualifier.java new file mode 100644 index 0000000000000..d5213c4751bb2 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/BuiltinQualifier.java @@ -0,0 +1,44 @@ +package org.jboss.protean.arc.processor; + +import java.util.Collections; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.protean.gizmo.BytecodeCreator; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +enum BuiltinQualifier { + + DEFAULT(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList()), + Default.Literal.class.getName()), ANY(AnnotationInstance.create(DotNames.ANY, null, Collections.emptyList()), Any.Literal.class.getName()),; + + private final AnnotationInstance instance; + + private final String literalType; + + private BuiltinQualifier(AnnotationInstance instance, String literalType) { + this.instance = instance; + this.literalType = literalType; + } + + AnnotationInstance getInstance() { + return instance; + } + + ResultHandle getLiteralInstance(BytecodeCreator creator) { + return creator.readStaticField(FieldDescriptor.of(literalType, "INSTANCE", literalType)); + } + + static BuiltinQualifier of(AnnotationInstance instance) { + for (BuiltinQualifier qualifier : values()) { + if (qualifier.getInstance().name().equals(instance.name())) { + return qualifier; + } + } + return null; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ClientProxyGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ClientProxyGenerator.java new file mode 100644 index 0000000000000..d8504b642c746 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ClientProxyGenerator.java @@ -0,0 +1,200 @@ +package org.jboss.protean.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.spi.Context; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.jandex.TypeVariable; +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.ClientProxy; +import org.jboss.protean.arc.CreationalContextImpl; +import org.jboss.protean.arc.InjectableBean; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.DescriptorUtils; +import org.jboss.protean.gizmo.FieldCreator; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +public class ClientProxyGenerator extends AbstractGenerator { + + static final String CLIENT_PROXY_SUFFIX = "_ClientProxy"; + + static final String DEFAULT_PACKAGE = Arc.class.getPackage().getName() + ".proxies"; + + /** + * + * @param bean + * @param beanClassName Fully qualified class name + * @return a collection of resources + */ + Collection generate(BeanInfo bean, String beanClassName) { + + ResourceClassOutput classOutput = new ResourceClassOutput(); + + Type providerType = bean.getProviderType(); + ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String baseName = getBaseName(bean, beanClassName); + String generatedName = getProxyPackageName(bean).replace(".", "/") + "/" + baseName + CLIENT_PROXY_SUFFIX; + + // Foo_ClientProxy extends Foo implements ClientProxy + List interfaces = new ArrayList<>(); + String superClass = Object.class.getName(); + interfaces.add(ClientProxy.class.getName()); + boolean isInterface = false; + + if (Modifier.isInterface(providerClass.flags())) { + isInterface = true; + interfaces.add(providerTypeName); + } else { + superClass = providerTypeName; + } + + ClassCreator clientProxy = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(superClass) + .interfaces(interfaces.toArray(new String[0])).build(); + FieldCreator beanField = clientProxy.getFieldCreator("bean", DescriptorUtils.extToInt(beanClassName)).setModifiers(ACC_PRIVATE | ACC_FINAL); + + createConstructor(clientProxy, beanClassName, superClass, beanField.getFieldDescriptor()); + createDelegate(clientProxy, providerTypeName, beanField.getFieldDescriptor()); + createGetContextualInstance(clientProxy, providerTypeName); + + for (MethodInfo method : getDelegatingMethods(bean)) { + + MethodCreator forward = clientProxy.getMethodCreator(MethodDescriptor.of(method)); + + // Exceptions + for (Type exception : method.exceptions()) { + forward.addException(exception.asClassType().toString()); + } + // Method params + ResultHandle[] params = new ResultHandle[method.parameters().size()]; + for (int i = 0; i < method.parameters().size(); ++i) { + params[i] = forward.getMethodParam(i); + } + + ResultHandle delegate = forward + .invokeVirtualMethod(MethodDescriptor.ofMethod(generatedName, "delegate", DescriptorUtils.typeToString(providerType)), forward.getThis()); + ResultHandle ret; + + if (isInterface) { + ret = forward.invokeInterfaceMethod(method, delegate, params); + } else if (Modifier.isPrivate(method.flags())) { + // Reflection fallback for private methods + ResultHandle paramTypesArray = forward.newArray(Class.class, forward.load(method.parameters().size())); + int idx = 0; + for (Type param : method.parameters()) { + forward.writeArrayValue(paramTypesArray, forward.load(idx++), forward.loadClass(param.name().toString())); + } + ResultHandle argsArray = forward.newArray(Object.class, forward.load(params.length)); + idx = 0; + for (ResultHandle argHandle : params) { + forward.writeArrayValue(argsArray, forward.load(idx++), argHandle); + } + ret = forward.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, forward.loadClass(method.declaringClass().name().toString()), + forward.load(method.name()), paramTypesArray, delegate, argsArray); + } else { + ret = forward.invokeVirtualMethod(method, delegate, params); + } + // Finally write the bytecode + forward.returnValue(ret); + } + + clientProxy.close(); + return classOutput.getResources(); + } + + void createConstructor(ClassCreator clientProxy, String beanClassName, String superClasName, FieldDescriptor beanField) { + MethodCreator creator = clientProxy.getMethodCreator("", void.class, beanClassName); + creator.invokeSpecialMethod(MethodDescriptor.ofConstructor(superClasName), creator.getThis()); + creator.writeInstanceField(beanField, creator.getThis(), creator.getMethodParam(0)); + creator.returnValue(null); + } + + void createDelegate(ClassCreator clientProxy, String providerTypeName, FieldDescriptor beanField) { + // Arc.container().getContext(bean.getScope()).get(bean, new CreationalContextImpl<>()); + MethodCreator creator = clientProxy.getMethodCreator("delegate", providerTypeName).setModifiers(Modifier.PRIVATE); + // Arc.container() + ResultHandle container = creator.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class)); + // bean.getScope() + ResultHandle bean = creator.readInstanceField(beanField, creator.getThis()); + ResultHandle scope = creator.invokeInterfaceMethod(MethodDescriptor.ofMethod(InjectableBean.class, "getScope", Class.class), bean); + // getContext() + ResultHandle context = creator.invokeInterfaceMethod(MethodDescriptor.ofMethod(ArcContainer.class, "getContext", Context.class, Class.class), container, + scope); + // new CreationalContextImpl<>() + ResultHandle creationContext = creator.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); + ResultHandle result = creator.invokeInterfaceMethod( + MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, CreationalContext.class), context, bean, creationContext); + creator.returnValue(result); + } + + void createGetContextualInstance(ClassCreator clientProxy, String providerTypeName) { + MethodCreator creator = clientProxy.getMethodCreator("getContextualInstance", Object.class).setModifiers(Modifier.PUBLIC); + creator.returnValue( + creator.invokeVirtualMethod(MethodDescriptor.ofMethod(clientProxy.getClassName(), "delegate", providerTypeName), creator.getThis())); + } + + Collection getDelegatingMethods(BeanInfo bean) { + Map methods = new HashMap<>(); + + if (bean.isClassBean()) { + Methods.addDelegatingMethods(bean.getDeployment().getIndex(), bean.getTarget().asClass(), Collections.emptyMap(), methods); + } else if (bean.isProducerMethod()) { + MethodInfo producerMethod = bean.getTarget().asMethod(); + Map resolved = Collections.emptyMap(); + ClassInfo returnTypeClass = bean.getDeployment().getIndex().getClassByName(producerMethod.returnType().name()); + if (!returnTypeClass.typeParameters().isEmpty()) { + resolved = Types.buildResolvedMap(producerMethod.returnType().asParameterizedType().arguments(), returnTypeClass.typeParameters(), + Collections.emptyMap()); + } + Methods.addDelegatingMethods(bean.getDeployment().getIndex(), returnTypeClass, resolved, methods); + } else if (bean.isProducerField()) { + FieldInfo producerField = bean.getTarget().asField(); + Map resolved = Collections.emptyMap(); + ClassInfo fieldClass = bean.getDeployment().getIndex().getClassByName(producerField.type().name()); + if (!fieldClass.typeParameters().isEmpty()) { + resolved = Types.buildResolvedMap(producerField.type().asParameterizedType().arguments(), fieldClass.typeParameters(), Collections.emptyMap()); + } + Methods.addDelegatingMethods(bean.getDeployment().getIndex(), fieldClass, resolved, methods); + } + return methods.values(); + } + + static String getProxyPackageName(BeanInfo bean) { + String packageName; + if (bean.isProducerMethod() || bean.isProducerField()) { + packageName = DotNames.packageName(bean.getDeclaringBean().getProviderType().name()); + } else { + packageName = DotNames.packageName(bean.getProviderType().name()); + } + if (packageName.startsWith("java.")) { + // It is not possible to place a class in a JDK package + packageName = DEFAULT_PACKAGE; + } + return packageName; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java new file mode 100644 index 0000000000000..d1bb639f2ad23 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/DotNames.java @@ -0,0 +1,59 @@ +package org.jboss.protean.arc.processor; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Priority; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.util.Nonbinding; +import javax.inject.Inject; +import javax.inject.Qualifier; +import javax.interceptor.AroundConstruct; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; + +import org.jboss.jandex.DotName; + +final class DotNames { + + static final DotName OBJECT = DotName.createSimple(Object.class.getName()); + static final DotName OBSERVES = DotName.createSimple(Observes.class.getName()); + static final DotName PRODUCES = DotName.createSimple(Produces.class.getName()); + static final DotName QUALIFIER = DotName.createSimple(Qualifier.class.getName()); + static final DotName NONBINDING = DotName.createSimple(Nonbinding.class.getName()); + static final DotName INJECT = DotName.createSimple(Inject.class.getName()); + static final DotName POST_CONSTRUCT = DotName.createSimple(PostConstruct.class.getName()); + static final DotName PRE_DESTROY = DotName.createSimple(PreDestroy.class.getName()); + static final DotName INSTANCE = DotName.createSimple(Instance.class.getName()); + static final DotName INJECTION_POINT = DotName.createSimple(InjectionPoint.class.getName()); + static final DotName INTERCEPTOR = DotName.createSimple(Interceptor.class.getName()); + static final DotName INTERCEPTOR_BINDING = DotName.createSimple(InterceptorBinding.class.getName()); + static final DotName AROUND_INVOKE = DotName.createSimple(AroundInvoke.class.getName()); + static final DotName AROUND_CONSTRUCT = DotName.createSimple(AroundConstruct.class.getName()); + static final DotName PRIORITY = DotName.createSimple(Priority.class.getName()); + static final DotName DEFAULT = DotName.createSimple(Default.class.getName()); + static final DotName ANY = DotName.createSimple(Any.class.getName()); + static final DotName BEAN = DotName.createSimple(Bean.class.getName()); + static final DotName BEAN_MANAGER = DotName.createSimple(BeanManager.class.getName()); + + private DotNames() { + } + + static String simpleName(DotName dotName) { + String local = dotName.local(); + return local.contains(".") ? Types.convertNested(local.substring(local.lastIndexOf("."), local.length())) : Types.convertNested(local); + } + + static String packageName(DotName dotName) { + String name = dotName.toString(); + return name.contains(".") ? name.substring(0, name.lastIndexOf(".")) : ""; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java new file mode 100644 index 0000000000000..d4ff508a3df6a --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Injection.java @@ -0,0 +1,79 @@ +package org.jboss.protean.arc.processor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.logging.Logger; + +/** + * + * @author Martin Kouba + */ +public class Injection { + + private static final Logger LOGGER = Logger.getLogger(Injection.class); + + /** + * + * @param beanTarget + * @param beanDeployment + * @return the list of injections + */ + static List init(AnnotationTarget beanTarget, BeanDeployment beanDeployment) { + if (Kind.CLASS.equals(beanTarget.kind())) { + List injections = new ArrayList<>(); + List injectAnnotations = beanTarget.asClass().annotations().get(DotNames.INJECT); + if (injectAnnotations != null) { + for (AnnotationInstance injectAnnotation : injectAnnotations) { + AnnotationTarget injectTarget = injectAnnotation.target(); + switch (injectAnnotation.target().kind()) { + case FIELD: + injections.add( + new Injection(injectTarget, Collections.singletonList(InjectionPointInfo.from(injectTarget.asField(), beanDeployment)))); + break; + case METHOD: + injections.add(new Injection(injectTarget, InjectionPointInfo.from(injectTarget.asMethod(), beanDeployment))); + break; + default: + LOGGER.warn("Unsupported @Inject target ignored: " + injectAnnotation.target()); + continue; + } + } + } + return injections; + } else if (Kind.METHOD.equals(beanTarget.kind())) { + if (beanTarget.asMethod().parameters().isEmpty()) { + return Collections.emptyList(); + } + // All parameters are injection points + return Collections.singletonList(new Injection(beanTarget.asMethod(), InjectionPointInfo.from(beanTarget.asMethod(), beanDeployment))); + } + throw new IllegalArgumentException("Unsupported annotation target"); + } + + final AnnotationTarget target; + + final List injectionPoints; + + public Injection(AnnotationTarget target, List injectionPoints) { + this.target = target; + this.injectionPoints = injectionPoints; + } + + boolean isMethod() { + return Kind.METHOD.equals(target.kind()); + } + + boolean isConstructor() { + return isMethod() && target.asMethod().name().equals(""); + } + + boolean isField() { + return Kind.FIELD.equals(target.kind()); + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java new file mode 100644 index 0000000000000..835b45f8cf9d4 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InjectionPointInfo.java @@ -0,0 +1,68 @@ +package org.jboss.protean.arc.processor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +/** + * + * @author Martin Kouba + */ +class InjectionPointInfo { + + static InjectionPointInfo from(FieldInfo field, BeanDeployment beanDeployment) { + return new InjectionPointInfo(field.type(), + field.annotations().stream().filter(a -> beanDeployment.getQualifier(a.name()) != null).collect(Collectors.toSet())); + } + + static InjectionPointInfo from(MethodInfo method, int position, BeanDeployment beanDeployment) { + Set paramQualifiers = method.annotations().stream().filter(a -> Kind.METHOD_PARAMETER.equals(a.target().kind()) + && a.target().asMethodParameter().position() == position && beanDeployment.getQualifier(a.name()) != null).collect(Collectors.toSet()); + return new InjectionPointInfo(method.parameters().get(position), paramQualifiers); + } + + static List from(MethodInfo method, BeanDeployment beanDeployment) { + List injectionPoints = new ArrayList<>(); + for (int i = 0; i < method.parameters().size(); i++) { + injectionPoints.add(from(method, i, beanDeployment)); + } + return injectionPoints; + } + + final Type requiredType; + + final Set requiredQualifiers; + + final AtomicReference resolvedBean; + + public InjectionPointInfo(Type requiredType, Set requiredQualifiers) { + this.requiredType = requiredType; + this.requiredQualifiers = requiredQualifiers.isEmpty() + ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) + : requiredQualifiers; + this.resolvedBean = new AtomicReference(null); + } + + void resolve(BeanInfo bean) { + resolvedBean.set(bean); + } + + BeanInfo getResolvedBean() { + return resolvedBean.get(); + } + + @Override + public String toString() { + return "InjectionPointInfo [requiredType=" + requiredType + ", requiredQualifiers=" + requiredQualifiers + "]"; + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java new file mode 100644 index 0000000000000..8ecdfe90756b2 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorGenerator.java @@ -0,0 +1,203 @@ +package org.jboss.protean.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; + +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; +import org.jboss.protean.arc.InjectableInterceptor; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType; +import org.jboss.protean.gizmo.BranchResult; +import org.jboss.protean.gizmo.BytecodeCreator; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.ClassOutput; +import org.jboss.protean.gizmo.FieldCreator; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +public class InterceptorGenerator extends BeanGenerator { + + private static final Logger LOGGER = Logger.getLogger(InterceptorGenerator.class); + + /** + * + * @param interceptor bean + * @return a collection of resources + */ + Collection generate(InterceptorInfo interceptor, AnnotationLiteralProcessor annotationLiterals) { + + Type providerType = interceptor.getProviderType(); + ClassInfo interceptorClass = interceptor.getTarget().asClass(); + String baseName; + if (interceptorClass.enclosingClass() != null) { + baseName = DotNames.simpleName(interceptorClass.enclosingClass()) + "_" + DotNames.simpleName(interceptorClass.name()); + } else { + baseName = DotNames.simpleName(interceptorClass.name()); + } + ClassInfo providerClass = interceptor.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String generatedName = DotNames.packageName(providerType.name()).replace(".", "/") + "/" + baseName + BEAN_SUFFIX; + + ResourceClassOutput classOutput = new ResourceClassOutput(name -> name.equals(generatedName) ? SpecialType.INTERCEPTOR_BEAN : null); + + // MyInterceptor_Bean implements InjectableInterceptor + ClassCreator interceptorCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableInterceptor.class) + .build(); + + // Fields + FieldCreator beanTypes = interceptorCreator.getFieldCreator("beanTypes", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator bindings = interceptorCreator.getFieldCreator("bindings", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + + Map providerToInjectionPoint = new HashMap<>(); + Map injectionPointToProviderField = new HashMap<>(); + Map interceptorToProviderField = new HashMap<>(); + initMaps(interceptor, providerToInjectionPoint, injectionPointToProviderField, interceptorToProviderField); + + createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, interceptorToProviderField); + createConstructor(classOutput, interceptorCreator, interceptor, baseName, injectionPointToProviderField, interceptorToProviderField, + annotationLiterals); + createCreate(interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField); + createGet(interceptor, interceptorCreator, providerTypeName); + createGetTypes(interceptorCreator, beanTypes.getFieldDescriptor()); + // Interceptors are always @Dependent and have always default qualifiers + + // InjectableInterceptor methods + createGetInterceptorBindings(interceptorCreator, bindings.getFieldDescriptor()); + createIntercepts(interceptorCreator, interceptor); + createIntercept(interceptorCreator, interceptor, providerTypeName); + createGetPriority(interceptorCreator, interceptor); + + interceptorCreator.close(); + return classOutput.getResources(); + + } + + protected void createConstructor(ClassOutput classOutput, ClassCreator creator, InterceptorInfo interceptor, String baseName, + Map injectionPointToProviderField, Map interceptorToProviderField, + AnnotationLiteralProcessor annotationLiterals, FieldDescriptor bindings) { + + MethodCreator constructor = initConstructor(classOutput, creator, interceptor, baseName, injectionPointToProviderField, interceptorToProviderField, + annotationLiterals); + + // Bindings + if (!interceptor.getBindings().isEmpty()) { + // bindings = new HashSet<>() + ResultHandle bindingsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + + for (AnnotationInstance bindingAnnotation : interceptor.getBindings()) { + // Create annotation literal first + ClassInfo bindingClass = interceptor.getDeployment().getInterceptorBinding(bindingAnnotation.name()); + String literalType = annotationLiterals.process(classOutput, bindingClass, bindingAnnotation, Types.getPackageName(creator.getClassName())); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, + constructor.newInstance(MethodDescriptor.ofConstructor(literalType))); + } + } + constructor.returnValue(null); + } + + /** + * + * @see InjectableInterceptor#getInterceptorBindings() + */ + protected void createGetInterceptorBindings(ClassCreator creator, FieldDescriptor bindingsField) { + MethodCreator getBindings = creator.getMethodCreator("getInterceptorBindings", Set.class).setModifiers(ACC_PUBLIC); + getBindings.returnValue(getBindings.readInstanceField(bindingsField, getBindings.getThis())); + } + + /** + * + * @see InjectableInterceptor#getPriority() + */ + protected void createGetPriority(ClassCreator creator, InterceptorInfo interceptor) { + MethodCreator getPriority = creator.getMethodCreator("getPriority", int.class).setModifiers(ACC_PUBLIC); + getPriority.returnValue(getPriority.load(interceptor.getPriority())); + } + + /** + * + * @return the method + * @see InjectableInterceptor#intercepts(javax.enterprise.inject.spi.InterceptionType) + */ + protected void createIntercepts(ClassCreator creator, InterceptorInfo interceptor) { + MethodCreator intercepts = creator.getMethodCreator("intercepts", boolean.class, InterceptionType.class).setModifiers(ACC_PUBLIC); + addIntercepts(interceptor, InterceptionType.AROUND_INVOKE, intercepts); + addIntercepts(interceptor, InterceptionType.POST_CONSTRUCT, intercepts); + addIntercepts(interceptor, InterceptionType.PRE_DESTROY, intercepts); + addIntercepts(interceptor, InterceptionType.AROUND_CONSTRUCT, intercepts); + intercepts.returnValue(intercepts.load(false)); + } + + private void addIntercepts(InterceptorInfo interceptor, InterceptionType interceptionType, MethodCreator intercepts) { + if (interceptor.intercepts(interceptionType)) { + ResultHandle enumValue = intercepts + .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), InterceptionType.class.getName())); + BranchResult result = intercepts + .ifNonZero(intercepts.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercepts.getMethodParam(0))); + result.trueBranch().returnValue(result.trueBranch().load(true)); + } + } + + /** + * + * @see InjectableInterceptor#intercept(InterceptionType, Object, javax.interceptor.InvocationContext) + */ + protected void createIntercept(ClassCreator creator, InterceptorInfo interceptor, String providerTypeName) { + MethodCreator intercept = creator.getMethodCreator("intercept", Object.class, InterceptionType.class, Object.class, InvocationContext.class) + .setModifiers(ACC_PUBLIC).addException(Exception.class); + + addIntercept(intercept, interceptor.getAroundInvoke(), InterceptionType.AROUND_INVOKE, providerTypeName); + addIntercept(intercept, interceptor.getPostConstruct(), InterceptionType.POST_CONSTRUCT, providerTypeName); + addIntercept(intercept, interceptor.getPreDestroy(), InterceptionType.PRE_DESTROY, providerTypeName); + addIntercept(intercept, interceptor.getAroundConstruct(), InterceptionType.AROUND_CONSTRUCT, providerTypeName); + intercept.returnValue(intercept.loadNull()); + } + + private void addIntercept(MethodCreator intercept, MethodInfo interceptorMethod, InterceptionType interceptionType, String providerTypeName) { + if (interceptorMethod != null) { + ResultHandle enumValue = intercept + .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), InterceptionType.class.getName())); + BranchResult result = intercept.ifNonZero(intercept.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercept.getMethodParam(0))); + BytecodeCreator trueBranch = result.trueBranch(); + Class retType = InterceptionType.AROUND_INVOKE.equals(interceptionType) ? Object.class : void.class; + ResultHandle ret; + if (Modifier.isPrivate(interceptorMethod.flags())) { + LOGGER.infof("Interceptor method %s#%s is private - Arc users are encouraged to avoid using private interceptor methods", + interceptorMethod.declaringClass().name(), interceptorMethod.name()); + // Use reflection fallback + ResultHandle paramTypesArray = trueBranch.newArray(Class.class, trueBranch.load(1)); + trueBranch.writeArrayValue(paramTypesArray, trueBranch.load(0), trueBranch.loadClass(InvocationContext.class)); + ResultHandle argsArray = trueBranch.newArray(Object.class, trueBranch.load(1)); + trueBranch.writeArrayValue(argsArray, trueBranch.load(0), intercept.getMethodParam(2)); + ret = trueBranch.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + trueBranch.loadClass(interceptorMethod.declaringClass().name().toString()), trueBranch.load(interceptorMethod.name()), paramTypesArray, + intercept.getMethodParam(1), argsArray); + + } else { + ret = trueBranch.invokeVirtualMethod(MethodDescriptor.ofMethod(providerTypeName, interceptorMethod.name(), retType, InvocationContext.class), + intercept.getMethodParam(1), intercept.getMethodParam(2)); + } + trueBranch.returnValue(InterceptionType.AROUND_INVOKE.equals(interceptionType) ? ret : trueBranch.loadNull()); + } + } +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java new file mode 100644 index 0000000000000..599fda36ed1aa --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorInfo.java @@ -0,0 +1,105 @@ +package org.jboss.protean.arc.processor; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.enterprise.inject.spi.InterceptionType; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; + +/** + * + * @author Martin Kouba + */ +class InterceptorInfo extends BeanInfo implements Comparable { + + private final Set bindings; + + private final MethodInfo aroundInvoke; + + private final MethodInfo aroundConstruct; + + private final MethodInfo postConstruct; + + private final MethodInfo preDestroy; + + private final int priority; + + /** + * + * @param target + * @param beanDeployment + * @param bindings + * @param injections + */ + InterceptorInfo(AnnotationTarget target, BeanDeployment beanDeployment, Set bindings, List injections, int priority) { + super(target, beanDeployment, ScopeInfo.DEPENDENT, Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, + null); + this.bindings = bindings; + this.priority = priority; + this.aroundInvoke = target.asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.AROUND_INVOKE)).findAny().orElse(null); + this.aroundConstruct = target.asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.AROUND_CONSTRUCT)).findAny().orElse(null); + this.postConstruct = target.asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.POST_CONSTRUCT)).findAny().orElse(null); + this.preDestroy = target.asClass().methods().stream().filter(m -> m.hasAnnotation(DotNames.PRE_DESTROY)).findAny().orElse(null); + } + + Set getBindings() { + return bindings; + } + + int getPriority() { + return priority; + } + + MethodInfo getAroundInvoke() { + return aroundInvoke; + } + + MethodInfo getAroundConstruct() { + return aroundConstruct; + } + + MethodInfo getPostConstruct() { + return postConstruct; + } + + MethodInfo getPreDestroy() { + return preDestroy; + } + + boolean intercepts(InterceptionType interceptionType) { + switch (interceptionType) { + case AROUND_INVOKE: + return aroundInvoke != null; + case AROUND_CONSTRUCT: + return aroundConstruct != null; + case POST_CONSTRUCT: + return postConstruct != null; + case PRE_DESTROY: + return preDestroy != null; + default: + return false; + } + } + + boolean isInterceptor() { + return true; + } + + @Override + public String toString() { + return "INTERCEPTOR bean [bindings=" + bindings + ", target=" + getTarget() + "]"; + } + + @Override + public int compareTo(InterceptorInfo other) { + return getTarget().toString().compareTo(other.getTarget().toString()); + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorResolver.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorResolver.java new file mode 100644 index 0000000000000..7d7d1233bad3b --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/InterceptorResolver.java @@ -0,0 +1,66 @@ +package org.jboss.protean.arc.processor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.enterprise.inject.spi.InterceptionType; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; + +public class InterceptorResolver { + + private final BeanDeployment beanDeployment; + + public InterceptorResolver(BeanDeployment beanDeployment) { + this.beanDeployment = beanDeployment; + } + + public List resolve(InterceptionType interceptionType, Set bindings) { + List interceptors = new ArrayList<>(); + for (InterceptorInfo interceptor : beanDeployment.getInterceptors()) { + if (!interceptor.intercepts(interceptionType)) { + continue; + } + boolean matches = true; + for (AnnotationInstance interceptorBinding : interceptor.getBindings()) { + if (!hasInterceptorBinding(bindings, interceptorBinding)) { + matches = false; + } + } + if (matches) { + interceptors.add(interceptor); + } + } + interceptors.sort(this::compare); + return interceptors; + } + + private int compare(InterceptorInfo i1, InterceptorInfo i2) { + return Integer.compare(i1.getPriority(), i2.getPriority()); + } + + private boolean hasInterceptorBinding(Set bindings, AnnotationInstance interceptorBinding) { + ClassInfo interceptorBindingClass = beanDeployment.getInterceptorBinding(interceptorBinding.name()); + for (AnnotationInstance binding : bindings) { + if (binding.name().equals(interceptorBinding.name())) { + // Must have the same annotation member value for each member which is not annotated @Nonbinding + boolean matches = true; + for (AnnotationValue value : binding.values()) { + if (!interceptorBindingClass.method(value.name()).hasAnnotation(DotNames.NONBINDING) + && !value.equals(interceptorBinding.value(value.name()))) { + matches = false; + break; + } + } + if (matches) { + return true; + } + } + } + return false; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Interceptors.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Interceptors.java new file mode 100644 index 0000000000000..783dc5be875b0 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Interceptors.java @@ -0,0 +1,33 @@ +package org.jboss.protean.arc.processor; + +import java.util.HashSet; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; + +final class Interceptors { + + private Interceptors() { + } + + /** + * + * @param interceptorClass + * @param beanDeployment + * @return a new interceptor info + */ + static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeployment beanDeployment) { + Set bindings = new HashSet<>(); + Integer priority = 0; + for (AnnotationInstance annotation : interceptorClass.classAnnotations()) { + if (beanDeployment.getInterceptorBinding(annotation.name()) != null) { + bindings.add(annotation); + } else if (annotation.name().equals(DotNames.PRIORITY)) { + priority = annotation.value().asInt(); + } + } + return new InterceptorInfo(interceptorClass, beanDeployment, bindings, Injection.init(interceptorClass, beanDeployment), priority); + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java new file mode 100644 index 0000000000000..061bc063a5bb8 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/MethodDescriptors.java @@ -0,0 +1,66 @@ +package org.jboss.protean.arc.processor; + +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; + +import org.jboss.protean.arc.ClientProxy; +import org.jboss.protean.arc.CreationalContextImpl; +import org.jboss.protean.arc.InjectableInterceptor; +import org.jboss.protean.arc.InjectableReferenceProvider; +import org.jboss.protean.arc.InvocationContextImpl.InterceptorInvocation; +import org.jboss.protean.arc.Reflections; +import org.jboss.protean.gizmo.MethodDescriptor; + +/** + * + * @author Martin Kouba + */ +final class MethodDescriptors { + + static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, + CreationalContext.class); + + static final MethodDescriptor MAP_GET = MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class); + + static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod(InjectableReferenceProvider.class, "get", Object.class, + CreationalContext.class); + + static final MethodDescriptor SET_ADD = MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class); + + static final MethodDescriptor LIST_ADD = MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class); + + static final MethodDescriptor OBJECT_EQUALS = MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class); + + static final MethodDescriptor OBJECT_CONSTRUCTOR = MethodDescriptor.ofConstructor(Object.class); + + static final MethodDescriptor INTERCEPTOR_INVOCATION_POST_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, "postConstruct", + InterceptorInvocation.class, InjectableInterceptor.class, Object.class); + + static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, "aroundConstruct", + InterceptorInvocation.class, InjectableInterceptor.class, Object.class); + + static final MethodDescriptor REFLECTIONS_FIND_CONSTRUCTOR = MethodDescriptor.ofMethod(Reflections.class, "findConstructor", Constructor.class, Class.class, + Class[].class); + + static final MethodDescriptor REFLECTIONS_WRITE_FIELD = MethodDescriptor.ofMethod(Reflections.class, "writeField", void.class, Class.class, String.class, + Object.class, Object.class); + + static final MethodDescriptor REFLECTIONS_READ_FIELD = MethodDescriptor.ofMethod(Reflections.class, "readField", Object.class, Class.class, String.class, + Object.class); + + static final MethodDescriptor REFLECTIONS_INVOKE_METHOD = MethodDescriptor.ofMethod(Reflections.class, "invokeMethod", Object.class, Class.class, + String.class, Class[].class, Object.class, Object[].class); + + static final MethodDescriptor REFLECTIONS_NEW_INSTANCE = MethodDescriptor.ofMethod(Reflections.class, "newInstance", Object.class, Class.class, + Class[].class, Object[].class); + + static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, "getContextualInstance", Object.class); + + private MethodDescriptors() { + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Methods.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Methods.java new file mode 100644 index 0000000000000..f5d04d744bf82 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Methods.java @@ -0,0 +1,206 @@ +package org.jboss.protean.arc.processor; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.WildcardType; + +final class Methods { + + private static final List IGNORED_METHODS = initIgnoredMethods(); + + private static List initIgnoredMethods() { + List ignored = new ArrayList<>(); + ignored.add(""); + ignored.add(""); + return ignored; + } + + private Methods() { + } + + static void addDelegatingMethods(IndexView index, ClassInfo classInfo, Map resolvedTypeParameters, + Map methods) { + // TODO support interfaces default methods + if (classInfo != null) { + for (MethodInfo method : classInfo.methods()) { + if (skipForClientProxy(method)) { + continue; + } + methods.computeIfAbsent(new Methods.MethodKey(method), key -> { + // If parameterized try to resolve the type variables + Type returnType = resolveType(key.method.returnType(), resolvedTypeParameters); + Type[] params = new Type[key.method.parameters().size()]; + for (int i = 0; i < params.length; i++) { + params[i] = resolveType(key.method.parameters().get(i), resolvedTypeParameters); + } + List typeVariables = key.method.typeParameters(); + return MethodInfo.create(classInfo, key.method.name(), params, returnType, key.method.flags(), typeVariables.toArray(new TypeVariable[] {}), + key.method.exceptions().toArray(Type.EMPTY_ARRAY)); + }); + } + // Interfaces + for (Type interfaceType : classInfo.interfaceTypes()) { + ClassInfo interfaceClassInfo = index.getClassByName(interfaceType.name()); + if (interfaceClassInfo != null) { + Map resolved = Collections.emptyMap(); + if (org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE.equals(interfaceType.kind())) { + resolved = Types.buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), + resolvedTypeParameters); + } + addDelegatingMethods(index, interfaceClassInfo, resolved, methods); + } + } + if (classInfo.superClassType() != null) { + ClassInfo superClassInfo = index.getClassByName(classInfo.superName()); + if (superClassInfo != null) { + Map resolved = Collections.emptyMap(); + if (org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE.equals(classInfo.superClassType().kind())) { + resolved = Types.buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), superClassInfo.typeParameters(), + resolvedTypeParameters); + } + addDelegatingMethods(index, superClassInfo, resolved, methods); + } + } + } + } + + private static boolean skipForClientProxy(MethodInfo method) { + if (Modifier.isStatic(method.flags()) || Modifier.isFinal(method.flags()) || Modifier.isPrivate(method.flags())) { + return true; + } + if (IGNORED_METHODS.contains(method.name())) { + return true; + } + if (method.declaringClass().name().equals(DotNames.OBJECT)) { + return true; + } + return false; + } + + static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, Map> candidates, + List classLevelBindings) { + for (MethodInfo method : classInfo.methods()) { + if (skipForSubclass(method)) { + continue; + } + List methodLevelBindings = method.annotations().stream().filter(a -> a.target().kind().equals(Kind.METHOD)) + .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null).collect(Collectors.toList()); + Set merged = new HashSet<>(); + merged.addAll(methodLevelBindings); + for (AnnotationInstance classLevelBinding : classLevelBindings) { + if (methodLevelBindings.stream().noneMatch(a -> classLevelBinding.name().equals(a.name()))) { + merged.add(classLevelBinding); + } + } + if (!merged.isEmpty()) { + candidates.computeIfAbsent(new Methods.MethodKey(method), key -> merged); + } + } + if (classInfo.superClassType() != null) { + ClassInfo superClassInfo = beanDeployment.getIndex().getClassByName(classInfo.superName()); + if (superClassInfo != null) { + addInterceptedMethodCandidates(beanDeployment, superClassInfo, candidates, classLevelBindings); + } + } + } + + private static boolean skipForSubclass(MethodInfo method) { + if (Modifier.isStatic(method.flags()) || Modifier.isFinal(method.flags())) { + return true; + } + if (IGNORED_METHODS.contains(method.name())) { + return true; + } + if (method.declaringClass().name().equals(DotNames.OBJECT)) { + return true; + } + return false; + } + + static Type resolveType(Type type, Map resolvedTypeParameters) { + switch (type.kind()) { + case CLASS: + case PRIMITIVE: + case VOID: + return type; + case TYPE_VARIABLE: + // TODO bounds + return resolvedTypeParameters.getOrDefault(type.asTypeVariable(), type); + case PARAMETERIZED_TYPE: + ParameterizedType parameterizedType = type.asParameterizedType(); + Type[] args = new Type[parameterizedType.arguments().size()]; + for (int i = 0; i < args.length; i++) { + args[i] = resolveType(parameterizedType.arguments().get(i), resolvedTypeParameters); + } + return ParameterizedType.create(parameterizedType.name(), args, null); + case WILDCARD_TYPE: + WildcardType wildcardType = type.asWildcardType(); + return WildcardType.create( + resolveType(wildcardType.superBound() != null ? wildcardType.superBound() : wildcardType.extendsBound(), resolvedTypeParameters), + wildcardType.superBound() == null); + case ARRAY: + ArrayType arrayType = type.asArrayType(); + return ArrayType.create(resolveType(arrayType.component(), resolvedTypeParameters), arrayType.dimensions()); + default: + throw new IllegalArgumentException("Unsupported type to resolve: " + type); + } + } + + static class MethodKey { + + final MethodInfo method; + + public MethodKey(MethodInfo method) { + this.method = method; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((method.name() == null) ? 0 : method.name().hashCode()); + result = prime * result + ((method.parameters() == null) ? 0 : method.parameters().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof MethodKey)) { + return false; + } + MethodKey other = (MethodKey) obj; + if (!method.name().equals(other.method.name())) { + return false; + } + // FIXME this does not handle generics!!! + if (!method.parameters().equals(other.method.parameters())) { + return false; + } + return true; + } + + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceClassOutput.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceClassOutput.java new file mode 100644 index 0000000000000..55ca43e547e3a --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceClassOutput.java @@ -0,0 +1,38 @@ +package org.jboss.protean.arc.processor; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.arc.processor.ResourceOutput.Resource.SpecialType; +import org.jboss.protean.gizmo.ClassOutput; + +/** + * + * @author Martin Kouba + */ +public class ResourceClassOutput implements ClassOutput { + + private final List resources = new ArrayList<>(); + + private final Function specialTypeFunction; + + public ResourceClassOutput() { + this(null); + } + + public ResourceClassOutput(Function specialTypeFunction) { + this.specialTypeFunction = specialTypeFunction; + } + + @Override + public void write(String name, byte[] data) { + resources.add(ResourceImpl.javaClass(name, data, specialTypeFunction != null ? specialTypeFunction.apply(name) : null)); + } + + List getResources() { + return resources; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceImpl.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceImpl.java new file mode 100644 index 0000000000000..9e6feb70b0888 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceImpl.java @@ -0,0 +1,101 @@ +package org.jboss.protean.arc.processor; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jboss.protean.arc.processor.ResourceOutput.Resource; + +/** + * + * @author Martin Kouba + */ +class ResourceImpl implements ResourceOutput.Resource { + + static Resource javaClass(String name, byte[] data) { + return javaClass(name, data, null); + } + + static Resource javaClass(String name, byte[] data, SpecialType specialType) { + return new ResourceImpl(name, data, Type.JAVA_CLASS, specialType); + + } + + static Resource serviceProvider(String name, byte[] data) { + return serviceProvider(name, data, null); + } + + static Resource serviceProvider(String name, byte[] data, SpecialType specialType) { + return new ResourceImpl(name, data, Type.SERVICE_PROVIDER, specialType); + } + + private final String name; + + private final byte[] data; + + private final Type type; + + private final SpecialType specialType; + + private ResourceImpl(String name, byte[] data, Type type, SpecialType specialType) { + this.name = name; + this.data = data; + this.type = type; + this.specialType = specialType; + } + + @Override + public File writeTo(File directory) throws IOException { + switch (type) { + case JAVA_CLASS: + Path outputPath = getOutputDirectory(directory).resolve(getSimpleName() + ".class"); + Files.write(outputPath, data); + return outputPath.toFile(); + default: + File file = new File(directory, name); + file.getParentFile().mkdirs(); + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(data); + } + return file; + } + } + + @Override + public byte[] getData() { + return data; + } + + @Override + public String getName() { + return name; + } + + @Override + public Type getType() { + return type; + } + + @Override + public SpecialType getSpecialType() { + return specialType; + } + + private Path getOutputDirectory(File directory) throws IOException { + Path outputDirectory = directory.toPath(); + if (name.contains("/")) { + for (String packageComponent : name.substring(0, name.lastIndexOf("/")).split("/")) { + outputDirectory = outputDirectory.resolve(packageComponent); + } + } + Files.createDirectories(outputDirectory); + return outputDirectory; + } + + private String getSimpleName() { + return name.contains("/") ? name.substring(name.lastIndexOf("/") + 1, name.length()) : name; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceOutput.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceOutput.java new file mode 100644 index 0000000000000..c8deb373140e5 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ResourceOutput.java @@ -0,0 +1,70 @@ +package org.jboss.protean.arc.processor; + +import java.io.File; +import java.io.IOException; + +/** + * Represents a generated resource. + * + * @author Martin Kouba + */ +public interface ResourceOutput { + + void writeResource(Resource resource) throws IOException; + + interface Resource { + + File writeTo(File directory) throws IOException; + + byte[] getData(); + + /** + *
+         * com/foo/MyBean
+         * com/foo/MyBean$Bar
+         * org.jboss.protean.arc.BeanProvider
+         * 
+ * + * @return the name + */ + String getName(); + + /** + *
+         * com.foo.MyBean
+         * 
+ * + * @return the fully qualified name as defined in JLS + */ + default String getFullyQualifiedName() { + if (Type.JAVA_CLASS.equals(getType()) || Type.JAVA_SOURCE.equals(getType())) { + return getName().replace("/", "."); + } + throw new UnsupportedOperationException(); + } + + /** + * + * @see Type + * @return the type + */ + Type getType(); + + /** + * + * @see SpecialType + * @return the special type or null + */ + SpecialType getSpecialType(); + + enum Type { + JAVA_CLASS, JAVA_SOURCE, SERVICE_PROVIDER, + } + + enum SpecialType { + BEAN, INTERCEPTOR_BEAN; + } + + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ScopeInfo.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ScopeInfo.java new file mode 100644 index 0000000000000..516468765b4f4 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/ScopeInfo.java @@ -0,0 +1,61 @@ +package org.jboss.protean.arc.processor; + +import java.lang.annotation.Annotation; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.context.RequestScoped; +import javax.inject.Singleton; + +import org.jboss.jandex.DotName; + +public enum ScopeInfo { + + DEPENDENT(Dependent.class), + SINGLETON(Singleton.class), + APPLICATION(ApplicationScoped.class, true), + REQUEST(RequestScoped.class, true), + ; + + private final DotName dotName; + + private final Class clazz; + + private final boolean isNormal; + + private ScopeInfo(Class clazz) { + this(clazz, false); + } + + private ScopeInfo(Class clazz, boolean isNormal) { + this.dotName = DotName.createSimple(clazz.getName()); + this.clazz = clazz; + this.isNormal = isNormal; + } + + DotName getDotName() { + return dotName; + } + + Class getClazz() { + return clazz; + } + + boolean isNormal() { + return isNormal; + } + + boolean isDefault() { + return DEPENDENT == this; + } + + static ScopeInfo from(DotName name) { + for (ScopeInfo scope : ScopeInfo.values()) { + if (scope.getDotName().equals(name)) { + return scope; + } + } + return null; + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/SubclassGenerator.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/SubclassGenerator.java new file mode 100644 index 0000000000000..78fb57b6b824b --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/SubclassGenerator.java @@ -0,0 +1,294 @@ +package org.jboss.protean.arc.processor; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.protean.arc.CreationalContextImpl; +import org.jboss.protean.arc.InjectableInterceptor; +import org.jboss.protean.arc.InjectableReferenceProvider; +import org.jboss.protean.arc.InvocationContextImpl; +import org.jboss.protean.arc.InvocationContextImpl.InterceptorInvocation; +import org.jboss.protean.arc.Reflections; +import org.jboss.protean.arc.Subclass; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.gizmo.BytecodeCreator; +import org.jboss.protean.gizmo.ClassCreator; +import org.jboss.protean.gizmo.DescriptorUtils; +import org.jboss.protean.gizmo.ExceptionTable; +import org.jboss.protean.gizmo.FieldCreator; +import org.jboss.protean.gizmo.FieldDescriptor; +import org.jboss.protean.gizmo.FunctionCreator; +import org.jboss.protean.gizmo.MethodCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +public class SubclassGenerator extends AbstractGenerator { + + static final String SUBCLASS_SUFFIX = "_Subclass"; + + static String generatedName(DotName providerTypeName, String baseName) { + return DotNames.packageName(providerTypeName).replace(".", "/") + "/" + baseName + SUBCLASS_SUFFIX; + } + + /** + * + * @param bean + * @param beanClassName Fully qualified class name + * @return a java file + */ + Collection generate(BeanInfo bean, String beanClassName) { + + ResourceClassOutput classOutput = new ResourceClassOutput(); + + Type providerType = bean.getProviderType(); + ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); + String providerTypeName = providerClass.name().toString(); + String baseName = getBaseName(bean, beanClassName); + String generatedName = generatedName(providerType.name(), baseName); + + // Foo_Subclass extends Foo implements Subclass + ClassCreator subclass = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(providerTypeName).interfaces(Subclass.class) + .build(); + + FieldDescriptor preDestroyField = createConstructor(bean, subclass, providerTypeName); + createDestroy(subclass, preDestroyField); + + subclass.close(); + return classOutput.getResources(); + } + + protected FieldDescriptor createConstructor(BeanInfo bean, ClassCreator subclass, String providerTypeName) { + + List parameterTypes = new ArrayList<>(); + + // First constructor injection points + Optional constructorInjection = bean.getConstructorInjection(); + if (constructorInjection.isPresent()) { + for (InjectionPointInfo injectionPoint : constructorInjection.get().injectionPoints) { + parameterTypes.add(injectionPoint.requiredType.name().toString()); + } + } + int superParamsSize = parameterTypes.size(); + + // CreationalContext + parameterTypes.add(CreationalContext.class.getName()); + + // Interceptor providers + List boundInterceptors = bean.getBoundInterceptors(); + for (InterceptorInfo interceptor : boundInterceptors) { + parameterTypes.add(InjectableInterceptor.class.getName()); + } + + MethodCreator constructor = subclass.getMethodCreator("", "V", parameterTypes.toArray(new String[0])); + + ResultHandle creationalContextHandle = constructor.getMethodParam(superParamsSize); + ResultHandle[] superParams = new ResultHandle[superParamsSize]; + for (int j = 0; j < superParamsSize; j++) { + superParams[j] = constructor.getMethodParam(j); + } + // super(fooProvider) + constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(providerTypeName, parameterTypes.subList(0, superParamsSize).toArray(new String[0])), + constructor.getThis(), superParams); + + Map interceptorToResultHandle = new HashMap<>(); + for (int j = 0; j < boundInterceptors.size(); j++) { + interceptorToResultHandle.put(boundInterceptors.get(j), constructor.getMethodParam(j + superParamsSize + 1)); + } + + // PreDestroy interceptors + FieldCreator preDestroysField = null; + List preDestroys = bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY); + if (!preDestroys.isEmpty()) { + // private final List preDestroys + preDestroysField = subclass.getFieldCreator("preDestroys", DescriptorUtils.extToInt(ArrayList.class.getName())) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + // preDestroys = new ArrayList<>() + constructor.writeInstanceField(preDestroysField.getFieldDescriptor(), constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class))); + for (InterceptorInfo interceptor : preDestroys) { + // preDestroys.add(InvocationContextImpl.InterceptorInvocation.preDestroy(provider1,provider1.get(CreationalContextImpl.child(ctx)))) + ResultHandle creationalContext = constructor.invokeStaticMethod( + MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, CreationalContext.class), + creationalContextHandle); + ResultHandle interceptorInstance = constructor.invokeInterfaceMethod( + MethodDescriptor.ofMethod(InjectableReferenceProvider.class, "get", Object.class, CreationalContext.class), + interceptorToResultHandle.get(interceptor), creationalContext); + ResultHandle interceptionInvocation = constructor.invokeStaticMethod(MethodDescriptor.ofMethod(InterceptorInvocation.class, "preDestroy", + InterceptorInvocation.class, InjectableInterceptor.class, Object.class), interceptorToResultHandle.get(interceptor), + interceptorInstance); + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class), + constructor.readInstanceField(preDestroysField.getFieldDescriptor(), constructor.getThis()), interceptionInvocation); + } + } + + // Init intercepted methods and interceptor chains + // private final Map> interceptorChains + FieldCreator interceptorChainsField = subclass.getFieldCreator("interceptorChains", DescriptorUtils.extToInt(Map.class.getName())) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + // interceptorChains = new HashMap<>() + constructor.writeInstanceField(interceptorChainsField.getFieldDescriptor(), constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); + ResultHandle interceptorChainsHandle = constructor.readInstanceField(interceptorChainsField.getFieldDescriptor(), constructor.getThis()); + // private final Map methods + FieldCreator methodsField = subclass.getFieldCreator("methods", DescriptorUtils.extToInt(Map.class.getName())).setModifiers(ACC_PRIVATE | ACC_FINAL); + constructor.writeInstanceField(methodsField.getFieldDescriptor(), constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); + ResultHandle methodsHandle = constructor.readInstanceField(methodsField.getFieldDescriptor(), constructor.getThis()); + + int methodIdx = 1; + for (Entry> entry : bean.getInterceptedMethods().entrySet()) { + String methodId = "m" + methodIdx++; + MethodInfo method = entry.getKey(); + ResultHandle methodIdHandle = constructor.load(methodId); + + // First create interceptor chains + // List m1Chain = new ArrayList<>() + ResultHandle chainHandle = constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (InterceptorInfo interceptor : entry.getValue()) { + // m1Chain.add(InvocationContextImpl.InterceptorInvocation.aroundInvoke(p3,p3.get(CreationalContextImpl.child(ctx)))) + ResultHandle creationalContext = constructor.invokeStaticMethod( + MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, CreationalContext.class), + creationalContextHandle); + ResultHandle interceptorInstance = constructor.invokeInterfaceMethod( + MethodDescriptor.ofMethod(InjectableReferenceProvider.class, "get", Object.class, CreationalContext.class), + interceptorToResultHandle.get(interceptor), creationalContext); + ResultHandle interceptionInvocation = constructor.invokeStaticMethod(MethodDescriptor.ofMethod(InterceptorInvocation.class, "aroundInvoke", + InterceptorInvocation.class, InjectableInterceptor.class, Object.class), interceptorToResultHandle.get(interceptor), + interceptorInstance); + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class), chainHandle, + interceptionInvocation); + } + // interceptorChains.put("m1", m1Chain) + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, Object.class), interceptorChainsHandle, + methodIdHandle, chainHandle); + // methods.put("m1", Reflections.findMethod(org.jboss.weld.arc.test.interceptors.SimpleBean.class,"foo",java.lang.String.class)) + ResultHandle[] paramsHandles = new ResultHandle[3]; + paramsHandles[0] = constructor.loadClass(providerTypeName); + paramsHandles[1] = constructor.load(method.name()); + if (!method.parameters().isEmpty()) { + ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parameters().size())); + for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { + constructor.writeArrayValue(paramsArray, constructor.load(iterator.nextIndex()), constructor.loadClass(iterator.next().name().toString())); + } + paramsHandles[2] = paramsArray; + } else { + paramsHandles[2] = constructor.newArray(Class.class, constructor.load(0)); + } + ResultHandle methodHandle = constructor.invokeStaticMethod( + MethodDescriptor.ofMethod(Reflections.class, "findMethod", Method.class, Class.class, String.class, Class[].class), paramsHandles); + constructor.invokeInterfaceMethod(MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, Object.class), methodsHandle, + methodIdHandle, methodHandle); + + // Finally create the forwarding method + createForwardingMethod(method, methodId, subclass, providerTypeName, interceptorChainsField.getFieldDescriptor(), + methodsField.getFieldDescriptor()); + } + + constructor.returnValue(null); + return preDestroysField != null ? preDestroysField.getFieldDescriptor() : null; + } + + private void createForwardingMethod(MethodInfo method, String methodId, ClassCreator subclass, String providerTypeName, + FieldDescriptor interceptorChainsField, FieldDescriptor methodsField) { + + MethodCreator forwardMethod = subclass.getMethodCreator(MethodDescriptor.of(method)); + + // Params + // Object[] params = new Object[] {p1} + ResultHandle paramsHandle = forwardMethod.newArray(Object.class, forwardMethod.load(method.parameters().size())); + for (int i = 0; i < method.parameters().size(); i++) { + forwardMethod.writeArrayValue(paramsHandle, forwardMethod.load(i), forwardMethod.getMethodParam(i)); + } + + // Forwarding function + // Function forward = ctx -> super.foo((java.lang.String)ctx.getParameters()[0]) + FunctionCreator func = forwardMethod.createFunction(Function.class); + BytecodeCreator funcBytecode = func.getBytecode(); + ResultHandle ctxHandle = funcBytecode.getMethodParam(0); + ResultHandle[] superParamHandles = new ResultHandle[method.parameters().size()]; + ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class), + ctxHandle); + for (int i = 0; i < superParamHandles.length; i++) { + superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, funcBytecode.load(i)); + } + ResultHandle superResult = funcBytecode.invokeSpecialMethod( + MethodDescriptor.ofMethod(providerTypeName, method.name(), method.returnType().name().toString(), + method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()).toArray(new String[0])), + forwardMethod.getThis(), superParamHandles); + funcBytecode.returnValue(superResult); + + // InvocationContext + // (java.lang.String) InvocationContextImpl.aroundInvoke(this, methods.get("m1"), params, interceptorChains.get("m1"), forward).proceed() + ExceptionTable tryCatch = forwardMethod.addTryCatch(); + // catch (Exception e) + BytecodeCreator exception = tryCatch.addCatchClause(Exception.class); + // throw new RuntimeException(e) + exception.throwException(RuntimeException.class, "Error invoking subclass", exception.getThis()); + // InvocationContextImpl.aroundInvoke(this, methods.get("m1"), params, interceptorChains.get("m1"), forward) + ResultHandle methodIdHandle = forwardMethod.load(methodId); + ResultHandle interceptedMethodHandle = forwardMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, + forwardMethod.readInstanceField(methodsField, forwardMethod.getThis()), methodIdHandle); + ResultHandle interceptedChainHandle = forwardMethod.invokeInterfaceMethod(MethodDescriptors.MAP_GET, + forwardMethod.readInstanceField(interceptorChainsField, forwardMethod.getThis()), methodIdHandle); + ResultHandle invocationContext = forwardMethod.invokeStaticMethod( + MethodDescriptor.ofMethod(InvocationContextImpl.class, "aroundInvoke", InvocationContextImpl.class, Object.class, Method.class, Object[].class, + List.class, Function.class), + forwardMethod.getThis(), interceptedMethodHandle, paramsHandle, interceptedChainHandle, func.getInstance()); + // InvocationContext.proceed() + forwardMethod.returnValue( + forwardMethod.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), invocationContext)); + tryCatch.complete(); + } + + /** + * + * @param subclass + * @param preDestroysField + * @see Subclass#destroy() + */ + protected void createDestroy(ClassCreator subclass, FieldDescriptor preDestroysField) { + if (preDestroysField != null) { + MethodCreator destroyMethod = subclass.getMethodCreator(MethodDescriptor.ofMethod(Subclass.class, "destroy", void.class)); + ResultHandle predestroysHandle = destroyMethod.readInstanceField(preDestroysField, destroyMethod.getThis()); + // try + ExceptionTable tryCatch = destroyMethod.addTryCatch(); + // catch (Exception e) + BytecodeCreator exception = tryCatch.addCatchClause(Exception.class); + // throw new RuntimeException(e) + exception.throwException(RuntimeException.class, "Error destroying subclass", exception.getThis()); + // InvocationContextImpl.preDestroy(this,predestroys) + ResultHandle invocationContext = destroyMethod.invokeStaticMethod( + MethodDescriptor.ofMethod(InvocationContextImpl.class, "preDestroy", InvocationContextImpl.class, Object.class, List.class), + destroyMethod.getThis(), predestroysHandle); + // InvocationContext.proceed() + destroyMethod.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), invocationContext); + tryCatch.complete(); + destroyMethod.returnValue(null); + } + } + +} diff --git a/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Types.java b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Types.java new file mode 100644 index 0000000000000..246d8b2de53b8 --- /dev/null +++ b/ext/arc/processor/src/main/java/org/jboss/protean/arc/processor/Types.java @@ -0,0 +1,190 @@ +package org.jboss.protean.arc.processor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.jboss.jandex.TypeVariable; +import org.jboss.protean.arc.GenericArrayTypeImpl; +import org.jboss.protean.arc.ParameterizedTypeImpl; +import org.jboss.protean.arc.TypeVariableImpl; +import org.jboss.protean.gizmo.BytecodeCreator; +import org.jboss.protean.gizmo.MethodDescriptor; +import org.jboss.protean.gizmo.ResultHandle; + +/** + * + * @author Martin Kouba + */ +final class Types { + + private Types() { + } + + static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { + if (Kind.CLASS.equals(type.kind())) { + return creator.loadClass(type.asClassType().name().toString()); + } else if (Kind.TYPE_VARIABLE.equals(type.kind())) { + // E.g. T -> new TypeVariableImpl("T") + TypeVariable typeVariable = type.asTypeVariable(); + ResultHandle boundsHandle; + List bounds = typeVariable.bounds(); + if (bounds.isEmpty()) { + boundsHandle = creator.newArray(java.lang.reflect.Type.class, creator.load(0)); + } else { + boundsHandle = creator.newArray(java.lang.reflect.Type.class, creator.load(bounds.size())); + for (int i = 0; i < bounds.size(); i++) { + creator.writeArrayValue(boundsHandle, creator.load(i), getTypeHandle(creator, bounds.get(i))); + } + } + return creator.newInstance(MethodDescriptor.ofConstructor(TypeVariableImpl.class, String.class, java.lang.reflect.Type[].class), + creator.load(typeVariable.identifier()), boundsHandle); + + } else if (Kind.PARAMETERIZED_TYPE.equals(type.kind())) { + // E.g. List -> new ParameterizedTypeImpl(List.class, String.class) + ParameterizedType parameterizedType = type.asParameterizedType(); + + List arguments = parameterizedType.arguments(); + ResultHandle typeArgsHandle = creator.newArray(java.lang.reflect.Type.class, creator.load(arguments.size())); + for (int i = 0; i < arguments.size(); i++) { + creator.writeArrayValue(typeArgsHandle, creator.load(i), getTypeHandle(creator, arguments.get(i))); + } + return creator.newInstance( + MethodDescriptor.ofConstructor(ParameterizedTypeImpl.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class), + creator.loadClass(parameterizedType.name().toString()), typeArgsHandle); + + } else if (Kind.ARRAY.equals(type.kind())) { + Type componentType = type.asArrayType().component(); + // E.g. String[] -> new GenericArrayTypeImpl(String.class) + return creator.newInstance(MethodDescriptor.ofConstructor(GenericArrayTypeImpl.class, java.lang.reflect.Type.class), + getTypeHandle(creator, componentType)); + + } else { + throw new IllegalArgumentException("Unsupported bean type: " + type.kind() + ", " + type); + } + } + + + static Type getProviderType(ClassInfo classInfo) { + // TODO hack + List typeParameters = classInfo.typeParameters(); + if (!typeParameters.isEmpty()) { + return ParameterizedType.create(classInfo.name(), typeParameters.toArray(new Type[] {}), null); + } else { + return Type.create(classInfo.name(), Kind.CLASS); + } + } + + static Set getTypeClosure(MethodInfo producerMethod, BeanDeployment beanDeployment) { + ClassInfo returnTypeClassInfo = beanDeployment.getIndex().getClassByName(producerMethod.returnType().name()); + if (returnTypeClassInfo == null) { + throw new IllegalArgumentException("Producer method return type not found in index: " + producerMethod.returnType().name()); + } + Type returnType = producerMethod.returnType(); + if (Kind.CLASS.equals(returnType.kind())) { + return getTypeClosure(returnTypeClassInfo, Collections.emptyMap(), beanDeployment); + } else if (Kind.PARAMETERIZED_TYPE.equals(returnType.kind())) { + return getTypeClosure(returnTypeClassInfo, + buildResolvedMap(returnType.asParameterizedType().arguments(), returnTypeClassInfo.typeParameters(), Collections.emptyMap()), + beanDeployment); + } else { + throw new IllegalArgumentException("Unsupported return type"); + } + } + + static Set getTypeClosure(FieldInfo producerField, BeanDeployment beanDeployment) { + ClassInfo fieldClassInfo = beanDeployment.getIndex().getClassByName(producerField.type().name()); + if (fieldClassInfo == null) { + throw new IllegalArgumentException("Producer field type not found in index: " + producerField.type().name()); + } + Type fieldType = producerField.type(); + if (Kind.CLASS.equals(fieldType.kind())) { + return getTypeClosure(fieldClassInfo, Collections.emptyMap(), beanDeployment); + } else if (Kind.PARAMETERIZED_TYPE.equals(fieldType.kind())) { + return getTypeClosure(fieldClassInfo, + buildResolvedMap(fieldType.asParameterizedType().arguments(), fieldClassInfo.typeParameters(), Collections.emptyMap()), beanDeployment); + } else { + throw new IllegalArgumentException("Unsupported return type"); + } + } + + static Set getTypeClosure(ClassInfo classInfo, Map resolvedTypeParameters, BeanDeployment beanDeployment) { + Set types = new HashSet<>(); + List typeParameters = classInfo.typeParameters(); + if (!typeParameters.isEmpty()) { + // Canonical ParameterizedType with unresolved type variables + Type[] typeParams = new Type[typeParameters.size()]; + for (int i = 0; i < typeParameters.size(); i++) { + typeParams[i] = resolvedTypeParameters.get(typeParameters.get(i)); + } + types.add(ParameterizedType.create(classInfo.name(), typeParams, null)); + } else { + types.add(Type.create(classInfo.name(), Kind.CLASS)); + } + // Interfaces + for (Type interfaceType : classInfo.interfaceTypes()) { + ClassInfo interfaceClassInfo = beanDeployment.getIndex().getClassByName(interfaceType.name()); + if (interfaceClassInfo != null) { + Map resolved = Collections.emptyMap(); + if (Kind.PARAMETERIZED_TYPE.equals(interfaceType.kind())) { + resolved = buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), resolvedTypeParameters); + } + types.addAll(getTypeClosure(interfaceClassInfo, resolved, beanDeployment)); + } + } + // Superclass + if (classInfo.superClassType() != null) { + ClassInfo superClassInfo = beanDeployment.getIndex().getClassByName(classInfo.superName()); + if (superClassInfo != null) { + Map resolved = Collections.emptyMap(); + if (Kind.PARAMETERIZED_TYPE.equals(classInfo.superClassType().kind())) { + resolved = buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), superClassInfo.typeParameters(), + resolvedTypeParameters); + } + types.addAll(getTypeClosure(superClassInfo, resolved, beanDeployment)); + } + } + return types; + } + + static Map buildResolvedMap(List resolvedTypeVariables, List typeVariables, + Map resolvedTypeParameters) { + Map resolvedMap = new HashMap<>(); + for (int i = 0; i < resolvedTypeVariables.size(); i++) { + Type resolvedTypeVariable = resolvedTypeVariables.get(i); + Type resolvedTypeParam = Kind.TYPE_VARIABLE.equals(resolvedTypeVariable.kind()) + ? resolvedTypeParameters.getOrDefault(resolvedTypeVariable, resolvedTypeVariable) + : resolvedTypeVariable; + resolvedMap.put(typeVariables.get(i), resolvedTypeParam); + } + return resolvedMap; + } + + static String convertNested(DotName name) { + return convertNested(name.toString()); + } + + static String convertNested(String name) { + return name.replace("$", "."); + } + + static String getPackageName(String className) { + className = className.replace("/", "."); + return className.contains(".") ? className.substring(0, className.lastIndexOf(".")) : ""; + } + + static String getSimpleName(String className) { + return className.contains(".") ? className.substring(className.lastIndexOf(".") + 1, className.length()) : className; + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/Basics.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/Basics.java new file mode 100644 index 0000000000000..92bf282e6a40b --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/Basics.java @@ -0,0 +1,26 @@ +package org.jboss.protean.arc.processor; + +import java.io.IOException; +import java.io.InputStream; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.Indexer; + +public final class Basics { + + public static DotName name(Class clazz) { + return DotName.createSimple(clazz.getName()); + } + + public static Index index(Class... classes) throws IOException { + Indexer indexer = new Indexer(); + for (Class clazz : classes) { + try (InputStream stream = Basics.class.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { + indexer.index(stream); + } + } + return indexer.complete(); + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java new file mode 100644 index 0000000000000..c85b456053dda --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanGeneratorTest.java @@ -0,0 +1,61 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; + +import java.io.IOException; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; + +import org.jboss.jandex.Index; +import org.jboss.protean.arc.processor.AnnotationLiteralProcessor; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanGenerator; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.types.Foo; +import org.jboss.protean.arc.processor.types.FooQualifier; +import org.junit.Test; + +public class BeanGeneratorTest { + + @Test + public void testGenerator() throws IOException { + + Index index = index(Foo.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, Iterable.class); + BeanDeployment deployment = new BeanDeployment(index, null); + deployment.init(); + + BeanGenerator generator = new BeanGenerator(); + + deployment.getBeans().forEach(bean -> generator.generate(bean, new AnnotationLiteralProcessor(BeanProcessor.DEFAULT_NAME, true))); + // TODO test generated bytecode + } + + @Test + public void testGeneratorForNormalScopedProducer() throws IOException { + + Index index = index(Producer.class, Collection.class, List.class, Iterable.class); + BeanDeployment deployment = new BeanDeployment(index, null); + deployment.init(); + + BeanGenerator generator = new BeanGenerator(); + + deployment.getBeans().forEach(bean -> generator.generate(bean, new AnnotationLiteralProcessor(BeanProcessor.DEFAULT_NAME, true))); + // TODO test generated bytecode + } + + @Dependent + static class Producer { + + @ApplicationScoped + @Produces + List list; + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoInjectionsTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoInjectionsTest.java new file mode 100644 index 0000000000000..069d86dbeae57 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoInjectionsTest.java @@ -0,0 +1,72 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; +import static org.jboss.protean.arc.processor.Basics.name; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanInfo; +import org.jboss.protean.arc.processor.Injection; +import org.jboss.protean.arc.processor.types.Bar; +import org.jboss.protean.arc.processor.types.Foo; +import org.jboss.protean.arc.processor.types.FooQualifier; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Martin Kouba + */ +public class BeanInfoInjectionsTest { + + @Test + public void testInjections() throws IOException { + + Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, + Iterable.class); + DotName barName = name(Bar.class); + ClassInfo barClass = index.getClassByName(barName); + Type fooType = Type.create(name(Foo.class), Kind.CLASS); + Type listStringType = ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null); + + BeanDeployment deployment = new BeanDeployment(index, null); + BeanInfo barBean = deployment.getBeans().stream().filter(b -> b.getTarget().equals(barClass)).findFirst().get(); + List injections = barBean.getInjections(); + assertEquals(3, injections.size()); + for (Injection injection : injections) { + if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.FIELD) && injection.target.asField().name().equals("foo")) { + assertEquals(1, injection.injectionPoints.size()); + assertEquals(fooType, injection.injectionPoints.get(0).requiredType); + assertEquals(1, injection.injectionPoints.get(0).requiredQualifiers.size()); + } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) && injection.target.asMethod().name().equals("")) { + // Constructor + assertEquals(2, injection.injectionPoints.size()); + assertEquals(listStringType, injection.injectionPoints.get(0).requiredType); + assertEquals(fooType, injection.injectionPoints.get(1).requiredType); + assertEquals(1, injection.injectionPoints.get(1).requiredQualifiers.size()); + } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) && injection.target.asMethod().name().equals("init")) { + // Initializer + assertEquals(2, injection.injectionPoints.size()); + assertEquals(listStringType, injection.injectionPoints.get(1).requiredType); + assertEquals(fooType, injection.injectionPoints.get(0).requiredType); + } else { + Assert.fail(); + } + + } + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoQualifiersTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoQualifiersTest.java new file mode 100644 index 0000000000000..0b12459bde1ea --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoQualifiersTest.java @@ -0,0 +1,46 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; +import static org.jboss.protean.arc.processor.Basics.name; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanInfo; +import org.jboss.protean.arc.processor.Beans; +import org.jboss.protean.arc.processor.types.Bar; +import org.jboss.protean.arc.processor.types.Foo; +import org.jboss.protean.arc.processor.types.FooQualifier; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.junit.Test; + +/** + * + * @author Martin Kouba + */ +public class BeanInfoQualifiersTest { + + @Test + public void testQualifiers() throws IOException { + Index index = index(Foo.class, Bar.class, FooQualifier.class); + DotName fooName = name(Foo.class); + DotName fooQualifierName = name(FooQualifier.class); + ClassInfo fooClass = index.getClassByName(fooName); + + BeanInfo bean = Beans.createClassBean(fooClass, new BeanDeployment(index, null)); + + AnnotationInstance requiredFooQualifier = index.getAnnotations(fooQualifierName).stream() + .filter(a -> Kind.FIELD.equals(a.target().kind()) && a.target().asField().name().equals("foo")).findFirst().orElse(null); + + assertNotNull(requiredFooQualifier); + // FooQualifier#alpha() is @Nonbinding + assertTrue(bean.hasQualifier(requiredFooQualifier)); + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoTypesTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoTypesTest.java new file mode 100644 index 0000000000000..9388e9c346ee8 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/BeanInfoTypesTest.java @@ -0,0 +1,59 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; +import static org.jboss.protean.arc.processor.Basics.name; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.AbstractCollection; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanInfo; +import org.jboss.protean.arc.processor.Beans; +import org.jboss.protean.arc.processor.types.Bar; +import org.jboss.protean.arc.processor.types.Foo; +import org.jboss.protean.arc.processor.types.FooQualifier; +import org.junit.Test; + +/** + * + * @author Martin Kouba + */ +public class BeanInfoTypesTest { + + @Test + public void testResolver() throws IOException { + + Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, + Iterable.class); + + BeanDeployment deployment = new BeanDeployment(index, null); + DotName fooName = name(Foo.class); + + ClassInfo fooClass = index.getClassByName(fooName); + BeanInfo fooBean = Beans.createClassBean(fooClass, deployment); + Set types = fooBean.getTypes(); + // System.out.println(types); + // Foo, AbstractList, AbstractCollection, List, Collection, Iterable + assertEquals(6, types.size()); + assertTrue(types.contains(Type.create(fooName, Kind.CLASS))); + assertTrue(types.contains(ParameterizedType.create(name(AbstractList.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(Collection.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(AbstractCollection.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(Iterable.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java new file mode 100644 index 0000000000000..46a1c73ac6ca5 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/ClientProxyGeneratorTest.java @@ -0,0 +1,72 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; + +import org.jboss.jandex.Index; +import org.jboss.protean.arc.processor.AnnotationLiteralProcessor; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanGenerator; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.ClientProxyGenerator; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.junit.Test; + +public class ClientProxyGeneratorTest { + + @Test + public void testGenerator() throws IOException { + + Index index = index(Producer.class, List.class, Collection.class, Iterable.class, AbstractList.class, MyList.class); + BeanDeployment deployment = new BeanDeployment(index, null); + deployment.init(); + + BeanGenerator beanGenerator = new BeanGenerator(); + ClientProxyGenerator proxyGenerator = new ClientProxyGenerator(); + + deployment.getBeans().stream().filter(bean -> bean.getScope().isNormal()).forEach(bean -> { + for (Resource resource : beanGenerator.generate(bean, new AnnotationLiteralProcessor(BeanProcessor.DEFAULT_NAME, true))) { + proxyGenerator.generate(bean, resource.getFullyQualifiedName()); + } + }); + // TODO test generated bytecode + } + + @Dependent + static class Producer { + + @ApplicationScoped + @Produces + List list() { + return null; + } + + } + + @ApplicationScoped + static class MyList extends AbstractList { + + @Override + public String get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + + void myMethod() throws IOException { + } + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java new file mode 100644 index 0000000000000..1fba11f96fc67 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/InterceptorGeneratorTest.java @@ -0,0 +1,71 @@ +package org.jboss.protean.arc.processor; + +import static org.jboss.protean.arc.processor.Basics.index; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.protean.arc.processor.AnnotationLiteralProcessor; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.InterceptorGenerator; +import org.jboss.protean.arc.processor.InterceptorInfo; +import org.jboss.protean.arc.processor.types.Baz; +import org.junit.Test; + +public class InterceptorGeneratorTest { + + @Test + public void testGenerator() throws IOException { + + Index index = index(MyInterceptor.class, MyBinding.class, Baz.class); + BeanDeployment deployment = new BeanDeployment(index, null); + deployment.init(); + + InterceptorInfo myInterceptor = deployment.getInterceptors().stream() + .filter(i -> i.getTarget().asClass().name().equals(DotName.createSimple(MyInterceptor.class.getName()))).findAny().orElse(null); + assertNotNull(myInterceptor); + assertEquals(10, myInterceptor.getPriority()); + assertEquals(1, myInterceptor.getBindings().size()); + assertNotNull(myInterceptor.getAroundInvoke()); + + InterceptorGenerator generator = new InterceptorGenerator(); + + deployment.getInterceptors().forEach(interceptor -> generator.generate(interceptor, new AnnotationLiteralProcessor(BeanProcessor.DEFAULT_NAME, true))); + // TODO test generated bytecode + } + + @Priority(10) + @MyBinding + @Interceptor + static class MyInterceptor { + + @Inject + Baz baz; + + @AroundInvoke + Object superCoolAroundInvokeMethod(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + + } + + @Retention(RetentionPolicy.RUNTIME) + @InterceptorBinding + public @interface MyBinding { + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java new file mode 100644 index 0000000000000..58f73703f902a --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/SubclassGeneratorTest.java @@ -0,0 +1,92 @@ +package org.jboss.protean.arc.processor; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.jboss.protean.arc.processor.Basics.index; + +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.annotation.Priority; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.protean.arc.processor.AnnotationLiteralProcessor; +import org.jboss.protean.arc.processor.BeanDeployment; +import org.jboss.protean.arc.processor.BeanGenerator; +import org.jboss.protean.arc.processor.BeanInfo; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.SubclassGenerator; +import org.jboss.protean.arc.processor.ResourceOutput.Resource; +import org.jboss.protean.arc.processor.types.Baz; +import org.junit.Test; + +public class SubclassGeneratorTest { + + @Test + public void testGenerator() throws IOException { + + Index index = index(SimpleBean.class, Simple.class, SimpleInterceptor.class, Baz.class); + BeanDeployment deployment = new BeanDeployment(index, null); + deployment.init(); + + BeanGenerator beanGenerator = new BeanGenerator(); + SubclassGenerator generator = new SubclassGenerator(); + BeanInfo simpleBean = deployment.getBeans().stream() + .filter(b -> b.getTarget().asClass().name().equals(DotName.createSimple(SimpleBean.class.getName()))).findAny().get(); + for (Resource resource : beanGenerator.generate(simpleBean, new AnnotationLiteralProcessor(BeanProcessor.DEFAULT_NAME, true))) { + generator.generate(simpleBean, resource.getFullyQualifiedName()); + } + // TODO test generated bytecode + } + + @Dependent + static class SimpleBean { + + private Baz baz; + + @Inject + public SimpleBean(Baz baz) { + this.baz = baz; + } + + @Simple + String foo(String bar) { + return "" + baz.isListResolvable(); + } + + Integer fooNotIntercepted() { + return 1; + } + + } + + @Simple + @Priority(1) + @Interceptor + public class SimpleInterceptor { + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return ctx.proceed(); + } + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + public @interface Simple { + + } + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/TypesTest.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/TypesTest.java new file mode 100644 index 0000000000000..fdf29ce0d4ec6 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/TypesTest.java @@ -0,0 +1,5 @@ +package org.jboss.protean.arc.processor; + +public class TypesTest { + +} diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Bar.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Bar.java new file mode 100644 index 0000000000000..8dc6eea77f68a --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Bar.java @@ -0,0 +1,23 @@ +package org.jboss.protean.arc.processor.types; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@ApplicationScoped +public class Bar { + + @FooQualifier(alpha = "yes", bravo = "no") + @Inject + Foo foo; + + @Inject + Bar(List list, @FooQualifier(alpha = "1", bravo = "2") Foo foo) { + } + + @Inject + public void init(Foo foo, List list) { + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Baz.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Baz.java new file mode 100644 index 0000000000000..f2dd06a8107f5 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Baz.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc.processor.types; + +import java.util.List; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +@Dependent +public class Baz { + + @Inject + Instance> list; + + public boolean isListResolvable() { + return list.isResolvable(); + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Foo.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Foo.java new file mode 100644 index 0000000000000..08145f8e83934 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/Foo.java @@ -0,0 +1,28 @@ +package org.jboss.protean.arc.processor.types; + +import java.util.AbstractList; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Default; + +@Dependent +@Default +@FooQualifier(alpha = "ignored", bravo = "no") +public class Foo extends AbstractList { + + @PreDestroy + void superCoolDestroyCallback() { + } + + @Override + public String get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + +} \ No newline at end of file diff --git a/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/FooQualifier.java b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/FooQualifier.java new file mode 100644 index 0000000000000..c72c9f5c30482 --- /dev/null +++ b/ext/arc/processor/src/test/java/org/jboss/protean/arc/processor/types/FooQualifier.java @@ -0,0 +1,27 @@ +package org.jboss.protean.arc.processor.types; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; +import javax.inject.Qualifier; + +@Qualifier +@Inherited +@Target({ TYPE, METHOD, FIELD, PARAMETER }) +@Retention(RUNTIME) +public @interface FooQualifier { + + @Nonbinding + String alpha() default ""; + + String bravo() default ""; + +} \ No newline at end of file diff --git a/ext/arc/runtime/pom.xml b/ext/arc/runtime/pom.xml new file mode 100644 index 0000000000000..54e635ad7112e --- /dev/null +++ b/ext/arc/runtime/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + + org.jboss.protean.arc + arc-parent + 1.0.0.Alpha1-SNAPSHOT + ../ + + + arc-runtime + + + + + javax.enterprise + cdi-api + + + javax.el + javax.el-api + + + + + + junit + junit + + + + + diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/AbstractSharedContext.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/AbstractSharedContext.java new file mode 100644 index 0000000000000..975f33a994834 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/AbstractSharedContext.java @@ -0,0 +1,96 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.spi.AlterableContext; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; + +abstract class AbstractSharedContext implements AlterableContext { + + private final ComputingCache, InstanceHandleImpl> instances; + + @SuppressWarnings("rawtypes") + public AbstractSharedContext() { + this.instances = new ComputingCache<>(key -> createInstanceHandle((InjectableBean) key.contextual, key.creationalContext)); + } + + @SuppressWarnings("unchecked") + @Override + public T get(Contextual contextual, CreationalContext creationalContext) { + return (T) instances.getValue(new Key<>(contextual, creationalContext)).get(); + } + + @Override + public T get(Contextual contextual) { + return get(contextual, null); + } + + @Override + public boolean isActive() { + return true; + } + + @Override + public void destroy(Contextual contextual) { + InstanceHandleImpl handle = instances.remove(new Key<>(contextual, null)); + if (handle != null) { + handle.destroy(); + } + } + + public void destroy() { + synchronized (this) { + instances.forEachValue(instance -> instance.destroy()); + instances.clear(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static InstanceHandleImpl createInstanceHandle(InjectableBean bean, CreationalContext ctx) { + return new InstanceHandleImpl(bean, bean.create(ctx), ctx); + } + + private static class Key { + + private Contextual contextual; + + private CreationalContext creationalContext; + + public Key(Contextual contextual, CreationalContext creationalContext) { + this.contextual = contextual; + this.creationalContext = creationalContext; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((contextual == null) ? 0 : contextual.hashCode()); + return result; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Key)) { + return false; + } + Key other = (Key) obj; + if (contextual == null) { + if (other.contextual != null) { + return false; + } + } else if (!contextual.equals(other.contextual)) { + return false; + } + return true; + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ApplicationContext.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ApplicationContext.java new file mode 100644 index 0000000000000..f460da01775c6 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ApplicationContext.java @@ -0,0 +1,13 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; + +import javax.enterprise.context.ApplicationScoped; + +class ApplicationContext extends AbstractSharedContext { + + @Override + public Class getScope() { + return ApplicationScoped.class; + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Arc.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Arc.java new file mode 100644 index 0000000000000..cc70642a66d30 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Arc.java @@ -0,0 +1,28 @@ +package org.jboss.protean.arc; + +/** + * + * @author Martin Kouba + */ +public final class Arc { + + private static final LazyValue INSTANCE = new LazyValue<>(() -> new ArcContainerImpl()); + + /** + * + * @return the container instance which loads beans using the service provider + */ + public static ArcContainer container() { + return INSTANCE.get(); + } + + public static void shutdown() { + if (INSTANCE.isSet()) { + synchronized (INSTANCE) { + INSTANCE.get().shutdown(); + INSTANCE.clear(); + } + } + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainer.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainer.java new file mode 100644 index 0000000000000..9de20db9188c0 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainer.java @@ -0,0 +1,24 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; + +import javax.enterprise.context.spi.Context; +import javax.enterprise.util.TypeLiteral; + +/** + * + * @author Martin Kouba + */ +public interface ArcContainer { + + Context getContext(Class scopeType); + + InstanceHandle instance(Class type, Annotation... qualifiers); + + InstanceHandle instance(TypeLiteral type, Annotation... qualifiers); + + RequestContext requestContext(); + + void withinRequest(Runnable action); + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java new file mode 100644 index 0000000000000..c4a708dfe4ff6 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ArcContainerImpl.java @@ -0,0 +1,200 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.spi.Context; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.util.TypeLiteral; +import javax.inject.Singleton; + +/** + * + * @author Martin Kouba + */ +class ArcContainerImpl implements ArcContainer { + + private final List> beans; + + private final Map, Context> contexts; + + private final ComputingCache>> resolved; + + public ArcContainerImpl() { + beans = new CopyOnWriteArrayList<>(); + for (BeanProvider beanProvider : ServiceLoader.load(BeanProvider.class)) { + beans.addAll(beanProvider.getBeans()); + } + contexts = new HashMap<>(); + contexts.put(ApplicationScoped.class, new ApplicationContext()); + contexts.put(Singleton.class, new SingletonContext()); + contexts.put(RequestScoped.class, new RequestContext()); + resolved = new ComputingCache<>(this::resolve); + } + + @Override + public Context getContext(Class scopeType) { + return contexts.get(scopeType); + } + + @Override + public InstanceHandle instance(Class type, Annotation... qualifiers) { + return instanceHandle(type, qualifiers); + } + + @Override + public InstanceHandle instance(TypeLiteral type, Annotation... qualifiers) { + return instanceHandle(type.getType(), qualifiers); + } + + @Override + public RequestContext requestContext() { + return (RequestContext) getContext(RequestScoped.class); + } + + @Override + public void withinRequest(Runnable action) { + try { + requestContext().activate(); + action.run(); + } finally { + requestContext().deactivate(); + } + } + + synchronized void shutdown() { + ((ApplicationContext) contexts.get(ApplicationScoped.class)).destroy(); + ((SingletonContext) contexts.get(Singleton.class)).destroy(); + requestContext().deactivate(); + beans.clear(); + resolved.clear(); + } + + private InstanceHandle instanceHandle(Type type, Annotation... qualifiers) { + return instance(getBean(type, qualifiers)); + } + + private InstanceHandle instance(InjectableBean bean) { + if (bean != null) { + CreationalContextImpl parentContext = new CreationalContextImpl<>(); + InjectionPoint prev = InjectionPointProvider.CURRENT.get(); + InjectionPointProvider.CURRENT.set(CurrentInjectionPointProvider.EMPTY); + try { + CreationalContextImpl creationalContext = parentContext.child(); + return new InstanceHandleImpl(bean, bean.get(creationalContext), creationalContext, parentContext); + } finally { + if (prev != null) { + InjectionPointProvider.CURRENT.set(prev); + } else { + InjectionPointProvider.CURRENT.remove(); + } + } + } else { + return InstanceHandleImpl.unresolvable(); + } + } + + @SuppressWarnings("unchecked") + private InjectableBean getBean(Type requiredType, Annotation... qualifiers) { + if (qualifiers == null || qualifiers.length == 0) { + qualifiers = new Annotation[] { Default.Literal.INSTANCE }; + } + List> resolvedBeans = resolved.getValue(new Resolvable(requiredType, qualifiers)); + return resolvedBeans.size() == 1 ? (InjectableBean) resolvedBeans.get(0) : null; + } + + private List> resolve(Resolvable resolvable) { + List> resolvedBeans = new ArrayList<>(); + for (InjectableBean bean : beans) { + if (matches(bean, resolvable.requiredType, resolvable.qualifiers)) { + resolvedBeans.add(bean); + } + } + return resolvedBeans; + } + + List> geBeans(Type requiredType, Annotation... qualifiers) { + if (qualifiers == null || qualifiers.length == 0) { + qualifiers = new Annotation[] { Default.Literal.INSTANCE }; + } + return resolved.getValue(new Resolvable(requiredType, qualifiers)); + } + + private boolean matches(InjectableBean bean, Type requiredType, Annotation... qualifiers) { + if (!BeanTypeAssignabilityRules.matches(requiredType, bean.getTypes())) { + return false; + } + return Qualifiers.hasQualifiers(bean, qualifiers); + } + + static ArcContainerImpl unwrap(ArcContainer container) { + if (container instanceof ArcContainerImpl) { + return (ArcContainerImpl) container; + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public String toString() { + return "ArcContainerImpl [beans=" + beans + ", contexts=" + contexts + "]"; + } + + private static final class Resolvable { + + final Type requiredType; + + final Annotation[] qualifiers; + + Resolvable(Type requiredType, Annotation[] qualifiers) { + this.requiredType = requiredType; + this.qualifiers = qualifiers; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(qualifiers); + result = prime * result + ((requiredType == null) ? 0 : requiredType.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Resolvable)) { + return false; + } + Resolvable other = (Resolvable) obj; + if (requiredType == null) { + if (other.requiredType != null) { + return false; + } + } else if (!requiredType.equals(other.requiredType)) { + return false; + } + if (!Arrays.equals(qualifiers, other.qualifiers)) { + return false; + } + return true; + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java new file mode 100644 index 0000000000000..23bd0d8ce67de --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanManagerProvider.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.BeanManager; + +/** + * Dummy {@link BeanManager} provider. + * + * @author Martin Kouba + */ +public class BeanManagerProvider implements InjectableReferenceProvider { + + @Override + public BeanManager get(CreationalContext creationalContext) { + // TODO log a warning + return null; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanMetadataProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanMetadataProvider.java new file mode 100644 index 0000000000000..279107e51a563 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanMetadataProvider.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; + +/** + * Dummy {@link Bean} provider. + * + * @author Martin Kouba + */ +public class BeanMetadataProvider implements InjectableReferenceProvider { + + @Override + public T get(CreationalContext creationalContext) { + // TODO log a warning + return null; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanProvider.java new file mode 100644 index 0000000000000..6146d2e4efa9a --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanProvider.java @@ -0,0 +1,13 @@ +package org.jboss.protean.arc; + +import java.util.Collection; + +/** + * + * @author Martin Kouba + */ +public interface BeanProvider { + + Collection> getBeans(); + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanTypeAssignabilityRules.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanTypeAssignabilityRules.java new file mode 100644 index 0000000000000..2ea26833699b0 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/BeanTypeAssignabilityRules.java @@ -0,0 +1,235 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2014, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.protean.arc; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Set; + +/** + * This code was mainly copied from Weld codebase. + * + * Implementation of the Section 5.2.4 of the CDI specification. + * + * @author Jozef Hartinger + * @author Matus Abaffy + */ +final class BeanTypeAssignabilityRules { + + private BeanTypeAssignabilityRules() { + } + + static boolean matches(Type requiredType, Set beanTypes) { + for (Type beanType : beanTypes) { + if (matches(requiredType, beanType)) { + return true; + } + } + return false; + } + + private static boolean matches(Type requiredType, Type beanType) { + return matchesNoBoxing(Types.boxedType(requiredType), Types.boxedType(beanType)); + } + + private static boolean matchesNoBoxing(Type requiredType, Type beanType) { + if (requiredType instanceof Class) { + if (beanType instanceof Class) { + return matches((Class) requiredType, (Class) beanType); + } + if (beanType instanceof ParameterizedType) { + return matches((Class) requiredType, (ParameterizedType) beanType); + } + } else if (requiredType instanceof ParameterizedType) { + if (beanType instanceof Class) { + return matches((Class) beanType, (ParameterizedType) requiredType); + } + if (beanType instanceof ParameterizedType) { + return matches((ParameterizedType) requiredType, (ParameterizedType) beanType); + } + } + return false; + } + + private static boolean matches(Class requiredType, Class beanType) { + return requiredType.equals(beanType); + } + + /** + * A parameterized bean type is considered assignable to a raw required type if the raw types are identical and all type parameters of the bean type are + * either unbounded type variables or java.lang.Object. + *

+ * A raw bean type is considered assignable to a parameterized required type if the raw types are identical and all type parameters of the required type are + * either unbounded type variables or java.lang.Object. + * + */ + private static boolean matches(Class type1, ParameterizedType type2) { + if (!type1.equals(Types.getRawType(type2))) { + return false; + } + return Types.isArrayOfUnboundedTypeVariablesOrObjects(type2.getActualTypeArguments()); + } + + /** + * A parameterized bean type is considered assignable to a parameterized required type if they have identical raw type and for each parameter: + */ + private static boolean matches(ParameterizedType requiredType, ParameterizedType beanType) { + if (!requiredType.getRawType().equals(beanType.getRawType())) { + return false; + } + if (requiredType.getActualTypeArguments().length != beanType.getActualTypeArguments().length) { + throw new IllegalArgumentException("Invalid argument combination " + requiredType + "; " + beanType); + } + for (int i = 0; i < requiredType.getActualTypeArguments().length; i++) { + if (!parametersMatch(requiredType.getActualTypeArguments()[i], beanType.getActualTypeArguments()[i])) { + return false; + } + } + return true; + } + + /* + * Actual type parameters + */ + + private static boolean parametersMatch(Type requiredParameter, Type beanParameter) { + if (Types.isActualType(requiredParameter) && Types.isActualType(beanParameter)) { + /* + * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the type is parameterized, the bean + * type parameter is assignable to the required type parameter according to these rules, or + */ + return matches(requiredParameter, beanParameter); + } + if (requiredParameter instanceof WildcardType && Types.isActualType(beanParameter)) { + /* + * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is assignable to the upper bound, if + * any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + */ + return parametersMatch((WildcardType) requiredParameter, beanParameter); + } + if (requiredParameter instanceof WildcardType && beanParameter instanceof TypeVariable) { + /* + * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the type variable is assignable to + * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + */ + return parametersMatch((WildcardType) requiredParameter, (TypeVariable) beanParameter); + } + if (Types.isActualType(requiredParameter) && beanParameter instanceof TypeVariable) { + /* + * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is assignable to the upper bound, + * if any, of the type variable, or + */ + return parametersMatch(requiredParameter, (TypeVariable) beanParameter); + } + if (requiredParameter instanceof TypeVariable && beanParameter instanceof TypeVariable) { + /* + * the required type parameter and the bean type parameter are both type variables and the upper bound of the required type parameter is assignable + * to the upper bound, if any, of the bean type parameter + */ + return parametersMatch((TypeVariable) requiredParameter, (TypeVariable) beanParameter); + } + return false; + } + + private static boolean parametersMatch(WildcardType requiredParameter, Type beanParameter) { + return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); + } + + private static boolean parametersMatch(WildcardType requiredParameter, TypeVariable beanParameter) { + Type[] beanParameterBounds = getUppermostTypeVariableBounds(beanParameter); + if (!lowerBoundsOfWildcardMatch(beanParameterBounds, requiredParameter)) { + return false; + } + + Type[] requiredUpperBounds = requiredParameter.getUpperBounds(); + // upper bound of the type variable is assignable to OR assignable from the upper bound of the wildcard + return (boundsMatch(requiredUpperBounds, beanParameterBounds) || boundsMatch(beanParameterBounds, requiredUpperBounds)); + } + + private static boolean parametersMatch(Type requiredParameter, TypeVariable beanParameter) { + for (Type bound : getUppermostTypeVariableBounds(beanParameter)) { + if (!CovariantTypes.isAssignableFrom(bound, requiredParameter)) { + return false; + } + } + return true; + } + + private static boolean parametersMatch(TypeVariable requiredParameter, TypeVariable beanParameter) { + return boundsMatch(getUppermostTypeVariableBounds(beanParameter), getUppermostTypeVariableBounds(requiredParameter)); + } + + /* + * TypeVariable bounds are treated specially - CDI assignability rules are applied. + * Standard Java covariant assignability rules are applied to all other types of bounds. + * This is not explicitly mentioned in the specification but is implied. + */ + private static Type[] getUppermostTypeVariableBounds(TypeVariable bound) { + if (bound.getBounds()[0] instanceof TypeVariable) { + return getUppermostTypeVariableBounds((TypeVariable) bound.getBounds()[0]); + } + return bound.getBounds(); + } + + private static Type[] getUppermostBounds(Type[] bounds) { + // if a type variable (or wildcard) declares a bound which is a type variable, it can declare no other bound + if (bounds[0] instanceof TypeVariable) { + return getUppermostTypeVariableBounds((TypeVariable) bounds[0]); + } + return bounds; + } + + /** + * Returns true iff for each upper bound T, there is at least one bound from stricterUpperBounds + * assignable to T. This reflects that stricterUpperBounds are at least as strict as upperBounds are. + *

+ * Arguments passed to this method must be legal java bounds, i.e. bounds returned by {@link TypeVariable#getBounds()}, + * {@link WildcardType#getUpperBounds()} or {@link WildcardType#getLowerBounds()}. + */ + private static boolean boundsMatch(Type[] upperBounds, Type[] stricterUpperBounds) { + // getUppermostBounds to make sure that both arrays of bounds contain ONLY ACTUAL TYPES! otherwise, the CovariantTypes + // assignability rules do not reflect our needs + upperBounds = getUppermostBounds(upperBounds); + stricterUpperBounds = getUppermostBounds(stricterUpperBounds); + for (Type upperBound : upperBounds) { + if (!CovariantTypes.isAssignableFromAtLeastOne(upperBound, stricterUpperBounds)) { + return false; + } + } + return true; + } + + private static boolean lowerBoundsOfWildcardMatch(Type parameter, WildcardType requiredParameter) { + return lowerBoundsOfWildcardMatch(new Type[] { parameter }, requiredParameter); + } + + private static boolean lowerBoundsOfWildcardMatch(Type[] beanParameterBounds, WildcardType requiredParameter) { + if (requiredParameter.getLowerBounds().length > 0) { + Type[] lowerBounds = requiredParameter.getLowerBounds(); + if (!boundsMatch(beanParameterBounds, lowerBounds)) { + return false; + } + } + return true; + } + + private static boolean upperBoundsOfWildcardMatch(WildcardType requiredParameter, Type parameter) { + return boundsMatch(requiredParameter.getUpperBounds(), new Type[] { parameter }); + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ClientProxy.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ClientProxy.java new file mode 100644 index 0000000000000..acf894ce6cfd8 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ClientProxy.java @@ -0,0 +1,11 @@ +package org.jboss.protean.arc; + +/** + * + * @author Martin Kouba + */ +public interface ClientProxy { + + Object getContextualInstance(); + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ComputingCache.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ComputingCache.java new file mode 100644 index 0000000000000..e94e06fa92871 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ComputingCache.java @@ -0,0 +1,83 @@ +package org.jboss.protean.arc; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Computing cache backed by a {@link ConcurrentHashMap} which intentionally does not use {@link Map#computeIfAbsent(Object, Function)} and is reentrant. + * Derived from {@code org.jboss.weld.util.cache.ReentrantMapBackedComputingCache}. + * + * @param + * @param + */ +public class ComputingCache { + + private final ConcurrentMap> map; + + private final Function> function; + + public ComputingCache(Function computingFunction) { + this.map = new ConcurrentHashMap<>(); + this.function = new CacheFunction(computingFunction); + } + + public V getValue(K key) { + LazyValue value = map.get(key); + if (value == null) { + value = function.apply(key); + LazyValue previous = map.putIfAbsent(key, value); + if (previous != null) { + value = previous; + } + } + return value.get(); + } + + public V remove(K key) { + LazyValue previous = map.remove(key); + return previous != null ? previous.get() : null; + } + + public void clear() { + map.clear(); + } + + public void forEachValue(Consumer action) { + Objects.requireNonNull(action); + for (LazyValue value : map.values()) { + action.accept(value.get()); + } + } + + public void forEachEntry(BiConsumer action) { + Objects.requireNonNull(action); + for (Map.Entry> entry : map.entrySet()) { + action.accept(entry.getKey(), entry.getValue().get()); + } + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + class CacheFunction implements Function> { + + private final Function computingFunction; + + public CacheFunction(Function computingFunction) { + this.computingFunction = computingFunction; + } + + @Override + public LazyValue apply(K key) { + return new LazyValue(() -> computingFunction.apply(key)); + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CovariantTypes.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CovariantTypes.java new file mode 100644 index 0000000000000..9494fd72aa8ca --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CovariantTypes.java @@ -0,0 +1,335 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; + +/** + * This code was mainly copied from Weld codebase. + * + * Utility class that captures standard covariant Java assignability rules. + * + * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, GenericArrayType. + * To make this class easier to understand and maintain, there is a separate isAssignableFrom method for each combination + * of possible types. Each of these methods compares two type instances and determines whether the first one is assignable from + * the other. + * + * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is performed. + * + * @author Jozef Hartinger + * + */ +class CovariantTypes { + + private CovariantTypes() { + } + + static boolean isAssignableFromAtLeastOne(Type type1, Type[] types2) { + for (Type type2 : types2) { + if (isAssignableFrom(type1, type2)) { + return true; + } + } + return false; + } + + static boolean isAssignableFrom(Type type1, Type type2) { + if (type1 instanceof Class) { + if (type2 instanceof Class) { + return isAssignableFrom((Class) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((Class) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((Class) type1, (TypeVariable) type2); + } + if (type2 instanceof WildcardType) { + return isAssignableFrom((Class) type1, (WildcardType) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((Class) type1, (GenericArrayType) type2); + } + throw InvariantTypes.unknownType(type2); + } + if (type1 instanceof ParameterizedType) { + if (type2 instanceof Class) { + return isAssignableFrom((ParameterizedType) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((ParameterizedType) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((ParameterizedType) type1, (TypeVariable) type2); + } + if (type2 instanceof WildcardType) { + return isAssignableFrom((ParameterizedType) type1, (WildcardType) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((ParameterizedType) type1, (GenericArrayType) type2); + } + throw InvariantTypes.unknownType(type2); + } + if (type1 instanceof TypeVariable) { + if (type2 instanceof Class) { + return isAssignableFrom((TypeVariable) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((TypeVariable) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((TypeVariable) type1, (TypeVariable) type2); + } + if (type2 instanceof WildcardType) { + return isAssignableFrom((TypeVariable) type1, (WildcardType) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((TypeVariable) type1, (GenericArrayType) type2); + } + throw InvariantTypes.unknownType(type2); + } + if (type1 instanceof WildcardType) { + if (Types.isActualType(type2)) { + return isAssignableFrom((WildcardType) type1, type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((WildcardType) type1, (TypeVariable) type2); + } + if (type2 instanceof WildcardType) { + return isAssignableFrom((WildcardType) type1, (WildcardType) type2); + } + throw InvariantTypes.unknownType(type2); + } + if (type1 instanceof GenericArrayType) { + if (type2 instanceof Class) { + return isAssignableFrom((GenericArrayType) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((GenericArrayType) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((GenericArrayType) type1, (TypeVariable) type2); + } + if (type2 instanceof WildcardType) { + return isAssignableFrom((GenericArrayType) type1, (WildcardType) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((GenericArrayType) type1, (GenericArrayType) type2); + } + throw InvariantTypes.unknownType(type2); + } + throw InvariantTypes.unknownType(type1); + } + + /* + * Raw type + */ + private static boolean isAssignableFrom(Class type1, Class type2) { + return Types.boxedClass(type1).isAssignableFrom(Types.boxedClass(type2)); + } + + private static boolean isAssignableFrom(Class type1, ParameterizedType type2) { + return type1.isAssignableFrom(Types.getRawType(type2)); + } + + private static boolean isAssignableFrom(Class type1, TypeVariable type2) { + for (Type type : type2.getBounds()) { + if (isAssignableFrom(type1, type)) { + return true; + } + } + return false; + } + + private static boolean isAssignableFrom(Class type1, WildcardType type2) { + return false; + } + + private static boolean isAssignableFrom(Class type1, GenericArrayType type2) { + return type1.equals(Object.class) || type1.isArray() + && isAssignableFrom(type1.getComponentType(), Types.getRawType(type2.getGenericComponentType())); + } + + /* + * ParameterizedType + */ + private static boolean isAssignableFrom(ParameterizedType type1, Class type2) { + Class rawType1 = Types.getRawType(type1); + + // raw types have to be assignable + if (!isAssignableFrom(rawType1, type2)) { + return false; + } + // this is a raw type with missing type arguments + if (!Types.getCanonicalType(type2).equals(type2)) { + return true; + } + + return matches(type1, new HierarchyDiscovery(type2)); + } + + private static boolean isAssignableFrom(ParameterizedType type1, ParameterizedType type2) { + // first, raw types have to be assignable + if (!isAssignableFrom(Types.getRawType(type1), Types.getRawType(type2))) { + return false; + } + if (matches(type1, type2)) { + return true; + } + return matches(type1, new HierarchyDiscovery(type2)); + } + + private static boolean matches(ParameterizedType type1, HierarchyDiscovery type2) { + for (Type type : type2.getTypeClosure()) { + if (type instanceof ParameterizedType && matches(type1, (ParameterizedType) type)) { + return true; + } + } + return false; + } + + private static boolean matches(ParameterizedType type1, ParameterizedType type2) { + final Class rawType1 = Types.getRawType(type1); + final Class rawType2 = Types.getRawType(type2); + + if (!rawType1.equals(rawType2)) { + return false; + } + + final Type[] types1 = type1.getActualTypeArguments(); + final Type[] types2 = type2.getActualTypeArguments(); + + if (types1.length != types2.length) { + throw new IllegalArgumentException("Invalida argument combination: " + type1 + " and " + type2); + } + for (int i = 0; i < type1.getActualTypeArguments().length; i++) { + // Generics are invariant + if (!InvariantTypes.isAssignableFrom(types1[i], types2[i])) { + return false; + } + } + return true; + } + + private static boolean isAssignableFrom(ParameterizedType type1, TypeVariable type2) { + for (Type type : type2.getBounds()) { + if (isAssignableFrom(type1, type)) { + return true; + } + } + return false; + } + + private static boolean isAssignableFrom(ParameterizedType type1, WildcardType type2) { + return false; + } + + private static boolean isAssignableFrom(ParameterizedType type1, GenericArrayType type2) { + return false; + } + + /* + * Type variable + */ + private static boolean isAssignableFrom(TypeVariable type1, Class type2) { + return false; + } + + private static boolean isAssignableFrom(TypeVariable type1, ParameterizedType type2) { + return false; + } + + /** + * Returns true if type2 is a "sub-variable" of type1, i.e. if they are equal or if + * type2 (transitively) extends type1. + */ + private static boolean isAssignableFrom(TypeVariable type1, TypeVariable type2) { + if (type1.equals(type2)) { + return true; + } + // if a type variable extends another type variable, it cannot declare other bounds + if (type2.getBounds()[0] instanceof TypeVariable) { + return isAssignableFrom(type1, (TypeVariable) type2.getBounds()[0]); + } + return false; + } + + private static boolean isAssignableFrom(TypeVariable type1, WildcardType type2) { + return false; + } + + private static boolean isAssignableFrom(TypeVariable type1, GenericArrayType type2) { + return false; + } + + /* + * Wildcard + */ + + /** + * This logic is shared for all actual types i.e. raw types, parameterized types and generic array types. + */ + private static boolean isAssignableFrom(WildcardType type1, Type type2) { + if (!isAssignableFrom(type1.getUpperBounds()[0], type2)) { + return false; + } + if (type1.getLowerBounds().length > 0 && !isAssignableFrom(type2, type1.getLowerBounds()[0])) { + return false; + } + return true; + } + + private static boolean isAssignableFrom(WildcardType type1, TypeVariable type2) { + if (type1.getLowerBounds().length > 0) { + return isAssignableFrom(type2, type1.getLowerBounds()[0]); + } + return isAssignableFrom(type1.getUpperBounds()[0], type2); + } + + private static boolean isAssignableFrom(WildcardType type1, WildcardType type2) { + if (!isAssignableFrom(type1.getUpperBounds()[0], type2.getUpperBounds()[0])) { + return false; + } + + if (type1.getLowerBounds().length > 0) { + // the first type defines a lower bound + if (type2.getLowerBounds().length > 0) { + return isAssignableFrom(type2.getLowerBounds()[0], type1.getLowerBounds()[0]); + } else { + return false; + } + } else if (type2.getLowerBounds().length > 0) { + // only the second type defines a lower bound + return type1.getUpperBounds()[0].equals(Object.class); + } + return true; + } + + /* + * GenericArrayType + */ + private static boolean isAssignableFrom(GenericArrayType type1, Class type2) { + return type2.isArray() && isAssignableFrom(Types.getRawType(type1.getGenericComponentType()), type2.getComponentType()); + } + + private static boolean isAssignableFrom(GenericArrayType type1, ParameterizedType type2) { + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, TypeVariable type2) { + /* + * JLS does not allow array types to be used as bounds of type variables + */ + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, WildcardType type2) { + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, GenericArrayType type2) { + return isAssignableFrom(type1.getGenericComponentType(), type2.getGenericComponentType()); + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java new file mode 100644 index 0000000000000..7da95a24f89fa --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CreationalContextImpl.java @@ -0,0 +1,74 @@ +package org.jboss.protean.arc; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.enterprise.context.spi.CreationalContext; + +/** + * + * @author Martin Kouba + * + * @param + */ +public class CreationalContextImpl implements CreationalContext { + + private final CreationalContextImpl parent; + + private final List> dependentInstances; + + public CreationalContextImpl() { + this(null); + } + + public CreationalContextImpl(CreationalContextImpl parent) { + this.parent = parent; + this.dependentInstances = new CopyOnWriteArrayList<>(); + } + + public void addDependentInstance(InjectableBean bean, I instance, CreationalContext ctx) { + dependentInstances.add(new InstanceHandleImpl(bean, instance, ctx)); + } + + @Override + public void push(T incompleteInstance) { + // No-op + } + + @Override + public void release() { + synchronized (dependentInstances) { + for (InstanceHandle instance : dependentInstances) { + instance.release(); + } + } + } + + public CreationalContextImpl getParent() { + return parent; + } + + public CreationalContextImpl child() { + return new CreationalContextImpl<>(this); + } + + public static CreationalContextImpl unwrap(CreationalContext ctx) { + if (ctx instanceof CreationalContextImpl) { + return (CreationalContextImpl) ctx; + } else { + throw new IllegalArgumentException("Failed to unwrap CreationalContextImpl: "+ ctx); + } + } + + public static CreationalContextImpl child(CreationalContext creationalContext) { + return unwrap(creationalContext).child(); + } + + public static void addDependencyToParent(InjectableBean bean, I instance, CreationalContext ctx) { + CreationalContextImpl parent = unwrap(ctx).getParent(); + if (parent != null) { + parent.addDependentInstance(bean, instance, ctx); + } + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CurrentInjectionPointProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CurrentInjectionPointProvider.java new file mode 100644 index 0000000000000..4b4c0a9a046c5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/CurrentInjectionPointProvider.java @@ -0,0 +1,94 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Annotated; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.InjectionPoint; + +/** + * + * @author Martin Kouba + */ +public class CurrentInjectionPointProvider implements InjectableReferenceProvider { + + static final InjectionPoint EMPTY = new InjectionPointImpl(Object.class, Collections.emptySet()); + + private final InjectableReferenceProvider delegate; + + private final InjectionPoint injectionPoint; + + public CurrentInjectionPointProvider(InjectableReferenceProvider delegate, Type requiredType, Set qualifiers) { + this.delegate = delegate; + this.injectionPoint = new InjectionPointImpl(requiredType, qualifiers); + } + + @Override + public T get(CreationalContext creationalContext) { + InjectionPoint prev = InjectionPointProvider.CURRENT.get(); + InjectionPointProvider.CURRENT.set(injectionPoint); + try { + return delegate.get(creationalContext); + } finally { + if (prev != null) { + InjectionPointProvider.CURRENT.set(prev); + } else { + InjectionPointProvider.CURRENT.remove(); + } + } + } + + private static class InjectionPointImpl implements InjectionPoint { + + private final Type requiredType; + + private final Set qualifiers; + + InjectionPointImpl(Type requiredType, Set qualifiers) { + this.requiredType = requiredType; + this.qualifiers = qualifiers; + } + + @Override + public Type getType() { + return requiredType; + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public Bean getBean() { + return null; + } + + @Override + public Member getMember() { + throw new UnsupportedOperationException(); + } + + @Override + public Annotated getAnnotated() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDelegate() { + return false; + } + + @Override + public boolean isTransient() { + return false; + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/GenericArrayTypeImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/GenericArrayTypeImpl.java new file mode 100644 index 0000000000000..46fffd4a9ac8a --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/GenericArrayTypeImpl.java @@ -0,0 +1,52 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; + +/** + * @author Marko Luksa + * @author Jozef Hartinger + */ +public class GenericArrayTypeImpl implements GenericArrayType { + + private Type genericComponentType; + + public GenericArrayTypeImpl(Type genericComponentType) { + this.genericComponentType = genericComponentType; + } + + public GenericArrayTypeImpl(Class rawType, Type... actualTypeArguments) { + this.genericComponentType = new ParameterizedTypeImpl(rawType, actualTypeArguments); + } + + public Type getGenericComponentType() { + return genericComponentType; + } + + @Override + public int hashCode() { + return ((genericComponentType == null) ? 0 : genericComponentType.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof GenericArrayType) { + GenericArrayType that = (GenericArrayType) obj; + if (genericComponentType == null) { + return that.getGenericComponentType() == null; + } else { + return genericComponentType.equals(that.getGenericComponentType()); + } + } else { + return false; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(genericComponentType.toString()); + sb.append("[]"); + return sb.toString(); + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/HierarchyDiscovery.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/HierarchyDiscovery.java new file mode 100644 index 0000000000000..03d455637ecf0 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/HierarchyDiscovery.java @@ -0,0 +1,148 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * This code was mainly copied from Weld codebase. + * + * Utility class that discovers transitive type closure of a given type. + * + * @author Weld Community + * @author Ales Justin + * @author Marko Luksa + * @author Jozef Hartinger + */ +class HierarchyDiscovery { + + private final Map, Type> types; + private final Map, Type> resolvedTypeVariables; + private final TypeResolver resolver; + private final Set typeClosure; + + /** + * Constructs a new {@link HierarchyDiscovery} instance. + * @param type the type whose hierarchy will be discovered + */ + HierarchyDiscovery(Type type) { + this(type, new TypeResolver(new HashMap, Type>())); + } + + HierarchyDiscovery(Type type, TypeResolver resolver) { + this.types = new HashMap, Type>(); + this.resolver = resolver; + this.resolvedTypeVariables = resolver.getResolvedTypeVariables(); + discoverTypes(type, false); + this.typeClosure = new HashSet<>(types.values()); + } + + public Set getTypeClosure() { + return typeClosure; + } + + public Map, Type> getTypeMap() { + return types; + } + + protected void discoverTypes(Type type, boolean rawGeneric) { + if (!rawGeneric) { + rawGeneric = Types.isRawGenericType(type); + } + if (type instanceof Class) { + Class clazz = (Class) type; + this.types.put(clazz, clazz); + discoverFromClass(clazz, rawGeneric); + } else if (rawGeneric) { + discoverTypes(Types.getRawType(type), rawGeneric); + } else if (type instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) type; + Type genericComponentType = arrayType.getGenericComponentType(); + Class rawComponentType = Types.getRawType(genericComponentType); + if (rawComponentType != null) { + Class arrayClass = Array.newInstance(rawComponentType, 0).getClass(); + this.types.put(arrayClass, type); + discoverFromClass(arrayClass, rawGeneric); + } + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = (parameterizedType).getRawType(); + if (rawType instanceof Class) { + Class clazz = (Class) rawType; + processTypeVariables(clazz.getTypeParameters(), parameterizedType.getActualTypeArguments()); + this.types.put(clazz, type); + discoverFromClass(clazz, rawGeneric); + } + } + } + + protected void discoverFromClass(Class clazz, boolean rawGeneric) { + if (clazz.getSuperclass() != null) { + discoverTypes(processAndResolveType(clazz.getGenericSuperclass(), clazz.getSuperclass()), rawGeneric); + } + discoverInterfaces(clazz, rawGeneric); + } + + protected void discoverInterfaces(Class clazz, boolean rawGeneric) { + Type[] genericInterfaces = clazz.getGenericInterfaces(); + Class[] interfaces = clazz.getInterfaces(); + if (genericInterfaces.length == interfaces.length) { + // this branch should execute every time! + for (int i = 0; i < interfaces.length; i++) { + discoverTypes(processAndResolveType(genericInterfaces[i], interfaces[i]), rawGeneric); + } + } + } + + protected Type processAndResolveType(Type superclass, Class rawSuperclass) { + if (superclass instanceof ParameterizedType) { + ParameterizedType parameterizedSuperclass = (ParameterizedType) superclass; + processTypeVariables(rawSuperclass.getTypeParameters(), parameterizedSuperclass.getActualTypeArguments()); + return resolveType(parameterizedSuperclass); + } else if (superclass instanceof Class) { + // this is not a parameterized type, nothing to resolve + return superclass; + } + throw new RuntimeException("Unexpected type: " + superclass); + } + + /* + * Processing part. Every type variable is mapped to the actual type in the resolvedTypeVariablesMap. This map is used later + * on for resolving types. + */ + private void processTypeVariables(TypeVariable[] variables, Type[] values) { + for (int i = 0; i < variables.length; i++) { + processTypeVariable(variables[i], values[i]); + } + } + + private void processTypeVariable(TypeVariable variable, Type value) { + if (value instanceof TypeVariable) { + value = resolveType(value); + } + this.resolvedTypeVariables.put(variable, value); + } + + /* + * Resolving part. Using resolvedTypeVariables map which was prepared in the processing part. + */ + public Type resolveType(Type type) { + if (type instanceof Class) { + Type resolvedType = types.get(type); + if (resolvedType != null) { + return resolvedType; + } + } + return resolver.resolveType(type); + } + + public TypeResolver getResolver() { + return resolver; + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java new file mode 100644 index 0000000000000..04485f4d5750d --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InitializedInterceptor.java @@ -0,0 +1,82 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +/** + * + * @author Martin Kouba + * + * @param + */ +public class InitializedInterceptor implements InjectableInterceptor { + + public static InitializedInterceptor of(I interceptorInstance, InjectableInterceptor delegate) { + return new InitializedInterceptor<>(interceptorInstance, delegate); + } + + private final T interceptorInstance; + + private final InjectableInterceptor delegate; + + InitializedInterceptor(T interceptorInstance, InjectableInterceptor delegate) { + this.interceptorInstance = interceptorInstance; + this.delegate = delegate; + } + + @Override + public Class getScope() { + return delegate.getScope(); + } + + @Override + public Set getTypes() { + return delegate.getTypes(); + } + + @Override + public Set getQualifiers() { + return delegate.getQualifiers(); + } + + @Override + public T create(CreationalContext creationalContext) { + return delegate.create(creationalContext); + } + + @Override + public void destroy(T instance, CreationalContext creationalContext) { + delegate.destroy(instance, creationalContext); + } + + @Override + public T get(CreationalContext creationalContext) { + return interceptorInstance; + } + + @Override + public Set getInterceptorBindings() { + return delegate.getInterceptorBindings(); + } + + @Override + public boolean intercepts(InterceptionType type) { + return delegate.intercepts(type); + } + + @Override + public Object intercept(InterceptionType type, T instance, InvocationContext ctx) throws Exception { + return delegate.intercept(type, instance, ctx); + } + + @Override + public int getPriority() { + return delegate.getPriority(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java new file mode 100644 index 0000000000000..8eec97635f599 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableBean.java @@ -0,0 +1,46 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.enterprise.context.Dependent; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; + +/** + * + * @author Martin Kouba + * + * @param + */ +public interface InjectableBean extends Contextual, InjectableReferenceProvider { + + /** + * + * @return the scope + */ + default Class getScope() { + return Dependent.class; + } + + /** + * + * @return the set of bean type + */ + Set getTypes(); + + /** + * + * @return the set of qualifiers + */ + default Set getQualifiers() { + return Qualifiers.DEFAULT_QUALIFIERS; + } + + @Override + default void destroy(T instance, CreationalContext creationalContext) { + creationalContext.release(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableInterceptor.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableInterceptor.java new file mode 100644 index 0000000000000..1ed363ae4de91 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableInterceptor.java @@ -0,0 +1,25 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +/** + * + * @author Martin Kouba + * + * @param + */ +public interface InjectableInterceptor extends InjectableBean { + + Set getInterceptorBindings(); + + boolean intercepts(InterceptionType type); + + Object intercept(InterceptionType type, T instance, InvocationContext ctx) throws Exception; + + int getPriority(); + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableReferenceProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableReferenceProvider.java new file mode 100644 index 0000000000000..7e2932e7d0574 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectableReferenceProvider.java @@ -0,0 +1,20 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.spi.CreationalContext; + +/** + * + * @author Martin Kouba + * + * @param + */ +public interface InjectableReferenceProvider { + + /** + * + * @param creationalContext + * @return a contextual reference + */ + T get(CreationalContext creationalContext); + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectionPointProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectionPointProvider.java new file mode 100644 index 0000000000000..11082eefe57f9 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InjectionPointProvider.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.InjectionPoint; + +/** + * + * @author Martin Kouba + */ +public class InjectionPointProvider implements InjectableReferenceProvider { + + static final ThreadLocal CURRENT = new ThreadLocal<>(); + + @Override + public InjectionPoint get(CreationalContext creationalContext) { + return CURRENT.get(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java new file mode 100644 index 0000000000000..fab2d28fafdd5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandle.java @@ -0,0 +1,21 @@ +package org.jboss.protean.arc; + +/** + * + * @author Martin Kouba + * + * @param + */ +public interface InstanceHandle extends AutoCloseable { + + boolean isAvailable(); + + T get(); + + void release(); + + default void close() { + release(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java new file mode 100644 index 0000000000000..51299120262e4 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceHandleImpl.java @@ -0,0 +1,72 @@ +package org.jboss.protean.arc; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.spi.AlterableContext; +import javax.enterprise.context.spi.CreationalContext; +import javax.inject.Singleton; + +/** + * + * @author Martin Kouba + * + * @param + */ +class InstanceHandleImpl implements InstanceHandle { + + @SuppressWarnings("unchecked") + public static final InstanceHandle unresolvable() { + return (InstanceHandle) UNRESOLVABLE; + } + + static final InstanceHandleImpl UNRESOLVABLE = new InstanceHandleImpl(null, null, null, null); + + private final InjectableBean bean; + + private final T instance; + + private final CreationalContext creationalContext; + + private final CreationalContext parentCreationalContext; + + InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext) { + this(bean, instance, creationalContext, null); + } + + InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext, CreationalContext parentCreationalContext) { + this.bean = bean; + this.instance = instance; + this.creationalContext = creationalContext; + this.parentCreationalContext = parentCreationalContext; + } + + @Override + public boolean isAvailable() { + return instance != null; + } + + @Override + public T get() { + return instance; + } + + @Override + public void release() { + if (isAvailable()) { + if (bean.getScope().equals(ApplicationScoped.class) || bean.getScope().equals(RequestScoped.class) || bean.getScope().equals(Singleton.class)) { + ((AlterableContext) Arc.container().getContext(bean.getScope())).destroy(bean); + } else { + destroy(); + } + } + } + + void destroy() { + if (parentCreationalContext != null) { + parentCreationalContext.release(); + } else { + bean.destroy(instance, creationalContext); + } + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java new file mode 100644 index 0000000000000..6e5b184c6c59d --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceImpl.java @@ -0,0 +1,136 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.AmbiguousResolutionException; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.enterprise.util.TypeLiteral; + +/** + * + * @author Martin Kouba + */ +class InstanceImpl implements Instance { + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[] {}; + + private final Type type; + + private final Set qualifiers; + + private final CreationalContextImpl creationalContext; + + InstanceImpl(Type type, Set qualifiers, CreationalContextImpl creationalContext) { + if (type instanceof ParameterizedType) { + this.type = ((ParameterizedType) type).getActualTypeArguments()[0]; + } else { + throw new IllegalArgumentException(); + } + this.qualifiers = qualifiers != null ? qualifiers : Collections.emptySet(); + this.creationalContext = creationalContext; + } + + @Override + public Iterator iterator() { + return new InstanceIterator(getBeans()); + } + + @SuppressWarnings("unchecked") + @Override + public T get() { + List> beans = getBeans(); + if (beans.isEmpty()) { + throw new UnsatisfiedResolutionException(); + } else if (beans.size() > 1) { + throw new AmbiguousResolutionException(); + } + return getBeanInstance((InjectableBean) beans.get(0)); + } + + @Override + public Instance select(Annotation... qualifiers) { + Set newQualifiers = new HashSet<>(this.qualifiers); + Collections.addAll(newQualifiers, qualifiers); + return new InstanceImpl<>(type, newQualifiers, creationalContext); + } + + @Override + public Instance select(Class subtype, Annotation... qualifiers) { + Set newQualifiers = new HashSet<>(this.qualifiers); + Collections.addAll(newQualifiers, qualifiers); + return new InstanceImpl<>(type, newQualifiers, creationalContext); + } + + @Override + public Instance select(TypeLiteral subtype, Annotation... qualifiers) { + Set newQualifiers = new HashSet<>(this.qualifiers); + Collections.addAll(newQualifiers, qualifiers); + return new InstanceImpl<>(subtype.getType(), newQualifiers, creationalContext); + } + + @Override + public boolean isUnsatisfied() { + return getBeans().isEmpty(); + } + + @Override + public boolean isAmbiguous() { + return getBeans().size() > 1; + } + + @Override + public void destroy(T instance) { + throw new UnsupportedOperationException(); + } + + private T getBeanInstance(InjectableBean bean) { + CreationalContextImpl ctx = creationalContext.child(); + // TODO current injection point? + T instance = bean.get(ctx); + if (Dependent.class.equals(bean.getScope())) { + creationalContext.addDependentInstance(bean, instance, ctx); + } + return instance; + } + + private List> getBeans() { + return ArcContainerImpl.unwrap(Arc.container()).geBeans(type, qualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); + } + + class InstanceIterator implements Iterator { + + protected final Iterator> delegate; + + private InstanceIterator(Collection> beans) { + this.delegate = beans.iterator(); + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public T next() { + return getBeanInstance((InjectableBean) delegate.next()); + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceProvider.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceProvider.java new file mode 100644 index 0000000000000..cf4e9d13f2c69 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InstanceProvider.java @@ -0,0 +1,30 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Instance; + +/** + * + * @author Martin Kouba + */ +public class InstanceProvider implements InjectableReferenceProvider> { + + private final Type requiredType; + + private final Set qualifiers; + + public InstanceProvider(Type type, Set qualifiers) { + this.requiredType = type; + this.qualifiers = qualifiers; + } + + @Override + public Instance get(CreationalContext> creationalContext) { + return new InstanceImpl(requiredType, qualifiers, CreationalContextImpl.unwrap(creationalContext)); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvariantTypes.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvariantTypes.java new file mode 100644 index 0000000000000..b934f68217a46 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvariantTypes.java @@ -0,0 +1,191 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; + +/** + * This code was mainly copied from Weld codebase. + * + * Utility class that captures invariant Java assignability rules (used with generics). + * + * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, GenericArrayType. To make this class easier to + * understand and maintain, there is a separate isAssignableFrom method for each combination of possible types. Each of these methods compares two type + * instances and determines whether the first one is assignable from the other. + * + * Since Java wildcards are by definition covariant, this class does not operate on wildcards and instead delegates to {@link CovariantTypes}. + * + * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is performed. + * + * @author Jozef Hartinger + * + */ +class InvariantTypes { + + private InvariantTypes() { + } + + static boolean isAssignableFrom(Type type1, Type type2) { + if (type1 instanceof WildcardType || type2 instanceof WildcardType) { + // Wildcards are by definition covariant + return CovariantTypes.isAssignableFrom(type1, type2); + } + if (type1 instanceof Class) { + if (type2 instanceof Class) { + return isAssignableFrom((Class) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((Class) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((Class) type1, (TypeVariable) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((Class) type1, (GenericArrayType) type2); + } + throw unknownType(type2); + } + if (type1 instanceof ParameterizedType) { + if (type2 instanceof Class) { + return isAssignableFrom((ParameterizedType) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((ParameterizedType) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((ParameterizedType) type1, (TypeVariable) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((ParameterizedType) type1, (GenericArrayType) type2); + } + throw unknownType(type2); + } + if (type1 instanceof TypeVariable) { + if (type2 instanceof Class) { + return isAssignableFrom((TypeVariable) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((TypeVariable) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((TypeVariable) type1, (TypeVariable) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((TypeVariable) type1, (GenericArrayType) type2); + } + throw unknownType(type2); + } + if (type1 instanceof GenericArrayType) { + if (type2 instanceof Class) { + return isAssignableFrom((GenericArrayType) type1, (Class) type2); + } + if (type2 instanceof ParameterizedType) { + return isAssignableFrom((GenericArrayType) type1, (ParameterizedType) type2); + } + if (type2 instanceof TypeVariable) { + return isAssignableFrom((GenericArrayType) type1, (TypeVariable) type2); + } + if (type2 instanceof GenericArrayType) { + return isAssignableFrom((GenericArrayType) type1, (GenericArrayType) type2); + } + throw unknownType(type2); + } + throw unknownType(type1); + } + + static IllegalArgumentException unknownType(Type type) { + return new IllegalArgumentException("Unknown type: " + type); + } + + /* + * Raw type + */ + private static boolean isAssignableFrom(Class type1, Class type2) { + return Types.boxedClass(type1).equals(Types.boxedClass(type2)); + } + + private static boolean isAssignableFrom(Class type1, ParameterizedType type2) { + return false; + } + + private static boolean isAssignableFrom(Class type1, TypeVariable type2) { + return false; + } + + private static boolean isAssignableFrom(Class type1, GenericArrayType type2) { + return false; + } + + /* + * ParameterizedType + */ + private static boolean isAssignableFrom(ParameterizedType type1, Class type2) { + return false; + } + + private static boolean isAssignableFrom(ParameterizedType type1, ParameterizedType type2) { + // first, raw types have to be equal + if (!Types.getRawType(type1).equals(Types.getRawType(type2))) { + return false; + } + final Type[] types1 = type1.getActualTypeArguments(); + final Type[] types2 = type2.getActualTypeArguments(); + if (types1.length != types2.length) { + throw new IllegalArgumentException("Invalida argument combination: " + type1 + " and " + type2); + } + for (int i = 0; i < types1.length; i++) { + if (!isAssignableFrom(types1[i], types2[i])) { + return false; + } + } + return true; + } + + private static boolean isAssignableFrom(ParameterizedType type1, TypeVariable type2) { + return false; + } + + private static boolean isAssignableFrom(ParameterizedType type1, GenericArrayType type2) { + return false; + } + + /* + * Type variable + */ + private static boolean isAssignableFrom(TypeVariable type1, Class type2) { + return false; + } + + private static boolean isAssignableFrom(TypeVariable type1, ParameterizedType type2) { + return false; + } + + private static boolean isAssignableFrom(TypeVariable type1, TypeVariable type2) { + return type1.equals(type2); + } + + private static boolean isAssignableFrom(TypeVariable type1, GenericArrayType type2) { + return false; + } + + /* + * GenericArrayType + */ + private static boolean isAssignableFrom(GenericArrayType type1, Class type2) { + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, ParameterizedType type2) { + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, TypeVariable type2) { + return false; + } + + private static boolean isAssignableFrom(GenericArrayType type1, GenericArrayType type2) { + return isAssignableFrom(type1.getGenericComponentType(), type2.getGenericComponentType()); + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvocationContextImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvocationContextImpl.java new file mode 100644 index 0000000000000..e1df08f3fa9f5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/InvocationContextImpl.java @@ -0,0 +1,233 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +/** + * + * @author Martin Kouba + */ +public class InvocationContextImpl implements InvocationContext { + + /** + * + * @param target + * @param method + * @param args + * @param chain + * @param aroundInvokeForward + * @return a new {@link javax.interceptor.AroundInvoke} invocation context + */ + public static InvocationContextImpl aroundInvoke(Object target, Method method, Object[] args, List chain, + Function aroundInvokeForward) { + return new InvocationContextImpl(target, method, null, args, chain, aroundInvokeForward, null); + } + + /** + * + * @param target + * @param chain + * @return a new {@link javax.annotation.PostConstruct} invocation context + */ + public static InvocationContextImpl postConstruct(Object target, List chain) { + return new InvocationContextImpl(target, null, null, null, chain, null, null); + } + + /** + * + * @param target + * @param chain + * @return a new {@link javax.annotation.PreDestroy} invocation context + */ + public static InvocationContextImpl preDestroy(Object target, List chain) { + return new InvocationContextImpl(target, null, null, null, chain, null, null); + } + + /** + * + * @param target + * @param chain + * @return a new {@link javax.interceptor.AroundConstruct} invocation context + */ + public static InvocationContextImpl aroundConstruct(Constructor constructor, List chain, + Supplier aroundConstructForward) { + return new InvocationContextImpl(null, null, constructor, null, chain, null, aroundConstructForward); + } + + private final AtomicReference target; + + private final Method method; + + private final Constructor constructor; + + private Object[] args; + + private int position; + + private final Map contextData; + + private final List chain; + + private final Function aroundInvokeForward; + + private final Supplier aroundConstructForward; + + /** + * @param target + * @param method + * @param args + * @param chain + * @param aroundInvokeForward + */ + InvocationContextImpl(Object target, Method method, Constructor constructor, Object[] args, List chain, + Function aroundInvokeForward, Supplier aroundConstructForward) { + this.target = new AtomicReference<>(target); + this.method = method; + this.constructor = constructor; + this.args = args; + this.contextData = new HashMap<>(); + this.position = 0; + this.chain = chain; + this.aroundInvokeForward = aroundInvokeForward; + this.aroundConstructForward = aroundConstructForward; + } + + boolean hasNextInterceptor() { + return position < chain.size(); + } + + protected Object invokeNext() throws Exception { + int oldPosition = position; + try { + return chain.get(position++).invoke(this); + } finally { + position = oldPosition; + } + } + + protected Object interceptorChainCompleted() throws Exception { + if (aroundInvokeForward != null) { + return aroundInvokeForward.apply(this); + } + if (aroundConstructForward != null) { + target.set(aroundConstructForward.get()); + } + return null; + } + + @Override + public Object proceed() throws Exception { + try { + if (hasNextInterceptor()) { + if (aroundConstructForward != null) { + invokeNext(); + return target.get(); + } else { + return invokeNext(); + } + } else { + // The last interceptor in chain called proceed() + return interceptorChainCompleted(); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } + } + + @Override + public Object getTarget() { + return target.get(); + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Constructor getConstructor() { + return constructor; + } + + @Override + public Object[] getParameters() throws IllegalStateException { + if (args == null) { + throw new IllegalStateException(); + } + return args; + } + + @Override + public void setParameters(Object[] params) throws IllegalStateException, IllegalArgumentException { + if (args == null) { + throw new IllegalStateException(); + } + this.args = params; + } + + @Override + public Map getContextData() { + return contextData; + } + + @Override + public Object getTimer() { + return null; + } + + public static class InterceptorInvocation { + + public static InterceptorInvocation aroundInvoke(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.AROUND_INVOKE, interceptor, interceptorInstance); + } + + public static InterceptorInvocation postConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.POST_CONSTRUCT, interceptor, interceptorInstance); + } + + public static InterceptorInvocation preDestroy(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.PRE_DESTROY, interceptor, interceptorInstance); + } + + public static InterceptorInvocation aroundConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.AROUND_CONSTRUCT, interceptor, interceptorInstance); + } + + private final InterceptionType interceptionType; + + @SuppressWarnings("rawtypes") + private final InjectableInterceptor interceptor; + + private final Object interceptorInstance; + + InterceptorInvocation(InterceptionType interceptionType, InjectableInterceptor interceptor, Object interceptorInstance) { + this.interceptionType = interceptionType; + this.interceptor = interceptor; + this.interceptorInstance = interceptorInstance; + } + + @SuppressWarnings("unchecked") + Object invoke(InvocationContext ctx) throws Exception { + return interceptor.intercept(interceptionType, interceptorInstance, ctx); + } + + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/LazyValue.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/LazyValue.java new file mode 100644 index 0000000000000..2a3c883d8275d --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/LazyValue.java @@ -0,0 +1,42 @@ +package org.jboss.protean.arc; + +import java.util.function.Supplier; + +/** + * + * @author Martin Kouba + */ +public class LazyValue { + + private final Supplier supplier; + + private transient volatile T value; + + public LazyValue(Supplier supplier) { + this.supplier = supplier; + } + + public T get() { + T valueCopy = value; + if (valueCopy != null) { + return valueCopy; + } + synchronized (this) { + if (value == null) { + value = supplier.get(); + } + return value; + } + } + + public void clear() { + synchronized (this) { + value = null; + } + } + + public boolean isSet() { + return value != null; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ParameterizedTypeImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ParameterizedTypeImpl.java new file mode 100644 index 0000000000000..11563e7b41f3d --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/ParameterizedTypeImpl.java @@ -0,0 +1,83 @@ +package org.jboss.protean.arc; + +import java.io.Serializable; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; + +public class ParameterizedTypeImpl implements ParameterizedType, Serializable { + + private static final long serialVersionUID = -3005183010706452884L; + + private final Type[] actualTypeArguments; + private final Type rawType; + private final Type ownerType; + + public ParameterizedTypeImpl(Type rawType, Type... actualTypeArguments) { + this(rawType, actualTypeArguments, null); + } + + public ParameterizedTypeImpl(Type rawType, Type[] actualTypeArguments, Type ownerType) { + this.actualTypeArguments = actualTypeArguments; + this.rawType = rawType; + this.ownerType = ownerType; + } + + public Type[] getActualTypeArguments() { + return Arrays.copyOf(actualTypeArguments, actualTypeArguments.length); + } + + public Type getOwnerType() { + return ownerType; + } + + public Type getRawType() { + return rawType; + } + + @Override + public int hashCode() { + return Arrays.hashCode(actualTypeArguments) ^ (ownerType == null ? 0 : ownerType.hashCode()) ^ (rawType == null ? 0 : rawType.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ParameterizedType) { + ParameterizedType that = (ParameterizedType) obj; + Type thatOwnerType = that.getOwnerType(); + Type thatRawType = that.getRawType(); + return (ownerType == null ? thatOwnerType == null : ownerType.equals(thatOwnerType)) + && (rawType == null ? thatRawType == null : rawType.equals(thatRawType)) + && Arrays.equals(actualTypeArguments, that.getActualTypeArguments()); + } else { + return false; + } + + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (rawType instanceof Class) { + sb.append(((Class) rawType).getName()); + } else { + sb.append(rawType); + } + if (actualTypeArguments.length > 0) { + sb.append("<"); + for (Type actualType : actualTypeArguments) { + if (actualType instanceof Class) { + sb.append(((Class) actualType).getName()); + } else { + sb.append(actualType); + } + sb.append(","); + } + sb.delete(sb.length() - 1, sb.length()); + sb.append(">"); + } + return sb.toString(); + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Qualifiers.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Qualifiers.java new file mode 100644 index 0000000000000..9248c7549ca8f --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Qualifiers.java @@ -0,0 +1,70 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.enterprise.util.Nonbinding; + +public final class Qualifiers { + + public static final Set DEFAULT_QUALIFIERS = initDefaultQualifiers(); + + private Qualifiers() { + } + + static boolean hasQualifiers(InjectableBean bean, Annotation... requiredQualifiers) { + for (Annotation qualifier : requiredQualifiers) { + if (!hasQualifier(bean, qualifier)) { + return false; + } + } + return true; + } + + static boolean hasQualifier(InjectableBean bean, Annotation requiredQualifier) { + + Class requiredQualifierClass = requiredQualifier.annotationType(); + Method[] members = requiredQualifierClass.getDeclaredMethods(); + + for (Annotation qualifier : bean.getQualifiers()) { + Class qualifierClass = qualifier.annotationType(); + if (qualifierClass.equals(requiredQualifier.annotationType())) { + boolean matches = true; + for (Method value : members) { + if (!value.isAnnotationPresent(Nonbinding.class) && !invoke(value, requiredQualifier).equals(invoke(value, qualifier))) { + matches = false; + break; + } + } + if (matches) { + return true; + } + } + } + return false; + } + + private static Set initDefaultQualifiers() { + Set qualifiers = new HashSet<>(); + qualifiers.add(Default.Literal.INSTANCE); + qualifiers.add(Any.Literal.INSTANCE); + return qualifiers; + } + + private static Object invoke(Method method, Object instance) { + try { + if (!method.isAccessible()) { + method.setAccessible(true); + } + return method.invoke(instance); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Error checking value of member method " + method.getName() + " on " + method.getDeclaringClass(), e); + } + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Reflections.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Reflections.java new file mode 100644 index 0000000000000..5e4d709bc9cdc --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Reflections.java @@ -0,0 +1,98 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * TODO security + * + * @author Martin Kouba + */ +public final class Reflections { + + private Reflections() { + } + + public static Method findMethod(Class clazz, String methodName, Class... parameterTypes) { + try { + return clazz.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null) { + return findMethod(clazz.getSuperclass(), methodName, parameterTypes); + } + throw new IllegalArgumentException(e); + } + } + + public static Constructor findConstructor(Class clazz, Class... parameterTypes) { + try { + return clazz.getDeclaredConstructor(parameterTypes); + } catch (NoSuchMethodException e) { + if (clazz.getSuperclass() != null) { + return findConstructor(clazz.getSuperclass(), parameterTypes); + } + throw new IllegalArgumentException(e); + } + } + + public static Object newInstance(Class clazz, Class[] parameterTypes, Object[] args) { + Constructor constructor = findConstructor(clazz, parameterTypes); + if (constructor != null) { + if (!constructor.isAccessible()) { + constructor.setAccessible(true); + } + try { + return constructor.newInstance(args); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("Cannot invoke constructor: " + clazz.getName(), e); + } + } + throw new RuntimeException("No " + clazz.getName() + "constructor found for params: " + Arrays.toString(parameterTypes)); + } + + public static Object readField(Class clazz, String name, Object instance) { + try { + Field field = clazz.getDeclaredField(name); + if (!field.isAccessible()) { + field.setAccessible(true); + } + return field.get(instance); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("Cannot read field value: " + clazz.getName() + "#" + name, e); + } + } + + public static void writeField(Class clazz, String name, Object instance, Object value) { + try { + Field field = clazz.getDeclaredField(name); + if (!field.isAccessible()) { + field.setAccessible(true); + } + field.set(instance, value); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("Cannot set field value: " + clazz.getName() + "#" + name, e); + } + } + + public static Object invokeMethod(Class clazz, String name, Class[] paramTypes, Object instance, Object[] args) { + try { + Method method = clazz.getDeclaredMethod(name, paramTypes); + if (!method.isAccessible()) { + method.setAccessible(true); + } + return method.invoke(instance, args); + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + System.out.println("INSTANCE: " + instance); + throw new RuntimeException("Cannot invoke method: " + clazz.getName() + "#" + name, e); + } + } + + @SuppressWarnings("unchecked") + static T cast(Object obj) { + return (T) obj; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/RequestContext.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/RequestContext.java new file mode 100644 index 0000000000000..030da0cb11c71 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/RequestContext.java @@ -0,0 +1,87 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.spi.AlterableContext; +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.context.spi.CreationalContext; + +public class RequestContext implements AlterableContext { + + // It's a normal scope so there may be no more than one mapped instance per contextual type per thread + private final ThreadLocal, InstanceHandleImpl>> currentContext = new ThreadLocal<>(); + + @Override + public Class getScope() { + return RequestScoped.class; + } + + @Override + public T get(Contextual contextual, CreationalContext creationalContext) { + Map, InstanceHandleImpl> ctx = currentContext.get(); + if (ctx == null) { + // Thread local not set - context is not active! + throw new ContextNotActiveException(); + } + InstanceHandleImpl instance = (InstanceHandleImpl) ctx.get(contextual); + if (instance == null && creationalContext != null) { + // Bean instance does not exist - create one if we have CreationalContext + instance = new InstanceHandleImpl((InjectableBean) contextual, contextual.create(creationalContext), creationalContext); + ctx.put(contextual, instance); + } + return instance != null ? instance.get() : null; + } + + @Override + public T get(Contextual contextual) { + return get(contextual, null); + } + + @Override + public boolean isActive() { + return currentContext.get() != null; + } + + @Override + public void destroy(Contextual contextual) { + Map, InstanceHandleImpl> ctx = currentContext.get(); + if (ctx == null) { + return; + } + InstanceHandleImpl instance = ctx.remove(contextual); + if (instance != null) { + instance.destroy(); + } + } + + public void activate() { + currentContext.set(new HashMap<>()); + } + + public void invalidate() { + Map, InstanceHandleImpl> ctx = currentContext.get(); + if (ctx != null) { + synchronized (ctx) { + for (InstanceHandleImpl instance : ctx.values()) { + try { + instance.destroy(); + } catch (Exception e) { + throw new IllegalStateException("Unable to destroy instance" + instance.get()); + } + } + } + ctx.clear(); + } + } + + public void deactivate() { + // TODO maybe change if context propagation is supported + invalidate(); + currentContext.remove(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/SingletonContext.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/SingletonContext.java new file mode 100644 index 0000000000000..319ee916ec1c5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/SingletonContext.java @@ -0,0 +1,14 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; + +import javax.inject.Singleton; + +class SingletonContext extends AbstractSharedContext { + + @Override + public Class getScope() { + return Singleton.class; + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Subclass.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Subclass.java new file mode 100644 index 0000000000000..7311c39a4cd3a --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Subclass.java @@ -0,0 +1,13 @@ +package org.jboss.protean.arc; + +/** + * + * @author Martin Kouba + */ +public interface Subclass { + + default void destroy() { + // Noop + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeResolver.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeResolver.java new file mode 100644 index 0000000000000..da63172cd8fe5 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeResolver.java @@ -0,0 +1,118 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Map; + +/** + * This code was mainly copied from Weld codebase. + */ +class TypeResolver { + + private final Map, Type> resolvedTypeVariables; + + public TypeResolver(Map, Type> resolvedTypeVariables) { + this.resolvedTypeVariables = resolvedTypeVariables; + } + + /** + * Resolves a given type variable. This is achieved by a lookup in the {@link #resolvedTypeVariables} map. + */ + public Type resolveType(TypeVariable variable) { + Type resolvedType = this.resolvedTypeVariables.get(variable); + if (resolvedType == null) { + return variable; // we are not able to resolve + } + return resolvedType; + } + + /** + * Resolves a given parameterized type. If the parameterized type contains no type variables it is returned untouched. + * Otherwise, a new {@link ParameterizedType} instance is returned in which each type variable is resolved using + * {@link #resolveType(TypeVariable)}. + */ + public Type resolveType(ParameterizedType type) { + Type[] unresolvedTypeArguments = type.getActualTypeArguments(); + + /* + * Indicates whether we managed to resolve any of type arguments. If we did not then there is no need to create a new + * ParameterizedType with the old parameters. Instead, we return the original type. + */ + boolean modified = false; + Type[] resolvedTypeArguments = new Type[unresolvedTypeArguments.length]; + + for (int i = 0; i < unresolvedTypeArguments.length; i++) { + Type resolvedType = unresolvedTypeArguments[i]; + if (resolvedType instanceof TypeVariable) { + resolvedType = resolveType((TypeVariable) resolvedType); + } + if (resolvedType instanceof ParameterizedType) { + resolvedType = resolveType((ParameterizedType) resolvedType); + } + resolvedTypeArguments[i] = resolvedType; + // This identity check is intentional. A different identity indicates that the type argument was resolved within #resolveType(). + if (unresolvedTypeArguments[i] != resolvedType) { + modified = true; + } + } + + if (modified) { + return new ParameterizedTypeImpl(type.getRawType(), resolvedTypeArguments, type.getOwnerType()); + } else { + return type; + } + } + + public Type resolveType(GenericArrayType type) { + Type genericComponentType = type.getGenericComponentType(); + // try to resolve the type + Type resolvedType = genericComponentType; + if (genericComponentType instanceof TypeVariable) { + resolvedType = resolveType((TypeVariable) genericComponentType); + } + if (genericComponentType instanceof ParameterizedType) { + resolvedType = resolveType((ParameterizedType) genericComponentType); + } + if (genericComponentType instanceof GenericArrayType) { + resolvedType = resolveType((GenericArrayType) genericComponentType); + } + /* + * If the generic component type resolved to a class (e.g. String) we return [Ljava.lang.String; (the class representing the + * array) instead of GenericArrayType with String as its generic component type. + */ + if (resolvedType instanceof Class) { + Class componentClass = (Class) resolvedType; + return Array.newInstance(componentClass, 0).getClass(); + } + /* + * This identity check is intentional. If the identity is different it indicates that we succeeded in resolving the type + * and a new GenericArrayType with resolved generic component type is returned. Otherwise, we were not able to resolve + * the type and therefore we do not create a new GenericArrayType. + */ + if (resolvedType == genericComponentType) { + return type; + } else { + return new GenericArrayTypeImpl(resolvedType); + } + } + + public Type resolveType(Type type) { + if (type instanceof ParameterizedType) { + return resolveType((ParameterizedType) type); + } + if (type instanceof TypeVariable) { + return resolveType((TypeVariable) type); + } + if (type instanceof GenericArrayType) { + return resolveType((GenericArrayType) type); + } + return type; + } + + public Map, Type> getResolvedTypeVariables() { + return resolvedTypeVariables; + } +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeVariableImpl.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeVariableImpl.java new file mode 100644 index 0000000000000..02ff907c976e0 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/TypeVariableImpl.java @@ -0,0 +1,57 @@ +package org.jboss.protean.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.List; + +public class TypeVariableImpl implements TypeVariable { + + private final String name; + + private final List bounds; + + public TypeVariableImpl(String name, Type... bounds) { + this.name = name; + this.bounds = Arrays.asList(bounds); + } + + @Override + public T getAnnotation(Class annotationClass) { + throw new UnsupportedOperationException(); + } + + @Override + public Annotation[] getAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + throw new UnsupportedOperationException(); + } + + @Override + public Type[] getBounds() { + return bounds.toArray(new Type[bounds.size()]); + } + + @Override + public D getGenericDeclaration() { + throw new UnsupportedOperationException(); + } + + @Override + public String getName() { + return name; + } + + @Override + public AnnotatedType[] getAnnotatedBounds() { + throw new UnsupportedOperationException(); + } + +} diff --git a/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Types.java b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Types.java new file mode 100644 index 0000000000000..5ca832fb2e807 --- /dev/null +++ b/ext/arc/runtime/src/main/java/org/jboss/protean/arc/Types.java @@ -0,0 +1,162 @@ +package org.jboss.protean.arc; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; + +/** + * This code was mainly copied from Weld codebase. + * + * @author Pete Muir and Weld contributors + */ +final class Types { + + private Types() { + } + + static Type boxedType(Type type) { + if (type instanceof Class) { + return boxedClass((Class) type); + } else { + return type; + } + } + + static Class boxedClass(Class type) { + if (!type.isPrimitive()) { + return type; + } else if (type.equals(Boolean.TYPE)) { + return Boolean.class; + } else if (type.equals(Character.TYPE)) { + return Character.class; + } else if (type.equals(Byte.TYPE)) { + return Byte.class; + } else if (type.equals(Short.TYPE)) { + return Short.class; + } else if (type.equals(Integer.TYPE)) { + return Integer.class; + } else if (type.equals(Long.TYPE)) { + return Long.class; + } else if (type.equals(Float.TYPE)) { + return Float.class; + } else if (type.equals(Double.TYPE)) { + return Double.class; + } else if (type.equals(Void.TYPE)) { + return Void.class; + } else { + throw new IllegalArgumentException(); + } + } + + static boolean isActualType(Type type) { + return (type instanceof Class) || (type instanceof ParameterizedType) || (type instanceof GenericArrayType); + } + + static boolean isArray(Type type) { + return (type instanceof GenericArrayType) || (type instanceof Class && ((Class) type).isArray()); + } + + /** + * Determines whether the given array only contains unbounded type variables or Object.class. + * + * @param types the given array of types + * @return true if and only if the given array only contains unbounded type variables or Object.class + */ + static boolean isArrayOfUnboundedTypeVariablesOrObjects(Type[] types) { + for (Type type : types) { + if (Object.class.equals(type)) { + continue; + } + if (type instanceof TypeVariable) { + Type[] bounds = ((TypeVariable) type).getBounds(); + if (bounds == null || bounds.length == 0 || (bounds.length == 1 && Object.class.equals(bounds[0]))) { + continue; + } + } + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + static Class getRawType(Type type) { + if (type instanceof Class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + if (((ParameterizedType) type).getRawType() instanceof Class) { + return (Class) ((ParameterizedType) type).getRawType(); + } + } + if (type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) type; + Type[] bounds = variable.getBounds(); + return getBound(bounds); + } + if (type instanceof WildcardType) { + WildcardType wildcard = (WildcardType) type; + return getBound(wildcard.getUpperBounds()); + } + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + Class rawType = getRawType(genericArrayType.getGenericComponentType()); + if (rawType != null) { + return (Class) Array.newInstance(rawType, 0).getClass(); + } + } + return null; + } + + /** + * Returns a canonical type for a given class. + * + * If the class is a raw type of a parameterized class, the matching {@link ParameterizedType} (with unresolved type variables) is resolved. + * + * If the class is an array then the component type of the array is canonicalized + * + * Otherwise, the class is returned. + * + * @return + */ + static Type getCanonicalType(Class clazz) { + if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + Type resolvedComponentType = getCanonicalType(componentType); + if (componentType != resolvedComponentType) { + // identity check intentional + // a different identity means that we actually replaced the component Class with a ParameterizedType + return new GenericArrayTypeImpl(resolvedComponentType); + } + } + if (clazz.getTypeParameters().length > 0) { + Type[] actualTypeParameters = clazz.getTypeParameters(); + return new ParameterizedTypeImpl(clazz, actualTypeParameters, clazz.getDeclaringClass()); + } + return clazz; + } + + static boolean isRawGenericType(Type type) { + if (!(type instanceof Class)) { + return false; + } + Class clazz = (Class) type; + if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + return isRawGenericType(componentType); + } + return clazz.getTypeParameters().length > 0; + } + + @SuppressWarnings("unchecked") + private static Class getBound(Type[] bounds) { + if (bounds.length == 0) { + return (Class) Object.class; + } else { + return getRawType(bounds[0]); + } + } + +} diff --git a/ext/arc/runtime/src/test/resources/META-INF/services/org.jboss.weld.arc.BeanProvider b/ext/arc/runtime/src/test/resources/META-INF/services/org.jboss.weld.arc.BeanProvider new file mode 100644 index 0000000000000..e84d37310ea7e --- /dev/null +++ b/ext/arc/runtime/src/test/resources/META-INF/services/org.jboss.weld.arc.BeanProvider @@ -0,0 +1 @@ +org.jboss.weld.arc.generate.TestBeanProvider \ No newline at end of file diff --git a/ext/arc/tests/pom.xml b/ext/arc/tests/pom.xml new file mode 100644 index 0000000000000..734eca4f7560c --- /dev/null +++ b/ext/arc/tests/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + + + org.jboss.protean.arc + arc-parent + 1.0.0.Alpha1-SNAPSHOT + ../ + + + arc-tests + + + + + org.jboss.protean.arc + arc-runtime + + + + org.jboss.protean.arc + arc-processor + + + + junit + junit + + + + + + diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java new file mode 100644 index 0000000000000..d9abf009c6322 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/ArcTestContainer.java @@ -0,0 +1,124 @@ +package org.jboss.protean.arc.test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import org.jboss.jandex.Index; +import org.jboss.jandex.Indexer; +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.BeanProvider; +import org.jboss.protean.arc.processor.BeanProcessor; +import org.jboss.protean.arc.processor.ResourceOutput; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class ArcTestContainer implements TestRule { + + private final List> beanClasses; + + public ArcTestContainer(Class... beanClasses) { + this.beanClasses = Arrays.asList(beanClasses); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + ClassLoader oldTccl = init(description.getTestClass()); + try { + base.evaluate(); + } finally { + Thread.currentThread().setContextClassLoader(oldTccl); + shutdown(); + } + } + }; + } + + private void shutdown() { + Arc.shutdown(); + } + + private ClassLoader init(Class testClass) { + + // Make sure Arc is down + Arc.shutdown(); + + // Build index + Index index; + try { + index = index(beanClasses); + } catch (IOException e) { + throw new IllegalStateException("Failed to create index", e); + } + + File generatedSourcesDirectory = new File("target/generated-arc-sources"); + File testOutputDirectory = new File("target/test-classes"); + File beanProviderFile = new File(generatedSourcesDirectory + "/" + nameToPath(testClass.getPackage().getName()), "BeanProvider"); + + BeanProcessor beanProcessor = BeanProcessor.builder().setName(testClass.getSimpleName()).setIndex(index).setOutput(new ResourceOutput() { + + @Override + public void writeResource(Resource resource) throws IOException { + switch (resource.getType()) { + case JAVA_CLASS: + resource.writeTo(testOutputDirectory); + break; + case SERVICE_PROVIDER: + if (resource.getName().endsWith(BeanProvider.class.getName())) { + beanProviderFile.getParentFile().mkdirs(); + try (FileOutputStream out = new FileOutputStream(beanProviderFile)) { + out.write(resource.getData()); + } + } + break; + default: + throw new IllegalArgumentException(); + } + } + }).build(); + try { + beanProcessor.process(); + } catch (IOException e) { + throw new IllegalStateException("Error generating resources", e); + } + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + ClassLoader testClassLoader = new URLClassLoader(new URL[] {}, old) { + @Override + public Enumeration getResources(String name) throws IOException { + if (("META-INF/services/" + BeanProvider.class.getName()).equals(name)) { + // return URL that points to the correct test bean provider + return Collections.enumeration(Collections.singleton(beanProviderFile.toURI().toURL())); + } + return super.getResources(name); + } + }; + Thread.currentThread().setContextClassLoader(testClassLoader); + return old; + } + + private Index index(Iterable> classes) throws IOException { + Indexer indexer = new Indexer(); + for (Class clazz : classes) { + try (InputStream stream = ArcTestContainer.class.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { + indexer.index(stream); + } + } + return indexer.complete(); + } + + private String nameToPath(String packName) { + return packName.replace('.', '/'); + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/clientproxy/ProducerClientProxyTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/clientproxy/ProducerClientProxyTest.java new file mode 100644 index 0000000000000..eb323c7a629cf --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/clientproxy/ProducerClientProxyTest.java @@ -0,0 +1,49 @@ +package org.jboss.protean.arc.test.clientproxy; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class ProducerClientProxyTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Producer.class, Product.class); + + @Test + public void testProducer() throws IOException { + assertEquals(Long.valueOf(1), Arc.container().instance(Product.class).get().get(Long.valueOf(1))); + } + + @Singleton + static class Producer { + + @Produces + @ApplicationScoped + Product produce() { + return new Product() { + + @Override + public T get(T number) throws IOException { + return number; + } + + }; + } + + } + + interface Product { + + T get(T number) throws IOException; + + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java new file mode 100644 index 0000000000000..eaad71a4bcd99 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java @@ -0,0 +1,44 @@ +package org.jboss.protean.arc.test.injection.privateconstructor; + +import static org.junit.Assert.assertNotNull; + +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class PrivateConstructorInjectionTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Head.class, CombineHarvester.class); + + @Test + public void testInjection() { + assertNotNull(Arc.container().instance(CombineHarvester.class).get().getHead()); + } + + @Dependent + static class Head { + + } + + @Singleton + static class CombineHarvester { + + private final Head head; + + @Inject + private CombineHarvester(Head head) { + this.head = head; + } + + public Head getHead() { + return head; + } + + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privatefield/PrivateFieldInjectionTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privatefield/PrivateFieldInjectionTest.java new file mode 100644 index 0000000000000..765734cfa7487 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/injection/privatefield/PrivateFieldInjectionTest.java @@ -0,0 +1,40 @@ +package org.jboss.protean.arc.test.injection.privatefield; + +import static org.junit.Assert.assertNotNull; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class PrivateFieldInjectionTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Head.class, CombineHarvester.class); + + @Test + public void testInjection() { + assertNotNull(Arc.container().instance(CombineHarvester.class).get().getHead()); + } + + @Dependent + static class Head { + + } + + @ApplicationScoped + static class CombineHarvester { + + @Inject + private Head head; + + public Head getHead() { + return head; + } + + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Counter.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Counter.java new file mode 100644 index 0000000000000..4b714aa9ab555 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Counter.java @@ -0,0 +1,24 @@ +package org.jboss.protean.arc.test.interceptors; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Singleton; + +@Singleton +public class Counter { + + private AtomicInteger counter = new AtomicInteger(); + + int incrementAndGet() { + return counter.incrementAndGet(); + } + + void reset() { + counter.set(0); + } + + int get() { + return counter.get(); + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Lifecycle.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Lifecycle.java new file mode 100644 index 0000000000000..c57872bbe31d8 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Lifecycle.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc.test.interceptors; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface Lifecycle { + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LifecycleInterceptor.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LifecycleInterceptor.java new file mode 100644 index 0000000000000..bdba6be30b10e --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LifecycleInterceptor.java @@ -0,0 +1,42 @@ +package org.jboss.protean.arc.test.interceptors; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Priority; +import javax.interceptor.AroundConstruct; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@Lifecycle +@Priority(1) +@Interceptor +public class LifecycleInterceptor { + + static final List AROUND_CONSTRUCTS = new CopyOnWriteArrayList<>(); + static final List POST_CONSTRUCTS = new CopyOnWriteArrayList<>(); + static final List PRE_DESTROYS = new CopyOnWriteArrayList<>(); + + @PostConstruct + void simpleInit(InvocationContext ctx) { + POST_CONSTRUCTS.add(ctx.getTarget()); + } + + @PreDestroy + void simpleDestroy(InvocationContext ctx) { + PRE_DESTROYS.add(ctx.getTarget()); + } + + @AroundConstruct + void simpleAroundConstruct(InvocationContext ctx) throws Exception { + try { + AROUND_CONSTRUCTS.add(ctx.getConstructor()); + ctx.proceed(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Logging.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Logging.java new file mode 100644 index 0000000000000..faae775c5c6f8 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Logging.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc.test.interceptors; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface Logging { + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LoggingInterceptor.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LoggingInterceptor.java new file mode 100644 index 0000000000000..4c86a38f5d6b9 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/LoggingInterceptor.java @@ -0,0 +1,23 @@ +package org.jboss.protean.arc.test.interceptors; + +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Priority; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@Logging +@Priority(10) +@Interceptor +public class LoggingInterceptor { + + static final AtomicReference LOG = new AtomicReference(); + + @AroundInvoke + Object log(InvocationContext ctx) throws Exception { + Object ret = ctx.proceed(); + LOG.set(ret); + return ret; + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Simple.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Simple.java new file mode 100644 index 0000000000000..4d6fc5f8200d2 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/Simple.java @@ -0,0 +1,19 @@ +package org.jboss.protean.arc.test.interceptors; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface Simple { + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleBean.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleBean.java new file mode 100644 index 0000000000000..e06a0e2a188a2 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleBean.java @@ -0,0 +1,39 @@ +package org.jboss.protean.arc.test.interceptors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; + +@Lifecycle +@Dependent +public class SimpleBean { + + private String val; + + private Counter counter; + + @Inject + SimpleBean(Counter counter) { + this.counter = counter; + } + + @PostConstruct + void superCoolInit() { + val = "foo"; + } + + @Logging + @Simple + String foo(String anotherVal) { + return val; + } + + String bar() { + return new StringBuilder(val).reverse().toString(); + } + + Counter getCounter() { + return counter; + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptor.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptor.java new file mode 100644 index 0000000000000..3819301691b3d --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptor.java @@ -0,0 +1,21 @@ +package org.jboss.protean.arc.test.interceptors; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@Simple +@Priority(1) +@Interceptor +public class SimpleInterceptor { + + @Inject + Counter counter; + + @AroundInvoke + Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception { + return "" + counter.get() + ctx.proceed() + counter.incrementAndGet(); + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptorTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptorTest.java new file mode 100644 index 0000000000000..e0c50718183c9 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/SimpleInterceptorTest.java @@ -0,0 +1,48 @@ +package org.jboss.protean.arc.test.interceptors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.InstanceHandle; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class SimpleInterceptorTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Counter.class, SimpleBean.class, Simple.class, SimpleInterceptor.class, Logging.class, + LoggingInterceptor.class, Lifecycle.class, LifecycleInterceptor.class); + + @Test + public void testInterception() { + ArcContainer arc = Arc.container(); + + LifecycleInterceptor.POST_CONSTRUCTS.clear(); + LifecycleInterceptor.PRE_DESTROYS.clear(); + LifecycleInterceptor.AROUND_CONSTRUCTS.clear(); + + InstanceHandle handle = arc.instance(SimpleBean.class); + SimpleBean simpleBean = handle.get(); + + assertEquals(1, LifecycleInterceptor.AROUND_CONSTRUCTS.size()); + assertNotNull(LifecycleInterceptor.AROUND_CONSTRUCTS.get(0)); + assertEquals(1, LifecycleInterceptor.POST_CONSTRUCTS.size()); + assertEquals(simpleBean, LifecycleInterceptor.POST_CONSTRUCTS.get(0)); + + Counter counter = arc.instance(Counter.class).get(); + LoggingInterceptor.LOG.set(null); + + assertEquals("0foo1", simpleBean.foo("0")); + assertEquals("oof", simpleBean.bar()); + assertEquals(1, counter.get()); + assertEquals("foo", LoggingInterceptor.LOG.get()); + + handle.release(); + assertEquals(1, LifecycleInterceptor.PRE_DESTROYS.size()); + assertEquals(simpleBean, LifecycleInterceptor.PRE_DESTROYS.get(0)); + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java new file mode 100644 index 0000000000000..481e662d64c0e --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java @@ -0,0 +1,51 @@ +package org.jboss.protean.arc.test.interceptors.privatemethod; + +import static org.junit.Assert.assertEquals; + +import javax.annotation.Priority; +import javax.inject.Singleton; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.jboss.protean.arc.test.interceptors.Simple; +import org.junit.Rule; +import org.junit.Test; + +public class PrivateInterceptorMethodTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Simple.class, SimpleBean.class, SimpleInterceptor.class); + + @Test + public void testInterception() { + ArcContainer arc = Arc.container(); + SimpleBean simpleBean = arc.instance(SimpleBean.class).get(); + assertEquals("privatefoo", simpleBean.foo()); + } + + @Singleton + static class SimpleBean { + + @Simple + String foo() { + return "foo"; + } + + } + + @Simple + @Priority(1) + @Interceptor + public static class SimpleInterceptor { + + @AroundInvoke + private Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception { + return "private" + ctx.proceed(); + } + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/BeanMetadataTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/BeanMetadataTest.java new file mode 100644 index 0000000000000..f984232e69f14 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/BeanMetadataTest.java @@ -0,0 +1,29 @@ +package org.jboss.protean.arc.test.metadata; + +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Qualifier; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class BeanMetadataTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Controller.class); + + @Test + public void testBeanMetadata() { + ArcContainer arc = Arc.container(); + Assert.assertNull(arc.instance(Controller.class).get().bean); + } + + @SuppressWarnings("serial") + static class TestLiteral extends AnnotationLiteral implements Qualifier { + + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/Controller.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/Controller.java new file mode 100644 index 0000000000000..b74c2348e2a59 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/Controller.java @@ -0,0 +1,13 @@ +package org.jboss.protean.arc.test.metadata; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.Bean; +import javax.inject.Inject; + +@Dependent +public class Controller { + + @Inject + Bean bean; + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/InjectionPointMetadataTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/InjectionPointMetadataTest.java new file mode 100644 index 0000000000000..34ce686334615 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/metadata/InjectionPointMetadataTest.java @@ -0,0 +1,47 @@ +package org.jboss.protean.arc.test.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class InjectionPointMetadataTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Controller.class, Controlled.class); + + @Test + public void testInjectionPointMetadata() { + ArcContainer arc = Arc.container(); + Controller controller = arc.instance(Controller.class).get(); + InjectionPoint injectionPoint = controller.controlled.injectionPoint; + assertNotNull(injectionPoint); + assertEquals(Controlled.class, injectionPoint.getType()); + } + + @Singleton + static class Controller { + + @Inject + Controlled controlled; + + } + + @Dependent + static class Controlled { + + @Inject + InjectionPoint injectionPoint; + + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerFieldTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerFieldTest.java new file mode 100644 index 0000000000000..d1cab1814a5cf --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerFieldTest.java @@ -0,0 +1,42 @@ +package org.jboss.protean.arc.test.producer.privatemember; + +import static org.junit.Assert.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class PrivateProducerFieldTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(HeadProducer.class); + + @Test + public void testInjection() { + assertEquals("foo", Arc.container().instance(Head.class).get().name()); + } + + static class Head { + + public String name() { + return null; + } + + } + + @ApplicationScoped + static class HeadProducer { + + @Produces + private Head head = new Head() { + public String name() { + return "foo"; + } + }; + + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerMethodTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerMethodTest.java new file mode 100644 index 0000000000000..f3e66719bb93d --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/producer/privatemember/PrivateProducerMethodTest.java @@ -0,0 +1,52 @@ +package org.jboss.protean.arc.test.producer.privatemember; + +import static org.junit.Assert.assertEquals; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class PrivateProducerMethodTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(HeadProducer.class); + + @Test + public void testInjection() { + assertEquals("foo", Arc.container().instance(Head.class).get().name()); + } + + static class Head { + + public String name() { + return null; + } + + } + + @ApplicationScoped + static class HeadProducer { + + private String name = null; + + @PostConstruct + void init() { + name = "foo"; + } + + @Produces + private Head produce() { + return new Head() { + public String name() { + return name; + } + }; + } + + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/Controller.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/Controller.java new file mode 100644 index 0000000000000..23506e79f652b --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/Controller.java @@ -0,0 +1,30 @@ +package org.jboss.protean.arc.test.requestcontext; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.RequestScoped; + +@RequestScoped +public class Controller { + + static final AtomicBoolean DESTROYED = new AtomicBoolean(); + + private String id; + + @PostConstruct + void init() { + id = UUID.randomUUID().toString(); + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + + String getId() { + return id; + } +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/RequestContextTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/RequestContextTest.java new file mode 100644 index 0000000000000..14e25e6ba47fb --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/requestcontext/RequestContextTest.java @@ -0,0 +1,52 @@ +package org.jboss.protean.arc.test.requestcontext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import javax.enterprise.context.ContextNotActiveException; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class RequestContextTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(Controller.class); + + @Test + public void testRequestContext() { + + ArcContainer arc = Arc.container(); + + try { + arc.instance(Controller.class).get().getId(); + fail(); + } catch (ContextNotActiveException expected) { + } + + arc.requestContext().activate(); + assertFalse(Controller.DESTROYED.get()); + Controller controller1 = arc.instance(Controller.class).get(); + Controller controller2 = arc.instance(Controller.class).get(); + String controller2Id = controller2.getId(); + assertEquals(controller1.getId(), controller2Id); + arc.requestContext().deactivate(); + assertTrue(Controller.DESTROYED.get()); + + try { + arc.instance(Controller.class).get().getId(); + fail(); + } catch (ContextNotActiveException expected) { + } + + // Id must be different in a different request + arc.withinRequest(() -> assertNotEquals(controller2Id, arc.instance(Controller.class).get().getId())); + } + +} diff --git a/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/resolution/RuntimeResolutionTest.java b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/resolution/RuntimeResolutionTest.java new file mode 100644 index 0000000000000..8c782788e4655 --- /dev/null +++ b/ext/arc/tests/src/test/java/org/jboss/protean/arc/test/resolution/RuntimeResolutionTest.java @@ -0,0 +1,51 @@ +package org.jboss.protean.arc.test.resolution; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.List; + +import javax.enterprise.util.TypeLiteral; +import javax.inject.Singleton; + +import org.jboss.protean.arc.Arc; +import org.jboss.protean.arc.ArcContainer; +import org.jboss.protean.arc.InstanceHandle; +import org.jboss.protean.arc.test.ArcTestContainer; +import org.junit.Rule; +import org.junit.Test; + +public class RuntimeResolutionTest { + + @Rule + public ArcTestContainer container = new ArcTestContainer(MyList.class); + + @SuppressWarnings("serial") + @Test + public void testResolution() throws IOException { + ArcContainer arc = Arc.container(); + // MyList bean types: MyList, AbstractList, List, AbstractCollection, Iterable, Object + InstanceHandle> list = arc.instance(new TypeLiteral>() { + }); + assertTrue(list.isAvailable()); + assertEquals(Integer.valueOf(7), list.get().get(1)); + } + + @Singleton + static class MyList extends AbstractList { + + @Override + public Integer get(int index) { + return Integer.valueOf(7); + } + + @Override + public int size() { + return 0; + } + + } + +} diff --git a/gizmo/pom.xml b/ext/gizmo/pom.xml similarity index 99% rename from gizmo/pom.xml rename to ext/gizmo/pom.xml index 29ace76f0c2ed..4f4be429aacf8 100644 --- a/gizmo/pom.xml +++ b/ext/gizmo/pom.xml @@ -18,7 +18,6 @@ gizmo 1.0.0.Alpha1-SNAPSHOT - org.ow2.asm diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotatedElement.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotatedElement.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/AnnotatedElement.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotatedElement.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/AnnotationCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResult.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResult.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/BranchResult.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResult.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResultImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResultImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/BranchResultImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/BranchResultImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/BytecodeCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/CatchBlockCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/ClassCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/ClassCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/ClassCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/ClassCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/ClassOutput.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/ClassOutput.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/ClassOutput.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/ClassOutput.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/DescriptorUtils.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/DescriptorUtils.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/DescriptorUtils.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/DescriptorUtils.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/ExceptionTable.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/ExceptionTable.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/ExceptionTable.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/ExceptionTable.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/FieldDescriptor.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldDescriptor.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/FieldDescriptor.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/FieldDescriptor.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/FunctionCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/JumpHandle.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/JumpHandle.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/JumpHandle.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/JumpHandle.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/MemberCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MemberCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/MemberCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/MemberCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreator.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreator.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreator.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreator.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreatorImpl.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreatorImpl.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreatorImpl.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodCreatorImpl.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/MethodDescriptor.java diff --git a/gizmo/src/main/java/org/jboss/protean/gizmo/ResultHandle.java b/ext/gizmo/src/main/java/org/jboss/protean/gizmo/ResultHandle.java similarity index 100% rename from gizmo/src/main/java/org/jboss/protean/gizmo/ResultHandle.java rename to ext/gizmo/src/main/java/org/jboss/protean/gizmo/ResultHandle.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/ArrayTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/ArrayTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/ArrayTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/ArrayTestCase.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionClass.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionClass.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionClass.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionClass.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionThrowingTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionThrowingTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionThrowingTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/ExceptionThrowingTestCase.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/FieldModifiersTest.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/FieldModifiersTest.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/FieldModifiersTest.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/FieldModifiersTest.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/FunctionTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/FunctionTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/FunctionTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/FunctionTestCase.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/LoadClassTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/LoadClassTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/LoadClassTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/LoadClassTestCase.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/LoadNullTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/LoadNullTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/LoadNullTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/LoadNullTestCase.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/MessageClass.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/MessageClass.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/MessageClass.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/MessageClass.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/MyInterface.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/MyInterface.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/MyInterface.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/MyInterface.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/SampleTest.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/SampleTest.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/SampleTest.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/SampleTest.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/Superclass.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/Superclass.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/Superclass.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/Superclass.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/TestClassLoader.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/TestClassLoader.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/TestClassLoader.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/TestClassLoader.java diff --git a/gizmo/src/test/java/org/jboss/protean/gizmo/TryCatchTestCase.java b/ext/gizmo/src/test/java/org/jboss/protean/gizmo/TryCatchTestCase.java similarity index 100% rename from gizmo/src/test/java/org/jboss/protean/gizmo/TryCatchTestCase.java rename to ext/gizmo/src/test/java/org/jboss/protean/gizmo/TryCatchTestCase.java diff --git a/openapi/deployment/pom.xml b/openapi/deployment/pom.xml index 812df870d6f1c..47b36c8a07728 100644 --- a/openapi/deployment/pom.xml +++ b/openapi/deployment/pom.xml @@ -17,10 +17,6 @@ org.jboss.shamrock shamrock-core-deployment - - org.jboss.shamrock - shamrock-weld-deployment - org.jboss.shamrock shamrock-undertow-deployment diff --git a/openapi/runtime/src/main/java/org/jboss/shamrock/openapi/runtime/OpenApiDeploymentTemplate.java b/openapi/runtime/src/main/java/org/jboss/shamrock/openapi/runtime/OpenApiDeploymentTemplate.java index 11fcff0c352c3..a47a7e671100a 100644 --- a/openapi/runtime/src/main/java/org/jboss/shamrock/openapi/runtime/OpenApiDeploymentTemplate.java +++ b/openapi/runtime/src/main/java/org/jboss/shamrock/openapi/runtime/OpenApiDeploymentTemplate.java @@ -1,11 +1,10 @@ package org.jboss.shamrock.openapi.runtime; -import javax.enterprise.inject.se.SeContainer; - import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.openapi.OASFilter; import org.eclipse.microprofile.openapi.models.OpenAPI; +import org.jboss.shamrock.runtime.BeanContainer; import org.jboss.shamrock.runtime.ContextObject; import org.jboss.shamrock.runtime.Shamrock; @@ -19,7 +18,7 @@ */ public class OpenApiDeploymentTemplate { - public void setupModel(@ContextObject("weld.container") SeContainer container, OpenAPI staticModel, OpenAPI annotationModel) { + public void setupModel(@ContextObject("bean.container") BeanContainer container, OpenAPI staticModel, OpenAPI annotationModel) { Config config = ConfigProvider.getConfig(); OpenApiConfig openApiConfig = new OpenApiConfigImpl(config); @@ -32,7 +31,7 @@ public void setupModel(@ContextObject("weld.container") SeContainer container, O document.filter(filter(openApiConfig)); document.initialize(); - container.select(OpenApiDocumentProducer.class).get().setDocument(document); + container.instance(OpenApiDocumentProducer.class).setDocument(document); } private OpenApiDocument createDocument(OpenApiConfig openApiConfig) { diff --git a/pom.xml b/pom.xml index 5578850059545..ad8429da16a55 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,6 @@ 2.9.5 1.8.3 1.2 - 1.0.0-SNAPSHOT @@ -61,8 +60,9 @@ openapi examples jpa - gizmo arc + ext/gizmo + ext/arc @@ -448,12 +448,12 @@ org.jboss.protean.arc arc-processor - ${arc.version} + ${project.version} org.jboss.protean.arc arc-runtime - ${arc.version} + ${project.version} org.jboss.xnio