Skip to content

Commit 116cbe4

Browse files
SentryManrbygrave
andauthored
Validate Qualifiers at compile time (#645)
* validate qualifiers at compile time * remove transitive aop and events from inject test * fix tests * fix optional types * final test * fix event publishers not getting detected * refactor reading external modules * Revert "refactor reading external modules" This reverts commit 96c3a94. * Add Util.addQualifierSuffix() helper methods * Format changes * Move and adjust the test to QualifierCircularDependency etc --------- Co-authored-by: Rob Bygrave <[email protected]>
1 parent 805d62b commit 116cbe4

File tree

19 files changed

+225
-85
lines changed

19 files changed

+225
-85
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.example.myapp.assist.droid;
22

3+
import jakarta.inject.Named;
34
import jakarta.inject.Singleton;
45

56
@Singleton
7+
@Named("red")
68
public class Radio {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example.myapp.other;
2+
3+
import io.avaje.inject.Bean;
4+
import io.avaje.inject.Factory;
5+
import jakarta.inject.Named;
6+
7+
@Factory
8+
public class QualifierCircularDependency {
9+
10+
@Bean
11+
@Named("parent") // for clarity, not necessary
12+
public String parent(@Named("child") String child) {
13+
return "parent-"+ child;
14+
}
15+
16+
@Bean
17+
@Named("child")
18+
public String child() {
19+
return "child";
20+
}
21+
22+
// @Bean compilation rightly fails when this is uncommented
23+
// public String child1(@Named("child2") String child) {
24+
// throw new AssertionError("Method body unimportant");
25+
// }
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example.myapp.other;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Named;
5+
import jakarta.inject.Singleton;
6+
7+
@Singleton
8+
final class QualifierConsumerComponent {
9+
10+
private final String parent;
11+
private final String child;
12+
13+
@Inject
14+
QualifierConsumerComponent(@Named("parent") String parent, @Named("child") String child) {
15+
this.parent = parent;
16+
this.child = child;
17+
}
18+
19+
String parent() {
20+
return parent;
21+
}
22+
23+
String child() {
24+
return child;
25+
}
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.example.myapp.other;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class QualifierConsumerComponentTest {
9+
10+
@Test
11+
void doStuff() {
12+
try (BeanScope beanScope = BeanScope.builder().build()) {
13+
var component = beanScope.get(QualifierConsumerComponent.class);
14+
15+
assertThat(component.parent()).isEqualTo("parent-child");
16+
assertThat(component.child()).isEqualTo("child");
17+
}
18+
}
19+
}

inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package io.avaje.inject.generator;
22

33
import static io.avaje.inject.generator.APContext.logError;
4+
import static java.util.stream.Collectors.toList;
5+
6+
import java.util.ArrayList;
7+
import java.util.Collections;
8+
import java.util.LinkedHashSet;
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.Set;
12+
import java.util.stream.Stream;
413

514
import javax.lang.model.element.Element;
615
import javax.lang.model.element.ExecutableElement;
@@ -9,9 +18,6 @@
918

1019
import io.avaje.inject.generator.MethodReader.MethodParam;
1120

12-
import java.util.*;
13-
import java.util.stream.Stream;
14-
1521

1622
final class BeanReader {
1723

@@ -188,11 +194,11 @@ List<MethodReader> factoryMethods() {
188194
}
189195

190196
List<String> provides() {
191-
return typeReader.provides();
197+
return Util.addQualifierSuffix(typeReader.provides(), name);
192198
}
193199

194200
List<String> autoProvides() {
195-
return typeReader.autoProvides();
201+
return Util.addQualifierSuffix(typeReader.autoProvides(), name);
196202
}
197203

198204
String providesAspect() {

inject-generator/src/main/java/io/avaje/inject/generator/Dependency.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@ final class Dependency {
99
private boolean softDependency;
1010
private final boolean conditionalDependency;
1111

12-
Dependency(String name) {
12+
Dependency(String type) {
13+
this(type, "");
14+
}
15+
16+
Dependency(String type, String qualifier) {
1317
String nameStr;
14-
if (name.startsWith(SOFT_DEPENDENCY)) {
18+
if (type.startsWith(SOFT_DEPENDENCY)) {
1519
this.softDependency = true;
1620
this.conditionalDependency = false;
17-
nameStr = ProcessorUtils.trimAnnotations(name.substring(5));
18-
} else if (name.startsWith(CONDITIONAL_DEPENDENCY)) {
21+
nameStr = ProcessorUtils.trimAnnotations(type.substring(5));
22+
} else if (type.startsWith(CONDITIONAL_DEPENDENCY)) {
1923
this.softDependency = true;
2024
this.conditionalDependency = true;
21-
nameStr = ProcessorUtils.trimAnnotations(name.substring(4));
25+
nameStr = ProcessorUtils.trimAnnotations(type.substring(4));
2226
} else {
2327
this.softDependency = false;
2428
this.conditionalDependency = false;
25-
nameStr = ProcessorUtils.trimAnnotations(name);
29+
nameStr = ProcessorUtils.trimAnnotations(type);
2630
}
27-
this.name = nameStr.replace(", ", ",");
31+
this.name = Util.addQualifierSuffixTrim(qualifier, nameStr);
2832
}
2933

30-
Dependency(String name, boolean softDependency) {
31-
this.name = ProcessorUtils.trimAnnotations(name).replace(", ", ",");
34+
Dependency(String name, String qualifier, boolean softDependency) {
35+
this.name = Util.addQualifierSuffixTrim(qualifier, ProcessorUtils.trimAnnotations(name));
3236
this.softDependency = softDependency;
3337
this.conditionalDependency = false;
3438
}
@@ -64,7 +68,9 @@ String dependsOn() {
6468
return toString();
6569
}
6670

67-
/** External dependency */
71+
/**
72+
* External dependency
73+
*/
6874
void markExternal() {
6975
softDependency = true;
7076
}

inject-generator/src/main/java/io/avaje/inject/generator/EventPublisherWriter.java

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,26 @@ static void write(Element element) {
4646
private EventPublisherWriter(Element element) {
4747
final var asType = element.asType();
4848
this.utype = UType.parse(asType).param0();
49-
50-
this.packageName =
51-
APContext.elements()
52-
.getPackageOf(APContext.typeElement(utype.mainType()))
53-
.getQualifiedName()
54-
.toString()
55-
.replaceFirst("java.", "")
56-
+ ".events";
49+
this.packageName = APContext.elements()
50+
.getPackageOf(APContext.typeElement(utype.mainType()))
51+
.getQualifiedName()
52+
.toString()
53+
.replaceFirst("java.", "")
54+
+ ".events";
5755
qualifier = Optional.ofNullable(Util.getNamed(element)).orElse("");
5856
var className =
59-
packageName
60-
+ "."
61-
+ (qualifier.isEmpty() ? "" : "Qualified")
62-
+ Util.shortName(utype).replace(".", "_")
63-
+ "$Publisher";
57+
packageName
58+
+ "."
59+
+ (qualifier.isEmpty() ? "" : "Qualified")
60+
+ Util.shortName(utype).replace(".", "_")
61+
+ "$Publisher";
6462

6563
originName = getUniqueClassName(className, 0);
6664

6765
if (GENERATED_PUBLISHERS.containsKey(originName)) {
66+
//in super niche situations when compiling the same module, we need to tell avaje that these types already exist
67+
//got this when running both my eclipse compiler and then the terminal build
68+
ProcessingContext.addOptionalType(UType.parse(asType).fullWithoutAnnotations(), Util.getNamed(element));
6869
return;
6970
}
7071
importTypes.addAll(utype.importTypes());
@@ -73,13 +74,10 @@ private EventPublisherWriter(Element element) {
7374
}
7475

7576
private String getUniqueClassName(String className, Integer recursiveIndex) {
76-
77-
Optional.ofNullable(APContext.typeElement(className))
78-
.ifPresent(
79-
e ->
80-
GENERATED_PUBLISHERS.put(
81-
e.getQualifiedName().toString(),
82-
Optional.ofNullable(Util.getNamed(e)).orElse("")));
77+
Optional.ofNullable(APContext.typeElement(className)).ifPresent(e ->
78+
GENERATED_PUBLISHERS.put(
79+
e.getQualifiedName().toString(),
80+
Optional.ofNullable(Util.getNamed(e)).orElse("")));
8381

8482
if (Optional.ofNullable(GENERATED_PUBLISHERS.get(className))
8583
.filter(not(qualifier::equals))
@@ -99,18 +97,14 @@ private Writer createFileWriter() throws IOException {
9997
void write() {
10098
try {
10199
var writer = new Append(createFileWriter());
102-
103100
final var shortType = utype.shortWithoutAnnotations();
104101
var typeString = utype.isGeneric() ? getGenericType() : shortType + ".class";
105102

106103
var name = qualifier.isBlank() ? "" : "@Named(\"" + qualifier + "\")\n";
107104
var className = originName.replace(packageName + ".", "");
108-
writer.append(
109-
MessageFormat.format(
110-
TEMPLATE, packageName, imports(), name, className, shortType, typeString, qualifier));
105+
writer.append(MessageFormat.format(TEMPLATE, packageName, imports(), name, className, shortType, typeString, qualifier));
111106
writer.close();
112107
} catch (Exception e) {
113-
e.printStackTrace();
114108
logError("Failed to write EventPublisher class " + e);
115109
}
116110
}
@@ -125,7 +119,6 @@ String imports() {
125119
importTypes.add(NamedPrism.PRISM_TYPE);
126120
}
127121
if (utype.isGeneric()) {
128-
129122
importTypes.add("io.avaje.inject.spi.GenericType");
130123
}
131124

@@ -146,13 +139,11 @@ String getGenericType() {
146139
return sb.toString();
147140
}
148141

149-
private void writeGenericType(
150-
UType type, Map<String, String> seenShortNames, StringBuilder writer) {
142+
private void writeGenericType(UType type, Map<String, String> seenShortNames, StringBuilder writer) {
151143
final var typeShortName = Util.shortName(type.mainType());
152144
final var mainType = seenShortNames.computeIfAbsent(typeShortName, k -> type.mainType());
153145
if (type.isGeneric()) {
154-
final var shortName =
155-
Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
146+
final var shortName = Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
156147
writer.append(shortName);
157148
writer.append("<");
158149
boolean first = true;
@@ -167,8 +158,7 @@ private void writeGenericType(
167158
}
168159
writer.append(">");
169160
} else {
170-
final var shortName =
171-
Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
161+
final var shortName = Objects.equals(type.mainType(), mainType) ? typeShortName : type.mainType();
172162
writer.append(shortName);
173163
}
174164
}

inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class FieldReader {
2727
this.type = utype.toUType();
2828
this.assisted = AssistedPrism.isPresent(element);
2929
if (nullable || element.asType().toString().startsWith("java.util.Optional<")) {
30-
ProcessingContext.addOptionalType(fieldType);
30+
ProcessingContext.addOptionalType(fieldType, name);
3131
}
3232
if (type.fullWithoutAnnotations().startsWith("io.avaje.inject.events.Event")) {
3333
EventPublisherWriter.write(element);

inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import javax.lang.model.util.Elements;
1616

1717
import static java.util.stream.Collectors.joining;
18-
import static java.util.stream.Collectors.toSet;
1918

2019
import java.io.IOException;
2120
import java.nio.file.Files;
@@ -156,11 +155,12 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
156155

157156
maybeElements(roundEnv, ExternalPrism.PRISM_TYPE).stream()
158157
.flatMap(Set::stream)
159-
.map(Element::asType)
160-
.map(UType::parse)
161-
.map(u -> "java.util.List".equals(u.mainType()) ? u.param0() : u)
162-
.map(UType::fullWithoutAnnotations)
163-
.forEach(ProcessingContext::addOptionalType);
158+
.forEach(e -> {
159+
var type = UType.parse(e.asType());
160+
type = "java.util.List".equals(type.mainType()) ? type.param0() : type;
161+
ProcessingContext.addOptionalType(type.fullWithoutAnnotations(), Util.getNamed(e));
162+
ProcessingContext.addOptionalType(type.fullWithoutAnnotations(), null);
163+
});
164164

165165
maybeElements(roundEnv, "io.avaje.spi.ServiceProvider").ifPresent(this::registerSPI);
166166
allScopes.readBeans(roundEnv);

inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.avaje.inject.generator;
22

33
import static io.avaje.inject.generator.APContext.typeElement;
4-
4+
import static java.util.stream.Collectors.toList;
55
import java.util.*;
66
import java.util.stream.Collectors;
7+
import java.util.stream.Stream;
78

89

910
/**
@@ -46,9 +47,9 @@ final class MetaData {
4647
this.shortType = Util.shortName(type);
4748
this.method = meta.method();
4849
this.providesAspect = meta.providesAspect();
49-
this.provides = meta.provides();
50-
this.dependsOn = meta.dependsOn().stream().map(Dependency::new).collect(Collectors.toList());
51-
this.autoProvides = meta.autoProvides();
50+
this.dependsOn = meta.dependsOn().stream().map(d -> new Dependency(d, name)).collect(Collectors.toList());
51+
this.provides = Util.addQualifierSuffix(meta.provides(), name);
52+
this.autoProvides = Util.addQualifierSuffix(meta.autoProvides(), name);
5253
this.importedComponent = meta.importedComponent();
5354
}
5455

@@ -249,21 +250,26 @@ private boolean hasMethod() {
249250
}
250251

251252
private void appendProvides(Append sb, String attribute, List<String> types) {
253+
if (!"dependsOn".equals(attribute)) {
254+
types.removeIf(s -> s.contains(":"));
255+
}
256+
252257
sb.append(",").eol().append(" ").append(attribute).append(" = {");
253258
final var size = types.size();
254259
if (size > 1) {
255260
sb.eol().append(" ");
256261
}
257262
var seen = new HashSet<String>();
258263
for (int i = 0; i < types.size(); i++) {
259-
if (!seen.add(types.get(i))) {
264+
final var depType = types.get(i);
265+
if (!seen.add(depType)) {
260266
continue;
261267
}
262268
if (i > 0) {
263269
sb.append(",").eol().append(" ");
264270
}
265271
sb.append("\"");
266-
sb.append(types.get(i));
272+
sb.append(depType);
267273
sb.append("\"");
268274
}
269275
if (size > 1) {
@@ -276,7 +282,7 @@ void setProvides(List<String> provides) {
276282
this.provides = provides;
277283
}
278284

279-
void setDependsOn(List<String> dependsOn) {
285+
void setDependsOn(List<String> dependsOn, String name) {
280286
this.dependsOn = dependsOn.stream().map(Dependency::new).collect(Collectors.toList());
281287
}
282288

0 commit comments

Comments
 (0)