The following runs fine after adding in array support:
import java.lang.annotation.*
import org.codehaus.groovy.runtime.InvokerHelper
class ClosureTest {
static class Demo {
@Option(names = "-x",
completionCandidates = {["A", "B", "C"]},
converter = [{ str ->
java.security.MessageDigest.getInstance(str) }])
java.security.MessageDigest digest
}
static void main(String[] args) {
def annotation =
Demo.getDeclaredField("digest").getAnnotation(Option)
Class comp = annotation.completionCandidates()
assert comp != null
assert Closure.isAssignableFrom(comp)
assert ["A", "B", "C"] == InvokerHelper.invokeConstructorOf(comp,
[null, null] as Object[])()
Class[] conv = annotation.converter()
assert conv != null
assert conv.length == 1
assert Closure.isAssignableFrom(conv[0])
assert 'SHA-1' == InvokerHelper.invokeConstructorOf(conv[0], [null,
null] as Object[])('SHA-1').algorithm
}
}
interface ITypeConverter<K> {
K convert(String value) throws Exception
}
class NoCompletionCandidates {}
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.FIELD])
@interface Option {
Class<? extends ITypeConverter<?>>[] converter() default []
Class<? extends Iterable<String>> completionCandidates() default
NoCompletionCandidates
String names()
}
Probably worth adding. Did you want to create a Jira?
Cheers, Paul.
On Tue, Nov 17, 2020 at 12:32 PM Paul King <[email protected]> wrote:
> The Closure to Class conversion doesn't currently support arrays. If you
> change converter() to take just a single convert, your example works for
> me.
>
> Supporting arrays might be an interesting enhancement. I'll take a look at
> what would be involved.
>
> Cheers, Paul.
>
>
> On Tue, Nov 17, 2020 at 11:02 AM Remko Popma <[email protected]>
> wrote:
>
>> I’m probably overlooking something simple but I’m not seeing it yet.
>>
>> The below code demonstrates the issue when trying to pass a Groovy
>> closure to the @Option(converter = ...)attribute:
>>
>> class ClosureTest {
>> static class Demo {
>> @picocli.CommandLine.Option(names = "-x",
>> completionCandidates = {["A", "B", "C"]},
>> converter = [{ str ->
>> java.security.MessageDigest.getInstance(str) }])
>> java.security.MessageDigest digest
>> }
>>
>> static void main(String[] args) {
>> def annotation =
>> Demo.class.getDeclaredField("digest").getAnnotation(picocli.CommandLine.Option)
>> Class ok = annotation.completionCandidates()
>> assert ok != null
>> assert Closure.class.isAssignableFrom(ok)
>> assert ["A", "B", "C"] == ((Closure) ok.getConstructor(Object,
>> Object).newInstance(null, null)).call()
>>
>> Class[] bad = annotation.converter()
>> assert bad != null
>> assert bad.length == 1 // this assert fails:
>> //Exception in thread "main" Assertion failed:
>> //
>> //assert bad.length == 1
>> // | | |
>> // [] 0 false
>> //
>> // at
>> org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:434)
>> // at
>> org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:670)
>> // at closure.ClosureTest.main(ClosureTest.groovy:18)
>> }
>> }
>>
>>
>>
>>
>> On Mon, Nov 16, 2020 at 21:16 Remko Popma <[email protected]> wrote:
>>
>>> PS
>>>
>>> The ITypeConverter interface definition is here:
>>> https://picocli.info/apidocs/picocli/CommandLine.ITypeConverter.html
>>>
>>>
>>> On Mon, Nov 16, 2020 at 21:08 Remko Popma <[email protected]> wrote:
>>>
>>>> Hi all,
>>>>
>>>> I have a question about passing closures to annotations in Groovy.
>>>> To illustrate, consider the @Option annotation in the picocli library.
>>>> Relevant attributes are `completionCandidates` and `converter`, defined
>>>> in Java as follows:
>>>>
>>>> @Retention(RetentionPolicy.RUNTIME)
>>>> @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
>>>> public @interface Option {
>>>> Class<? extends ITypeConverter<?>>[] converter() default {};
>>>> Class<? extends Iterable<String>> completionCandidates() default
>>>> NoCompletionCandidates.class;
>>>> ...
>>>> }
>>>>
>>>> I am working on a change to picocli
>>>> <https://github.com/remkop/picocli/issues/1258> that would allow users
>>>> to specify closures for these and other attributes.
>>>> User code could look like this:
>>>>
>>>> @Option(names = '-s', completionCandidates = {["A", "B", "C"]})
>>>> @Field String s
>>>>
>>>> @Option(names = '-a', converter = [{ str ->
>>>> MessageDigest.getInstance(str) }] )
>>>> @Field MessageDigest algorithm
>>>>
>>>> I think this would be a nice addition and would make picocli more
>>>> "groovy".
>>>>
>>>> I have a prototype implementation, but it appears that only the first
>>>> example ( completionCandidates = {["A", "B", "C"]} ) works as expected.
>>>> When stepping through my prototype test in a debugger, it looks like
>>>> the second example (the converter attribute) receives a zero-length
>>>> array of classes when invoked from Groovy. I tried with Groovy 2.4.10 and
>>>> 3.0.6.
>>>>
>>>> Is this a known limitation of Groovy?
>>>> Is there a way to work around this?
>>>>
>>>> I can provide an example project to reproduce this if that is helpful.
>>>>
>>>> Kind regards,
>>>> Remko
>>>>
>>>>