Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic check for function return type #216

Closed
wants to merge 1 commit into from

Conversation

deepesh-verma
Copy link
Contributor

Fixes: #215
Signed-off-by: deepesh-verma deepeshvrm1@gmail.com

@pivotal-issuemaster
Copy link

@deepesh-verma Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

@pivotal-issuemaster
Copy link

@deepesh-verma Thank you for signing the Contributor License Agreement!

RecoverAnnotationRecoveryHandler.this.methods.put(method,
new SimpleMetadata(parameterTypes.length, null));
if (recover != null && failingMethod.getGenericReturnType() instanceof ParameterizedType) {
if (method.getGenericReturnType().equals(failingMethod.getGenericReturnType())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this fixes the reported issue, unfortunately, I think it needs to be more sophisticated; we need to examine the generic parameter(s) Map<String, Foo> should match Map<String, Bar> if Bar is a subclass of Foo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a method isParameterizedTypeAssignable to verify the actualTypeArguments with isAssignableFrom check. Please review and let me know if this is what we expect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@garyrussell I have made the changes as requested. Can you please review?

@garyrussell
Copy link
Contributor

Thanks for the contribution; sorry for the delay.

After further review; this is incorrect:

	protected static class GenericInheritanceReturnTypeRecover {

		@Retryable
		public Map<String, Double> bar(String name) {
			return Collections.singletonMap("bar", 0.0);
		}

		@Recover
		public Map<String, Number> quux(RuntimeException re, String name) {
			return Collections.singletonMap("bar", 3);
		}

	}

(My original comment was wrong). We can't return a wider type from the recover method; we can, however, return a narrower type, if there is no direct match. This should work...

	protected static class GenericInheritanceReturnTypeRecover {

		@Retryable
		public Map<String, Number> bar(String name) {
			return Collections.singletonMap("bar", 0.0);
		}

		@Recover
		public Map<String, Double> quux(RuntimeException re, String name) {
			return Collections.singletonMap("bar", 3);
		}

	}

...but I think it would be ok to only allow direct matches because the above would add a lot more complexity.

However, we can't stop at the first level of generics; we need to examine nested generic types; this should work, but does not.

	protected static class MoreComplexGenericInheritanceReturnTypeRecover {

		@Retryable
		public Map<String, List<Integer>> foo(String name) {
			return Collections.singletonMap("foo", Collections.singletonList(0));
		}

		@Retryable
		public Map<String, List<Double>> bar(String name) {
			return Collections.singletonMap("bar", Collections.singletonList(0.0));
		}

		@Recover
		public Map<String, List<Integer>> baz(RuntimeException re, String name) {
			return Collections.singletonMap("foo", Collections.singletonList(1));
		}

		@Recover
		public Map<String, List<Double>> qux(RuntimeException re, String name) {
			return Collections.singletonMap("bar", Collections.singletonList(1.0));
		}

	}
	@Test
	public void genericMapListIntegerReturnTypeRecoverMethod() {
		RecoverAnnotationRecoveryHandler<?> fooHandler = new RecoverAnnotationRecoveryHandler<Integer>(
				new MoreComplexGenericInheritanceReturnTypeRecover(),
				ReflectionUtils.findMethod(
						MoreComplexGenericInheritanceReturnTypeRecover.class, "foo",
						String.class));
		@SuppressWarnings("unchecked")
		Map<String, List<Integer>> recoverResponseMapRe = (Map<String, List<Integer>>) fooHandler
				.recover(new Object[] { "Aldo" }, new RuntimeException("Planned"));
		assertEquals(1, recoverResponseMapRe.get("foo").get(0).intValue());
	}

	@Test
	public void genericMapListDoubleReturnTypeRecoverMethod() {
		RecoverAnnotationRecoveryHandler<?> barHandler = new RecoverAnnotationRecoveryHandler<Double>(
				new MoreComplexGenericInheritanceReturnTypeRecover(),
				ReflectionUtils.findMethod(
						MoreComplexGenericInheritanceReturnTypeRecover.class, "bar",
						String.class));
		@SuppressWarnings("unchecked")
		Map<String, List<Double>> recoverResponseMapRe = (Map<String, List<Double>>) barHandler
				.recover(new Object[] { "Aldo" }, new RuntimeException("Planned"));
		assertEquals(1.0, recoverResponseMapRe.get("bar").get(0).doubleValue(), 0.0);
	}

The second test matches baz instead of qux.

@deepesh-verma
Copy link
Contributor Author

deepesh-verma commented Oct 3, 2020

Thanks for the feedback and the test cases; It helps a lot to clearly understand what is expected here;
Let me try to work on below points -

  • Generic return type match should only be a direct match (not narrower)
  • We should consider all the nested level of generics for matching method return type

Fixes: [spring-projects#215](spring-projects#215)
Signed-off-by: deepesh-verma <deepeshvrm1@gmail.com>
@deepesh-verma
Copy link
Contributor Author

Changes -

  • Update isParameterizedTypeAssignable to recusrively call itself, if the actualTypeArguments are again instanceof ParameterizedType.
  • Add test cases with generic return type having 3 levels of nesting to test this out.

@garyrussell, Please review.

@garyrussell
Copy link
Contributor

It's best to add a new commit rather than force-pushing so we can more easily see what changed.

@deepesh-verma
Copy link
Contributor Author

deepesh-verma commented Oct 6, 2020

Sorry for that. I will make sure to add new commits going forward.
Please let me know if there is anything I can do to make it easier for now.

@garyrussell
Copy link
Contributor

Sorry for the delay; finally merged as 4d2bb2b and ae3ad4a

@garyrussell garyrussell closed this Mar 9, 2021
@garyrussell garyrussell added this to the 1.3.2 milestone Mar 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants