First off: I absolutely LOVE Project Lombok. Awesome tool! There's so many excellent aspects to this 'compile time' library.
Loving the @ExtensionMethod
s, I have already hit this 'feature' a few times, so now it's time for me to ask this question:
Suppose I have the following classes:
@UtilityClass
public class AObject {
static public String message(final Object pObject) {
return "AObject = " + (pObject != null);
}
}
@UtilityClass
public class AString {
static public String message(final String pObject) {
return "AString = " + (pObject != null);
}
}
@ExtensionMethod({ AObject.class, AString.class })
public class Run_Object_String {
public static void main(final String[] args) {
System.out.println("\nRun_Object_String.main()");
final String s = "Bier!";
final Object o = new Object();
System.out.println("Testing s: " + s.message());
System.out.println("Testing o: " + o.message());
System.out.println("Testing s: " + s.message());
}
}
@ExtensionMethod({ AString.class, AObject.class })
public class Run_String_Object {
public static void main(final String[] args) {
System.out.println("\nRun_String_Object.main()");
final String s = "Bier!";
final Object o = new Object();
System.out.println("Testing s: " + s.message());
System.out.println("Testing o: " + o.message());
System.out.println("Testing s: " + s.message());
}
}
public class ClassPrevalenceTest {
public static void main(final String[] args) {
Run_Object_String.main(args);
Run_String_Object.main(args);
}
}
With the output:
Run_Object_String.main()
Testing s: AObject = true
Testing o: AObject = true
Testing s: AObject = true
Run_String_Object.main()
Testing s: AString = true
Testing o: AObject = true
Testing s: AString = true
message(String)
not called in the first example, even though it has a better method signature fit than message(Object)
?@ExtensionMethod
dependent on sequence of the arguments?Here's what I blindly assume:
Run_Object_String
that means: first AObject
, then AString
Run_String_Object
that means: first AString
, then AObject
AObject
into class Run_Object_String
, the message(Object)
method will be added. And when patching in AString
with the message(String)
method, it will not be added.
Presumably because the message(Object)
also matches a call to message(String)
, so message(String)
will not be added.AString
into class Run_String_Object
, the message(String)
method will be added.
When patching in AObject
class with message(Object)
, the old and present message(String)
method will NOT accept the call message(Object)
, thus the method message(Object)
will be added.So, apart from taking great care of what order I add the @UtilityClass
references, are there any other solutions to this?
This is a fascinating use of Lombok I wasn't aware of. The best place I think you could delve to find your answers is the source itself since the docs on this experimental work seems pretty light, understandably.
Take a look on git here: HandleExtensionMethod.
I am guessing based on the logic that the area that's effectively "fitting" the right method from the annotation is as below..
Instead of trying for a "best" fit, it seems to be aiming for a "first" fit.
That is, it appears to iterate over List<Extension> extensions
. Since it's a Java list, we assume ordering is preserved in the order the extensions were specified in the original annotation.
It appears to simply work in order of the list and return
as soon as something matches the right method and type shape.
Types types = Types.instance(annotationNode.getContext());
for (Extension extension : extensions) {
TypeSymbol extensionProvider = extension.extensionProvider;
if (surroundingTypeSymbol == extensionProvider) continue;
for (MethodSymbol extensionMethod : extension.extensionMethods) {
if (!methodName.equals(extensionMethod.name.toString())) continue;
Type extensionMethodType = extensionMethod.type;
if (!MethodType.class.isInstance(extensionMethodType) && !ForAll.class.isInstance(extensionMethodType)) continue;
Type firstArgType = types.erasure(extensionMethodType.asMethodType().argtypes.get(0));
if (!types.isAssignable(receiverType, firstArgType)) continue;
methodCall.args = methodCall.args.prepend(receiver);
methodCall.meth = chainDotsString(annotationNode, extensionProvider.toString() + "." + methodName);
recursiveSetGeneratedBy(methodCall.meth, methodCallNode);
return;
}
}
You can look at the rest of the code for other insight as there doesn't seem to be too much there (i.e. number of lines) to look at, though admittedly it's an impressive enough a feat to do in that space.