Skip to content

Commit

Permalink
Merge pull request #30 from palantir/feature/duplicate-name-resolver
Browse files Browse the repository at this point in the history
Added ability to resolve duplicate names
  • Loading branch information
ryanmcnamara committed Mar 17, 2016
2 parents a540b95 + 4d76854 commit ad79e2a
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@

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

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointModel;
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointParameterModel;
Expand All @@ -46,7 +48,7 @@
public final class ServiceClassParser {
@SuppressWarnings("unchecked")
private final static List<Class<? extends Annotation>> ANNOTATION_CLASSES = Lists.newArrayList(POST.class, GET.class, DELETE.class, PUT.class, OPTIONS.class);

public Set<Method> getAllServiceMethods(Class<?> serviceClass) {
Set<Method> serviceMethods = Sets.newHashSet();
for (Class<? extends Annotation> annotation : ANNOTATION_CLASSES) {
Expand Down Expand Up @@ -74,74 +76,89 @@ public ServiceModel parseServiceClass(Class<?> serviceClass, TypescriptServiceGe
ModelCompiler compiler = new TypeScriptGenerator(settings.getSettings()).getModelCompiler();

List<ServiceEndpointModel> endpointModels = Lists.newArrayList();
for (Method method : serviceMethods) {
endpointModels.add(computeEndpointModel(method, compiler, settings));
}
endpointModels = computeEndpointModels(serviceMethods, compiler, settings);

Collections.sort(endpointModels);
serviceModel.endpointModels(endpointModels);
return serviceModel.build();
}

private static ServiceEndpointModel computeEndpointModel(Method endpoint, ModelCompiler compiler, TypescriptServiceGeneratorConfiguration settings) {
ImmutableServiceEndpointModel.Builder ret = ImmutableServiceEndpointModel.builder();
ret.endpointName(endpoint.getName());
ret.javaReturnType(endpoint.getGenericReturnType());
ret.tsReturnType(compiler.typeFromJavaWithReplacement(endpoint.getGenericReturnType()));
ret.endpointMethodType(getMethodType(endpoint));

String annotationValue = "";
if (endpoint.getAnnotation(Path.class) != null) {
annotationValue = endpoint.getAnnotation(Path.class).value();
private static List<ServiceEndpointModel> computeEndpointModels(Set<Method> endpoints, ModelCompiler compiler, TypescriptServiceGeneratorConfiguration settings) {
Multimap<String, Method> endpointNameMap = ArrayListMultimap.create();
Map<Method, String> endpointNameGetter = Maps.newHashMap();
for (Method endpoint : endpoints) {
endpointNameMap.put(endpoint.getName(), endpoint);
endpointNameGetter.put(endpoint, endpoint.getName());
}
ret.endpointPath(PathUtils.trimSlashes(annotationValue));
Consumes consumes = endpoint.getAnnotation(Consumes.class);
if (consumes != null) {
if (consumes.value().length > 1) {
throw new IllegalArgumentException("Don't know how to handle an endpoint with multiple consume types");
for (String endpointName : endpointNameMap.keySet()) {
List<Method> maybeDuplicates = Lists.newArrayList(endpointNameMap.get(endpointName).iterator());
if (maybeDuplicates != null && maybeDuplicates.size() > 1) {
endpointNameGetter.putAll(settings.duplicateEndpointNameResolver().resolveDuplicateNames(maybeDuplicates));
}
ret.endpointMediaType(consumes.value()[0]);
}

List<Map<Class<?>, Annotation>> annotationList = getParamterAnnotationMaps(endpoint);
List<ServiceEndpointParameterModel> mandatoryParameters = Lists.newArrayList();
List<ServiceEndpointParameterModel> optionalParameters = Lists.newArrayList();
int annotationListIndex = 0;
for (Type javaParameterType : endpoint.getGenericParameterTypes()) {
Map<Class<?>, Annotation> annotations = annotationList.get(annotationListIndex);
ImmutableServiceEndpointParameterModel.Builder parameterModel = ImmutableServiceEndpointParameterModel.builder();

// if parameter is annotated with any ignored annotations, skip it entirely
if (!Collections.disjoint(annotations.keySet(), settings.ignoredAnnotations())) {
annotationListIndex++;
continue;
}

PathParam path = (PathParam) annotations.get(PathParam.class);
if (path != null) {
parameterModel.pathParam(path.value());
}
HeaderParam header = (HeaderParam) annotations.get(HeaderParam.class);
if (header != null) {
parameterModel.headerParam(header.value());
List<ServiceEndpointModel> result = Lists.newArrayList();
for (Method endpoint : endpoints) {
ImmutableServiceEndpointModel.Builder ret = ImmutableServiceEndpointModel.builder();
ret.endpointName(endpointNameGetter.get(endpoint));
ret.javaReturnType(endpoint.getGenericReturnType());
ret.tsReturnType(compiler.typeFromJavaWithReplacement(endpoint.getGenericReturnType()));
ret.endpointMethodType(getMethodType(endpoint));

String annotationValue = "";
if (endpoint.getAnnotation(Path.class) != null) {
annotationValue = endpoint.getAnnotation(Path.class).value();
}
QueryParam query = (QueryParam) annotations.get(QueryParam.class);
if (query != null) {
parameterModel.queryParam(query.value());
ret.endpointPath(PathUtils.trimSlashes(annotationValue));
Consumes consumes = endpoint.getAnnotation(Consumes.class);
if (consumes != null) {
if (consumes.value().length > 1) {
throw new IllegalArgumentException("Don't know how to handle an endpoint with multiple consume types");
}
ret.endpointMediaType(consumes.value()[0]);
}

parameterModel.javaType(javaParameterType);
TsType tsType = compiler.typeFromJavaWithReplacement(javaParameterType);
parameterModel.tsType(tsType);
if (tsType instanceof TsType.OptionalType || query != null) {
optionalParameters.add(parameterModel.build());
} else {
mandatoryParameters.add(parameterModel.build());
List<Map<Class<?>, Annotation>> annotationList = getParamterAnnotationMaps(endpoint);
List<ServiceEndpointParameterModel> mandatoryParameters = Lists.newArrayList();
List<ServiceEndpointParameterModel> optionalParameters = Lists.newArrayList();
int annotationListIndex = 0;
for (Type javaParameterType : endpoint.getGenericParameterTypes()) {
Map<Class<?>, Annotation> annotations = annotationList.get(annotationListIndex);
ImmutableServiceEndpointParameterModel.Builder parameterModel = ImmutableServiceEndpointParameterModel.builder();

// if parameter is annotated with any ignored annotations, skip it entirely
if (!Collections.disjoint(annotations.keySet(), settings.ignoredAnnotations())) {
annotationListIndex++;
continue;
}

PathParam path = (PathParam) annotations.get(PathParam.class);
if (path != null) {
parameterModel.pathParam(path.value());
}
HeaderParam header = (HeaderParam) annotations.get(HeaderParam.class);
if (header != null) {
parameterModel.headerParam(header.value());
}
QueryParam query = (QueryParam) annotations.get(QueryParam.class);
if (query != null) {
parameterModel.queryParam(query.value());
}

parameterModel.javaType(javaParameterType);
TsType tsType = compiler.typeFromJavaWithReplacement(javaParameterType);
parameterModel.tsType(tsType);
if (tsType instanceof TsType.OptionalType || query != null) {
optionalParameters.add(parameterModel.build());
} else {
mandatoryParameters.add(parameterModel.build());
}
annotationListIndex++;
}
annotationListIndex++;
}

ret.parameters(ImmutableList.<ServiceEndpointParameterModel> builder().addAll(mandatoryParameters).addAll(optionalParameters).build());
return ret.build();
ret.parameters(ImmutableList.<ServiceEndpointParameterModel> builder().addAll(mandatoryParameters).addAll(optionalParameters).build());
result.add(ret.build());
}
return result;
}

private static String getMethodType(Method endpoint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
package com.palantir.code.ts.generator;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.immutables.value.Value;
Expand All @@ -19,6 +21,7 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import cz.habarta.typescript.generator.GenericsTypeProcessor;
Expand Down Expand Up @@ -56,6 +59,20 @@ public Result processType(Type javaType, Context context) {
};
}

/**
* Provides a strategy for resolving duplicate method names
* @return
*/
@Value.Default
public DuplicateMethodNameResolver duplicateEndpointNameResolver() {
return new DuplicateMethodNameResolver() {
@Override
public Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName) {
return Maps.newHashMap();
}
};
}

/**
* If true, emit method signatures even when it would create duplicate java names (results in typescript that does not compile).
* If faslse, emit a warning instead.
Expand Down Expand Up @@ -164,4 +181,12 @@ public Settings getSettings() {

return settings;
}

public interface DuplicateMethodNameResolver {
/**
* Takes a list of methods that all have the same name, returns a map of resolved names, or
* null if the names can't be resolved
*/
Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
package com.palantir.code.ts.generator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.CheckForNull;

Expand All @@ -17,13 +22,16 @@

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.palantir.code.ts.generator.TypescriptServiceGeneratorConfiguration.DuplicateMethodNameResolver;
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointModel;
import com.palantir.code.ts.generator.model.ImmutableServiceEndpointParameterModel;
import com.palantir.code.ts.generator.model.ServiceEndpointModel;
import com.palantir.code.ts.generator.model.ServiceEndpointParameterModel;
import com.palantir.code.ts.generator.model.ServiceModel;
import com.palantir.code.ts.generator.utils.TestUtils.DataObject;
import com.palantir.code.ts.generator.utils.TestUtils.DuplicateMethodNamesService;
import com.palantir.code.ts.generator.utils.TestUtils.GenericObject;
import com.palantir.code.ts.generator.utils.TestUtils.IgnoredParametersClass;
import com.palantir.code.ts.generator.utils.TestUtils.ImmutablesObject;
Expand Down Expand Up @@ -113,8 +121,6 @@ public void parseComplexClassTest() throws NoSuchMethodException, SecurityExcept
.build());
}
// To string because TsType has no equals method
System.out.println(model.endpointModels().toString());
System.out.println(endpoints.toString());
assertEquals(endpoints.toString(), model.endpointModels().toString());
}

Expand All @@ -137,4 +143,30 @@ public void parseIgnoredTest() {
.build();
assertEquals(model.endpointModels(), Lists.newArrayList(stringGetterEndpointModel));
}

@Test
public void duplicateNameResolutionTest() {
// Mock out a simple resolver that does the resolution based on number of parameters
Mockito.when(settings.duplicateEndpointNameResolver()).thenReturn(new DuplicateMethodNameResolver() {
@Override
public Map<Method, String> resolveDuplicateNames(List<Method> methodsWithSameName) {
Map<Method, String> result = Maps.newHashMap();
for (Method method : methodsWithSameName) {
if (method.getParameterTypes().length > 0) {
result.put(method, "nonZeroParameters");
} else {
result.put(method, "zeroParameters");
}
}
return result;
}
});
ServiceModel model = serviceClassParser.parseServiceClass(DuplicateMethodNamesService.class, settings);
List<ServiceEndpointModel> endpointModels = Lists.newArrayList(model.endpointModels().iterator());
Collections.sort(endpointModels);
assertEquals(Lists.newArrayList("nonZeroParameters", "zeroParameters"),
endpointModels.stream().map(endpoint -> endpoint.endpointName()).collect(Collectors.toList()));
assertTrue(endpointModels.get(0).parameters().size() > 0);
assertTrue(endpointModels.get(1).parameters().size() == 0);
}
}

0 comments on commit ad79e2a

Please sign in to comment.