org.gradle.util.ConfigureUtil Maven / Gradle / Ivy
Show all versions of gradle-tooling-api Show documentation
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.util;
import groovy.lang.Closure;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.gradle.api.Action;
import org.gradle.internal.metaobject.DynamicObjectUtil;
import org.gradle.internal.Actions;
import org.gradle.internal.metaobject.ConfigureDelegate;
import org.gradle.internal.metaobject.DynamicInvokeResult;
import org.gradle.internal.metaobject.DynamicObject;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Map;
import static org.gradle.util.internal.CollectionUtils.toStringList;
/**
* Contains utility methods to configure objects with Groovy Closures.
*
* Plugins should avoid using this class and methods that use {@link groovy.lang.Closure} as this makes the plugin harder to use in other languages. Instead, plugins should create methods that use {@link Action}.
* Here's an example pseudocode:
*
* interface MyOptions {
* RegularFileProperty getOptionsFile()
* }
* abstract class MyExtension {
* private final MyOptions options
*
* {@literal @}Inject abstract ObjectFactory getObjectFactory()
*
* public MyExtension() {
* this.options = getObjectFactory().newInstance(MyOptions)
* }
*
* public void options(Action{@literal } extends MyOptions{@literal >} action) {
* action.execute(options)
* }
* }
* extensions.create("myExtension", MyExtension)
* myExtension {
* options {
* optionsFile = layout.projectDirectory.file("options.properties")
* }
* }
*
*
* Gradle automatically generates a Closure-taking method at runtime for each method with an {@link Action} as a single argument as long as the object is created with {@link org.gradle.api.model.ObjectFactory#newInstance(Class, Object...)}.
*
* As a last resort, to apply some configuration represented by a Groovy Closure, a plugin can use {@link org.gradle.api.Project#configure(Object, Closure)}.
*
* @deprecated Will be removed in Gradle 8.0.
*/
@Deprecated
public class ConfigureUtil {
public static T configureByMap(Map, ?> properties, T delegate) {
if (properties.isEmpty()) {
return delegate;
}
DynamicObject dynamicObject = DynamicObjectUtil.asDynamicObject(delegate);
for (Map.Entry, ?> entry : properties.entrySet()) {
String name = entry.getKey().toString();
Object value = entry.getValue();
DynamicInvokeResult result = dynamicObject.trySetProperty(name, value);
if (result.isFound()) {
continue;
}
result = dynamicObject.tryInvokeMethod(name, value);
if (!result.isFound()) {
throw dynamicObject.setMissingProperty(name);
}
}
return delegate;
}
public static T configureByMap(Map, ?> properties, T delegate, Collection> mandatoryKeys) {
if (!mandatoryKeys.isEmpty()) {
Collection missingKeys = toStringList(mandatoryKeys);
missingKeys.removeAll(toStringList(properties.keySet()));
if (!missingKeys.isEmpty()) {
throw new IncompleteInputException("Input configuration map does not contain following mandatory keys: " + missingKeys, missingKeys);
}
}
return configureByMap(properties, delegate);
}
/**
* Incomplete input exception.
*/
@Deprecated
public static class IncompleteInputException extends RuntimeException {
private final Collection missingKeys;
public IncompleteInputException(String message, Collection missingKeys) {
super(message);
this.missingKeys = missingKeys;
}
public Collection getMissingKeys() {
return missingKeys;
}
}
/**
* Configures {@code target} with {@code configureClosure}, via the {@link Configurable} interface if necessary.
*
* If {@code target} does not implement {@link Configurable} interface, it is set as the delegate of a clone of
* {@code configureClosure} with a resolve strategy of {@code DELEGATE_FIRST}.
*
* If {@code target} does implement the {@link Configurable} interface, the {@code configureClosure} will be passed to
* {@code delegate}'s {@link Configurable#configure(Closure)} method.
*
* @param configureClosure The configuration closure
* @param target The object to be configured
* @return The delegate param
*/
public static T configure(@Nullable Closure configureClosure, T target) {
if (configureClosure == null) {
return target;
}
if (target instanceof Configurable) {
((Configurable) target).configure(configureClosure);
} else {
configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
}
return target;
}
/**
* Creates an action that uses the given closure to configure objects of type T.
*/
public static Action configureUsing(@Nullable final Closure configureClosure) {
if (configureClosure == null) {
return Actions.doNothing();
}
return new WrappedConfigureAction(configureClosure);
}
/**
* Called from an object's {@link Configurable#configure} method.
*/
public static T configureSelf(@Nullable Closure configureClosure, T target) {
if (configureClosure == null) {
return target;
}
configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
return target;
}
/**
* Called from an object's {@link Configurable#configure} method.
*/
public static T configureSelf(@Nullable Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
if (configureClosure == null) {
return target;
}
configureTarget(configureClosure, target, closureDelegate);
return target;
}
private static void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
if (!(configureClosure instanceof GeneratedClosure)) {
new ClosureBackedAction(configureClosure, Closure.DELEGATE_FIRST, false).execute(target);
return;
}
// Hackery to make closure execution faster, by short-circuiting the expensive property and method lookup on Closure
Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
new ClosureBackedAction(withNewOwner, Closure.OWNER_ONLY, false).execute(target);
}
/**
* Wrapper configure action.
*
* @param the action type.
*/
@Deprecated
public static class WrappedConfigureAction implements Action {
private final Closure configureClosure;
WrappedConfigureAction(Closure configureClosure) {
this.configureClosure = configureClosure;
}
@Override
public void execute(T t) {
configure(configureClosure, t);
}
public Closure getConfigureClosure() {
return configureClosure;
}
}
}