Skip to content

Commit 0867541

Browse files
authored
Merge pull request #36 from SentryMan/mixin
Add a feature like Jackson Mixin
2 parents 1f50b0d + 0a0d75e commit 0867541

File tree

8 files changed

+228
-24
lines changed

8 files changed

+228
-24
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.example.customer.mixin;
2+
3+
public class CrewMate {
4+
5+
private String c;
6+
private Integer susLv;
7+
private Integer taskNumber;
8+
9+
public CrewMate(String c, Integer susLv, Integer taskNumber) {
10+
this.c = c;
11+
this.susLv = susLv;
12+
this.taskNumber = taskNumber;
13+
}
14+
15+
public String getC() {
16+
return c;
17+
}
18+
19+
public void setC(String c) {
20+
this.c = c;
21+
}
22+
23+
public Integer getSusLv() {
24+
return susLv;
25+
}
26+
27+
public void setSusLv(Integer susLv) {
28+
this.susLv = susLv;
29+
}
30+
31+
public Integer getTaskNumber() {
32+
return taskNumber;
33+
}
34+
35+
public void setTaskNumber(Integer taskNumber) {
36+
this.taskNumber = taskNumber;
37+
}
38+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.example.customer.mixin;
2+
3+
import io.avaje.jsonb.Json;
4+
5+
@Json.MixIn(CrewMate.class)
6+
public abstract class CrewMateMixIn {
7+
8+
@Json.Property("color")
9+
private String c;
10+
11+
@Json.Ignore(deserialize = true)
12+
private Integer susLv;
13+
14+
@Json.Property("wrongtype")
15+
private int taskNumber;
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.example.customer.mixin;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.avaje.jsonb.Jsonb;
8+
9+
class MixinTest {
10+
11+
Jsonb jsonb = Jsonb.builder().build();
12+
13+
@Test
14+
void toJsonFromJson() {
15+
final var bean = new CrewMate("red", 999, 45);
16+
17+
final var asJson = jsonb.toJson(bean);
18+
assertThat(asJson).isEqualTo("{\"color\":\"red\",\"taskNumber\":45}");
19+
20+
final var fromJson = jsonb.type(CrewMate.class).fromJson("{\"color\":\"blue\",\"susLv\":\"0\",\"taskNumber\":45}}");
21+
assertThat(fromJson.getC()).isEqualTo("blue");
22+
assertThat(fromJson.getSusLv()).isZero();
23+
assertThat(fromJson.getTaskNumber()).isEqualTo(45);
24+
}
25+
}

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package io.avaje.jsonb.generator;
22

3-
import io.avaje.jsonb.Json;
4-
5-
import javax.lang.model.element.Element;
6-
import javax.lang.model.element.TypeElement;
73
import java.util.HashSet;
84
import java.util.List;
5+
import java.util.Map;
96
import java.util.Set;
107
import java.util.TreeSet;
118

9+
import javax.lang.model.element.Element;
10+
import javax.lang.model.element.TypeElement;
11+
12+
import io.avaje.jsonb.Json;
13+
1214
class BeanReader {
1315

1416
private final TypeElement beanType;
@@ -42,7 +44,27 @@ class BeanReader {
4244
this.constructor = typeReader.constructor();
4345
}
4446

45-
@Override
47+
public BeanReader(
48+
TypeElement beanType,
49+
TypeElement mixInElement,
50+
ProcessingContext context) {
51+
52+
this.beanType = beanType;
53+
this.type = beanType.getQualifiedName().toString();
54+
this.shortName = shortName(beanType);
55+
final NamingConventionReader ncReader = new NamingConventionReader(beanType);
56+
this.namingConvention = ncReader.get();
57+
this.typeProperty = ncReader.typeProperty();
58+
this.typeReader = new TypeReader(beanType, mixInElement, context, namingConvention);
59+
typeReader.process();
60+
this.nonAccessibleField = typeReader.nonAccessibleField();
61+
this.hasSubTypes = typeReader.hasSubTypes();
62+
this.allFields = typeReader.allFields();
63+
this.constructor = typeReader.constructor();
64+
65+
}
66+
67+
@Override
4668
public String toString() {
4769
return beanType.toString();
4870
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class ImportReader {
1212

1313
private static final String JSON_IMPORT = "io.avaje.jsonb.Json.Import";
14+
private static final String JSON_MIXIN = "io.avaje.jsonb.Json.MixIn";
1415

1516
/**
1617
* Read the Json.Import annotation using annotation mirrors.
@@ -31,4 +32,15 @@ List<String> read(Element element) {
3132
return fullNames;
3233
}
3334

35+
String readMixin(Element element) {
36+
for (final AnnotationMirror mirror : element.getAnnotationMirrors()) {
37+
if (JSON_MIXIN.equals(mirror.getAnnotationType().toString())) {
38+
for (final Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
39+
mirror.getElementValues().entrySet()) {
40+
return Util.trimClassSuffix(entry.getValue().toString());
41+
}
42+
}
43+
}
44+
return null;
45+
}
3446
}

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

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package io.avaje.jsonb.generator;
22

3-
import io.avaje.jsonb.Json;
3+
import java.io.IOException;
4+
import java.util.ArrayList;
5+
import java.util.HashSet;
6+
import java.util.LinkedHashSet;
7+
import java.util.List;
8+
import java.util.Set;
9+
import java.util.TreeSet;
410

511
import javax.annotation.processing.AbstractProcessor;
612
import javax.annotation.processing.ProcessingEnvironment;
@@ -9,15 +15,16 @@
915
import javax.lang.model.element.Element;
1016
import javax.lang.model.element.ElementKind;
1117
import javax.lang.model.element.TypeElement;
12-
import java.io.IOException;
13-
import java.util.*;
18+
19+
import io.avaje.jsonb.Json;
1420

1521
public class Processor extends AbstractProcessor {
1622

1723
private final ComponentMetaData metaData = new ComponentMetaData();
1824
private final ImportReader importReader = new ImportReader();
1925
private final List<BeanReader> allReaders = new ArrayList<>();
2026
private final Set<String> sourceTypes = new HashSet<>();
27+
private final Set<String> mixInImports = new HashSet<>();
2128

2229
private ProcessingContext context;
2330
private SimpleComponentWriter componentWriter;
@@ -43,6 +50,7 @@ public Set<String> getSupportedAnnotationTypes() {
4350
Set<String> annotations = new LinkedHashSet<>();
4451
annotations.add(Json.class.getCanonicalName());
4552
annotations.add(Json.Import.class.getCanonicalName());
53+
annotations.add(Json.MixIn.class.getCanonicalName());
4654
return annotations;
4755
}
4856

@@ -61,6 +69,7 @@ private void readModule() {
6169
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment round) {
6270
readModule();
6371
writeAdapters(round.getElementsAnnotatedWith(Json.class));
72+
writeAdaptersForMixInTypes(round.getElementsAnnotatedWith(Json.MixIn.class));
6473
writeAdaptersForImported(round.getElementsAnnotatedWith(Json.Import.class));
6574
initialiseComponent();
6675
cascadeTypes();
@@ -108,13 +117,35 @@ private boolean ignoreType(String type) {
108117
|| sourceTypes.contains(type);
109118
}
110119

111-
/**
112-
* Elements that have a {@code @Json.Import} annotation.
113-
*/
120+
/** Elements that have a {@code @Json.MixIn} annotation. */
121+
private void writeAdaptersForMixInTypes(Set<? extends Element> mixInElements) {
122+
123+
for (final Element mixin : mixInElements) {
124+
final String importType = importReader.readMixin(mixin);
125+
if (importType != null) {
126+
final TypeElement element = context.element(importType);
127+
if (element == null) {
128+
context.logError("Unable to find imported element " + importType);
129+
} else {
130+
mixInImports.add(importType);
131+
writeAdapterForMixInType(element, context.element(mixin.asType().toString()));
132+
}
133+
}
134+
}
135+
}
136+
137+
/** Elements that have a {@code @Json.Import} annotation. */
114138
private void writeAdaptersForImported(Set<? extends Element> importedElements) {
115-
for (Element importedElement : importedElements) {
116-
for (String importType : importReader.read(importedElement)) {
117-
TypeElement element = context.element(importType);
139+
140+
for (final Element importedElement : importedElements) {
141+
for (final String importType : importReader.read(importedElement)) {
142+
// if imported by mixin annotation skip
143+
if (mixInImports.contains(importType)) {
144+
continue;
145+
}
146+
147+
final TypeElement element = context.element(importType);
148+
118149
if (element == null) {
119150
context.logError("Unable to find imported element " + importType);
120151
} else {
@@ -128,7 +159,7 @@ private void initialiseComponent() {
128159
metaData.initialiseFullName();
129160
try {
130161
componentWriter.initialise();
131-
} catch (IOException e) {
162+
} catch (final IOException e) {
132163
context.logError("Error creating writer for JsonbComponent", e);
133164
}
134165
}
@@ -138,7 +169,7 @@ private void writeComponent(boolean processingOver) {
138169
try {
139170
componentWriter.write();
140171
componentWriter.writeMetaInf();
141-
} catch (IOException e) {
172+
} catch (final IOException e) {
142173
context.logError("Error writing component", e);
143174
}
144175
}
@@ -148,7 +179,7 @@ private void writeComponent(boolean processingOver) {
148179
* Read the beans that have changed.
149180
*/
150181
private void writeAdapters(Set<? extends Element> beans) {
151-
for (Element element : beans) {
182+
for (final Element element : beans) {
152183
if (!(element instanceof TypeElement)) {
153184
context.logError("unexpected type [" + element + "]");
154185
} else {
@@ -158,7 +189,16 @@ private void writeAdapters(Set<? extends Element> beans) {
158189
}
159190

160191
private void writeAdapterForType(TypeElement typeElement) {
161-
BeanReader beanReader = new BeanReader(typeElement, context);
192+
final BeanReader beanReader = new BeanReader(typeElement, context);
193+
writeAdapter(typeElement, beanReader);
194+
}
195+
196+
private void writeAdapterForMixInType(TypeElement typeElement, TypeElement mixin) {
197+
final BeanReader beanReader = new BeanReader(typeElement, mixin, context);
198+
writeAdapter(typeElement, beanReader);
199+
}
200+
201+
private void writeAdapter(TypeElement typeElement, BeanReader beanReader) {
162202
beanReader.read();
163203
if (beanReader.nonAccessibleField()) {
164204
if (beanReader.hasJsonAnnotation()) {
@@ -167,14 +207,13 @@ private void writeAdapterForType(TypeElement typeElement) {
167207
return;
168208
}
169209
try {
170-
SimpleAdapterWriter beanWriter = new SimpleAdapterWriter(beanReader, context);
210+
final SimpleAdapterWriter beanWriter = new SimpleAdapterWriter(beanReader, context);
171211
metaData.add(beanWriter.fullName());
172212
beanWriter.write();
173213
allReaders.add(beanReader);
174214
sourceTypes.add(typeElement.getSimpleName().toString());
175-
} catch (IOException e) {
215+
} catch (final IOException e) {
176216
context.logError("Error writing JsonAdapter for %s %s", beanReader, e);
177217
}
178218
}
179-
180219
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import javax.lang.model.element.*;
66
import java.util.*;
7+
import java.util.stream.Collectors;
78

89
/**
910
* Read points for field injection and method injection
@@ -32,9 +33,29 @@ class TypeReader {
3233
private TypeSubTypeMeta currentSubType;
3334
private boolean nonAccessibleField;
3435

36+
private final Map<String, Element> mixInFields;
37+
3538
TypeReader(TypeElement baseType, ProcessingContext context, NamingConvention namingConvention) {
3639
this.baseType = baseType;
3740
this.context = context;
41+
this.mixInFields = new HashMap<>();
42+
this.namingConvention = namingConvention;
43+
this.hasJsonAnnotation = baseType.getAnnotation(Json.class) != null;
44+
this.subTypes = new TypeSubTypeReader(baseType, context);
45+
}
46+
47+
public TypeReader(
48+
TypeElement baseType,
49+
TypeElement mixInType,
50+
ProcessingContext context,
51+
NamingConvention namingConvention) {
52+
53+
this.baseType = baseType;
54+
this.mixInFields =
55+
mixInType.getEnclosedElements().stream()
56+
.filter(e -> e.getKind() == ElementKind.FIELD)
57+
.collect(Collectors.toMap(e -> e.getSimpleName().toString(), e -> e));
58+
this.context = context;
3859
this.namingConvention = namingConvention;
3960
this.hasJsonAnnotation = baseType.getAnnotation(Json.class) != null;
4061
this.subTypes = new TypeSubTypeReader(baseType, context);
@@ -74,6 +95,11 @@ void read(TypeElement type) {
7495
}
7596

7697
private void readField(Element element, List<FieldReader> localFields) {
98+
final Element mixInField = mixInFields.get(element.getSimpleName().toString());
99+
if (mixInField != null && mixInField.asType().equals(element.asType())) {
100+
101+
element = mixInField;
102+
}
77103
if (includeField(element)) {
78104
localFields.add(new FieldReader(element, namingConvention, currentSubType));
79105
}

0 commit comments

Comments
 (0)