diff --git a/core/src/main/java/feign/Contract.java b/core/src/main/java/feign/Contract.java index e0b0dbcff..61d9059a1 100644 --- a/core/src/main/java/feign/Contract.java +++ b/core/src/main/java/feign/Contract.java @@ -13,15 +13,23 @@ */ package feign; -import static feign.Util.checkState; -import static feign.Util.emptyToNull; +import feign.Request.HttpMethod; import java.lang.annotation.Annotation; -import java.lang.reflect.*; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import feign.Request.HttpMethod; +import static feign.Util.checkState; +import static feign.Util.emptyToNull; /** * Defines what annotations and values are valid on interfaces. @@ -47,11 +55,6 @@ public List parseAndValidateMetadata(Class targetType) { targetType.getSimpleName()); checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s", targetType.getSimpleName()); - if (targetType.getInterfaces().length == 1) { - checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, - "Only single-level inheritance supported: %s", - targetType.getSimpleName()); - } final Map result = new LinkedHashMap(); for (final Method method : targetType.getMethods()) { if (method.getDeclaringClass() == Object.class || @@ -60,8 +63,16 @@ public List parseAndValidateMetadata(Class targetType) { continue; } final MethodMetadata metadata = parseAndValidateMetadata(targetType, method); - checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", - metadata.configKey()); + if (result.containsKey(metadata.configKey())) { + MethodMetadata existingMetadata = result.get(metadata.configKey()); + Type existingReturnType = existingMetadata.returnType(); + Type overridingReturnType = metadata.returnType(); + Type resolvedType = Types.resolveReturnType(existingReturnType, overridingReturnType); + if (resolvedType.equals(overridingReturnType)) { + result.put(metadata.configKey(), metadata); + } + continue; + } result.put(metadata.configKey(), metadata); } return new ArrayList<>(result.values()); @@ -82,7 +93,8 @@ protected MethodMetadata parseAndValidateMetadata(Class targetType, Method me final MethodMetadata data = new MethodMetadata(); data.targetType(targetType); data.method(method); - data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType())); + data.returnType( + Types.resolve(targetType, targetType, method.getGenericReturnType())); data.configKey(Feign.configKey(targetType, method)); if (targetType.getInterfaces().length == 1) { @@ -127,7 +139,8 @@ protected MethodMetadata parseAndValidateMetadata(Class targetType, Method me checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s%s", method, data.warnings()); data.bodyIndex(i); - data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i])); + data.bodyType( + Types.resolve(targetType, targetType, genericParameterTypes[i])); } } } @@ -162,15 +175,13 @@ private static void checkMapKeys(String name, Type genericType) { } else if (genericType instanceof Class) { // raw class, type parameters cannot be inferred directly, but we can scan any extended // interfaces looking for any explict types - final Type[] interfaces = ((Class) genericType).getGenericInterfaces(); - if (interfaces != null) { - for (final Type extended : interfaces) { - if (ParameterizedType.class.isAssignableFrom(extended.getClass())) { - // use the first extended interface we find. - final Type[] parameterTypes = ((ParameterizedType) extended).getActualTypeArguments(); - keyClass = (Class) parameterTypes[0]; - break; - } + final Type[] interfaces = ((Class) genericType).getGenericInterfaces(); + for (final Type extended : interfaces) { + if (ParameterizedType.class.isAssignableFrom(extended.getClass())) { + // use the first extended interface we find. + final Type[] parameterTypes = ((ParameterizedType) extended).getActualTypeArguments(); + keyClass = (Class) parameterTypes[0]; + break; } } } @@ -316,6 +327,5 @@ private static Map> toMap(String[] input) { } return result; } - } } diff --git a/core/src/main/java/feign/DeclarativeContract.java b/core/src/main/java/feign/DeclarativeContract.java index adf7dc7b3..754ab877d 100644 --- a/core/src/main/java/feign/DeclarativeContract.java +++ b/core/src/main/java/feign/DeclarativeContract.java @@ -13,13 +13,13 @@ */ package feign; +import feign.Contract.BaseContract; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; -import feign.Contract.BaseContract; /** * {@link Contract} base implementation that works by declaring witch annotations should be diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index 1d1f387ae..5d96be2f6 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -13,13 +13,6 @@ */ package feign; -import java.io.IOException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import feign.Logger.Level; import feign.Logger.NoOpLogger; import feign.ReflectiveFeign.ParseHandlersByName; import feign.Request.Options; @@ -28,6 +21,12 @@ import feign.codec.Encoder; import feign.codec.ErrorDecoder; import feign.querymap.FieldQueryMapEncoder; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import static feign.ExceptionPropagationPolicy.NONE; /** diff --git a/core/src/main/java/feign/Types.java b/core/src/main/java/feign/Types.java index 90d35c8bd..44ee96a43 100644 --- a/core/src/main/java/feign/Types.java +++ b/core/src/main/java/feign/Types.java @@ -306,6 +306,25 @@ private static void checkNotPrimitive(Type type) { } } + public static Type resolveReturnType(Type baseType, Type overridingType) { + if (baseType instanceof Class && overridingType instanceof Class && + ((Class) baseType).isAssignableFrom((Class) overridingType)) { + // NOTE: javac generates multiple same methods for multiple inherited generic interfaces + return overridingType; + } + if (baseType instanceof Class && overridingType instanceof ParameterizedType) { + // NOTE: javac will generate multiple methods with different return types + // base interface declares generic method, override declares parameterized generic method + return overridingType; + } + if (baseType instanceof Class && overridingType instanceof TypeVariable) { + // NOTE: javac will generate multiple methods with different return types + // base interface declares non generic method, override declares generic method + return overridingType; + } + return baseType; + } + static final class ParameterizedTypeImpl implements ParameterizedType { private final Type ownerType; diff --git a/core/src/test/java/feign/DefaultContractInheritanceTest.java b/core/src/test/java/feign/DefaultContractInheritanceTest.java new file mode 100644 index 000000000..4784bded8 --- /dev/null +++ b/core/src/test/java/feign/DefaultContractInheritanceTest.java @@ -0,0 +1,181 @@ +/** + * Copyright 2012-2020 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import java.util.List; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; + +/** + * Tests interface inheritance defined per {@link Contract.Default} are interpreted into expected + * {@link feign .RequestTemplate template} instances. + */ +public class DefaultContractInheritanceTest { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + Contract.Default contract = new Contract.Default(); + + @Headers("Foo: Bar") + interface SimpleParameterizedBaseApi { + + @RequestLine("GET /api/{zoneId}") + M get(@Param("key") String key); + } + + interface SimpleParameterizedApi extends SimpleParameterizedBaseApi { + + } + + @Test + public void simpleParameterizedBaseApi() throws Exception { + final List md = contract.parseAndValidateMetadata(SimpleParameterizedApi.class); + + assertThat(md).hasSize(1); + + assertThat(md.get(0).configKey()) + .isEqualTo("SimpleParameterizedApi#get(String)"); + assertThat(md.get(0).returnType()) + .isEqualTo(String.class); + assertThat(md.get(0).template()) + .hasHeaders(entry("Foo", asList("Bar"))); + } + + @Test + public void parameterizedApiUnsupported() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Parameterized types unsupported: SimpleParameterizedBaseApi"); + contract.parseAndValidateMetadata(SimpleParameterizedBaseApi.class); + } + + interface OverrideParameterizedApi extends SimpleParameterizedBaseApi { + + @Override + @RequestLine("GET /api/{zoneId}") + String get(@Param("key") String key); + } + + @Headers("Foo: Bar") + interface SimpleBaseApi { + + @RequestLine("GET /api/{zoneId}") + String get(@Param("key") String key); + } + + interface OverrideSimpleApi extends SimpleBaseApi { + + @RequestLine("GET /api/v2/{zoneId}") + String get(@Param("key") String key); + } + + @Test + public void overrideParameterizedApiSupported() { + contract.parseAndValidateMetadata(OverrideParameterizedApi.class); + } + + @Test + public void overrideSimpleApiSupported() { + contract.parseAndValidateMetadata(OverrideSimpleApi.class); + } + + interface Child extends SimpleParameterizedBaseApi> { + + } + + interface GrandChild extends Child { + + } + + interface SingleInheritanceChild extends SimpleParameterizedBaseApi { + } + + interface FirstServiceBaseApi { + T get(String key); + } + + interface SecondServiceBaseApi { + void update(String key, T value); + } + + interface MultipleInheritanceDoneWrong + extends SimpleParameterizedBaseApi, FirstServiceBaseApi, + SecondServiceBaseApi { + } + + @Headers("Foo: Bar") + interface MultipleInheritanceParameterizedBaseApi + extends FirstServiceBaseApi, SecondServiceBaseApi { + + @Override + @RequestLine("GET /api/{zoneId}") + T get(@Param("key") String key); + + @Override + @RequestLine("POST /api/{zoneId}") + @Body("%7B\"value\": \"{value}\"%7D") + void update(@Param("key") String key, @Param("value") T value); + } + + interface MultipleInheritanceDoneCorrectly + extends MultipleInheritanceParameterizedBaseApi { + } + + interface ServiceApi extends FirstServiceBaseApi, SecondServiceBaseApi { + } + + @Headers("Foo: Bar") + interface MultipleInheritanceApi extends ServiceApi { + + @Override + @RequestLine("GET /api/{zoneId}") + String get(@Param("key") String key); + + @Override + @RequestLine("POST /api/{zoneId}") + @Body("%7B\"value\": \"{value}\"%7D") + void update(@Param("key") String key, @Param("value") String value); + } + + @Test + public void singleInheritance() { + contract.parseAndValidateMetadata(SingleInheritanceChild.class); + } + + @Test + public void multipleInheritanceDoneWrong() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Only single inheritance supported: MultipleInheritanceDoneWrong"); + contract.parseAndValidateMetadata(MultipleInheritanceDoneWrong.class); + } + + @Test + public void multipleInheritanceDoneCorrectly() { + contract.parseAndValidateMetadata(MultipleInheritanceDoneCorrectly.class); + } + + @Test + public void multipleInheritanceDoneCorrectly2() { + contract.parseAndValidateMetadata(MultipleInheritanceApi.class); + } + + @Test + public void multipleInheritanceSupported() { + contract.parseAndValidateMetadata(GrandChild.class); + } +} diff --git a/core/src/test/java/feign/DefaultContractTest.java b/core/src/test/java/feign/DefaultContractTest.java index 5df85461e..d492e1163 100644 --- a/core/src/test/java/feign/DefaultContractTest.java +++ b/core/src/test/java/feign/DefaultContractTest.java @@ -13,10 +13,6 @@ */ package feign; -import static feign.assertj.FeignAssertions.assertThat; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.MapEntry.entry; import com.google.gson.reflect.TypeToken; import org.assertj.core.api.Fail; import org.junit.Rule; @@ -25,6 +21,9 @@ import org.junit.rules.ExpectedException; import java.net.URI; import java.util.*; +import static feign.assertj.FeignAssertions.assertThat; +import static java.util.Arrays.asList; +import static org.assertj.core.data.MapEntry.entry; /** * Tests interfaces defined per {@link Contract.Default} are interpreted into expected @@ -643,67 +642,6 @@ interface SlashNeedToBeEncoded { String getZone(@Param("ZoneId") String vhost); } - @Headers("Foo: Bar") - interface SimpleParameterizedBaseApi { - - @RequestLine("GET /api/{zoneId}") - M get(@Param("key") String key); - } - - interface SimpleParameterizedApi extends SimpleParameterizedBaseApi { - - } - - @Test - public void simpleParameterizedBaseApi() throws Exception { - final List md = contract.parseAndValidateMetadata(SimpleParameterizedApi.class); - - assertThat(md).hasSize(1); - - assertThat(md.get(0).configKey()) - .isEqualTo("SimpleParameterizedApi#get(String)"); - assertThat(md.get(0).returnType()) - .isEqualTo(String.class); - assertThat(md.get(0).template()) - .hasHeaders(entry("Foo", asList("Bar"))); - } - - @Test - public void parameterizedApiUnsupported() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Parameterized types unsupported: SimpleParameterizedBaseApi"); - contract.parseAndValidateMetadata(SimpleParameterizedBaseApi.class); - } - - interface OverrideParameterizedApi extends SimpleParameterizedBaseApi { - - @Override - @RequestLine("GET /api/{zoneId}") - String get(@Param("key") String key); - } - - @Test - public void overrideBaseApiUnsupported() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Overrides unsupported: OverrideParameterizedApi#get(String)"); - contract.parseAndValidateMetadata(OverrideParameterizedApi.class); - } - - interface Child extends SimpleParameterizedBaseApi> { - - } - - interface GrandChild extends Child { - - } - - @Test - public void onlySingleLevelInheritanceSupported() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Only single-level inheritance supported: GrandChild"); - contract.parseAndValidateMetadata(GrandChild.class); - } - @Headers("Foo: Bar") interface ParameterizedBaseApi { diff --git a/core/src/test/java/feign/TypesResolveReturnTypeTest.java b/core/src/test/java/feign/TypesResolveReturnTypeTest.java new file mode 100644 index 000000000..ce5212158 --- /dev/null +++ b/core/src/test/java/feign/TypesResolveReturnTypeTest.java @@ -0,0 +1,559 @@ +/** + * Copyright 2012-2020 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package feign; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TypesResolveReturnTypeTest { + + interface Simple { + String get(); + } + + interface ConcreteSimple extends Simple { + } + + interface OverridingConcreteSimple extends Simple { + @Override + String get(); + } + + @Test + public void simple() { + Method[] methods = Simple.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = + Types.resolve(Simple.class, Simple.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(String.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteSimple() { + Method[] methods = ConcreteSimple.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteSimple.class, ConcreteSimple.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(String.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overridingConcreteSimple() { + Method[] methods = OverridingConcreteSimple.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(OverridingConcreteSimple.class, + OverridingConcreteSimple.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(String.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface SimplePrimitive { + long get(); + } + + interface ConcreteSimplePrimitive extends SimplePrimitive { + } + + interface OverridingConcreteSimplePrimitive extends SimplePrimitive { + @Override + long get(); + } + + @Test + public void simplePrimitive() { + Method[] methods = SimplePrimitive.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(SimplePrimitive.class, SimplePrimitive.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(long.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(long.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteSimplePrimitive() { + Method[] methods = ConcreteSimplePrimitive.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteSimplePrimitive.class, + ConcreteSimplePrimitive.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(long.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(long.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overridingConcreteSimplePrimitive() { + Method[] methods = OverridingConcreteSimplePrimitive.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(OverridingConcreteSimplePrimitive.class, + OverridingConcreteSimplePrimitive.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(long.class); + // included for completeness sake only + Type resolvedType = Types.resolveReturnType(long.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface Generic { + T get(); + } + + interface ConcreteSimpleClassGenericSecondLevel extends Generic { + } + + interface OverridingConcreteSimpleClassGenericSecondLevel extends Generic { + @Override + Long get(); + } + + @Test + public void generic() { + Method[] methods = Generic.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(Generic.class, Generic.class, + Generic.class.getMethods()[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof TypeVariable).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteSimpleClassGenericSecondLevel() { + Method[] methods = ConcreteSimpleClassGenericSecondLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteSimpleClassGenericSecondLevel.class, + ConcreteSimpleClassGenericSecondLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overridingConcreteSimpleClassGenericSecondLevel() { + Method[] methods = OverridingConcreteSimpleClassGenericSecondLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(OverridingConcreteSimpleClassGenericSecondLevel.class, + OverridingConcreteSimpleClassGenericSecondLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(OverridingConcreteSimpleClassGenericSecondLevel.class, + OverridingConcreteSimpleClassGenericSecondLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface SecondGeneric { + T get(); + } + + interface ConcreteSimpleClassMultipleGenericSecondLevel + extends Generic, SecondGeneric { + } + + interface OverridingConcreteSimpleClassMultipleGenericSecondLevel + extends Generic, SecondGeneric { + @Override + Long get(); + } + + interface RealizingSimpleClassGenericThirdLevel + extends ConcreteSimpleClassMultipleGenericSecondLevel { + } + + interface RealizingSimpleClassMultipleGenericThirdLevel + extends OverridingConcreteSimpleClassMultipleGenericSecondLevel { + } + + interface RealizingOverridingSimpleClassGenericThirdLevel + extends ConcreteSimpleClassMultipleGenericSecondLevel { + @Override + Long get(); + } + + interface RealizingOverridingSimpleClassMultipleGenericThirdLevel + extends OverridingConcreteSimpleClassMultipleGenericSecondLevel { + @Override + Long get(); + } + + @Test + public void concreteSimpleClassMultipleGenericSecondLevel() { + Method[] methods = ConcreteSimpleClassMultipleGenericSecondLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(ConcreteSimpleClassMultipleGenericSecondLevel.class, + ConcreteSimpleClassMultipleGenericSecondLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(ConcreteSimpleClassMultipleGenericSecondLevel.class, + ConcreteSimpleClassMultipleGenericSecondLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overridingConcreteSimpleClassMultipleGenericSecondLevel() { + Method[] methods = OverridingConcreteSimpleClassMultipleGenericSecondLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = + Types.resolve(OverridingConcreteSimpleClassMultipleGenericSecondLevel.class, + OverridingConcreteSimpleClassMultipleGenericSecondLevel.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = + Types.resolve(OverridingConcreteSimpleClassMultipleGenericSecondLevel.class, + OverridingConcreteSimpleClassMultipleGenericSecondLevel.class, + methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void realizingSimpleClassGenericThirdLevel() { + Method[] methods = RealizingSimpleClassGenericThirdLevel.class.getMethods(); + // TODO: BUG IN Java Compiler? Multiple same name methods with same return type for same + // parameters + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(RealizingSimpleClassGenericThirdLevel.class, + RealizingSimpleClassGenericThirdLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(RealizingSimpleClassGenericThirdLevel.class, + RealizingSimpleClassGenericThirdLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Long.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void realizingSimpleClassMultipleGenericThirdLevel() { + Method[] methods = RealizingSimpleClassMultipleGenericThirdLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(RealizingSimpleClassMultipleGenericThirdLevel.class, + RealizingSimpleClassMultipleGenericThirdLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(RealizingSimpleClassMultipleGenericThirdLevel.class, + RealizingSimpleClassMultipleGenericThirdLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void realizingOverridingSimpleClassGenericThirdLevel() { + Method[] methods = RealizingOverridingSimpleClassGenericThirdLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(RealizingOverridingSimpleClassGenericThirdLevel.class, + RealizingOverridingSimpleClassGenericThirdLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(RealizingOverridingSimpleClassGenericThirdLevel.class, + RealizingOverridingSimpleClassGenericThirdLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void realizingOverridingSimpleClassMultipleGenericThirdLevel() { + Method[] methods = RealizingOverridingSimpleClassMultipleGenericThirdLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = + Types.resolve(RealizingOverridingSimpleClassMultipleGenericThirdLevel.class, + RealizingOverridingSimpleClassMultipleGenericThirdLevel.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = + Types.resolve(RealizingOverridingSimpleClassMultipleGenericThirdLevel.class, + RealizingOverridingSimpleClassMultipleGenericThirdLevel.class, + methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + Assertions.assertThat(resolvedType).isNotEqualTo(resolved2); + } + + interface MultipleInheritedGeneric extends Generic, SecondGeneric { + } + + @Test + public void multipleInheritedGeneric() { + Method[] methods = MultipleInheritedGeneric.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(MultipleInheritedGeneric.class, + MultipleInheritedGeneric.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof TypeVariable).isTrue(); + Type resolved2 = Types.resolve(MultipleInheritedGeneric.class, + MultipleInheritedGeneric.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2 instanceof TypeVariable).isTrue(); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface SecondLevelSimpleClassGeneric extends Generic { + } + + interface ConcreteSimpleClassGenericThirdLevel extends SecondLevelSimpleClassGeneric { + } + + interface OverridingConcreteSimpleClassGenericThirdLevel + extends SecondLevelSimpleClassGeneric { + @Override + Long get(); + } + + @Test + public void secondLevelSimpleClassGeneric() { + Method[] methods = SecondLevelSimpleClassGeneric.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(SecondLevelSimpleClassGeneric.class, + SecondLevelSimpleClassGeneric.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof TypeVariable).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteSimpleClassGenericThirdLevel() { + Method[] methods = ConcreteSimpleClassGenericThirdLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteSimpleClassGenericThirdLevel.class, + ConcreteSimpleClassGenericThirdLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void OverridingConcreteSimpleClassGenericThirdLevel() { + Method[] methods = OverridingConcreteSimpleClassGenericThirdLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(OverridingConcreteSimpleClassGenericThirdLevel.class, + OverridingConcreteSimpleClassGenericThirdLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved).isEqualTo(Long.class); + Type resolved2 = Types.resolve(OverridingConcreteSimpleClassGenericThirdLevel.class, + OverridingConcreteSimpleClassGenericThirdLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface SecondLevelGenericClassGeneric> extends Generic { + } + + interface RealizedGeneric extends Generic { + } + + interface ConcreteGenericClassThirdLevel extends SecondLevelGenericClassGeneric { + } + + interface SecondLevelCollectionGeneric> extends Generic { + } + + interface ConcreteCollectionGenericThirdLevel + extends SecondLevelCollectionGeneric> { + } + + interface ThirdLevelCollectionGeneric> extends SecondLevelCollectionGeneric { + } + + interface ConcreteCollectionGenericFourthLevel extends ThirdLevelCollectionGeneric> { + } + + interface OverridingConcreteCollectionGenericFourthLevel + extends ThirdLevelCollectionGeneric> { + @Override + List get(); + } + + interface OverrideOverridingConcreteCollectionGenericFourthLevel + extends OverridingConcreteCollectionGenericFourthLevel { + @Override + List get(); + } + + interface GenericFourthLevelCollectionGeneric extends ThirdLevelCollectionGeneric> { + } + + interface SecondGenericFourthLevelCollectionGeneric + extends ThirdLevelCollectionGeneric> { + } + + interface ConcreteGenericCollectionGenericFifthLevel + extends GenericFourthLevelCollectionGeneric { + } + + interface OverridingConcreteGenericCollectionGenericFifthLevel extends + GenericFourthLevelCollectionGeneric, SecondGenericFourthLevelCollectionGeneric { + @Override + List get(); + } + + @Test + public void secondLevelCollectionGeneric() { + Method[] methods = SecondLevelCollectionGeneric.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(SecondLevelCollectionGeneric.class, + SecondLevelCollectionGeneric.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof TypeVariable).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void thirdLevelCollectionGeneric() { + Method[] methods = ThirdLevelCollectionGeneric.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ThirdLevelCollectionGeneric.class, + ThirdLevelCollectionGeneric.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof TypeVariable).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteCollectionGenericFourthLevel() { + Method[] methods = ConcreteCollectionGenericFourthLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteCollectionGenericFourthLevel.class, + ConcreteCollectionGenericFourthLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overridingConcreteCollectionGenericFourthLevel() { + Method[] methods = OverridingConcreteCollectionGenericFourthLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = Types.resolve(OverridingConcreteCollectionGenericFourthLevel.class, + OverridingConcreteCollectionGenericFourthLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + Type resolved2 = Types.resolve(OverridingConcreteCollectionGenericFourthLevel.class, + OverridingConcreteCollectionGenericFourthLevel.class, methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void overrideOverridingConcreteCollectionGenericFourthLevel() { + Method[] methods = OverrideOverridingConcreteCollectionGenericFourthLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = + Types.resolve(OverrideOverridingConcreteCollectionGenericFourthLevel.class, + OverrideOverridingConcreteCollectionGenericFourthLevel.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + Type resolved2 = + Types.resolve(OverrideOverridingConcreteCollectionGenericFourthLevel.class, + OverrideOverridingConcreteCollectionGenericFourthLevel.class, + methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(resolved, resolved2); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void genericFourthLevelCollectionGeneric() { + Method[] methods = GenericFourthLevelCollectionGeneric.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(GenericFourthLevelCollectionGeneric.class, + GenericFourthLevelCollectionGeneric.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + Type resolvedType = Types.resolveReturnType(Object.class, resolved); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + @Test + public void concreteGenericCollectionGenericFifthLevel() { + Method[] methods = ConcreteGenericCollectionGenericFifthLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(1); + Type resolved = Types.resolve(ConcreteGenericCollectionGenericFifthLevel.class, + ConcreteGenericCollectionGenericFifthLevel.class, methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + ParameterizedType parameterizedType = (ParameterizedType) resolved; + } + + @Test + public void overridingConcreteGenericCollectionGenericFifthLevel() { + Method[] methods = OverridingConcreteGenericCollectionGenericFifthLevel.class.getMethods(); + Assertions.assertThat(methods.length).isEqualTo(2); + Type resolved = + Types.resolve(OverridingConcreteGenericCollectionGenericFifthLevel.class, + OverridingConcreteGenericCollectionGenericFifthLevel.class, + methods[0].getGenericReturnType()); + Assertions.assertThat(resolved instanceof ParameterizedType).isTrue(); + Type resolved2 = + Types.resolve(OverridingConcreteGenericCollectionGenericFifthLevel.class, + OverridingConcreteGenericCollectionGenericFifthLevel.class, + methods[1].getGenericReturnType()); + Assertions.assertThat(resolved2).isEqualTo(Object.class); + Type resolvedType = Types.resolveReturnType(methods[0].getGenericReturnType(), + methods[1].getGenericReturnType()); + Assertions.assertThat(resolvedType).isEqualTo(resolved); + } + + interface SecondLevelMapGeneric> extends Generic { + } + + interface ThirdLevelMapGeneric> extends SecondLevelMapGeneric { + } + + interface SimpleArrayGeneric { + T[] get(); + } + + interface SimpleClassNonGeneric { + Object get(); + } + + interface OverrideNonGenericSimpleClass extends SimpleClassNonGeneric { + @Override + Number get(); + } + + interface GenerifiedOverrideNonGenericSimpleClass + extends OverrideNonGenericSimpleClass { + @Override + T get(); + } + + interface RealizedGenerifiedOverrideNonGenericSimpleClass + extends GenerifiedOverrideNonGenericSimpleClass { + } + + interface OverridingGenerifiedOverrideNonGenericSimpleClass + extends GenerifiedOverrideNonGenericSimpleClass { + @Override + Long get(); + } +} diff --git a/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java b/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java index 5b6cd866c..ddd7d5af2 100644 --- a/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java +++ b/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java @@ -13,16 +13,23 @@ */ package feign.jaxrs; -import static feign.Util.checkState; -import static feign.Util.emptyToNull; -import static feign.Util.removeValues; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collections; -import javax.ws.rs.*; import feign.DeclarativeContract; import feign.MethodMetadata; import feign.Request; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collections; +import static feign.Util.checkState; +import static feign.Util.emptyToNull; +import static feign.Util.removeValues; /** * Please refer to the Feign diff --git a/jaxrs2/src/main/java/feign/jaxrs2/JAXRS2Contract.java b/jaxrs2/src/main/java/feign/jaxrs2/JAXRS2Contract.java index f3a291b37..26c4f82e1 100644 --- a/jaxrs2/src/main/java/feign/jaxrs2/JAXRS2Contract.java +++ b/jaxrs2/src/main/java/feign/jaxrs2/JAXRS2Contract.java @@ -13,10 +13,10 @@ */ package feign.jaxrs2; +import feign.jaxrs.JAXRSContract; import javax.ws.rs.BeanParam; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; -import feign.jaxrs.JAXRSContract; /** * Please refer to the Feign @@ -33,5 +33,4 @@ public JAXRS2Contract() { super.registerParameterAnnotation(Context.class, (ann, data, i) -> data.ignoreParamater(i)); super.registerParameterAnnotation(BeanParam.class, (ann, data, i) -> data.ignoreParamater(i)); } - } diff --git a/jaxrs2/src/test/java/feign/jaxrs2/JAXRS2ContractTest.java b/jaxrs2/src/test/java/feign/jaxrs2/JAXRS2ContractTest.java index 8714fd4b3..135c92073 100644 --- a/jaxrs2/src/test/java/feign/jaxrs2/JAXRS2ContractTest.java +++ b/jaxrs2/src/test/java/feign/jaxrs2/JAXRS2ContractTest.java @@ -13,18 +13,23 @@ */ package feign.jaxrs2; -import static feign.assertj.FeignAssertions.assertThat; -import org.assertj.core.data.MapEntry; +import feign.MethodMetadata; +import feign.jaxrs.JAXRSContract; +import feign.jaxrs.JAXRSContractTest; +import feign.jaxrs2.JAXRS2ContractTest.Jaxrs2Internals.Input; import org.junit.Test; -import javax.ws.rs.*; +import javax.ws.rs.BeanParam; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; -import feign.MethodMetadata; -import feign.jaxrs.JAXRSContract; -import feign.jaxrs.JAXRSContractTest; -import feign.jaxrs2.JAXRS2ContractTest.Jaxrs2Internals.Input; +import static feign.assertj.FeignAssertions.assertThat; /** * Tests interfaces defined per {@link JAXRS2Contract} are interpreted into expected diff --git a/spring4/src/main/java/feign/spring/SpringContract.java b/spring4/src/main/java/feign/spring/SpringContract.java index b97e5afcc..1cd690413 100755 --- a/spring4/src/main/java/feign/spring/SpringContract.java +++ b/spring4/src/main/java/feign/spring/SpringContract.java @@ -13,12 +13,22 @@ */ package feign.spring; -import java.util.ArrayList; -import java.util.Collection; -import org.springframework.web.bind.annotation.*; import feign.DeclarativeContract; import feign.MethodMetadata; import feign.Request; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import java.util.ArrayList; +import java.util.Collection; public class SpringContract extends DeclarativeContract { @@ -138,5 +148,4 @@ protected Collection addTemplatedParam(Collection possiblyNull, possiblyNull.add(String.format("{%s}", name)); return possiblyNull; } - }