Skip to content

Commit e78db17

Browse files
authored
Support package private classes (#300)
Like the Jsonb and the Http client PR before that, support package private classes by generating components in the same package
1 parent d523c88 commit e78db17

File tree

23 files changed

+302
-118
lines changed

23 files changed

+302
-118
lines changed

blackbox-test/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>io.avaje</groupId>
77
<artifactId>avaje-validator-parent</artifactId>
8-
<version>2.10</version>
8+
<version>2.11</version>
99
</parent>
1010

1111
<artifactId>validator-blackbox-test</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package example.avaje.pkg_private;
2+
3+
import io.avaje.validation.constraints.Positive;
4+
import jakarta.validation.Valid;
5+
6+
@Valid
7+
record PackagePrivate(@Positive int id) {}

blackbox-test/src/main/java/module-info.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
requires jakarta.inject;
99
requires org.jspecify;
1010

11-
provides io.avaje.validation.spi.ValidationExtension with example.avaje.valid.GeneratedValidatorComponent;
11+
provides io.avaje.validation.spi.ValidationExtension with example.avaje.pkg_private.PkgPrivateValidatorComponent, example.avaje.valid.GeneratedValidatorComponent;
1212
provides io.avaje.inject.spi.InjectExtension with example.avaje.GeneratedModule;
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package example.avaje.pkg_private;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.fail;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Locale;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
import io.avaje.validation.ConstraintViolation;
13+
import io.avaje.validation.ConstraintViolationException;
14+
import io.avaje.validation.Validator;
15+
16+
class PackagePrivateTest {
17+
18+
Validator validator = Validator.builder().build();
19+
20+
@Test
21+
void invalid() {
22+
var pkgPrivate = new PackagePrivate(-100);
23+
List<ConstraintViolation> violations = violations(pkgPrivate);
24+
25+
assertThat(violations).hasSize(1);
26+
}
27+
28+
List<ConstraintViolation> violations(Object any) {
29+
return violations(any, Locale.ENGLISH);
30+
}
31+
32+
List<ConstraintViolation> violations(Object any, Locale locale) {
33+
try {
34+
validator.validate(any, locale);
35+
fail("not expected");
36+
return null;
37+
} catch (ConstraintViolationException e) {
38+
return new ArrayList<>(e.violations());
39+
}
40+
}
41+
}

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<groupId>io.avaje</groupId>
1313
<artifactId>avaje-validator-parent</artifactId>
14-
<version>2.10</version>
14+
<version>2.11</version>
1515

1616
<packaging>pom</packaging>
1717
<name>validator parent</name>
@@ -29,7 +29,7 @@
2929
<maven.compiler.release>17</maven.compiler.release>
3030
<inject.version>11.4</inject.version>
3131
<spi.version>2.12</spi.version>
32-
<project.build.outputTimestamp>2025-04-22T20:38:01Z</project.build.outputTimestamp>
32+
<project.build.outputTimestamp>2025-04-29T01:04:37Z</project.build.outputTimestamp>
3333
</properties>
3434

3535
<modules>

validator-constraints/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>io.avaje</groupId>
77
<artifactId>avaje-validator-parent</artifactId>
8-
<version>2.10</version>
8+
<version>2.11</version>
99
</parent>
1010
<artifactId>avaje-validator-constraints</artifactId>
1111
<name>validator-constraints</name>

validator-generator/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>io.avaje</groupId>
88
<artifactId>avaje-validator-parent</artifactId>
9-
<version>2.10</version>
9+
<version>2.11</version>
1010
</parent>
1111

1212
<artifactId>avaje-validator-generator</artifactId>

validator-generator/src/main/java/io/avaje/validation/generator/AdapterName.java

+16-5
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,25 @@ final class AdapterName {
88
final String adapterPackage;
99
final String fullName;
1010

11-
AdapterName(TypeElement origin) {
12-
String originPackage = APContext.elements().getPackageOf(origin).getQualifiedName().toString();
13-
var name = shortName(origin);
11+
AdapterName(TypeElement type) {
12+
this(type, false);
13+
}
14+
15+
AdapterName(BeanReader beanReader) {
16+
this(beanReader.beanType(), beanReader.isPkgPrivate());
17+
}
18+
19+
AdapterName(TypeElement type, boolean pkgPrivate) {
20+
String originPackage = APContext.elements().getPackageOf(type).getQualifiedName().toString();
21+
var name = shortName(type);
1422
shortName = name.substring(0, name.length() - 1);
15-
if ("".equals(originPackage)) {
23+
if (pkgPrivate) {
24+
this.adapterPackage = originPackage;
25+
} else if ("".equals(originPackage)) {
1626
this.adapterPackage = "valid";
1727
} else {
18-
this.adapterPackage = ProcessingContext.isImported(origin) ? originPackage + ".valid" : originPackage;
28+
this.adapterPackage =
29+
ProcessingContext.isImported(type) ? originPackage + ".valid" : originPackage;
1930
}
2031
this.fullName = adapterPackage + "." + shortName + "ValidationAdapter";
2132
}

validator-generator/src/main/java/io/avaje/validation/generator/BeanReader.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ interface BeanReader {
1717

1818
void writeValidatorMethod(Append writer);
1919

20+
default boolean isPkgPrivate() {
21+
return false;
22+
}
23+
2024
String shortName();
2125

2226
/** Return the short name of the element. */
@@ -28,7 +32,7 @@ default int genericTypeParamsCount() {
2832
return 0;
2933
}
3034

31-
TypeElement getBeanType();
35+
TypeElement beanType();
3236

3337
void cascadeTypes(Set<String> extraTypes);
3438

validator-generator/src/main/java/io/avaje/validation/generator/ClassReader.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Set;
55
import java.util.TreeSet;
66

7+
import javax.lang.model.element.Modifier;
78
import javax.lang.model.element.TypeElement;
89

910
final class ClassReader implements BeanReader {
@@ -15,6 +16,7 @@ final class ClassReader implements BeanReader {
1516
private final Set<String> importTypes = new TreeSet<>();
1617
private final TypeReader typeReader;
1718
private final boolean nonAccessibleField;
19+
private final boolean pkgPrivate;
1820

1921
ClassReader(TypeElement beanType) {
2022
this(beanType, null);
@@ -28,6 +30,7 @@ final class ClassReader implements BeanReader {
2830
typeReader.process();
2931
this.nonAccessibleField = typeReader.nonAccessibleField();
3032
this.allFields = typeReader.allFields();
33+
this.pkgPrivate = !beanType.getModifiers().contains(Modifier.PUBLIC);
3134
importTypes.add("java.util.List");
3235
importTypes.add("java.util.Set");
3336
importTypes.add("java.util.Map");
@@ -54,7 +57,7 @@ public String shortName() {
5457
}
5558

5659
@Override
57-
public TypeElement getBeanType() {
60+
public TypeElement beanType() {
5861
return beanType;
5962
}
6063

@@ -137,4 +140,9 @@ public void writeValidatorMethod(Append writer) {
137140
writer.append(" return true;", shortName).eol();
138141
writer.append(" }").eol();
139142
}
143+
144+
@Override
145+
public boolean isPkgPrivate() {
146+
return pkgPrivate;
147+
}
140148
}

validator-generator/src/main/java/io/avaje/validation/generator/ComponentMetaData.java

+45-17
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ public String toString() {
2020
return allTypes.toString();
2121
}
2222

23-
/** Ensure the component name has been initialised. */
24-
void initialiseFullName() {
25-
fullName();
26-
}
27-
2823
boolean contains(String type) {
2924
return allTypes.contains(type);
3025
}
@@ -45,25 +40,24 @@ void setFullName(String fullName) {
4540
this.fullName = fullName;
4641
}
4742

48-
String fullName() {
43+
String fullName(boolean pkgPrivate) {
4944
if (fullName == null) {
5045
final List<String> types = new ArrayList<>(allTypes);
5146
for (final var adapter : annotationAdapters) {
5247
adapter.getQualifiedName().toString().transform(types::add);
5348
}
5449
String topPackage = TopPackage.of(types);
55-
if (!topPackage.endsWith(".valid")) {
50+
if (!topPackage.endsWith(".valid") && !pkgPrivate) {
5651
topPackage += ".valid";
5752
}
58-
fullName = topPackage + ".GeneratedValidatorComponent";
53+
fullName =
54+
pkgPrivate
55+
? topPackage + "." + name(topPackage) + "ValidatorComponent"
56+
: topPackage + ".GeneratedValidatorComponent";
5957
}
6058
return fullName;
6159
}
6260

63-
String packageName() {
64-
return ProcessorUtils.packageOf(fullName());
65-
}
66-
6761
List<String> all() {
6862
return allTypes;
6963
}
@@ -81,18 +75,20 @@ Collection<String> allImports() {
8175
final Set<String> packageImports = new TreeSet<>();
8276
for (final String adapterFullName : allTypes) {
8377
packageImports.add(adapterFullName);
84-
packageImports.add(ProcessorUtils.extractEnclosingFQN(Util.baseTypeOfAdapter(adapterFullName)));
78+
packageImports.add(
79+
ProcessorUtils.extractEnclosingFQN(Util.baseTypeOfAdapter(adapterFullName)));
8580
}
8681

8782
for (final var adapter : annotationAdapters) {
8883
final var adapterFullName = adapter.getQualifiedName().toString();
8984
packageImports.add(adapterFullName);
90-
packageImports.add(ProcessorUtils.extractEnclosingFQN(Util.baseTypeOfAdapter(adapterFullName)));
85+
packageImports.add(
86+
ProcessorUtils.extractEnclosingFQN(Util.baseTypeOfAdapter(adapterFullName)));
9187

9288
ConstraintAdapterPrism.getInstanceOn(adapter)
93-
.value()
94-
.toString()
95-
.transform(packageImports::add);
89+
.value()
90+
.toString()
91+
.transform(packageImports::add);
9692
}
9793

9894
return packageImports;
@@ -101,4 +97,36 @@ Collection<String> allImports() {
10197
public boolean isEmpty() {
10298
return allTypes.isEmpty() && factoryTypes.isEmpty() && annotationAdapters.isEmpty();
10399
}
100+
101+
static String name(String name) {
102+
if (name == null) {
103+
return null;
104+
}
105+
final int pos = name.lastIndexOf('.');
106+
if (pos > -1) {
107+
name = name.substring(pos + 1);
108+
}
109+
return camelCase(name).replaceFirst("Valid", "Generated");
110+
}
111+
112+
private static String camelCase(String name) {
113+
StringBuilder sb = new StringBuilder(name.length());
114+
boolean upper = true;
115+
for (char aChar : name.toCharArray()) {
116+
if (Character.isLetterOrDigit(aChar)) {
117+
if (upper) {
118+
aChar = Character.toUpperCase(aChar);
119+
upper = false;
120+
}
121+
sb.append(aChar);
122+
} else if (toUpperOn(aChar)) {
123+
upper = true;
124+
}
125+
}
126+
return sb.toString();
127+
}
128+
129+
private static boolean toUpperOn(char aChar) {
130+
return aChar == ' ' || aChar == '-' || aChar == '_';
131+
}
104132
}

0 commit comments

Comments
 (0)