All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jmolecules.bytebuddy.LifecycleMethods Maven / Gradle / Ivy

There is a newer version: 0.24.1
Show newest version
/*
 * Copyright 2021-2024 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
 *
 *      https://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.jmolecules.bytebuddy;

import static net.bytebuddy.matcher.ElementMatchers.*;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodDescription.InDefinedShape;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.matcher.ElementMatcher;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Abstraction over the lifecycle methods with the configured annotations.
 *
 * @author Oliver Drotbohm
 */
class LifecycleMethods {

	private final Builder builder;
	private final Map, Optional> methods;

	/**
	 * Creates a new {@link LifecycleMethods} for the given {@link Builder} and lifecycle method annotations.
	 *
	 * @param builder must not be {@literal null}.
	 * @param annotation must not be {@literal null}.
	 */
	@SafeVarargs
	LifecycleMethods(Builder builder, Class... annotation) {

		if (builder == null) {
			throw new IllegalArgumentException("Builder must not be null!");
		}

		if (annotation == null) {
			throw new IllegalArgumentException("Lifecycle methods must not be null!");
		}

		this.builder = builder;
		this.methods = detectCallbackMethods(builder.toTypeDescription(), annotation);
	}

	/**
	 * Applies the given {@link Advice} to already existing methods or the given {@link Implementation} for methods that
	 * will be newly registered.
	 *
	 * @param forExisting
	 * @param forNew
	 * @return will never be {@literal null}.
	 */
	public Builder apply(Function forExisting, Supplier forNew) {

		Builder result = builder;
		Set handledMethods = new HashSet<>();

		forNew = PluginUtils.memoized(forNew);

		for (Entry, Optional> entry : methods.entrySet()) {

			Class annotationType = entry.getKey();
			String name = entry.getValue().orElse(null);

			if (name != null) {

				if (handledMethods.contains(name)) {
					continue;
				}

				result = result.visit(forExisting.apply(name).on(nonStaticNoParametersNamed(name)));
				handledMethods.add(name);

			} else {
				result = result
						.defineMethod("__jMolecules__" + annotationType.getSimpleName(), void.class, Visibility.PACKAGE_PRIVATE)
						.intercept(forNew.get())
						.annotateMethod(PluginUtils.getAnnotation(annotationType));
			}
		}

		return result;
	}

	/**
	 * Detects all methods annotated with the given annotation types. Assumes that only one method can be annotated with
	 * one particular annotation but a method might be annotated with multiple annotations.
	 *
	 * @param type the type to inspect for methods, must not be {@literal null}.
	 * @param annotations the annotations to detect.
	 * @return a {@link Map} of annotations to method names, if no method carrying the annotation is found, the value is
	 *         {@link Optional#empty}.
	 */
	@SafeVarargs
	private static Map, Optional> detectCallbackMethods(TypeDescription type,
			Class... annotations) {

		Map, Optional> result = new HashMap<>();

		type.getDeclaredMethods().stream()
				.filter(it -> !it.isStatic())
				.filter(it -> it.getParameters().isEmpty())
				.forEach(method -> {

					Arrays.stream(annotations).forEach(annotation -> {
						result.compute(annotation, (annotationType, methodName) -> methodName != null && methodName.isPresent()
								? methodName
								: getMethodNameIfAnnotated(method, annotationType));
					});
				});

		return result;

	}

	private static Optional getMethodNameIfAnnotated(InDefinedShape method, Class type) {

		return method.getDeclaredAnnotations().isAnnotationPresent(type)
				? Optional.of(method.getName())
				: Optional.empty();
	}

	private static ElementMatcher nonStaticNoParametersNamed(String name) {

		ElementMatcher doesNotHaveParameters = method -> method.getParameters().isEmpty();

		return hasMethodName(name)
				.and(not(isStatic()))
				.and(doesNotHaveParameters);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy