xvik / generics-resolver

Java generics runtime resolver
https://xvik.github.io/generics-resolver
MIT License
45 stars 9 forks source link

UnknownGenericException thrown when class implements multiple sub-interfaces of the same generic base-interface #10

Open buko opened 3 years ago

buko commented 3 years ago

It seems like generics-resolver doesn't handle the case where a (generic) class implements multiple sub-interfaces and an attempt is made to resolve against one of those sub-interfaces. Given:

interface Participant<P> {
}

interface FooParticipant<P> extends Participant<P> {
   foo(P target);
}

interface BarParticipant<P> extends Participant<P> {
   bar(P target);
}

class Impl<P> implements FooParticipant<P>, BarParticipant<P> {
  ...
}

Then attempts to invoke to call GenericsResolver.resolve(Impl.class, FooParticipant.class).genericTypes() throws a very nasty exception stack trace[1].

There are two problems here:

  1. The constructor for UnknownGenericException calls is attempting to do some reflection resolution of its own. This leads to a nasty case where the constructor itself throws an exception and the original stack trace is lost completely.
  2. The loop-checking code seems off.

Note that the code above works fine if Impl implements only FooParticipant or BarParticipant. The problem only occurs when Impl implements both.

Workaround

A workaround to the issue is to resolve Impl.class against the base interface Participant.class. This produces the desired behavior and is equivalent because the same generic parameters must be passed to all the generic sub-interfaces interfaces. It seems like generics-resolver could do something similar, automatically: if a class implements multiple generic sub-interfaces of the same base generic-interface then attempts to resolve the implementation class against a generic sub-interface should be treated as an attempt to resolve against the base generic-interface.

[1]

java.lang.StackOverflowError
    at java.base/java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:4019)
    at java.base/java.util.regex.Pattern$GroupHead.match(Pattern.java:4855)
    at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4800)
    at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4798)
    at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4798)
    at java.base/java.util.regex.Pattern$BranchConn.match(Pattern.java:4763)
    at java.base/java.util.regex.Pattern$GroupTail.match(Pattern.java:4886)
    at java.base/java.util.regex.Pattern$BmpCharPropertyGreedy.match(Pattern.java:4394)
    at java.base/java.util.regex.Pattern$GroupHead.match(Pattern.java:4855)
    at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4800)
    at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4798)
    at java.base/java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:4020)
    at java.base/java.util.regex.Pattern$Start.match(Pattern.java:3673)
    at java.base/java.util.regex.Matcher.search(Matcher.java:1729)
    at java.base/java.util.regex.Matcher.find(Matcher.java:773)
    at java.base/java.util.Formatter.parse(Formatter.java:2702)
    at java.base/java.util.Formatter.format(Formatter.java:2655)
    at java.base/java.util.Formatter.format(Formatter.java:2609)
    at java.base/java.lang.String.format(String.java:3292)
    at ru.vyarus.java.generics.resolver.error.UnknownGenericException.<init>(UnknownGenericException.java:46)
    at ru.vyarus.java.generics.resolver.error.UnknownGenericException.<init>(UnknownGenericException.java:40)
    at ru.vyarus.java.generics.resolver.error.UnknownGenericException.<init>(UnknownGenericException.java:30)
    at ru.vyarus.java.generics.resolver.util.GenericsUtils.declaredGeneric(GenericsUtils.java:528)
    at ru.vyarus.java.generics.resolver.util.GenericsUtils.resolveTypeVariables(GenericsUtils.java:222)
    at ru.vyarus.java.generics.resolver.util.GenericsUtils.resolveTypeVariables(GenericsUtils.java:268)
    at ru.vyarus.java.generics.resolver.util.GenericsUtils.resolveTypeVariables(GenericsUtils.java:231)
    at ru.vyarus.java.generics.resolver.util.GenericsResolutionUtils.resolveRawGeneric(GenericsResolutionUtils.java:212)
    at ru.vyarus.java.generics.resolver.util.GenericsResolutionUtils.resolveRawGenericsChain(GenericsResolutionUtils.java:305)
    at ru.vyarus.java.generics.resolver.util.GenericsResolutionUtils.resolveDirectRawGenerics(GenericsResolutionUtils.java:136)
    at ru.vyarus.java.generics.resolver.util.GenericsResolutionUtils.resolveRawGenerics(GenericsResolutionUtils.java:123)
    at ru.vyarus.java.generics.resolver.util.GenericsResolutionUtils.resolveGenerics(GenericsResolutionUtils.java:105)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.resolveUpperGenerics(TypesWalker.java:266)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.visitGenerics(TypesWalker.java:121)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.doWalk(TypesWalker.java:93)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.visitGenerics(TypesWalker.java:133)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.doWalk(TypesWalker.java:93)
    at ru.vyarus.java.generics.resolver.util.walk.TypesWalker.visitGenerics(TypesWalker.java:133)
        ***Several more lines like above***
xvik commented 3 years ago

Thank you very much for the detailed report! I'll look it next week

xvik commented 3 years ago

I tried this on 3.0.3 and see no errors. Probably you are using not the latest version: could you please try 3.0.3.

Also, probably there is some misunderstanding:

an attempt is made to resolve against one of those sub-interfaces. ... GenericsResolver.resolve(Impl.class, FooParticipant.class).genericTypes()

This is not a resolution by the interface: it is a resolution of Impl, ignoring sub-hierarchy starting with FooParticipant.

For example, this is the resolution context of Impl alone: GenericsResolver.resolve(Impl.class)

class IgnoringFailTest.Impl<Object>    <-- current
  implements FooParticipant<Object>
    extends Participant<Object>
  implements BarParticipant<Object>
    extends Participant<Object>

And this is your case: GenericsResolver.resolve(Impl.class, FooParticipant.class)

class IgnoringFailTest.Impl<Object>    <-- current
  implements BarParticipant<Object>
    extends Participant<Object>

Ignored classes were introduced to speed up the resolution on very large hierarchies or to workaround some possible bugs.

Also, GenericsResolver.resolve(Impl.class, FooParticipant.class).genericTypes() will show you generics of the Impl class and not FooParticipant.

When you need to resolve generics of some subtype in the hierarchy you first need to resolve complete hierarchy from the root class, navigate to target subtype and only then get generics:

GenericsContext.resolve(Impl.class).type(FooParticipant.class)

is (note "current" marker shifted)

class IgnoringFailTest.Impl<Object>
  implements FooParticipant<Object>    <-- current
    extends Participant<Object>
  implements BarParticipant<Object>
    extends Participant<Object>

Now .genericTypes() will return generics of FooPrticipant.

Note: Hierarchies presented above are rendered with context.toString(): you can always look it in the debugger (or just log the entire context)