Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Generate Event Publishers #554

Merged
merged 16 commits into from
May 2, 2024
Merged
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);
rbygrave marked this conversation as resolved.
Show resolved Hide resolved
} 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
Loading