Skip to content

Commit

Permalink
Auto Generate Event Publishers (#554)
Browse files Browse the repository at this point in the history
* Auto Generate Event Publishers

* Update BeanReader.java

* allow generated extends type in autoprovides

* Update TypeExtendsReader.java

* fix imported type gen

* fix restricted packages

* blackbox test

* Delete module-info.java

* doc

* static write

* fix register param name

* Revert the change to name qualifier

* Rename Event.defaultQualifier -> qualifier

* format javadoc only

* Rename field back to defaultQualifier

---------

Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
  • Loading branch information
SentryMan and rbygrave authored May 2, 2024
1 parent 5daa258 commit 7b0cfa3
Show file tree
Hide file tree
Showing 21 changed files with 300 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.other.one.events;

import io.avaje.inject.Component;
import io.avaje.inject.event.Event;
import io.avaje.inject.event.ObserverManager;
import io.avaje.inject.spi.Generated;
import java.lang.reflect.Type;
import org.other.one.SomeOptionalDep;

@Component
@Generated("avaje-inject-generator")
public class SomeOptionalDep$Publisher extends Event<SomeOptionalDep> {

private static final Type TYPE = SomeOptionalDep.class;

public SomeOptionalDep$Publisher(ObserverManager manager) {
super(manager, TYPE, "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.example.myapp.events;

import org.example.external.aspect.MyExternalAspect;
import org.other.one.SomeOptionalDep;

import io.avaje.inject.Component;
import io.avaje.inject.event.Event;

@Component
public class ExternalEventPublisher {
Event<SomeOptionalDep> event;

public ExternalEventPublisher(Event<SomeOptionalDep> event) {

this.event = event;
}

@MyExternalAspect
public void fire(SomeOptionalDep dep) {
event.fire(dep);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.myapp.events;

import org.other.one.SomeOptionalDep;

import io.avaje.inject.event.Observes;
import jakarta.inject.Singleton;

@Singleton
public class ExternalObserver {

boolean invoked;
SomeOptionalDep recievedEvent;

public void observe(@Observes SomeOptionalDep event) {
invoked = true;

this.recievedEvent = event;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.example.myapp.events;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.other.one.SomeOptionalDep;

import io.avaje.inject.test.InjectTest;
import jakarta.inject.Inject;

@InjectTest
class TestEventMessaging {

@Inject ExternalObserver observer;
@Inject ExternalEventPublisher event;

@BeforeEach
void before() {
observer.invoked = false;
observer.recievedEvent = null;
}

@Test
void test() {
var message = new SomeOptionalDep() {};

event.fire(message);

assertThat(observer.invoked).isTrue();
assertThat(observer.recievedEvent).isSameAs(message);
}
}
13 changes: 13 additions & 0 deletions inject-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-prisms</artifactId>
<version>${avaje.prisms.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,93 @@

import static io.avaje.inject.generator.APContext.createSourceFile;
import static io.avaje.inject.generator.APContext.logError;
import static java.util.function.Predicate.not;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import javax.lang.model.element.Element;
import javax.tools.JavaFileObject;

/** Write the source code for the bean. */
final class EventPublisherWriter {

private static final String TEMPLATE =
private static final Map<String, String> GENERATED_PUBLISHERS = new HashMap<>();
private final String TEMPLATE =
"package {0};\n\n"
+ "{1}"
+ "@Component\n"
+ "{2}"
+ "@Generated(\"avaje-inject-generator\")\n"
+ "public class {2}Publisher extends Event<{2}> '{'\n"
+ "public class {3} extends Event<{4}> '{'\n"
+ "\n"
+ " private static final Type TYPE = {5};\n"
+ "\n"
+ " public {2}Publisher(ObserverManager manager) '{'\n"
+ " super(manager, {2}.class);\n"
+ " public {3}(ObserverManager manager) '{'\n"
+ " super(manager, TYPE, \"{6}\");\n"
+ " '}'\n"
+ "'}'\n";
private final String originName;
private final ImportTypeMap importTypes = new ImportTypeMap();
private final UType utype;
private final String packageName;
private final String qualifier;

EventPublisherWriter(Element element) {
this.packageName = APContext.elements().getPackageOf(element).getQualifiedName().toString();
this.utype = UType.parse(element.asType());
this.originName = utype.mainType() + "Publisher";
importTypes.addAll(utype.importTypes());
if (utype.isGeneric()) {
APContext.logError(element, "Event publishers generation may not be used for generic classes. Generic event publishers must be constructed manually");
static void write(Element element) {
new EventPublisherWriter(element);
}

private EventPublisherWriter(Element element) {
final var asType = element.asType();
this.utype = UType.parse(asType).param0();

this.packageName =
APContext.elements()
.getPackageOf(APContext.typeElement(utype.mainType()))
.getQualifiedName()
.toString()
.replaceFirst("java.", "")
+ ".events";
qualifier = Optional.ofNullable(Util.getNamed(element)).orElse("");
var className =
packageName
+ "."
+ (qualifier.isEmpty() ? "" : "Qualified")
+ Util.shortName(utype).replace(".", "_")
+ "$Publisher";

originName = getUniqueClassName(className, 0);

if (GENERATED_PUBLISHERS.containsKey(originName)) {
return;
}
importTypes.addAll(utype.importTypes());
write();
GENERATED_PUBLISHERS.put(originName, qualifier);
}

private String getUniqueClassName(String className, Integer recursiveIndex) {

Optional.ofNullable(APContext.typeElement(className))
.ifPresent(
e ->
GENERATED_PUBLISHERS.put(
e.getQualifiedName().toString(),
Optional.ofNullable(Util.getNamed(e)).orElse("")));

if (Optional.ofNullable(GENERATED_PUBLISHERS.get(className))
.filter(not(qualifier::equals))
.isPresent()) {
var index = className.indexOf("$Publisher");
className = className.substring(0, index) + "$Publisher" + ++recursiveIndex;
return getUniqueClassName(className, recursiveIndex);
}
return className;
}

private Writer createFileWriter() throws IOException {
Expand All @@ -48,7 +99,15 @@ private Writer createFileWriter() throws IOException {
void write() {
try {
var writer = new Append(createFileWriter());
writer.append(MessageFormat.format(TEMPLATE, packageName, imports(), utype.shortType()));

final var shortType = utype.shortWithoutAnnotations();
var typeString = utype.isGeneric() ? getGenericType() : shortType + ".class";

var name = qualifier.isBlank() ? "" : "@Named(\"" + qualifier + "\")\n";
var className = originName.replace(packageName + ".", "");
writer.append(
MessageFormat.format(
TEMPLATE, packageName, imports(), name, className, shortType, typeString, qualifier));
writer.close();
} catch (Exception e) {
e.printStackTrace();
Expand All @@ -61,6 +120,15 @@ String imports() {
importTypes.add("io.avaje.inject.event.Event");
importTypes.add("io.avaje.inject.event.ObserverManager");
importTypes.add("io.avaje.inject.spi.Generated");
importTypes.add(Type.class.getCanonicalName());
if (!qualifier.isBlank()) {
importTypes.add(NamedPrism.PRISM_TYPE);
}
if (utype.isGeneric()) {

importTypes.add("io.avaje.inject.spi.GenericType");
}

StringBuilder writer = new StringBuilder();
for (String importType : importTypes.forImport()) {
if (Util.validImportType(importType)) {
Expand All @@ -69,4 +137,39 @@ String imports() {
}
return writer.append("\n").toString();
}

String getGenericType() {
var sb = new StringBuilder();
sb.append("\n new GenericType<");
writeGenericType(utype, new HashMap<>(), sb);
sb.append(">(){}.type();");
return sb.toString();
}

private void writeGenericType(
UType type, Map<String, String> seenShortNames, StringBuilder writer) {
final var typeShortName = Util.shortName(type.mainType());
final var mainType = seenShortNames.computeIfAbsent(typeShortName, k -> type.mainType());
if (type.isGeneric()) {
final var shortName =
Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
writer.append(shortName);
writer.append("<");
boolean first = true;
for (final var param : type.componentTypes()) {
if (first) {
first = false;
writeGenericType(param, seenShortNames, writer);
continue;
}
writer.append(", ");
writeGenericType(param, seenShortNames, writer);
}
writer.append(">");
} else {
final var shortName =
Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
writer.append(shortName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ final class FieldReader {
if (nullable || element.asType().toString().startsWith("java.util.Optional<")) {
ProcessingContext.addOptionalType(fieldType);
}
if (type.fullWithoutAnnotations().startsWith("io.avaje.inject.event.Event")) {
EventPublisherWriter.write(element);
}
}

boolean isGenericParam() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
@GenerateAPContext
@GenerateModuleInfoReader
@SupportedAnnotationTypes({
ApplicationEventPrism.PRISM_TYPE,
AspectImportPrism.PRISM_TYPE,
AssistFactoryPrism.PRISM_TYPE,
ComponentPrism.PRISM_TYPE,
Expand Down Expand Up @@ -146,9 +145,6 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
maybeElements(roundEnv, Constants.CONTROLLER).ifPresent(this::readBeans);
maybeElements(roundEnv, ProxyPrism.PRISM_TYPE).ifPresent(this::readBeans);
maybeElements(roundEnv, AssistFactoryPrism.PRISM_TYPE).ifPresent(this::readAssisted);
maybeElements(roundEnv, ApplicationEventPrism.PRISM_TYPE).stream()
.flatMap(Set::stream)
.forEach(EventPublisherWriter::new);

maybeElements(roundEnv, ExternalPrism.PRISM_TYPE).stream()
.flatMap(Set::stream)
Expand All @@ -157,11 +153,12 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
.map(u -> "java.util.List".equals(u.mainType()) ? u.param0() : u)
.map(UType::fullWithoutAnnotations)
.forEach(ProcessingContext::addOptionalType);

allScopes.readBeans(roundEnv);
defaultScope.write(processingOver);
allScopes.write(processingOver);

if (roundEnv.processingOver()) {
if (processingOver) {
var order =
new FactoryOrder(ProcessingContext.avajeModules(), defaultScope.pluginProvided())
.orderModules();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ static class MethodParam {
if (nullable || param.asType().toString().startsWith("java.util.Optional<")) {
ProcessingContext.addOptionalType(paramType);
}

if (fullUType.fullWithoutAnnotations().startsWith("io.avaje.inject.event.Event")) {
EventPublisherWriter.write(param);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,16 @@ String providesAspect() {
}

List<UType> autoProvides() {
if (!autoProvide || !providesAspect.isEmpty()) {
return List.of();
}
if (baseTypeIsInterface) {
return List.of(Util.unwrapProvider(baseUType));
}
var autoProvides = new ArrayList<>(interfaceTypes);
autoProvides.addAll(extendsTypes);
autoProvides.add(Util.unwrapProvider(baseUType));
if (!autoProvide || !providesAspect.isEmpty()) {
autoProvides.remove(baseUType);
} else {
autoProvides.add(Util.unwrapProvider(baseUType));
}
return autoProvides;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@GeneratePrism(AOPFallback.class)
@GeneratePrism(ApplicationEvent.class)
@GeneratePrism(Aspect.class)
@GeneratePrism(value = Aspect.Import.class, name = "AspectImportPrism")
@GeneratePrism(Assisted.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
Expand All @@ -30,7 +31,9 @@ class InjectProcessorTest {
@AfterEach
void deleteGeneratedFiles() throws IOException {
try {
Files.walk(Paths.get("io").toAbsolutePath())
Stream.concat(
Files.walk(Paths.get("io").toAbsolutePath()),
Files.walk(Paths.get("lang").toAbsolutePath()))
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
Expand Down
Loading

0 comments on commit 7b0cfa3

Please sign in to comment.