groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Cédric Champeau <cedric.champ...@gmail.com>
Subject Re: Automatic closure coercion and delegate
Date Tue, 03 May 2016 06:26:27 GMT
2016-05-02 18:11 GMT+02:00 Jochen Theodorou <blackdrag@gmx.org>:

> On 02.05.2016 16:44, Cédric Champeau wrote:
> [...]
>
>> Of course, it may look a bit superficial but it is super important for
>> nice DSLs like in Gradle.
>>
>
> could you give an example of a more complex closure usage?
>

Sure, basically you can have that kind of configuration:

dependencies {
   compile '...'
}

(the method is dependencies(Action<? super DependencyContainer>, and we
generate a Closure version at runtime) or:

repositories { // Action<? super RepositoryHander
   maven { Action<? super MavenRepository>
        url '....'
   }
}


> We should be also aware that this change may break code, since it is
> semantic change and a local method of the same name will no longer be
> called if it exists on the delegate. A functional interface is after all
> not something that came really to exist with java8 only, Callable and
> Runnable are examples that existed before and work with Closure already.
>
> Implementation wise to have something like
>
> void configure(Action<Person> config) {
>> config.execute(person)
>> }
>>
>
> working we need to set the delegate to person, but we don´t know person
> before the method invocation. This means the proxy for the Action
> delegating to the closure config represents must also make the delegation
> setter call.
>
> And there is also the problem of what we do if Action contains default
> methods - I do not consider our current solution as appropriate anymore.
> But of course that is not essential for the idea at hand.
>
>
I have a patch that does the work for functional interfaces already:

public static Object coerceToSAM(Closure argument, Method method,
Class clazz, boolean isInterface) {
    if (argument!=null && clazz.isAssignableFrom(argument.getClass())) {
        return argument;
    }
    if (isInterface) {
        if (Traits.isTrait(clazz)) {
            Map<String,Closure> impl = Collections.singletonMap(
                    method.getName(),
                    argument
            );
            return
ProxyGenerator.INSTANCE.instantiateAggregate(impl,Collections.singletonList(clazz));
        }
        return Proxy.newProxyInstance(
                clazz.getClassLoader(),
                new Class[]{clazz},
                new SAMClosure(argument));
    } else {
        Map<String, Object> m = new HashMap<String,Object>();
        m.put(method.getName(), argument);
        return ProxyGenerator.INSTANCE.
                instantiateAggregateFromBaseClass(m, clazz);
    }
}

private static class SAMClosure extends ConvertedClosure {
    public SAMClosure(final Closure closure) {
        super(closure);
    }

    @Override
    public Object invokeCustom(final Object proxy, final Method
method, final Object[] args) throws Throwable {
        if (args!=null && args.length>0) {
            Closure delegate = (Closure) getDelegate();
            delegate.setResolveStrategy(Closure.DELEGATE_FIRST);
            delegate.setDelegate(args[0]);
        }
        return super.invokeCustom(proxy, method, args);
    }
}


Doing the same for abstract classes should be straightforward. For static
compilation, it's going to be more complicated and probably requires
transparently invoking a configurer (like Gradle does).

>
> bye Jochen
>

Mime
View raw message