Skip to content

Commit 05d896f

Browse files
refs #30 - @javax.annotation.Nullable test, AnnotationIntrospector in Jackson1Parser, updated build plugins
1 parent 6e05719 commit 05d896f

File tree

7 files changed

+125
-47
lines changed

7 files changed

+125
-47
lines changed

typescript-generator-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
<version>4.11</version>
3737
<scope>test</scope>
3838
</dependency>
39+
<dependency>
40+
<groupId>com.google.code.findbugs</groupId>
41+
<artifactId>annotations</artifactId>
42+
<version>3.0.1u2</version>
43+
<scope>test</scope>
44+
</dependency>
3945
</dependencies>
4046

4147
<build>

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import java.util.*;
88

99

10+
/**
11+
* @see cz.habarta.typescript.generator.maven.GenerateMojo
12+
* @see <a href="https://github.com/vojtechhabarta/typescript-generator">README.md</a> on GitHub or in project root directory
13+
* @see <a href="https://github.com/vojtechhabarta/typescript-generator/wiki">Wiki</a> on GitHub
14+
*/
1015
public class Settings {
1116
public String newline = String.format("%n");
1217
public String quotes = "\"";
@@ -28,22 +33,27 @@ public class Settings {
2833
public boolean noFileComment = false;
2934
public List<File> javadocXmlFiles = null;
3035
public List<EmitterExtension> extensions = new ArrayList<>();
31-
32-
/**
33-
* The presence of any annotation in this list on a json property will cause the
34-
* typescript generator to treat that property as optional when generating the
35-
* corresponding typescript interface.
36-
* <p>
37-
* Note: When using a {@link cz.habarta.typescript.generator.parser.Jackson1Parser}
38-
* to generate your model, any annotation specified here will need to pass the
39-
* {@link org.codehaus.jackson.map.AnnotationIntrospector#isHandled} check performed
40-
* by the {@link org.codehaus.jackson.map.ObjectMapper} used to construct your model
41-
* parser. If you control the annotations in question, the easiest way to do this is
42-
* to annotate your annotations as
43-
* {@link org.codehaus.jackson.annotate.JacksonAnnotation}s.
44-
*/
4536
public List<Class<? extends Annotation>> optionalAnnotations = new ArrayList<>();
4637

38+
39+
public void loadCustomTypeProcessor(ClassLoader classLoader, String customTypeProcessor) {
40+
if (customTypeProcessor != null) {
41+
this.customTypeProcessor = loadInstance(classLoader, customTypeProcessor, TypeProcessor.class);
42+
}
43+
}
44+
45+
public void loadExtensions(ClassLoader classLoader, List<String> extensions) {
46+
if (extensions != null) {
47+
this.extensions = loadInstances(classLoader, extensions, EmitterExtension.class);
48+
}
49+
}
50+
51+
public void loadOptionalAnnotations(ClassLoader classLoader, List<String> optionalAnnotations) {
52+
if (optionalAnnotations != null) {
53+
this.optionalAnnotations = loadClasses(classLoader, optionalAnnotations, Annotation.class);
54+
}
55+
}
56+
4757
public void validate() {
4858
if (jsonLibrary == null) {
4959
throw new RuntimeException("Required 'jsonLibrary' is not configured.");
@@ -70,4 +80,46 @@ public void validateFileName(File outputFile) {
7080
}
7181
}
7282

83+
84+
private static <T> List<Class<? extends T>> loadClasses(ClassLoader classLoader, List<String> classNames, Class<T> requiredClassType) {
85+
if (classNames == null) {
86+
return null;
87+
}
88+
try {
89+
final List<Class<? extends T>> classes = new ArrayList<>();
90+
for (String className : classNames) {
91+
final Class<?> loadedClass = classLoader.loadClass(className);
92+
if (requiredClassType.isAssignableFrom(loadedClass)) {
93+
@SuppressWarnings("unchecked")
94+
final Class<? extends T> castedClass = (Class<? extends T>) loadedClass;
95+
classes.add(castedClass);
96+
} else {
97+
throw new RuntimeException(String.format("Class '%s' is not assignable to '%s'.", loadedClass, requiredClassType));
98+
}
99+
}
100+
return classes;
101+
} catch (ReflectiveOperationException e) {
102+
throw new RuntimeException(e);
103+
}
104+
}
105+
106+
private static <T> List<T> loadInstances(ClassLoader classLoader, List<String> classNames, Class<T> requiredType) {
107+
if (classNames == null) {
108+
return null;
109+
}
110+
final List<T> instances = new ArrayList<>();
111+
for (String className : classNames) {
112+
instances.add(loadInstance(classLoader, className, requiredType));
113+
}
114+
return instances;
115+
}
116+
117+
private static <T> T loadInstance(ClassLoader classLoader, String className, Class<T> requiredType) {
118+
try {
119+
return requiredType.cast(classLoader.loadClass(className).newInstance());
120+
} catch (ReflectiveOperationException e) {
121+
throw new RuntimeException(e);
122+
}
123+
}
124+
73125
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,27 @@
88
import org.codehaus.jackson.JsonNode;
99
import org.codehaus.jackson.annotate.JsonSubTypes;
1010
import org.codehaus.jackson.map.*;
11+
import org.codehaus.jackson.map.introspect.NopAnnotationIntrospector;
1112
import org.codehaus.jackson.map.ser.*;
1213
import org.codehaus.jackson.type.JavaType;
1314

1415

1516
public class Jackson1Parser extends ModelParser {
1617

17-
private final ObjectMapper objectMapper;
18+
private final ObjectMapper objectMapper = new ObjectMapper();
1819

1920
public Jackson1Parser(Settings settings, TypeProcessor typeProcessor) {
20-
this(settings, typeProcessor, new ObjectMapper());
21-
}
22-
23-
public Jackson1Parser(Settings settings, TypeProcessor typeProcessor, ObjectMapper objectMapper) {
2421
super(settings, typeProcessor);
25-
this.objectMapper = objectMapper;
22+
if (!settings.optionalAnnotations.isEmpty()) {
23+
final AnnotationIntrospector defaultAnnotationIntrospector = objectMapper.getSerializationConfig().getAnnotationIntrospector();
24+
final AnnotationIntrospector allAnnotationIntrospector = new NopAnnotationIntrospector() {
25+
@Override
26+
public boolean isHandled(Annotation ann) {
27+
return true;
28+
}
29+
};
30+
this.objectMapper.setAnnotationIntrospector(new AnnotationIntrospector.Pair(defaultAnnotationIntrospector, allAnnotationIntrospector));
31+
}
2632
}
2733

2834
@Override

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,10 @@
1313

1414
public class Jackson2Parser extends ModelParser {
1515

16-
private final ObjectMapper objectMapper;
16+
private final ObjectMapper objectMapper = new ObjectMapper();
1717

1818
public Jackson2Parser(Settings settings, TypeProcessor typeProcessor) {
19-
this(settings, typeProcessor, new ObjectMapper());
20-
}
21-
22-
public Jackson2Parser(Settings settings, TypeProcessor typeProcessor, ObjectMapper objectMapper) {
2319
super(settings, typeProcessor);
24-
this.objectMapper = objectMapper;
2520
}
2621

2722
@Override

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ private void testModel(Model model, boolean optional) {
4848
}
4949
}
5050

51+
@Test
52+
public void testJavaxNullableWithJackson1() {
53+
testJavaxNullableUsingTypeScriptGenerator(JsonLibrary.jackson1);
54+
}
55+
56+
@Test
57+
public void testJavaxNullableWithJackson2() {
58+
testJavaxNullableUsingTypeScriptGenerator(JsonLibrary.jackson2);
59+
}
60+
61+
private void testJavaxNullableUsingTypeScriptGenerator(JsonLibrary jsonLibrary) {
62+
Settings settings = TestUtils.settings();
63+
settings.jsonLibrary = jsonLibrary;
64+
settings.optionalAnnotations.add(javax.annotation.Nullable.class);
65+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(BeanWithJavaxNullable.class));
66+
Assert.assertTrue(output.contains("property1?: string;"));
67+
}
68+
5169
@org.codehaus.jackson.annotate.JacksonAnnotation
5270
@Retention(RetentionPolicy.RUNTIME)
5371
static @interface Nullable {
@@ -77,4 +95,10 @@ public String getMethodProperty() {
7795
return fieldProperty;
7896
}
7997
}
98+
99+
static class BeanWithJavaxNullable {
100+
@javax.annotation.Nullable
101+
public String property1;
102+
}
103+
80104
}

typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import cz.habarta.typescript.generator.*;
55
import cz.habarta.typescript.generator.Input;
6-
import cz.habarta.typescript.generator.emitter.EmitterExtension;
76
import java.io.*;
87
import java.net.*;
98
import java.util.*;
@@ -33,6 +32,7 @@ public class GenerateTask extends DefaultTask {
3332
public boolean noFileComment;
3433
public List<File> javadocXmlFiles;
3534
public List<String> extensions;
35+
public List<String> optionalAnnotations;
3636

3737

3838
@TaskAction
@@ -73,19 +73,13 @@ public void generate() throws Exception {
7373
settings.addTypeNamePrefix = addTypeNamePrefix;
7474
settings.addTypeNameSuffix = addTypeNameSuffix;
7575
settings.mapDate = mapDate;
76-
if (customTypeProcessor != null) {
77-
settings.customTypeProcessor = (TypeProcessor) classLoader.loadClass(customTypeProcessor).newInstance();
78-
}
76+
settings.loadCustomTypeProcessor(classLoader, customTypeProcessor);
7977
settings.sortDeclarations = sortDeclarations;
8078
settings.sortTypeDeclarations = sortTypeDeclarations;
8179
settings.noFileComment = noFileComment;
8280
settings.javadocXmlFiles = javadocXmlFiles;
83-
if (extensions != null) {
84-
settings.extensions = new ArrayList<>();
85-
for (String extensionClassName : extensions) {
86-
settings.extensions.add((EmitterExtension) classLoader.loadClass(extensionClassName).newInstance());
87-
}
88-
}
81+
settings.loadExtensions(classLoader, extensions);
82+
settings.loadOptionalAnnotations(classLoader, optionalAnnotations);
8983
settings.validateFileName(new File(outputFile));
9084

9185
// TypeScriptGenerator

typescript-generator-maven-plugin/src/main/java/cz/habarta/typescript/generator/maven/GenerateMojo.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
package cz.habarta.typescript.generator.maven;
33

44
import cz.habarta.typescript.generator.*;
5-
import cz.habarta.typescript.generator.emitter.EmitterExtension;
65
import java.io.*;
76
import java.net.*;
87
import java.util.*;
@@ -168,6 +167,14 @@ public class GenerateMojo extends AbstractMojo {
168167
@Parameter
169168
private List<String> extensions;
170169

170+
/**
171+
* The presence of any annotation in this list on a JSON property will cause
172+
* the typescript-generator to treat that property as optional when generating
173+
* the corresponding TypeScript interface.
174+
* Example optional annotation: @javax.annotation.Nullable
175+
*/
176+
@Parameter
177+
private List<String> optionalAnnotations;
171178

172179
@Parameter(defaultValue = "${project}", readonly = true, required = true)
173180
private MavenProject project;
@@ -199,19 +206,13 @@ public void execute() {
199206
settings.addTypeNamePrefix = addTypeNamePrefix;
200207
settings.addTypeNameSuffix = addTypeNameSuffix;
201208
settings.mapDate = mapDate;
202-
if (customTypeProcessor != null) {
203-
settings.customTypeProcessor = (TypeProcessor) classLoader.loadClass(customTypeProcessor).newInstance();
204-
}
209+
settings.loadCustomTypeProcessor(classLoader, customTypeProcessor);
205210
settings.sortDeclarations = sortDeclarations;
206211
settings.sortTypeDeclarations = sortTypeDeclarations;
207212
settings.noFileComment = noFileComment;
208213
settings.javadocXmlFiles = javadocXmlFiles;
209-
if (extensions != null) {
210-
settings.extensions = new ArrayList<>();
211-
for (String extensionClassName : extensions) {
212-
settings.extensions.add((EmitterExtension) classLoader.loadClass(extensionClassName).newInstance());
213-
}
214-
}
214+
settings.loadExtensions(classLoader, extensions);
215+
settings.loadOptionalAnnotations(classLoader, optionalAnnotations);
215216
settings.validateFileName(outputFile);
216217

217218
// TypeScriptGenerator
@@ -220,7 +221,7 @@ public void execute() {
220221
Output.to(outputFile)
221222
);
222223

223-
} catch (DependencyResolutionRequiredException | ReflectiveOperationException | IOException e) {
224+
} catch (DependencyResolutionRequiredException | IOException e) {
224225
throw new RuntimeException(e);
225226
}
226227
}

0 commit comments

Comments
 (0)