Skip to content

Commit ad79e2a

Browse files
committed
Merge pull request #30 from palantir/feature/duplicate-name-resolver
Added ability to resolve duplicate names
2 parents a540b95 + 4d76854 commit ad79e2a

File tree

3 files changed

+132
-58
lines changed

3 files changed

+132
-58
lines changed

typescript-service-generator-core/src/main/java/com/palantir/code/ts/generator/ServiceClassParser.java

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626

2727
import org.apache.commons.lang3.reflect.MethodUtils;
2828

29+
import com.google.common.collect.ArrayListMultimap;
2930
import com.google.common.collect.ImmutableList;
3031
import com.google.common.collect.Lists;
3132
import com.google.common.collect.Maps;
33+
import com.google.common.collect.Multimap;
3234
import com.google.common.collect.Sets;
3335
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointModel;
3436
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointParameterModel;
@@ -46,7 +48,7 @@
4648
public final class ServiceClassParser {
4749
@SuppressWarnings("unchecked")
4850
private final static List<Class<? extends Annotation>> ANNOTATION_CLASSES = Lists.newArrayList(POST.class, GET.class, DELETE.class, PUT.class, OPTIONS.class);
49-
51+
5052
public Set<Method> getAllServiceMethods(Class<?> serviceClass) {
5153
Set<Method> serviceMethods = Sets.newHashSet();
5254
for (Class<? extends Annotation> annotation : ANNOTATION_CLASSES) {
@@ -74,74 +76,89 @@ public ServiceModel parseServiceClass(Class<?> serviceClass, TypescriptServiceGe
7476
ModelCompiler compiler = new TypeScriptGenerator(settings.getSettings()).getModelCompiler();
7577

7678
List<ServiceEndpointModel> endpointModels = Lists.newArrayList();
77-
for (Method method : serviceMethods) {
78-
endpointModels.add(computeEndpointModel(method, compiler, settings));
79-
}
79+
endpointModels = computeEndpointModels(serviceMethods, compiler, settings);
80+
8081
Collections.sort(endpointModels);
8182
serviceModel.endpointModels(endpointModels);
8283
return serviceModel.build();
8384
}
8485

85-
private static ServiceEndpointModel computeEndpointModel(Method endpoint, ModelCompiler compiler, TypescriptServiceGeneratorConfiguration settings) {
86-
ImmutableServiceEndpointModel.Builder ret = ImmutableServiceEndpointModel.builder();
87-
ret.endpointName(endpoint.getName());
88-
ret.javaReturnType(endpoint.getGenericReturnType());
89-
ret.tsReturnType(compiler.typeFromJavaWithReplacement(endpoint.getGenericReturnType()));
90-
ret.endpointMethodType(getMethodType(endpoint));
91-
92-
String annotationValue = "";
93-
if (endpoint.getAnnotation(Path.class) != null) {
94-
annotationValue = endpoint.getAnnotation(Path.class).value();
86+
private static List<ServiceEndpointModel> computeEndpointModels(Set<Method> endpoints, ModelCompiler compiler, TypescriptServiceGeneratorConfiguration settings) {
87+
Multimap<String, Method> endpointNameMap = ArrayListMultimap.create();
88+
Map<Method, String> endpointNameGetter = Maps.newHashMap();
89+
for (Method endpoint : endpoints) {
90+
endpointNameMap.put(endpoint.getName(), endpoint);
91+
endpointNameGetter.put(endpoint, endpoint.getName());
9592
}
96-
ret.endpointPath(PathUtils.trimSlashes(annotationValue));
97-
Consumes consumes = endpoint.getAnnotation(Consumes.class);
98-
if (consumes != null) {
99-
if (consumes.value().length > 1) {
100-
throw new IllegalArgumentException("Don't know how to handle an endpoint with multiple consume types");
93+
for (String endpointName : endpointNameMap.keySet()) {
94+
List<Method> maybeDuplicates = Lists.newArrayList(endpointNameMap.get(endpointName).iterator());
95+
if (maybeDuplicates != null && maybeDuplicates.size() > 1) {
96+
endpointNameGetter.putAll(settings.duplicateEndpointNameResolver().resolveDuplicateNames(maybeDuplicates));
10197
}
102-
ret.endpointMediaType(consumes.value()[0]);
10398
}
104-
105-
List<Map<Class<?>, Annotation>> annotationList = getParamterAnnotationMaps(endpoint);
106-
List<ServiceEndpointParameterModel> mandatoryParameters = Lists.newArrayList();
107-
List<ServiceEndpointParameterModel> optionalParameters = Lists.newArrayList();
108-
int annotationListIndex = 0;
109-
for (Type javaParameterType : endpoint.getGenericParameterTypes()) {
110-
Map<Class<?>, Annotation> annotations = annotationList.get(annotationListIndex);
111-
ImmutableServiceEndpointParameterModel.Builder parameterModel = ImmutableServiceEndpointParameterModel.builder();
112-
113-
// if parameter is annotated with any ignored annotations, skip it entirely
114-
if (!Collections.disjoint(annotations.keySet(), settings.ignoredAnnotations())) {
115-
annotationListIndex++;
116-
continue;
117-
}
118-
119-
PathParam path = (PathParam) annotations.get(PathParam.class);
120-
if (path != null) {
121-
parameterModel.pathParam(path.value());
122-
}
123-
HeaderParam header = (HeaderParam) annotations.get(HeaderParam.class);
124-
if (header != null) {
125-
parameterModel.headerParam(header.value());
99+
List<ServiceEndpointModel> result = Lists.newArrayList();
100+
for (Method endpoint : endpoints) {
101+
ImmutableServiceEndpointModel.Builder ret = ImmutableServiceEndpointModel.builder();
102+
ret.endpointName(endpointNameGetter.get(endpoint));
103+
ret.javaReturnType(endpoint.getGenericReturnType());
104+
ret.tsReturnType(compiler.typeFromJavaWithReplacement(endpoint.getGenericReturnType()));
105+
ret.endpointMethodType(getMethodType(endpoint));
106+
107+
String annotationValue = "";
108+
if (endpoint.getAnnotation(Path.class) != null) {
109+
annotationValue = endpoint.getAnnotation(Path.class).value();
126110
}
127-
QueryParam query = (QueryParam) annotations.get(QueryParam.class);
128-
if (query != null) {
129-
parameterModel.queryParam(query.value());
111+
ret.endpointPath(PathUtils.trimSlashes(annotationValue));
112+
Consumes consumes = endpoint.getAnnotation(Consumes.class);
113+
if (consumes != null) {
114+
if (consumes.value().length > 1) {
115+
throw new IllegalArgumentException("Don't know how to handle an endpoint with multiple consume types");
116+
}
117+
ret.endpointMediaType(consumes.value()[0]);
130118
}
131119

132-
parameterModel.javaType(javaParameterType);
133-
TsType tsType = compiler.typeFromJavaWithReplacement(javaParameterType);
134-
parameterModel.tsType(tsType);
135-
if (tsType instanceof TsType.OptionalType || query != null) {
136-
optionalParameters.add(parameterModel.build());
137-
} else {
138-
mandatoryParameters.add(parameterModel.build());
120+
List<Map<Class<?>, Annotation>> annotationList = getParamterAnnotationMaps(endpoint);
121+
List<ServiceEndpointParameterModel> mandatoryParameters = Lists.newArrayList();
122+
List<ServiceEndpointParameterModel> optionalParameters = Lists.newArrayList();
123+
int annotationListIndex = 0;
124+
for (Type javaParameterType : endpoint.getGenericParameterTypes()) {
125+
Map<Class<?>, Annotation> annotations = annotationList.get(annotationListIndex);
126+
ImmutableServiceEndpointParameterModel.Builder parameterModel = ImmutableServiceEndpointParameterModel.builder();
127+
128+
// if parameter is annotated with any ignored annotations, skip it entirely
129+
if (!Collections.disjoint(annotations.keySet(), settings.ignoredAnnotations())) {
130+
annotationListIndex++;
131+
continue;
132+
}
133+
134+
PathParam path = (PathParam) annotations.get(PathParam.class);
135+
if (path != null) {
136+
parameterModel.pathParam(path.value());
137+
}
138+
HeaderParam header = (HeaderParam) annotations.get(HeaderParam.class);
139+
if (header != null) {
140+
parameterModel.headerParam(header.value());
141+
}
142+
QueryParam query = (QueryParam) annotations.get(QueryParam.class);
143+
if (query != null) {
144+
parameterModel.queryParam(query.value());
145+
}
146+
147+
parameterModel.javaType(javaParameterType);
148+
TsType tsType = compiler.typeFromJavaWithReplacement(javaParameterType);
149+
parameterModel.tsType(tsType);
150+
if (tsType instanceof TsType.OptionalType || query != null) {
151+
optionalParameters.add(parameterModel.build());
152+
} else {
153+
mandatoryParameters.add(parameterModel.build());
154+
}
155+
annotationListIndex++;
139156
}
140-
annotationListIndex++;
141-
}
142157

143-
ret.parameters(ImmutableList.<ServiceEndpointParameterModel> builder().addAll(mandatoryParameters).addAll(optionalParameters).build());
144-
return ret.build();
158+
ret.parameters(ImmutableList.<ServiceEndpointParameterModel> builder().addAll(mandatoryParameters).addAll(optionalParameters).build());
159+
result.add(ret.build());
160+
}
161+
return result;
145162
}
146163

147164
private static String getMethodType(Method endpoint) {

typescript-service-generator-core/src/main/java/com/palantir/code/ts/generator/TypescriptServiceGeneratorConfiguration.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
package com.palantir.code.ts.generator;
66

77
import java.io.File;
8+
import java.lang.reflect.Method;
89
import java.lang.reflect.ParameterizedType;
910
import java.lang.reflect.Type;
1011
import java.net.URI;
1112
import java.util.ArrayList;
1213
import java.util.HashSet;
1314
import java.util.List;
15+
import java.util.Map;
1416
import java.util.Set;
1517

1618
import org.immutables.value.Value;
@@ -19,6 +21,7 @@
1921
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2022
import com.google.common.base.Optional;
2123
import com.google.common.collect.Lists;
24+
import com.google.common.collect.Maps;
2225
import com.google.common.collect.Sets;
2326

2427
import cz.habarta.typescript.generator.GenericsTypeProcessor;
@@ -56,6 +59,20 @@ public Result processType(Type javaType, Context context) {
5659
};
5760
}
5861

62+
/**
63+
* Provides a strategy for resolving duplicate method names
64+
* @return
65+
*/
66+
@Value.Default
67+
public DuplicateMethodNameResolver duplicateEndpointNameResolver() {
68+
return new DuplicateMethodNameResolver() {
69+
@Override
70+
public Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName) {
71+
return Maps.newHashMap();
72+
}
73+
};
74+
}
75+
5976
/**
6077
* If true, emit method signatures even when it would create duplicate java names (results in typescript that does not compile).
6178
* If faslse, emit a warning instead.
@@ -164,4 +181,12 @@ public Settings getSettings() {
164181

165182
return settings;
166183
}
184+
185+
public interface DuplicateMethodNameResolver {
186+
/**
187+
* Takes a list of methods that all have the same name, returns a map of resolved names, or
188+
* null if the names can't be resolved
189+
*/
190+
Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName);
191+
}
167192
}

typescript-service-generator-core/src/test/java/com/palantir/code/ts/generator/ServiceClassParserTest.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
package com.palantir.code.ts.generator;
66

77
import static org.junit.Assert.assertEquals;
8+
import static org.junit.Assert.assertTrue;
89

10+
import java.lang.reflect.Method;
911
import java.lang.reflect.Type;
12+
import java.util.Collections;
1013
import java.util.List;
14+
import java.util.Map;
15+
import java.util.stream.Collectors;
1116

1217
import javax.annotation.CheckForNull;
1318

@@ -17,13 +22,16 @@
1722

1823
import com.google.common.collect.ImmutableSet;
1924
import com.google.common.collect.Lists;
25+
import com.google.common.collect.Maps;
2026
import com.google.common.collect.Sets;
27+
import com.palantir.code.ts.generator.TypescriptServiceGeneratorConfiguration.DuplicateMethodNameResolver;
2128
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointModel;
2229
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointParameterModel;
2330
import com.palantir.code.ts.generator.model.ServiceEndpointModel;
2431
import com.palantir.code.ts.generator.model.ServiceEndpointParameterModel;
2532
import com.palantir.code.ts.generator.model.ServiceModel;
2633
import com.palantir.code.ts.generator.utils.TestUtils.DataObject;
34+
import com.palantir.code.ts.generator.utils.TestUtils.DuplicateMethodNamesService;
2735
import com.palantir.code.ts.generator.utils.TestUtils.GenericObject;
2836
import com.palantir.code.ts.generator.utils.TestUtils.IgnoredParametersClass;
2937
import com.palantir.code.ts.generator.utils.TestUtils.ImmutablesObject;
@@ -113,8 +121,6 @@ public void parseComplexClassTest() throws NoSuchMethodException, SecurityExcept
113121
.build());
114122
}
115123
// To string because TsType has no equals method
116-
System.out.println(model.endpointModels().toString());
117-
System.out.println(endpoints.toString());
118124
assertEquals(endpoints.toString(), model.endpointModels().toString());
119125
}
120126

@@ -137,4 +143,30 @@ public void parseIgnoredTest() {
137143
.build();
138144
assertEquals(model.endpointModels(), Lists.newArrayList(stringGetterEndpointModel));
139145
}
146+
147+
@Test
148+
public void duplicateNameResolutionTest() {
149+
// Mock out a simple resolver that does the resolution based on number of parameters
150+
Mockito.when(settings.duplicateEndpointNameResolver()).thenReturn(new DuplicateMethodNameResolver() {
151+
@Override
152+
public Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName) {
153+
Map<Method, String> result = Maps.newHashMap();
154+
for (Method method : methodsWithSameName) {
155+
if (method.getParameterTypes().length > 0) {
156+
result.put(method, "nonZeroParameters");
157+
} else {
158+
result.put(method, "zeroParameters");
159+
}
160+
}
161+
return result;
162+
}
163+
});
164+
ServiceModel model = serviceClassParser.parseServiceClass(DuplicateMethodNamesService.class, settings);
165+
List<ServiceEndpointModel> endpointModels = Lists.newArrayList(model.endpointModels().iterator());
166+
Collections.sort(endpointModels);
167+
assertEquals(Lists.newArrayList("nonZeroParameters", "zeroParameters"),
168+
endpointModels.stream().map(endpoint -> endpoint.endpointName()).collect(Collectors.toList()));
169+
assertTrue(endpointModels.get(0).parameters().size() > 0);
170+
assertTrue(endpointModels.get(1).parameters().size() == 0);
171+
}
140172
}

0 commit comments

Comments
 (0)