Skip to content

Commit 949f780

Browse files
authored
Merge pull request #368 from SentryMan/pkg-private
Fully Support pkg-private json adapters
2 parents 5ac2eae + 63a3d83 commit 949f780

File tree

17 files changed

+358
-123
lines changed

17 files changed

+358
-123
lines changed
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import io.avaje.jsonb.spi.JsonbExtension;
2+
13
module blackbox.test {
24

35
requires static io.avaje.jsonb;
46
requires static io.avaje.spi;
57
requires java.validation;
68
requires io.avaje.json.node;
79

8-
provides io.avaje.jsonb.spi.JsonbExtension with org.example.customer.customtype.CustomTypeComponent, org.example.jsonb.GeneratedJsonComponent;
10+
provides JsonbExtension
11+
with
12+
org.example.customer.customtype.CustomTypeComponent,
13+
org.example.jsonb.GeneratedJsonComponent,
14+
org.example.pkg_private.PkgPrivateJsonComponent;
915

1016
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.example.pkg_private;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json
6+
record PackagePrivate(long id) {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.example.pkg_private;
2+
3+
import io.avaje.jsonb.Json;
4+
import io.avaje.jsonb.Json.Creator;
5+
6+
@Json
7+
public class PackagePrivateCreator {
8+
9+
long id;
10+
11+
@Creator
12+
PackagePrivateCreator(long id) {
13+
this.id = id;
14+
}
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.example.pkg_private;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.avaje.jsonb.JsonType;
8+
import io.avaje.jsonb.Jsonb;
9+
10+
class PackagePrivateCreatorTest {
11+
12+
final JsonType<PackagePrivateCreator> jsonb =
13+
Jsonb.builder().build().type(PackagePrivateCreator.class);
14+
15+
@Test
16+
void to_From_Json() {
17+
final var bean = new PackagePrivateCreator(5);
18+
final var str = jsonb.toJson(bean);
19+
assertThat(str).isEqualTo("{\"id\":5}");
20+
21+
final var from = jsonb.fromJson(str);
22+
assertThat(bean.id).isEqualTo(from.id);
23+
}
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.pkg_private;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.avaje.jsonb.JsonType;
8+
import io.avaje.jsonb.Jsonb;
9+
10+
class PackagePrivateTest {
11+
12+
final JsonType<PackagePrivate> jsonb = Jsonb.builder().build().type(PackagePrivate.class);
13+
14+
@Test
15+
void to_From_Json() {
16+
final var bean = new PackagePrivate(5);
17+
final var str = jsonb.toJson(bean);
18+
assertThat(str).isEqualTo("{\"id\":5}");
19+
20+
final var from = jsonb.fromJson(str);
21+
assertThat(bean).isEqualTo(from);
22+
}
23+
}

jsonb-generator/src/main/java/io/avaje/jsonb/generator/AdapterName.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ 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(BeanReader beanReader) {
12+
String originPackage = APContext.elements().getPackageOf(beanReader.beanType()).getQualifiedName().toString();
13+
var name = shortName(beanReader.beanType());
1414
shortName = name.substring(0, name.length() - 1);
15-
if ("".equals(originPackage)) {
15+
if (beanReader.isPkgPrivate()) {
16+
this.adapterPackage = originPackage;
17+
} else if ("".equals(originPackage)) {
1618
this.adapterPackage = "jsonb";
1719
} else {
18-
this.adapterPackage = ProcessingContext.isImported(origin) ? originPackage + ".jsonb" : originPackage;
20+
this.adapterPackage = ProcessingContext.isImported(beanReader.beanType()) ? originPackage + ".jsonb" : originPackage;
1921
}
2022
this.fullName = adapterPackage + "." + shortName + "JsonAdapter";
2123
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ default void writeViewSupport(Append writer) {}
2323

2424
boolean supportsViewBuilder();
2525

26+
boolean isPkgPrivate();
27+
2628
String shortName();
2729

2830
/** Return the short name of the element. */

jsonb-generator/src/main/java/io/avaje/jsonb/generator/ClassReader.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
final class ClassReader implements BeanReader {
1616

17+
private static final boolean useInstanceofPattern = jdkVersion() >= 17;
18+
private static final boolean nullSwitch = jdkVersion() >= 21 || jdkVersion() >= 17 && previewEnabled();
19+
1720
private final TypeElement beanType;
1821
private final String shortName;
1922
private final String type;
@@ -34,12 +37,12 @@ final class ClassReader implements BeanReader {
3437
private final boolean isRecord;
3538
private final boolean usesTypeProperty;
3639
private final boolean useEnum;
37-
private static final boolean useInstanceofPattern = jdkVersion() >= 17;
38-
private static final boolean nullSwitch = jdkVersion() >= 21 || (jdkVersion() >= 17 && previewEnabled());
3940
private final Map<String, Integer> frequencyMap = new HashMap<>();
4041
private final Map<String, Boolean> isCommonFieldMap = new HashMap<>();
4142
private final boolean optional;
4243
private final List<TypeSubTypeMeta> subTypes;
44+
private final boolean pkgPrivate;
45+
4346
/** An Interface/abstract type with a single implementation */
4447
private ClassReader implementation;
4548

@@ -66,6 +69,7 @@ final class ClassReader implements BeanReader {
6669
this.subTypes = typeReader.subTypes();
6770
this.readOnlyInterface = typeReader.extendsThrowable() || allFields.isEmpty() && subTypes.isEmpty();
6871
this.methodProperties = typeReader.methodProperties();
72+
this.pkgPrivate = typeReader.isPkgPrivate();
6973

7074
subTypes.stream().map(TypeSubTypeMeta::type).forEach(importTypes::add);
7175

@@ -442,7 +446,7 @@ public void writeFromJson(Append writer) {
442446
}
443447

444448
private void writeFromJsonImplementation(Append writer, String varName) {
445-
final boolean directLoad = (constructor == null && !hasSubTypes && !optional);
449+
final boolean directLoad = constructor == null && !hasSubTypes && !optional;
446450
if (directLoad) {
447451
// default public constructor
448452
writer.append(" %s _$%s = new %s();", shortName, varName, shortName).eol();
@@ -587,7 +591,7 @@ private void writeFromJsonWithSubTypes(Append writer) {
587591
}
588592

589593
String constructorParamName(String name) {
590-
if ((unmappedField != null) && unmappedField.fieldName().equals(name)) {
594+
if (unmappedField != null && unmappedField.fieldName().equals(name)) {
591595
return "unmapped";
592596
}
593597
return "_val$" + name;
@@ -708,4 +712,9 @@ private void writeSubTypeCase(String name, Append writer, List<FieldReader> comm
708712
private String typePropertyKey() {
709713
return caseInsensitiveKeys ? typeProperty.toLowerCase() : typeProperty;
710714
}
715+
716+
@Override
717+
public boolean isPkgPrivate() {
718+
return pkgPrivate;
719+
}
711720
}

jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentMetaData.java

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ public String toString() {
1616
return allTypes.toString();
1717
}
1818

19-
/**
20-
* Ensure the component name has been initialised.
21-
*/
22-
void initialiseFullName() {
23-
fullName();
24-
}
25-
2619
boolean contains(String type) {
2720
return allTypes.contains(type);
2821
}
@@ -46,21 +39,20 @@ void setFullName(String fullName) {
4639
this.fullName = fullName;
4740
}
4841

49-
String fullName() {
42+
String fullName(boolean pkgPrivate) {
5043
if (fullName == null) {
5144
String topPackage = TopPackage.of(allTypes);
52-
if (!topPackage.endsWith(".jsonb")) {
45+
if (!topPackage.endsWith(".jsonb") && !pkgPrivate) {
5346
topPackage += ".jsonb";
5447
}
55-
fullName = topPackage + ".GeneratedJsonComponent";
48+
fullName =
49+
pkgPrivate
50+
? topPackage + "." + name(topPackage) + "JsonComponent"
51+
: topPackage + ".GeneratedJsonComponent";
5652
}
5753
return fullName;
5854
}
5955

60-
String packageName() {
61-
return Util.packageOf(fullName());
62-
}
63-
6456
List<String> all() {
6557
return allTypes;
6658
}
@@ -94,4 +86,36 @@ Collection<String> allImports() {
9486
public boolean isEmpty() {
9587
return allTypes.isEmpty() && factoryTypes.isEmpty();
9688
}
89+
90+
static String name(String name) {
91+
if (name == null) {
92+
return null;
93+
}
94+
final int pos = name.lastIndexOf('.');
95+
if (pos > -1) {
96+
name = name.substring(pos + 1);
97+
}
98+
return camelCase(name).replaceFirst("Jsonb", "Generated");
99+
}
100+
101+
private static String camelCase(String name) {
102+
StringBuilder sb = new StringBuilder(name.length());
103+
boolean upper = true;
104+
for (char aChar : name.toCharArray()) {
105+
if (Character.isLetterOrDigit(aChar)) {
106+
if (upper) {
107+
aChar = Character.toUpperCase(aChar);
108+
upper = false;
109+
}
110+
sb.append(aChar);
111+
} else if (toUpperOn(aChar)) {
112+
upper = true;
113+
}
114+
}
115+
return sb.toString();
116+
}
117+
118+
private static boolean toUpperOn(char aChar) {
119+
return aChar == ' ' || aChar == '-' || aChar == '_';
120+
}
97121
}

jsonb-generator/src/main/java/io/avaje/jsonb/generator/ComponentReader.java

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
11
package io.avaje.jsonb.generator;
22

3-
import static io.avaje.jsonb.generator.APContext.*;
4-
import javax.annotation.processing.FilerException;
3+
import static io.avaje.jsonb.generator.APContext.typeElement;
4+
import static java.util.stream.Collectors.toList;
5+
6+
import java.util.Map;
7+
58
import javax.lang.model.element.AnnotationMirror;
9+
import javax.lang.model.element.Modifier;
610
import javax.lang.model.element.TypeElement;
711
import javax.lang.model.type.TypeMirror;
8-
import javax.tools.FileObject;
9-
import javax.tools.StandardLocation;
10-
import java.io.FileNotFoundException;
11-
import java.io.LineNumberReader;
12-
import java.io.Reader;
13-
import java.nio.file.NoSuchFileException;
14-
import java.util.ArrayList;
15-
import java.util.Collections;
16-
import java.util.List;
17-
import java.util.Objects;
1812

1913
final class ComponentReader {
2014

2115
private final ComponentMetaData componentMetaData;
16+
private final Map<String, ComponentMetaData> privateMetaData;
2217

23-
ComponentReader(ComponentMetaData metaData) {
18+
ComponentReader(ComponentMetaData metaData, Map<String, ComponentMetaData> privateMetaData) {
2419
this.componentMetaData = metaData;
20+
this.privateMetaData = privateMetaData;
2521
}
2622

2723
void read() {
28-
ProcessingContext.readExistingMetaInfServices().stream()
29-
.map(APContext::typeElement)
30-
.filter(Objects::nonNull)
31-
.filter(t -> "io.avaje.jsonb.spi.GeneratedComponent".equals(t.getSuperclass().toString()))
32-
.findFirst()
33-
.ifPresent(
34-
moduleType -> {
35-
if (moduleType != null) {
36-
componentMetaData.setFullName(moduleType.getQualifiedName().toString());
37-
readMetaData(moduleType);
38-
}
39-
});
24+
for (String fqn : ProcessingContext.readExistingMetaInfServices()) {
25+
final TypeElement moduleType = typeElement(fqn);
26+
27+
if (isGeneratedComponent(moduleType)) {
28+
var adapters =
29+
MetaDataPrism.getInstanceOn(moduleType).value().stream()
30+
.map(APContext::asTypeElement)
31+
.collect(toList());
32+
33+
if (adapters.get(0).getModifiers().contains(Modifier.PUBLIC)) {
34+
componentMetaData.setFullName(fqn);
35+
adapters.forEach(t -> readMetaData(moduleType));
36+
37+
} else {
38+
// non-public adapters grouped by packageName, does not support generic types (JsonFactory)
39+
var packageName = APContext.elements().getPackageOf(moduleType).getQualifiedName().toString();
40+
var meta = privateMetaData.computeIfAbsent(packageName, k -> new ComponentMetaData());
41+
adapters.stream()
42+
.map(TypeElement::getQualifiedName)
43+
.map(Object::toString)
44+
.forEach(meta::add);
45+
}
46+
}
47+
}
48+
}
49+
50+
private static boolean isGeneratedComponent(TypeElement moduleType) {
51+
return moduleType != null && "io.avaje.jsonb.spi.GeneratedComponent".equals(moduleType.getSuperclass().toString());
4052
}
4153

42-
/**
43-
* Read the existing JsonAdapters from the MetaData annotation of the generated component.
44-
*/
54+
/** Read the existing JsonAdapters from the MetaData annotation of the generated component. */
4555
private void readMetaData(TypeElement moduleType) {
4656
for (final AnnotationMirror annotationMirror : moduleType.getAnnotationMirrors()) {
4757

@@ -50,13 +60,13 @@ private void readMetaData(TypeElement moduleType) {
5060

5161
if (metaData != null) {
5262
metaData.value().stream()
53-
.map(TypeMirror::toString)
54-
.forEach(componentMetaData::add);
63+
.map(TypeMirror::toString)
64+
.forEach(componentMetaData::add);
5565

5666
} else if (metaDataFactory != null) {
5767
metaDataFactory.value().stream()
58-
.map(TypeMirror::toString)
59-
.forEach(componentMetaData::addFactory);
68+
.map(TypeMirror::toString)
69+
.forEach(componentMetaData::addFactory);
6070
}
6171
}
6272
}

0 commit comments

Comments
 (0)