Skip to content

Commit d1d1c80

Browse files
Support for "optional" annotations with TYPE_USE target (#412)
(for example `@Nullable` from Checker Framework) getting "optional" annotations directly from property original `Member`
1 parent 59c922f commit d1d1c80

File tree

5 files changed

+134
-32
lines changed

5 files changed

+134
-32
lines changed

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/Jackson1Parser.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cz.habarta.typescript.generator.ExcludingTypeProcessor;
55
import cz.habarta.typescript.generator.Settings;
66
import cz.habarta.typescript.generator.TypeProcessor;
7+
import cz.habarta.typescript.generator.util.PropertyMember;
78
import java.lang.annotation.Annotation;
89
import java.lang.reflect.Member;
910
import java.lang.reflect.Type;
@@ -81,14 +82,14 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass) {
8182
final BeanHelper beanHelper = getBeanHelper(sourceClass.type);
8283
if (beanHelper != null) {
8384
for (final BeanPropertyWriter beanPropertyWriter : beanHelper.getProperties()) {
84-
final Member propertyMember = beanPropertyWriter.getMember().getMember();
85-
checkMember(propertyMember, beanPropertyWriter.getName(), sourceClass.type);
85+
final Member member = beanPropertyWriter.getMember().getMember();
86+
final PropertyMember propertyMember = wrapMember(member, beanPropertyWriter.getName(), sourceClass.type);
8687
Type propertyType = beanPropertyWriter.getGenericPropertyType();
8788
if (!isAnnotatedPropertyIncluded(beanPropertyWriter::getAnnotation, sourceClass.type.getName() + "." + beanPropertyWriter.getName())) {
8889
continue;
8990
}
90-
final boolean optional = isAnnotatedPropertyOptional(beanPropertyWriter::getAnnotation);
91-
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, null, optional, sourceClass.type, propertyMember, null, null));
91+
final boolean optional = isPropertyOptional(propertyMember);
92+
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, null, optional, sourceClass.type, member, null, null));
9293
}
9394
}
9495

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/Jackson2Parser.java

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@
3838
import cz.habarta.typescript.generator.TypeScriptGenerator;
3939
import cz.habarta.typescript.generator.compiler.EnumKind;
4040
import cz.habarta.typescript.generator.compiler.EnumMemberModel;
41+
import cz.habarta.typescript.generator.util.PropertyMember;
4142
import cz.habarta.typescript.generator.util.UnionType;
4243
import cz.habarta.typescript.generator.util.Utils;
4344
import java.lang.annotation.Annotation;
4445
import java.lang.reflect.Field;
4546
import java.lang.reflect.Member;
46-
import java.lang.reflect.Method;
4747
import java.lang.reflect.Modifier;
4848
import java.lang.reflect.Type;
4949
import java.util.ArrayList;
@@ -195,9 +195,9 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass, List<String> class
195195
final BeanHelper beanHelper = getBeanHelper(sourceClass.type);
196196
if (beanHelper != null) {
197197
for (final BeanPropertyWriter beanPropertyWriter : beanHelper.getProperties()) {
198-
final Member propertyMember = beanPropertyWriter.getMember().getMember();
199-
checkMember(propertyMember, beanPropertyWriter.getName(), sourceClass.type);
200-
Type propertyType = getGenericType(propertyMember);
198+
final Member member = beanPropertyWriter.getMember().getMember();
199+
final PropertyMember propertyMember = wrapMember(member, beanPropertyWriter.getName(), sourceClass.type);
200+
Type propertyType = propertyMember.getType();
201201
final List<String> propertyComments = getComments(beanPropertyWriter.getAnnotation(JsonPropertyDescription.class));
202202

203203
// Map.Entry
@@ -222,14 +222,14 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass, List<String> class
222222
}
223223
final boolean optional = settings.optionalProperties == OptionalProperties.useLibraryDefinition
224224
? !beanPropertyWriter.isRequired()
225-
: isAnnotatedPropertyOptional(beanPropertyWriter::getAnnotation);
225+
: isPropertyOptional(propertyMember);
226226
// @JsonUnwrapped
227227
PropertyModel.PullProperties pullProperties = null;
228228
final JsonUnwrapped annotation = beanPropertyWriter.getAnnotation(JsonUnwrapped.class);
229229
if (annotation != null && annotation.enabled()) {
230230
pullProperties = new PropertyModel.PullProperties(annotation.prefix(), annotation.suffix());
231231
}
232-
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, jackson2TypeContext, optional, sourceClass.type, propertyMember, pullProperties, propertyComments));
232+
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, jackson2TypeContext, optional, sourceClass.type, member, pullProperties, propertyComments));
233233
}
234234
}
235235
if (sourceClass.type.isEnum()) {
@@ -317,9 +317,9 @@ private Type processIdentity(Type propertyType, BeanPropertyWriter propertyWrite
317317
.findFirst();
318318
if (idProperty.isPresent()) {
319319
final BeanPropertyWriter idPropertyWriter = idProperty.get();
320-
final Member idPropertyMember = idPropertyWriter.getMember().getMember();
321-
checkMember(idPropertyMember, idPropertyWriter.getName(), cls);
322-
idType = getGenericType(idPropertyMember);
320+
final Member idMember = idPropertyWriter.getMember().getMember();
321+
final PropertyMember idPropertyMember = wrapMember(idMember, idPropertyWriter.getName(), cls);
322+
idType = idPropertyMember.getType();
323323
} else {
324324
return null;
325325
}
@@ -339,16 +339,6 @@ private Type processIdentity(Type propertyType, BeanPropertyWriter propertyWrite
339339
return null;
340340
}
341341

342-
private static Type getGenericType(Member member) {
343-
if (member instanceof Method) {
344-
return ((Method) member).getGenericReturnType();
345-
}
346-
if (member instanceof Field) {
347-
return ((Field) member).getGenericType();
348-
}
349-
return null;
350-
}
351-
352342
private static boolean isSupported(JsonTypeInfo jsonTypeInfo) {
353343
return jsonTypeInfo != null &&
354344
jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY &&

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/ModelParser.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import cz.habarta.typescript.generator.compiler.EnumKind;
1010
import cz.habarta.typescript.generator.compiler.EnumMemberModel;
1111
import cz.habarta.typescript.generator.util.GenericsResolver;
12+
import cz.habarta.typescript.generator.util.PropertyMember;
1213
import cz.habarta.typescript.generator.util.Utils;
1314
import java.lang.annotation.Annotation;
1415
import java.lang.reflect.Field;
@@ -117,14 +118,20 @@ private Model parseQueue() {
117118

118119
protected abstract DeclarationModel parseClass(SourceType<Class<?>> sourceClass);
119120

120-
protected static void checkMember(Member propertyMember, String propertyName, Class<?> sourceClass) {
121-
if (!(propertyMember instanceof Field) && !(propertyMember instanceof Method)) {
122-
throw new RuntimeException(String.format(
123-
"Unexpected member type '%s' in property '%s' in class '%s'",
124-
propertyMember != null ? propertyMember.getClass().getName() : null,
125-
propertyName,
126-
sourceClass.getName()));
121+
protected static PropertyMember wrapMember(Member propertyMember, String propertyName, Class<?> sourceClass) {
122+
if (propertyMember instanceof Field) {
123+
final Field field = (Field) propertyMember;
124+
return new PropertyMember.FieldPropertyMember(field);
127125
}
126+
if (propertyMember instanceof Method) {
127+
final Method method = (Method) propertyMember;
128+
return new PropertyMember.MethodPropertyMember(method);
129+
}
130+
throw new RuntimeException(String.format(
131+
"Unexpected member type '%s' in property '%s' in class '%s'",
132+
propertyMember != null ? propertyMember.getClass().getName() : null,
133+
propertyName,
134+
sourceClass.getName()));
128135
}
129136

130137
protected boolean isAnnotatedPropertyIncluded(Function<Class<? extends Annotation>, Annotation> getAnnotationFunction, String propertyDescription) {
@@ -142,12 +149,12 @@ protected boolean isAnnotatedPropertyIncluded(Function<Class<? extends Annotatio
142149
return true;
143150
}
144151

145-
protected boolean isAnnotatedPropertyOptional(Function<Class<? extends Annotation>, Annotation> getAnnotationFunction) {
152+
protected boolean isPropertyOptional(PropertyMember propertyMember) {
146153
if (settings.optionalProperties == OptionalProperties.all) {
147154
return true;
148155
}
149156
if (settings.optionalProperties == null || settings.optionalProperties == OptionalProperties.useSpecifiedAnnotations) {
150-
return Utils.hasAnyAnnotation(getAnnotationFunction, settings.optionalAnnotations);
157+
return Utils.hasAnyAnnotation(propertyMember::getAnnotation, settings.optionalAnnotations);
151158
}
152159
return false;
153160
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
package cz.habarta.typescript.generator.util;
3+
4+
import java.lang.annotation.Annotation;
5+
import java.lang.reflect.AnnotatedElement;
6+
import java.lang.reflect.AnnotatedType;
7+
import java.lang.reflect.Field;
8+
import java.lang.reflect.Method;
9+
import java.lang.reflect.Type;
10+
11+
12+
public abstract class PropertyMember {
13+
14+
public abstract Type getType();
15+
16+
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
17+
final A annotation = getAnnotatedElement().getAnnotation(annotationClass);
18+
return annotation != null
19+
? annotation
20+
: getAnnotatedType().getAnnotation(annotationClass);
21+
}
22+
23+
protected abstract AnnotatedElement getAnnotatedElement();
24+
25+
protected abstract AnnotatedType getAnnotatedType();
26+
27+
28+
public static class FieldPropertyMember extends PropertyMember {
29+
30+
private final Field field;
31+
32+
public FieldPropertyMember(Field field) {
33+
this.field = field;
34+
}
35+
36+
@Override
37+
public Type getType() {
38+
return field.getGenericType();
39+
}
40+
41+
@Override
42+
public AnnotatedElement getAnnotatedElement() {
43+
return field;
44+
}
45+
46+
@Override
47+
public AnnotatedType getAnnotatedType() {
48+
return field.getAnnotatedType();
49+
}
50+
51+
}
52+
53+
public static class MethodPropertyMember extends PropertyMember {
54+
55+
private final Method method;
56+
57+
public MethodPropertyMember(Method method) {
58+
this.method = method;
59+
}
60+
61+
@Override
62+
public Type getType() {
63+
return method.getGenericReturnType();
64+
}
65+
66+
@Override
67+
public AnnotatedElement getAnnotatedElement() {
68+
return method;
69+
}
70+
71+
@Override
72+
public AnnotatedType getAnnotatedType() {
73+
return method.getAnnotatedReturnType();
74+
}
75+
76+
}
77+
78+
}

typescript-generator-core/src/test/java/cz/habarta/typescript/generator/OptionalAnnotationTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import cz.habarta.typescript.generator.parser.Model;
77
import cz.habarta.typescript.generator.parser.ModelParser;
88
import cz.habarta.typescript.generator.parser.PropertyModel;
9+
import java.lang.annotation.ElementType;
910
import java.lang.annotation.Retention;
1011
import java.lang.annotation.RetentionPolicy;
12+
import java.lang.annotation.Target;
1113
import org.junit.Assert;
1214
import org.junit.Test;
1315

@@ -106,4 +108,28 @@ static class BeanWithJavaxNullable {
106108
public String property1;
107109
}
108110

111+
@Test
112+
public void testNullableTypeAnnotation() {
113+
Settings settings = TestUtils.settings();
114+
settings.optionalAnnotations.add(NullableType.class);
115+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(BeanWithNullableType.class));
116+
Assert.assertTrue(output.contains("property1?: string;"));
117+
Assert.assertTrue(output.contains("property2?: string;"));
118+
}
119+
120+
@Retention(RetentionPolicy.RUNTIME)
121+
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
122+
public @interface NullableType {
123+
}
124+
125+
private static class BeanWithNullableType {
126+
@NullableType
127+
public String property1;
128+
129+
@NullableType
130+
public String getProperty2() {
131+
return null;
132+
}
133+
}
134+
109135
}

0 commit comments

Comments
 (0)