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

org.springframework.aot.hint.TypeHint Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2023 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.springframework.aot.hint;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * A hint that describes the need for reflection on a type.
 *
 * @author Stephane Nicoll
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 6.0
 */
public final class TypeHint implements ConditionalHint {

	private final TypeReference type;

	@Nullable
	private final TypeReference reachableType;

	private final Set fields;

	private final Set constructors;

	private final Set methods;

	private final Set memberCategories;


	private TypeHint(Builder builder) {
		this.type = builder.type;
		this.reachableType = builder.reachableType;
		this.memberCategories = Set.copyOf(builder.memberCategories);
		this.fields = builder.fields.stream().map(FieldHint::new).collect(Collectors.toSet());
		this.constructors = builder.constructors.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
		this.methods = builder.methods.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
	}

	/**
	 * Initialize a builder for the type defined by the specified
	 * {@link TypeReference}.
	 * @param type the type to use
	 * @return a builder
	 */
	static Builder of(TypeReference type) {
		Assert.notNull(type, "'type' must not be null");
		return new Builder(type);
	}

	/**
	 * Return the type that this hint handles.
	 * @return the type
	 */
	public TypeReference getType() {
		return this.type;
	}

	@Nullable
	@Override
	public TypeReference getReachableType() {
		return this.reachableType;
	}

	/**
	 * Return the fields that require reflection.
	 * @return a stream of {@link FieldHint}
	 */
	public Stream fields() {
		return this.fields.stream();
	}

	/**
	 * Return the constructors that require reflection.
	 * @return a stream of {@link ExecutableHint}
	 */
	public Stream constructors() {
		return this.constructors.stream();
	}

	/**
	 * Return the methods that require reflection.
	 * @return a stream of {@link ExecutableHint}
	 */
	public Stream methods() {
		return this.methods.stream();
	}

	/**
	 * Return the member categories that apply.
	 * @return the member categories to enable
	 */
	public Set getMemberCategories() {
		return this.memberCategories;
	}

	@Override
	public String toString() {
		return new StringJoiner(", ", TypeHint.class.getSimpleName() + "[", "]")
				.add("type=" + this.type)
				.toString();
	}

	/**
	 * Return a {@link Consumer} that applies the given {@link MemberCategory
	 * MemberCategories} to the accepted {@link Builder}.
	 * @param memberCategories the memberCategories to apply
	 * @return a consumer to apply the member categories
	 */
	public static Consumer builtWith(MemberCategory... memberCategories) {
		return builder -> builder.withMembers(memberCategories);
	}


	/**
	 * Builder for {@link TypeHint}.
	 */
	public static class Builder {

		private final TypeReference type;

		@Nullable
		private TypeReference reachableType;

		private final Set fields = new HashSet<>();

		private final Map constructors = new HashMap<>();

		private final Map methods = new HashMap<>();

		private final Set memberCategories = new HashSet<>();


		Builder(TypeReference type) {
			this.type = type;
		}

		/**
		 * Make this hint conditional on the fact that the specified type
		 * is in a reachable code path from a static analysis point of view.
		 * @param reachableType the type that should be reachable for this
		 * hint to apply
		 * @return {@code this}, to facilitate method chaining
		 */
		public Builder onReachableType(TypeReference reachableType) {
			this.reachableType = reachableType;
			return this;
		}

		/**
		 * Make this hint conditional on the fact that the specified type
		 * is in a reachable code path from a static analysis point of view.
		 * @param reachableType the type that should be reachable for this
		 * hint to apply
		 * @return {@code this}, to facilitate method chaining
		 */
		public Builder onReachableType(Class reachableType) {
			this.reachableType = TypeReference.of(reachableType);
			return this;
		}

		/**
		 * Register the need for reflection on the field with the specified name.
		 * @param name the name of the field
		 * @return {@code this}, to facilitate method chaining
		 */
		public Builder withField(String name) {
			this.fields.add(name);
			return this;
		}

		/**
		 * Register the need for reflection on the constructor with the specified
		 * parameter types, using the specified {@link ExecutableMode}.
		 * @param parameterTypes the parameter types of the constructor
		 * @param mode the requested mode
		 * @return {@code this}, to facilitate method chaining
		 */
		public Builder withConstructor(List parameterTypes, ExecutableMode mode) {
			return withConstructor(parameterTypes, ExecutableHint.builtWith(mode));
		}

		/**
		 * Register the need for reflection on the constructor with the specified
		 * parameter types.
		 * @param parameterTypes the parameter types of the constructor
		 * @param constructorHint a builder to further customize the hints of this
		 * constructor
		 * @return {@code this}, to facilitate method chaining
		 */
		private Builder withConstructor(List parameterTypes,
				Consumer constructorHint) {
			ExecutableKey key = new ExecutableKey("", parameterTypes);
			ExecutableHint.Builder builder = this.constructors.computeIfAbsent(key,
					k -> ExecutableHint.ofConstructor(parameterTypes));
			constructorHint.accept(builder);
			return this;
		}

		/**
		 * Register the need for reflection on the method with the specified name
		 * and parameter types, using the specified {@link ExecutableMode}.
		 * @param name the name of the method
		 * @param parameterTypes the parameter types of the constructor
		 * @param mode the requested mode
		 * @return {@code this}, to facilitate method chaining
		 */
		public Builder withMethod(String name, List parameterTypes, ExecutableMode mode) {
			return withMethod(name, parameterTypes, ExecutableHint.builtWith(mode));
		}

		/**
		 * Register the need for reflection on the method with the specified name
		 * and parameter types.
		 * @param name the name of the method
		 * @param parameterTypes the parameter types of the constructor
		 * @param methodHint a builder to further customize the hints of this method
		 * @return {@code this}, to facilitate method chaining
		 */
		private Builder withMethod(String name, List parameterTypes,
				Consumer methodHint) {
			ExecutableKey key = new ExecutableKey(name, parameterTypes);
			ExecutableHint.Builder builder = this.methods.computeIfAbsent(key,
					k -> ExecutableHint.ofMethod(name, parameterTypes));
			methodHint.accept(builder);
			return this;
		}

		/**
		 * Adds the specified {@linkplain MemberCategory member categories}.
		 * @param memberCategories the categories to apply
		 * @return {@code this}, to facilitate method chaining
		 * @see TypeHint#builtWith(MemberCategory...)
		 */
		public Builder withMembers(MemberCategory... memberCategories) {
			this.memberCategories.addAll(Arrays.asList(memberCategories));
			return this;
		}

		/**
		 * Create a {@link TypeHint} based on the state of this builder.
		 * @return a type hint
		 */
		TypeHint build() {
			return new TypeHint(this);
		}

	}

	private static final class ExecutableKey {

		private final String name;

		private final List parameterTypes;


		private ExecutableKey(String name, List parameterTypes) {
			this.name = name;
			this.parameterTypes = parameterTypes.stream().map(TypeReference::getCanonicalName).toList();
		}

		@Override
		public boolean equals(@Nullable Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}
			ExecutableKey that = (ExecutableKey) o;
			return this.name.equals(that.name) && this.parameterTypes.equals(that.parameterTypes);
		}

		@Override
		public int hashCode() {
			return Objects.hash(this.name, this.parameterTypes);
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy