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 <pa...@asert.com.au> 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 <remko.po...@gmail.com>
> 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 <remko.po...@gmail.com> 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 <remko.po...@gmail.com> 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
>>>>
>>>>

Reply via email to