Skip to content

Commit d22fc4a

Browse files
authored
Merge pull request #148 from avaje/feature/module-info-generator
Add built in support for Throwable and StackTraceElement
2 parents 6c76839 + 2d4c404 commit d22fc4a

File tree

4 files changed

+154
-4
lines changed

4 files changed

+154
-4
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.example.customer.throwable;
2+
3+
public class MyThrowable extends IllegalArgumentException {
4+
5+
public MyThrowable(String message) {
6+
super(message);
7+
}
8+
public MyThrowable(String message, IllegalArgumentException cause) {
9+
super(message, cause);
10+
}
11+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.example.customer.throwable;
2+
3+
import io.avaje.jsonb.Json;
4+
import io.avaje.jsonb.JsonType;
5+
import io.avaje.jsonb.Jsonb;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
@Json.Import(MyThrowable.class)
11+
class ThrowableTest {
12+
13+
Jsonb jsonb = Jsonb.builder().build();
14+
15+
@Test
16+
void toJson() {
17+
MyThrowable e = new MyThrowable("foo");
18+
String asJson = jsonb.toJson(e);
19+
assertThat(asJson).startsWith("{\"message\":\"foo\",\"stackTrace\":[\"org.example.customer.throwable.ThrowableTest.toJson(ThrowableTest.java:17)\",");
20+
}
21+
22+
23+
@Test
24+
void toJsonViaThrowable() {
25+
MyThrowable e = new MyThrowable("foo");
26+
27+
JsonType<Throwable> jsonThrowable = jsonb.type(Throwable.class);
28+
String asJson = jsonThrowable.toJson(e);
29+
assertThat(asJson).startsWith("{\"type\":\"class org.example.customer.throwable.MyThrowable\",\"message\":\"foo\",\"stackTrace\":[\"org.example.customer.throwable.ThrowableTest.toJsonViaThrowable");
30+
}
31+
32+
@Test
33+
void toJsonWithCauseViaThrowable() {
34+
IllegalArgumentException cause = new IllegalArgumentException("bar");
35+
MyThrowable e = new MyThrowable("foo", cause);
36+
37+
JsonType<Throwable> jsonThrowable = jsonb.type(Throwable.class);
38+
String asJson = jsonThrowable.toJson(e);
39+
assertThat(asJson).startsWith("{\"type\":\"class org.example.customer.throwable.MyThrowable\",\"message\":\"foo\",\"stackTrace\":[\"org.example.customer.throwable.ThrowableTest.toJsonWithCauseViaThrowable");
40+
assertThat(asJson).contains("],\"cause\":{\"type\":\"class java.lang.IllegalArgumentException\",\"message\":\"bar\",\"stackTrace\":[");
41+
}
42+
}

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static io.avaje.jsonb.generator.ProcessingContext.*;
44

55
import javax.lang.model.element.*;
6+
import javax.lang.model.type.TypeMirror;
67
import java.util.*;
78
import java.util.stream.Collectors;
89

@@ -13,6 +14,8 @@
1314
final class TypeReader {
1415

1516
private static final String JAVA_LANG_OBJECT = "java.lang.Object";
17+
private static final String JAVA_LANG_THROWABLE = "java.lang.Throwable";
18+
private static final Set<String> THROWABLE_INCLUDES = Set.of("getMessage", "getCause", "getStackTrace", "getSuppressed");
1619

1720
private final List<MethodReader> publicConstructors = new ArrayList<>();
1821
private final List<FieldReader> allFields = new ArrayList<>();
@@ -42,6 +45,8 @@ final class TypeReader {
4245
private final List<MethodProperty> methodProperties = new ArrayList<>();
4346

4447
private boolean optional;
48+
/** Set when the type is known to extend Throwable */
49+
private boolean extendsThrowable;
4550

4651
TypeReader(TypeElement baseType, TypeElement mixInType, NamingConvention namingConvention, String typePropertyKey) {
4752
this.baseType = baseType;
@@ -156,7 +161,7 @@ private void readConstructor(Element element, TypeElement type) {
156161

157162
private void readMethod(Element element, TypeElement type) {
158163
ExecutableElement methodElement = (ExecutableElement) element;
159-
if (methodElement.getModifiers().contains(Modifier.PUBLIC)) {
164+
if (checkMethod2(methodElement)) {
160165
List<? extends VariableElement> parameters = methodElement.getParameters();
161166
final String methodKey = methodElement.getSimpleName().toString();
162167
MethodReader methodReader = new MethodReader(methodElement, type).read();
@@ -166,14 +171,27 @@ private void readMethod(Element element, TypeElement type) {
166171
}
167172
allSetterMethods.put(methodKey.toLowerCase(), methodReader);
168173
} else if (parameters.size() == 0) {
169-
if (!maybeGetterMethods.containsKey(methodKey)) {
170-
maybeGetterMethods.put(methodKey, methodReader);
174+
TypeMirror returnType = methodElement.getReturnType();
175+
if (!"void".equals(returnType.toString())) {
176+
if (!maybeGetterMethods.containsKey(methodKey)) {
177+
maybeGetterMethods.put(methodKey, methodReader);
178+
}
179+
allGetterMethods.put(methodKey.toLowerCase(), methodReader);
171180
}
172-
allGetterMethods.put(methodKey.toLowerCase(), methodReader);
173181
}
174182
}
175183
}
176184

185+
private boolean checkMethod2(ExecutableElement methodElement) {
186+
if (!methodElement.getModifiers().contains(Modifier.PUBLIC)) {
187+
return false;
188+
}
189+
if (extendsThrowable) {
190+
return THROWABLE_INCLUDES.contains(methodElement.getSimpleName().toString());
191+
}
192+
return true;
193+
}
194+
177195
private void matchFieldsToSetterOrConstructor() {
178196
for (FieldReader field : allFields) {
179197
if (field.includeFromJson()) {
@@ -416,6 +434,9 @@ private void setFieldPositions() {
416434
private void addSuperType(TypeElement element) {
417435
String type = element.getQualifiedName().toString();
418436
if (!JAVA_LANG_OBJECT.equals(type) && !GenericType.isGeneric(type)) {
437+
if (JAVA_LANG_THROWABLE.equals(type)) {
438+
extendsThrowable = true;
439+
}
419440
read(element);
420441
addSuperType(superOf(element));
421442
}

jsonb/src/main/java/io/avaje/jsonb/core/BasicTypeAdapters.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ final class BasicTypeAdapters {
5151
if (type == UUID.class) return new UuidAdapter().nullSafe();
5252
if (type == URL.class) return new UrlAdapter().nullSafe();
5353
if (type == URI.class) return new UriAdapter().nullSafe();
54+
if (type == StackTraceElement.class) return new StackTraceElementAdapter().nullSafe();
5455
if (type == Object.class) return new ObjectJsonAdapter(jsonb).nullSafe();
56+
if (type == Throwable.class) return new ThrowableAdapter(jsonb).nullSafe();
5557

5658
final Class<?> rawType = Util.rawType(type);
5759
if (rawType.isEnum()) {
@@ -115,6 +117,80 @@ public String toString() {
115117
}
116118
}
117119

120+
private static final class StackTraceElementAdapter implements JsonAdapter<StackTraceElement> {
121+
@Override
122+
public StackTraceElement fromJson(JsonReader reader) {
123+
throw new UnsupportedOperationException();
124+
}
125+
126+
@Override
127+
public void toJson(JsonWriter writer, StackTraceElement value) {
128+
writer.value(value.toString());
129+
}
130+
131+
@Override
132+
public String toString() {
133+
return "JsonAdapter(StackTraceElement)";
134+
}
135+
}
136+
137+
private static final class ThrowableAdapter implements JsonAdapter<Throwable> {
138+
139+
private static final int MAX_STACK = 5;
140+
private final JsonAdapter<StackTraceElement> stackTraceElementAdapter;
141+
142+
private ThrowableAdapter(Jsonb jsonb) {
143+
this.stackTraceElementAdapter = jsonb.adapter(StackTraceElement.class);
144+
}
145+
146+
@Override
147+
public Throwable fromJson(JsonReader reader) {
148+
throw new UnsupportedOperationException();
149+
}
150+
151+
@Override
152+
public void toJson(JsonWriter writer, Throwable value) {
153+
writer.beginObject();
154+
writer.name("type");
155+
writer.value(value.getClass().toString());
156+
writer.name("message");
157+
writer.value(value.getMessage());
158+
159+
StackTraceElement[] stackTrace = value.getStackTrace();
160+
if (stackTrace != null && stackTrace.length > 0) {
161+
int end = Math.min(MAX_STACK, stackTrace.length);
162+
List<StackTraceElement> stackTraceElements = Arrays.asList(stackTrace).subList(0, end);
163+
writer.name("stackTrace");
164+
writer.beginArray();
165+
for (StackTraceElement element : stackTraceElements) {
166+
stackTraceElementAdapter.toJson(writer, element);
167+
}
168+
writer.endArray();
169+
}
170+
171+
final Throwable cause = value.getCause();
172+
if (cause != null) {
173+
writer.name("cause");
174+
toJson(writer, cause);
175+
}
176+
final Throwable[] suppressed = value.getSuppressed();
177+
if (suppressed != null && suppressed.length > 0) {
178+
writer.name("suppressed");
179+
writer.beginArray();
180+
for (Throwable sup : suppressed) {
181+
toJson(writer, sup);
182+
}
183+
writer.endArray();
184+
}
185+
writer.endObject();
186+
}
187+
188+
@Override
189+
public String toString() {
190+
return "JsonAdapter(URI)";
191+
}
192+
}
193+
118194
static final class BooleanAdapter implements JsonAdapter<Boolean> {
119195
@Override
120196
public Boolean fromJson(JsonReader reader) {

0 commit comments

Comments
 (0)