org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.5 API redistribution.
/*
* 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.configuration.internal;
import com.google.common.collect.ImmutableSet;
import groovy.lang.Closure;
import org.apache.commons.lang.ClassUtils;
import org.gradle.BuildListener;
import org.gradle.api.Action;
import org.gradle.api.ProjectEvaluationListener;
import org.gradle.api.execution.TaskExecutionGraphListener;
import org.gradle.configuration.internal.ExecuteListenerBuildOperationType.DetailsImpl;
import org.gradle.internal.InternalListener;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.operations.BuildOperationContext;
import org.gradle.internal.operations.BuildOperationDescriptor;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.operations.RunnableBuildOperation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
import static org.gradle.configuration.internal.ExecuteListenerBuildOperationType.RESULT;
public class DefaultListenerBuildOperationDecorator implements ListenerBuildOperationDecorator {
private static final ImmutableSet> SUPPORTED_INTERFACES = ImmutableSet.of(
BuildListener.class,
ProjectEvaluationListener.class,
TaskExecutionGraphListener.class
);
// we don't decorate everything in BuildListener, just projectsLoaded/projectsEvaluated
private static final ImmutableSet UNDECORATED_METHOD_NAMES = ImmutableSet.of(
"buildStarted",
"beforeSettings",
"settingsEvaluated",
"buildFinished"
);
private final BuildOperationExecutor buildOperationExecutor;
private final UserCodeApplicationContext userCodeApplicationContext;
public DefaultListenerBuildOperationDecorator(BuildOperationExecutor buildOperationExecutor, UserCodeApplicationContext userCodeApplicationContext) {
this.buildOperationExecutor = buildOperationExecutor;
this.userCodeApplicationContext = userCodeApplicationContext;
}
@Override
public Action decorate(String registrationPoint, Action action) {
UserCodeApplicationId applicationId = userCodeApplicationContext.current();
if (applicationId == null || action instanceof InternalListener) {
return action;
}
return new BuildOperationEmittingAction<>(applicationId, registrationPoint, action);
}
@Override
public Closure decorate(String registrationPoint, Closure closure) {
UserCodeApplicationId applicationId = userCodeApplicationContext.current();
if (applicationId == null) {
return closure;
}
return new BuildOperationEmittingClosure<>(applicationId, registrationPoint, closure);
}
@Override
@SuppressWarnings("unchecked")
public T decorate(String registrationPoint, Class targetClass, T listener) {
if (listener instanceof InternalListener || !isSupported(listener)) {
return listener;
}
UserCodeApplicationId applicationId = userCodeApplicationContext.current();
if (applicationId == null) {
return listener;
}
Class listenerClass = listener.getClass();
List> allInterfaces = ClassUtils.getAllInterfaces(listenerClass);
BuildOperationEmittingInvocationHandler handler = new BuildOperationEmittingInvocationHandler(applicationId, registrationPoint, listener);
return targetClass.cast(Proxy.newProxyInstance(listenerClass.getClassLoader(), allInterfaces.toArray(new Class[0]), handler));
}
@Override
public Object decorateUnknownListener(String registrationPoint, Object listener) {
return decorate(registrationPoint, Object.class, listener);
}
private static boolean isSupported(Object listener) {
for (Class i : SUPPORTED_INTERFACES) {
if (i.isInstance(listener)) {
return true;
}
}
return false;
}
private static abstract class Operation implements RunnableBuildOperation {
private final UserCodeApplicationId applicationId;
private final String registrationPoint;
Operation(UserCodeApplicationId applicationId, String registrationPoint) {
this.applicationId = applicationId;
this.registrationPoint = registrationPoint;
}
@Override
public BuildOperationDescriptor.Builder description() {
return BuildOperationDescriptor
.displayName("Execute " + registrationPoint + " listener")
.details(new DetailsImpl(applicationId, registrationPoint));
}
}
private class BuildOperationEmittingAction implements Action {
private final UserCodeApplicationId applicationId;
private final String registrationPoint;
private final Action delegate;
private BuildOperationEmittingAction(UserCodeApplicationId applicationId, String registrationPoint, Action delegate) {
this.applicationId = applicationId;
this.delegate = delegate;
this.registrationPoint = registrationPoint;
}
@Override
public void execute(final T arg) {
buildOperationExecutor.run(new Operation(applicationId, registrationPoint) {
@Override
public void run(final BuildOperationContext context) {
userCodeApplicationContext.reapply(applicationId, () -> delegate.execute(arg));
context.setResult(RESULT);
}
});
}
}
private class BuildOperationEmittingClosure extends Closure {
private final UserCodeApplicationId applicationId;
private final String registrationPoint;
private final Closure delegate;
private BuildOperationEmittingClosure(UserCodeApplicationId application, String registrationPoint, Closure delegate) {
super(delegate.getOwner(), delegate.getThisObject());
this.applicationId = application;
this.delegate = delegate;
this.registrationPoint = registrationPoint;
}
@SuppressWarnings("unused")
public void doCall(final Object... args) {
buildOperationExecutor.run(new Operation(applicationId, registrationPoint) {
@Override
public void run(final BuildOperationContext context) {
userCodeApplicationContext.reapply(applicationId, () -> {
int numClosureArgs = delegate.getMaximumNumberOfParameters();
Object[] finalArgs = numClosureArgs < args.length ? Arrays.copyOf(args, numClosureArgs) : args;
delegate.call(finalArgs);
context.setResult(RESULT);
});
}
});
}
@Override
public void setDelegate(Object delegateObject) {
delegate.setDelegate(delegateObject);
}
@Override
public void setResolveStrategy(int resolveStrategy) {
delegate.setResolveStrategy(resolveStrategy);
}
@Override
public int getMaximumNumberOfParameters() {
return delegate.getMaximumNumberOfParameters();
}
}
private class BuildOperationEmittingInvocationHandler implements InvocationHandler {
private final UserCodeApplicationId applicationId;
private final String registrationPoint;
private final Object delegate;
private BuildOperationEmittingInvocationHandler(UserCodeApplicationId applicationId, String registrationPoint, Object delegate) {
this.applicationId = applicationId;
this.registrationPoint = registrationPoint;
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
final String methodName = method.getName();
if (methodName.equals("toString") && (args == null || args.length == 0)) {
return "BuildOperationEmittingBuildListenerInvocationHandler{delegate: " + delegate + "}";
} else if (methodName.equals("hashCode") && (args == null || args.length == 0)) {
return delegate.hashCode();
} else if (methodName.equals("equals") && args.length == 1) {
return proxy == args[0] || isSame(args[0]);
} else if (!SUPPORTED_INTERFACES.contains(method.getDeclaringClass()) || UNDECORATED_METHOD_NAMES.contains(methodName)) {
try {
return method.invoke(delegate, args);
} catch (InvocationTargetException e) {
throw UncheckedException.unwrapAndRethrow(e);
}
} else {
buildOperationExecutor.run(new Operation(applicationId, registrationPoint) {
@Override
public void run(final BuildOperationContext context) {
userCodeApplicationContext.reapply(applicationId, () -> {
try {
method.invoke(delegate, args);
context.setResult(RESULT);
} catch (InvocationTargetException e) {
throw UncheckedException.unwrapAndRethrow(e);
} catch (Exception e) {
throw UncheckedException.throwAsUncheckedException(e);
}
});
}
});
// all of the interfaces that we decorate have 100% void methods
//noinspection ConstantConditions
return null;
}
}
private boolean isSame(Object arg) {
if (Proxy.isProxyClass(arg.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(arg);
if (getClass() == invocationHandler.getClass()) {
BuildOperationEmittingInvocationHandler cast = (BuildOperationEmittingInvocationHandler) invocationHandler;
return cast.delegate.equals(delegate);
}
}
return false;
}
}
}