Skip to content

Commit afc2a07

Browse files
Merge pull request #126 from vojtechhabarta/jaxrs-namespacing
JAX-RS namespacing options
2 parents 11ed233 + bff38cc commit afc2a07

File tree

21 files changed

+298
-153
lines changed

21 files changed

+298
-153
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
package cz.habarta.typescript.generator;
3+
4+
5+
public enum JaxrsNamespacing {
6+
7+
singleObject, perResource, byAnnotation
8+
9+
}

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

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public class Settings {
4545
public boolean ignoreSwaggerAnnotations = false;
4646
public boolean generateJaxrsApplicationInterface = false;
4747
public boolean generateJaxrsApplicationClient = false;
48+
public JaxrsNamespacing jaxrsNamespacing;
49+
public Class<? extends Annotation> jaxrsNamespacingAnnotation = null;
50+
public String jaxrsNamespacingAnnotationElement; // default is "value"
4851
public String restResponseType = null;
4952
public String restOptionsType = null;
5053
public TypeProcessor customTypeProcessor = null;
@@ -164,6 +167,15 @@ public void validate() {
164167
throw new RuntimeException("'generateJaxrsApplicationClient' can only be used when generating implementation file ('outputFileType' parameter is 'implementationFile').");
165168
}
166169
final boolean generateJaxrs = generateJaxrsApplicationClient || generateJaxrsApplicationInterface;
170+
if (jaxrsNamespacing != null && !generateJaxrs) {
171+
throw new RuntimeException("'jaxrsNamespacing' parameter can only be used when generating JAX-RS client or interface.");
172+
}
173+
if (jaxrsNamespacingAnnotation != null && jaxrsNamespacing != JaxrsNamespacing.byAnnotation) {
174+
throw new RuntimeException("'jaxrsNamespacingAnnotation' parameter can only be used when 'jaxrsNamespacing' parameter is set to 'byAnnotation'.");
175+
}
176+
if (jaxrsNamespacingAnnotation == null && jaxrsNamespacing == JaxrsNamespacing.byAnnotation) {
177+
throw new RuntimeException("'jaxrsNamespacingAnnotation' must be specified when 'jaxrsNamespacing' parameter is set to 'byAnnotation'.");
178+
}
167179
if (restResponseType != null && !generateJaxrs) {
168180
throw new RuntimeException("'restResponseType' parameter can only be used when generating JAX-RS client or interface.");
169181
}
@@ -228,6 +240,14 @@ public boolean test(String className) {
228240
};
229241
}
230242

243+
public void setJaxrsNamespacingAnnotation(ClassLoader classLoader, String jaxrsNamespacingAnnotation) {
244+
final String[] split = jaxrsNamespacingAnnotation.split("#");
245+
final String className = split[0];
246+
final String elementName = split.length > 1 ? split[1] : "value";
247+
this.jaxrsNamespacingAnnotation = loadClass(classLoader, className, Annotation.class);
248+
this.jaxrsNamespacingAnnotationElement = elementName;
249+
}
250+
231251
public boolean areDefaultStringEnumsOverriddenByExtension() {
232252
return defaultStringEnumsOverriddenByExtension;
233253
}
@@ -240,20 +260,24 @@ private static <T> List<Class<? extends T>> loadClasses(ClassLoader classLoader,
240260
if (classNames == null) {
241261
return null;
242262
}
263+
final List<Class<? extends T>> classes = new ArrayList<>();
264+
for (String className : classNames) {
265+
classes.add(loadClass(classLoader, className, requiredClassType));
266+
}
267+
return classes;
268+
}
269+
270+
private static <T> Class<? extends T> loadClass(ClassLoader classLoader, String className, Class<T> requiredClassType) {
243271
try {
244-
final List<Class<? extends T>> classes = new ArrayList<>();
245-
for (String className : classNames) {
246-
System.out.println("Loading class " + className);
247-
final Class<?> loadedClass = classLoader.loadClass(className);
248-
if (requiredClassType.isAssignableFrom(loadedClass)) {
249-
@SuppressWarnings("unchecked")
250-
final Class<? extends T> castedClass = (Class<? extends T>) loadedClass;
251-
classes.add(castedClass);
252-
} else {
253-
throw new RuntimeException(String.format("Class '%s' is not assignable to '%s'.", loadedClass, requiredClassType));
254-
}
272+
System.out.println("Loading class " + className);
273+
final Class<?> loadedClass = classLoader.loadClass(className);
274+
if (requiredClassType.isAssignableFrom(loadedClass)) {
275+
@SuppressWarnings("unchecked")
276+
final Class<? extends T> castedClass = (Class<? extends T>) loadedClass;
277+
return castedClass;
278+
} else {
279+
throw new RuntimeException(String.format("Class '%s' is not assignable to '%s'.", loadedClass, requiredClassType));
255280
}
256-
return classes;
257281
} catch (ReflectiveOperationException e) {
258282
throw new RuntimeException(e);
259283
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java

Lines changed: 65 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import cz.habarta.typescript.generator.emitter.*;
66
import cz.habarta.typescript.generator.parser.*;
77
import cz.habarta.typescript.generator.util.Utils;
8+
import java.lang.annotation.Annotation;
89
import java.lang.reflect.*;
910
import java.util.*;
1011
import javax.ws.rs.core.Application;
@@ -55,10 +56,10 @@ public TsModel javaToTypeScript(Model model) {
5556
: null;
5657

5758
if (settings.generateJaxrsApplicationInterface) {
58-
tsModel = createJaxrsInterface(symbolTable, tsModel, jaxrsApplication, responseSymbol, optionsType);
59+
tsModel = createJaxrsInterfaces(symbolTable, tsModel, jaxrsApplication, responseSymbol, optionsType);
5960
}
6061
if (settings.generateJaxrsApplicationClient) {
61-
tsModel = createJaxrsClient(symbolTable, tsModel, jaxrsApplication, responseSymbol, optionsType);
62+
tsModel = createJaxrsClients(symbolTable, tsModel, jaxrsApplication, responseSymbol, optionsType);
6263
}
6364
}
6465

@@ -314,19 +315,20 @@ private Symbol createJaxrsResponseType(SymbolTable symbolTable, TsModel tsModel)
314315
return responseSymbol;
315316
}
316317

317-
private TsModel createJaxrsInterface(SymbolTable symbolTable, TsModel tsModel, JaxrsApplicationModel jaxrsApplication, Symbol responseSymbol, TsType optionsType) {
318-
final List<TsMethodModel> methods = processJaxrsMethods(jaxrsApplication, symbolTable, responseSymbol, optionsType, false);
319-
final String applicationName = getApplicationName(jaxrsApplication);
320-
final TsBeanModel interfaceModel = new TsBeanModel(Application.class, TsBeanCategory.Service, false, symbolTable.getSyntheticSymbol(applicationName), null, null, null, null, null, null, methods, null);
321-
tsModel.getBeans().add(interfaceModel);
318+
private TsModel createJaxrsInterfaces(SymbolTable symbolTable, TsModel tsModel, JaxrsApplicationModel jaxrsApplication, Symbol responseSymbol, TsType optionsType) {
319+
final Map<Symbol, List<TsMethodModel>> groupedMethods = processJaxrsMethods(jaxrsApplication, symbolTable, null, responseSymbol, optionsType, false);
320+
for (Map.Entry<Symbol, List<TsMethodModel>> entry : groupedMethods.entrySet()) {
321+
final TsBeanModel interfaceModel = new TsBeanModel(null, TsBeanCategory.Service, false, entry.getKey(), null, null, null, null, null, null, entry.getValue(), null);
322+
tsModel.getBeans().add(interfaceModel);
323+
}
322324
return tsModel;
323325
}
324326

325-
private TsModel createJaxrsClient(SymbolTable symbolTable, TsModel tsModel, JaxrsApplicationModel jaxrsApplication, Symbol responseSymbol, TsType optionsType) {
327+
private TsModel createJaxrsClients(SymbolTable symbolTable, TsModel tsModel, JaxrsApplicationModel jaxrsApplication, Symbol responseSymbol, TsType optionsType) {
326328
final Symbol httpClientSymbol = symbolTable.getSyntheticSymbol("HttpClient");
327329

328330
// HttpClient interface
329-
tsModel.getBeans().add(new TsBeanModel(null, TsBeanCategory.Service, false, httpClientSymbol, null, null, null, null, null, null, Arrays.asList(
331+
tsModel.getBeans().add(new TsBeanModel(null, TsBeanCategory.ServicePrerequisite, false, httpClientSymbol, null, null, null, null, null, null, Arrays.asList(
330332
new TsMethodModel("request", new TsType.GenericReferenceType(responseSymbol, TsType.Any), Arrays.asList(
331333
new TsParameterModel("requestConfig", new TsType.ObjectType(
332334
new TsProperty("method", TsType.String),
@@ -338,32 +340,72 @@ private TsModel createJaxrsClient(SymbolTable symbolTable, TsModel tsModel, Jaxr
338340
), null, null)
339341
), null));
340342

341-
// application client class
343+
// application client classes
342344
final TsConstructorModel constructor = new TsConstructorModel(
343345
Arrays.asList(new TsParameterModel(TsAccessibilityModifier.Protected, "httpClient", new TsType.ReferenceType(httpClientSymbol))),
344346
Collections.<TsStatement>emptyList(),
345347
null
346348
);
347-
final List<TsMethodModel> methods = processJaxrsMethods(jaxrsApplication, symbolTable, responseSymbol, optionsType, true);
348-
final String applicationName = getApplicationName(jaxrsApplication);
349-
final String applicationClientName = applicationName + "Client";
350-
final TsType interfaceType = settings.generateJaxrsApplicationInterface ? new TsType.ReferenceType(symbolTable.getSyntheticSymbol(applicationName)) : null;
351-
final TsBeanModel clientModel = new TsBeanModel(Application.class, TsBeanCategory.Service, true, symbolTable.getSyntheticSymbol(applicationClientName), null, null, null, Utils.listFromNullable(interfaceType), null, constructor, methods, null);
352-
tsModel.getBeans().add(clientModel);
349+
final String groupingSuffix = settings.generateJaxrsApplicationInterface ? null : "Client";
350+
final Map<Symbol, List<TsMethodModel>> groupedMethods = processJaxrsMethods(jaxrsApplication, symbolTable, groupingSuffix, responseSymbol, optionsType, true);
351+
for (Map.Entry<Symbol, List<TsMethodModel>> entry : groupedMethods.entrySet()) {
352+
final Symbol symbol = settings.generateJaxrsApplicationInterface ? symbolTable.addSuffixToSymbol(entry.getKey(), "Client") : entry.getKey();
353+
final TsType interfaceType = settings.generateJaxrsApplicationInterface ? new TsType.ReferenceType(entry.getKey()) : null;
354+
final TsBeanModel clientModel = new TsBeanModel(null, TsBeanCategory.Service, true, symbol, null, null, null,
355+
Utils.listFromNullable(interfaceType), null, constructor, entry.getValue(), null);
356+
tsModel.getBeans().add(clientModel);
357+
}
353358
// helper
354359
tsModel.getHelpers().add(TsHelper.loadFromResource("/helpers/uriEncoding.ts"));
355360
return tsModel;
356361
}
357362

358-
private List<TsMethodModel> processJaxrsMethods(JaxrsApplicationModel jaxrsApplication, SymbolTable symbolTable, Symbol responseSymbol, TsType optionsType, boolean implement) {
359-
final List<TsMethodModel> methods = new ArrayList<>();
360-
final String applicationPath = jaxrsApplication.getApplicationPath();
361-
final Map<String, Long> methodNamesCount = groupingByMethodName(jaxrsApplication.getMethods());
362-
for (JaxrsMethodModel method : jaxrsApplication.getMethods()) {
363+
private Map<Symbol, List<TsMethodModel>> processJaxrsMethods(JaxrsApplicationModel jaxrsApplication, SymbolTable symbolTable, String nameSuffix, Symbol responseSymbol, TsType optionsType, boolean implement) {
364+
final Map<Symbol, List<TsMethodModel>> result = new LinkedHashMap<>();
365+
final Map<Symbol, List<JaxrsMethodModel>> groupedMethods = groupingByMethodContainer(jaxrsApplication, symbolTable, nameSuffix);
366+
for (Map.Entry<Symbol, List<JaxrsMethodModel>> entry : groupedMethods.entrySet()) {
367+
result.put(entry.getKey(), processJaxrsMethodGroup(jaxrsApplication, entry.getValue(), symbolTable, responseSymbol, optionsType, implement));
368+
}
369+
return result;
370+
}
371+
372+
private List<TsMethodModel> processJaxrsMethodGroup(JaxrsApplicationModel jaxrsApplication, List<JaxrsMethodModel> methods, SymbolTable symbolTable, Symbol responseSymbol, TsType optionsType, boolean implement) {
373+
final List<TsMethodModel> resultMethods = new ArrayList<>();
374+
final Map<String, Long> methodNamesCount = groupingByMethodName(methods);
375+
for (JaxrsMethodModel method : methods) {
363376
final boolean createLongName = methodNamesCount.get(method.getName()) > 1;
364-
methods.add(processJaxrsMethod(symbolTable, applicationPath, responseSymbol, method, createLongName, optionsType, implement));
377+
resultMethods.add(processJaxrsMethod(symbolTable, jaxrsApplication.getApplicationPath(), responseSymbol, method, createLongName, optionsType, implement));
378+
}
379+
return resultMethods;
380+
}
381+
382+
private Map<Symbol, List<JaxrsMethodModel>> groupingByMethodContainer(JaxrsApplicationModel jaxrsApplication, SymbolTable symbolTable, String nameSuffix) {
383+
// rewrite on Java 8 using streams
384+
final Map<Symbol, List<JaxrsMethodModel>> groupedMethods = new LinkedHashMap<>();
385+
for (JaxrsMethodModel method : jaxrsApplication.getMethods()) {
386+
final Symbol symbol = getContainerSymbol(jaxrsApplication, symbolTable, nameSuffix, method);
387+
if (!groupedMethods.containsKey(symbol)) {
388+
groupedMethods.put(symbol, new ArrayList<JaxrsMethodModel>());
389+
}
390+
groupedMethods.get(symbol).add(method);
391+
}
392+
return groupedMethods;
393+
}
394+
395+
private Symbol getContainerSymbol(JaxrsApplicationModel jaxrsApplication, SymbolTable symbolTable, String nameSuffix, JaxrsMethodModel method) {
396+
if (settings.jaxrsNamespacing == JaxrsNamespacing.perResource) {
397+
return symbolTable.getSymbol(method.getRootResource(), nameSuffix);
365398
}
366-
return methods;
399+
if (settings.jaxrsNamespacing == JaxrsNamespacing.byAnnotation) {
400+
final Annotation annotation = method.getRootResource().getAnnotation(settings.jaxrsNamespacingAnnotation);
401+
final String element = settings.jaxrsNamespacingAnnotationElement != null ? settings.jaxrsNamespacingAnnotationElement : "value";
402+
final String annotationValue = Utils.getAnnotationElementValue(annotation, element, String.class);
403+
if (annotationValue != null && Emitter.isValidIdentifierName(annotationValue)) {
404+
return symbolTable.getSyntheticSymbol(annotationValue, nameSuffix);
405+
}
406+
}
407+
final String applicationName = getApplicationName(jaxrsApplication);
408+
return symbolTable.getSyntheticSymbol(applicationName, nameSuffix);
367409
}
368410

369411
private static String getApplicationName(JaxrsApplicationModel jaxrsApplication) {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/SymbolTable.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ public Symbol getSymbol(Class<?> cls) {
2929
}
3030

3131
public Symbol getSymbol(Class<?> cls, String suffix) {
32-
final Pair<Class<?>, String> key = Pair.<Class<?>, String>of(cls, suffix);
32+
final String suffixString = suffix != null ? suffix : "";
33+
final Pair<Class<?>, String> key = Pair.<Class<?>, String>of(cls, suffixString);
3334
if (!symbols.containsKey(key)) {
34-
final String suffixString = suffix != null ? suffix : "";
3535
symbols.put(key, new Symbol("$" + cls.getName().replace('.', '$') + suffixString + "$"));
3636
}
3737
return symbols.get(key);
@@ -57,6 +57,21 @@ public Symbol getSyntheticSymbol(String name) {
5757
return syntheticSymbols.get(name);
5858
}
5959

60+
public Symbol getSyntheticSymbol(String name, String suffix) {
61+
return getSyntheticSymbol(name + (suffix != null ? suffix : ""));
62+
}
63+
64+
public Symbol addSuffixToSymbol(Symbol symbol, String suffix) {
65+
// try symbols
66+
for (Map.Entry<Pair<Class<?>, String>, Symbol> entry : symbols.entrySet()) {
67+
if (entry.getValue() == symbol) {
68+
return getSymbol(entry.getKey().getValue1(), entry.getKey().getValue2() + suffix);
69+
}
70+
}
71+
// syntheticSymbols
72+
return getSyntheticSymbol(symbol.name + suffix);
73+
}
74+
6075
public void resolveSymbolNames() {
6176
final Map<String, List<Class<?>>> names = new LinkedHashMap<>();
6277
for (Map.Entry<Pair<Class<?>, String>, Symbol> entry : symbols.entrySet()) {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/Emitter.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import java.util.*;
1111

1212

13-
public class Emitter {
13+
public class Emitter implements EmitterExtension.Writer {
1414

1515
private final Settings settings;
1616
private Writer writer;
@@ -111,12 +111,7 @@ private void emitElements(TsModel model, boolean exportKeyword, boolean declareK
111111
writeNewLine();
112112
writeNewLine();
113113
writeIndentedLine(String.format("// Added by '%s' extension", emitterExtension.getClass().getSimpleName()));
114-
emitterExtension.emitElements(new EmitterExtension.Writer() {
115-
@Override
116-
public void writeIndentedLine(String line) {
117-
Emitter.this.writeIndentedLine(line);
118-
}
119-
}, settings, exportKeyword, model);
114+
emitterExtension.emitElements(this, settings, exportKeyword, model);
120115
}
121116
}
122117

@@ -251,12 +246,7 @@ private void emitNumberEnums(TsModel model, boolean exportKeyword, boolean decla
251246
private void emitHelpers(TsModel model) {
252247
for (TsHelper helper : model.getHelpers()) {
253248
writeNewLine();
254-
for (String line : helper.getLines()) {
255-
writeIndentedLine(line
256-
.replace("\t", settings.indentString)
257-
.replace("\"", settings.quotes)
258-
);
259-
}
249+
writeTemplate(this, settings, helper.getLines(), null);
260250
}
261251
}
262252

@@ -277,11 +267,26 @@ private void emitComments(List<String> comments) {
277267
}
278268
}
279269

270+
public static void writeTemplate(EmitterExtension.Writer writer, Settings settings, List<String> template, Map<String, String> replacements) {
271+
for (String line : template) {
272+
if (replacements != null) {
273+
for (Map.Entry<String, String> entry : replacements.entrySet()) {
274+
line = line.replace(entry.getKey(), entry.getValue());
275+
}
276+
}
277+
writer.writeIndentedLine(line
278+
.replace("\t", settings.indentString)
279+
.replace("\"", settings.quotes)
280+
);
281+
}
282+
}
283+
280284
private void writeIndentedLine(boolean exportKeyword, String line) {
281285
writeIndentedLine((exportKeyword ? "export " : "") + line);
282286
}
283287

284-
private void writeIndentedLine(String line) {
288+
@Override
289+
public void writeIndentedLine(String line) {
285290
try {
286291
if (!line.isEmpty()) {
287292
for (int i = 0; i < indent; i++) {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanCategory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
public enum TsBeanCategory {
66

77
// order of these constants determines order of emitted declarations
8+
ServicePrerequisite,
89
Service,
910
Data,
1011

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,8 @@ public TsBeanModel withMethods(List<TsMethodModel> methods) {
9191
return new TsBeanModel(origin, category, isClass, name, typeParameters, parent, taggedUnionClasses, interfaces, properties, constructor, methods, comments);
9292
}
9393

94+
public boolean isJaxrsApplicationClientBean() {
95+
return category== TsBeanCategory.Service && isClass;
96+
}
97+
9498
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsDeclarationModel.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ public Class<?> getOrigin() {
2323
return origin;
2424
}
2525

26+
public TsBeanCategory getCategory() {
27+
return category;
28+
}
29+
2630
public Symbol getName() {
2731
return name;
2832
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsHelper.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ public TsHelper(List<String> lines) {
1414
}
1515

1616
public static TsHelper loadFromResource(String resourceName) {
17-
final String text = Utils.readString(TsHelper.class.getResourceAsStream(resourceName));
18-
return new TsHelper(Utils.splitMultiline(text, false));
17+
return new TsHelper(Utils.readLines(TsHelper.class.getResourceAsStream(resourceName)));
1918
}
2019

2120
public List<String> getLines() {

0 commit comments

Comments
 (0)