From c9178431327356aaedc22b0e94097154e4578bc7 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Fri, 13 Sep 2024 11:47:41 +0200
Subject: [PATCH 01/14] Prepare issue branch.

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index cdd68fe2d4..0d10b48560 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 
 	<groupId>org.springframework.data</groupId>
 	<artifactId>spring-data-commons</artifactId>
-	<version>3.5.0-SNAPSHOT</version>
+	<version>3.5.x-GENERATED-REPOSITORIES-SNAPSHOT</version>
 
 	<name>Spring Data Core</name>
 	<description>Core Spring concepts underpinning every Spring Data module.</description>

From 0d6521c1908a847fcc8029b0ea1d8e2616b31b5e Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Fri, 20 Sep 2024 11:36:54 +0200
Subject: [PATCH 02/14] Hacking - add code contributor for derived repository
 methods

---
 .../aot/generate/AotCodeContributor.java      |  25 ++
 .../aot/generate/AotRepositoryBuilder.java    | 175 +++++++++++++
 .../AotRepositoryConstructorBuilder.java      |  99 +++++++
 .../AotRepositoryDerivedMethodBuilder.java    |  74 ++++++
 .../generate/AotRepositoryMethodBuilder.java  | 181 +++++++++++++
 .../aot/generate/RepositoryContributor.java   |  69 +++++
 src/test/java/example/UserRepository.java     |  46 ++++
 .../generate/RepositoryBuilderUnitTests.java  | 246 ++++++++++++++++++
 8 files changed, 915 insertions(+)
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotCodeContributor.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
 create mode 100644 src/test/java/example/UserRepository.java
 create mode 100644 src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotCodeContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/AotCodeContributor.java
new file mode 100644
index 0000000000..350afa7681
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotCodeContributor.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import org.springframework.aot.generate.GenerationContext;
+
+/**
+ * @author Christoph Strobl
+ */
+public interface AotCodeContributor {
+	void contribute(GenerationContext generationContext);
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
new file mode 100644
index 0000000000..26e112f432
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+
+import javax.lang.model.element.Modifier;
+
+import org.springframework.aot.generate.ClassNameGenerator;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.ClassName;
+import org.springframework.javapoet.FieldSpec;
+import org.springframework.javapoet.JavaFile;
+import org.springframework.javapoet.TypeName;
+import org.springframework.javapoet.TypeSpec;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Christoph Strobl
+ */
+public class AotRepositoryBuilder {
+
+	private final RepositoryInformation repositoryInformation;
+	private final GenerationMetadata generationMetadata;
+
+	private Consumer<AotRepositoryConstructorBuilder> constructorBuilderCustomizer;
+	private Consumer<AotRepositoryMethodBuilder> derivedMethodBuilderCustomizer;
+	private RepositoryCustomizer customizer;
+
+	public static AotRepositoryBuilder forRepository(RepositoryInformation repositoryInformation) {
+		return new AotRepositoryBuilder(repositoryInformation);
+	}
+
+	AotRepositoryBuilder(RepositoryInformation repositoryInformation) {
+
+		this.repositoryInformation = repositoryInformation;
+		this.generationMetadata = new GenerationMetadata(className());
+		this.customizer = (info, metadata, builder) -> {};
+	}
+
+	public JavaFile javaFile() {
+
+		// start creating the type
+		TypeSpec.Builder builder = TypeSpec.classBuilder(this.generationMetadata.getTargetTypeName())
+				.addModifiers(Modifier.PUBLIC);
+		// TODO: we do not need that here
+		// .addSuperinterface(repositoryInformation.getRepositoryInterface());
+
+		// create the constructor
+		AotRepositoryConstructorBuilder constructorBuilder = new AotRepositoryConstructorBuilder(repositoryInformation,
+				generationMetadata);
+		constructorBuilderCustomizer.accept(constructorBuilder);
+		builder.addMethod(constructorBuilder.buildConstructor());
+
+		// write methods
+		// start with the derived ones
+		ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), method -> {
+
+			AotRepositoryDerivedMethodBuilder derivedMethodBuilder = new AotRepositoryDerivedMethodBuilder(method,
+					repositoryInformation, generationMetadata);
+			derivedMethodBuilderCustomizer.accept(derivedMethodBuilder);
+			builder.addMethod(derivedMethodBuilder.buildMethod());
+		}, it -> {
+			return !repositoryInformation.isBaseClassMethod(it) && !repositoryInformation.isCustomMethod(it) && !it.isDefault();
+		});
+
+		// write fields at the end so we make sure to capture things added by methods
+		generationMetadata.getFields().values().forEach(builder::addField);
+
+		// finally customize the file itself
+		this.customizer.customize(repositoryInformation, generationMetadata, builder);
+		return JavaFile.builder(packageName(), builder.build()).build();
+	}
+
+	AotRepositoryBuilder withConstructorCustomizer(Consumer<AotRepositoryConstructorBuilder> constuctorBuilder) {
+
+		this.constructorBuilderCustomizer = constuctorBuilder;
+		return this;
+	}
+
+	AotRepositoryBuilder withDerivedMethodCustomizer(Consumer<AotRepositoryMethodBuilder> methodBuilder) {
+		this.derivedMethodBuilderCustomizer = methodBuilder;
+		return this;
+	}
+
+	AotRepositoryBuilder withFileCustomizer(RepositoryCustomizer repositoryCustomizer) {
+
+		this.customizer = repositoryCustomizer;
+		return this;
+	}
+
+	GenerationMetadata getGenerationMetadata() {
+		return generationMetadata;
+	}
+
+	private ClassName className() {
+		return new ClassNameGenerator(ClassName.get(packageName(), typeName())).generateClassName("Aot", null);
+	}
+
+	private String packageName() {
+		return repositoryInformation.getRepositoryInterface().getPackageName();
+	}
+
+	private String typeName() {
+		return "%sImpl".formatted(repositoryInformation.getRepositoryInterface().getSimpleName());
+	}
+
+	public interface RepositoryCustomizer {
+
+		void customize(RepositoryInformation repositoryInformation, GenerationMetadata metadata, TypeSpec.Builder builder);
+	}
+
+	public class GenerationMetadata {
+
+		private ClassName className;
+		private Map<String, FieldSpec> fields = new HashMap<>();
+
+		public GenerationMetadata(ClassName className) {
+			this.className = className;
+		}
+
+		@Nullable
+		public String fieldNameOf(Class<?> type) {
+
+			// TODO: might be more than one match
+			return fields.entrySet().stream().filter(entry -> entry.getValue().name.equals(type.getName())).map(Entry::getKey)
+					.findFirst().orElse(null);
+		}
+
+		public ClassName getTargetTypeName() {
+			return className;
+		}
+
+		public String getTargetTypeSimpleName() {
+			return className.simpleName();
+		}
+
+		public String getTargetTypePackageName() {
+			return className.packageName();
+		}
+
+		public boolean hasField(String fieldName) {
+			return fields.containsKey(fieldName);
+		}
+
+		public void addField(String fieldName, TypeName type, Modifier... modifiers) {
+			fields.put(fieldName, FieldSpec.builder(type, fieldName, modifiers).build());
+		}
+
+		public void addField(FieldSpec fieldSpec) {
+			fields.put(fieldSpec.name, fieldSpec);
+		}
+
+		Map<String, FieldSpec> getFields() {
+			return fields;
+		}
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
new file mode 100644
index 0000000000..91bd3fedb4
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.lang.model.element.Modifier;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.MethodSpec;
+import org.springframework.javapoet.ParameterizedTypeName;
+import org.springframework.javapoet.TypeName;
+
+/**
+ * @author Christoph Strobl
+ */
+public class AotRepositoryConstructorBuilder {
+
+	private final RepositoryInformation repositoryInformation;
+	private final AotRepositoryBuilder.GenerationMetadata metadata;
+	private final Map<String, TypeName> constructorArguments;
+
+	private ConstructorCustomizer customizer = (info, builder) -> {};
+
+	public AotRepositoryConstructorBuilder(RepositoryInformation repositoryInformation,
+			AotRepositoryBuilder.GenerationMetadata metadata) {
+
+		this.repositoryInformation = repositoryInformation;
+		this.metadata = metadata;
+		this.constructorArguments = new LinkedHashMap<>(3);
+		addParameter("delegate", getDefaultStoreRepositoryImplementationType(repositoryInformation));
+	}
+
+	public void addParameter(String parameterName, Class<?> type) {
+
+		ResolvableType resolvableType = ResolvableType.forClass(type);
+		if (!resolvableType.hasGenerics() || !resolvableType.hasResolvableGenerics()) {
+			addParameter(parameterName, TypeName.get(type));
+			return;
+		}
+		addParameter(parameterName, ParameterizedTypeName.get(type, resolvableType.resolveGenerics()));
+	}
+
+	public void addParameter(String parameterName, TypeName type) {
+
+		this.constructorArguments.put(parameterName, type);
+		this.metadata.addField(parameterName, type, Modifier.PRIVATE, Modifier.FINAL);
+	}
+
+	public void customize(ConstructorCustomizer customizer) {
+		this.customizer = customizer;
+	}
+
+	MethodSpec buildConstructor() {
+
+		MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);
+		for (Entry<String, TypeName> parameter : constructorArguments.entrySet()) {
+			builder.addParameter(parameter.getValue(), parameter.getKey()).addStatement("this.$N = $N", parameter.getKey(),
+					parameter.getKey());
+		}
+		customizer.customize(repositoryInformation, builder);
+		return builder.build();
+	}
+
+	private static TypeName getDefaultStoreRepositoryImplementationType(RepositoryInformation repositoryInformation) {
+
+		ResolvableType resolvableType = ResolvableType.forClass(repositoryInformation.getRepositoryBaseClass());
+		if (resolvableType.hasGenerics()) {
+			List<Class<?>> generics = List.of();
+			if (resolvableType.getGenerics().length == 2) { // TODO: Find some other way to resolve generics
+				generics = List.of(repositoryInformation.getDomainType(), repositoryInformation.getIdType());
+			}
+			return ParameterizedTypeName.get(repositoryInformation.getRepositoryBaseClass(), generics.toArray(Class[]::new));
+		}
+		return TypeName.get(repositoryInformation.getRepositoryBaseClass());
+	}
+
+	public interface ConstructorCustomizer {
+
+		void customize(RepositoryInformation repositoryInformation, MethodSpec.Builder builder);
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
new file mode 100644
index 0000000000..187b931a08
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.GenerationMetadata;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.ParameterizedTypeName;
+import org.springframework.javapoet.TypeName;
+
+/**
+ * @author Christoph Strobl
+ */
+public class AotRepositoryDerivedMethodBuilder extends AotRepositoryMethodBuilder {
+
+	public AotRepositoryDerivedMethodBuilder(Method method, RepositoryInformation repositoryInformation,
+			GenerationMetadata metadata) {
+
+		super(method, repositoryInformation, metadata);
+
+		initReturnType(method, repositoryInformation);
+		initParameters(method, repositoryInformation);
+	}
+
+	private void initParameters(Method method, RepositoryInformation repositoryInformation) {
+
+		ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
+		if (method.getParameterCount() > 0) {
+			int index = 0;
+			for (Parameter parameter : method.getParameters()) {
+
+				ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(new MethodParameter(method, index),
+						repositoryInterface);
+
+				TypeName parameterType = TypeName.get(resolvableParameterType.resolve());
+				if (resolvableParameterType.hasGenerics()) {
+					parameterType = ParameterizedTypeName.get(resolvableParameterType.resolve(),
+							resolvableParameterType.resolveGenerics());
+				}
+				addParameter(parameter.getName(), parameterType);
+			}
+		}
+	}
+
+	private void initReturnType(Method method, RepositoryInformation repositoryInformation) {
+
+		ResolvableType returnType = ResolvableType.forMethodReturnType(method,
+				repositoryInformation.getRepositoryInterface());
+
+		TypeName returnTypeName = TypeName.get(returnType.resolve());
+		if (returnType.hasGenerics()) {
+			returnTypeName = ParameterizedTypeName.get(returnType.resolve(), returnType.resolveGenerics());
+		}
+
+		setReturnType(returnTypeName);
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
new file mode 100644
index 0000000000..90ef94bebc
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.Modifier;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.FieldSpec;
+import org.springframework.javapoet.MethodSpec;
+import org.springframework.javapoet.ParameterSpec;
+import org.springframework.javapoet.ParameterizedTypeName;
+import org.springframework.javapoet.TypeName;
+import org.springframework.lang.Nullable;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Christoph Strobl
+ */
+public class AotRepositoryMethodBuilder {
+
+	private final Method method;
+	private final RepositoryInformation repositoryInformation;
+	private final MethodGenerationMetadata metadata;
+
+	private RepositoryMethodCustomizer customizer = (info, md, builder) -> {};
+
+	public AotRepositoryMethodBuilder(Method method, RepositoryInformation repositoryInformation,
+			AotRepositoryBuilder.GenerationMetadata metadata) {
+
+		this.method = method;
+		this.repositoryInformation = repositoryInformation;
+		this.metadata = new MethodGenerationMetadata(metadata, method);
+	}
+
+	public void addParameter(String parameterName, Class<?> type) {
+
+		ResolvableType resolvableType = ResolvableType.forClass(type);
+		if (!resolvableType.hasGenerics() || !resolvableType.hasResolvableGenerics()) {
+			addParameter(parameterName, TypeName.get(type));
+			return;
+		}
+		addParameter(parameterName, ParameterizedTypeName.get(type, resolvableType.resolveGenerics()));
+	}
+
+	public void addParameter(String parameterName, TypeName type) {
+		addParameter(ParameterSpec.builder(type, parameterName).build());
+	}
+
+	public void addParameter(ParameterSpec parameter) {
+		this.metadata.methodArguments.put(parameter.name, parameter);
+	}
+
+	public void setReturnType(@Nullable TypeName returnType) {
+		this.metadata.returnType = returnType;
+	}
+
+	public void customize(RepositoryMethodCustomizer customizer) {
+		this.customizer = customizer;
+	}
+
+	MethodSpec buildMethod() {
+
+		MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getName()).addModifiers(Modifier.PUBLIC);
+		if (!metadata.returnsVoid()) {
+			builder.returns(metadata.getReturnType());
+		}
+		builder.addJavadoc("AOT generated implementation of {@link $T#$L($L)}.", method.getDeclaringClass(),
+				method.getName(), StringUtils.collectionToCommaDelimitedString(
+						metadata.methodArguments.values().stream().map(it -> it.type.toString()).collect(Collectors.toList())));
+		metadata.methodArguments.forEach((name, spec) -> builder.addParameter(spec));
+		customizer.customize(repositoryInformation, metadata, builder);
+		return builder.build();
+	}
+
+	public interface RepositoryMethodCustomizer {
+
+		void customize(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata,
+				MethodSpec.Builder builder);
+	}
+
+	public static class MethodGenerationMetadata {
+
+		private final AotRepositoryBuilder.GenerationMetadata generationMetadata;
+		private final Method repositoryMethod;
+		private final Map<String, ParameterSpec> methodArguments;
+		@Nullable private TypeName returnType;
+
+		public MethodGenerationMetadata(AotRepositoryBuilder.GenerationMetadata generationMetadata,
+				Method repositoryMethod) {
+			this.generationMetadata = generationMetadata;
+			this.repositoryMethod = repositoryMethod;
+			this.methodArguments = new LinkedHashMap<>();
+		}
+
+		public Method getRepositoryMethod() {
+			return repositoryMethod;
+		}
+
+		@Nullable
+		public String getParameterNameOf(Class<?> type) {
+			for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
+				if (entry.getValue().type.equals(TypeName.get(type))) {
+					return entry.getKey();
+				}
+			}
+			return null;
+		}
+
+		public boolean returnsVoid() {
+			return repositoryMethod.getReturnType().equals(Void.TYPE);
+		}
+
+		@Nullable
+		public TypeName getReturnType() {
+			return returnType;
+		}
+
+		@Nullable
+		public String getSortParameterName() {
+			return getParameterNameOf(Sort.class);
+		}
+
+		@Nullable
+		public String getPageableParameterName() {
+			return getParameterNameOf(Pageable.class);
+		}
+
+		@Nullable
+		public String getLimitParameterName() {
+			return getParameterNameOf(Limit.class);
+		}
+
+		public void addParameter(ParameterSpec parameterSpec) {
+			this.methodArguments.put(parameterSpec.name, parameterSpec);
+		}
+
+		@Nullable
+		public String fieldNameOf(Class<?> type) {
+			return generationMetadata.fieldNameOf(type);
+		}
+
+		public boolean hasField(String fieldName) {
+			return generationMetadata.hasField(fieldName);
+		}
+
+		public void addField(String fieldName, TypeName type, Modifier... modifiers) {
+			generationMetadata.addField(fieldName, type, modifiers);
+		}
+
+		public void addField(FieldSpec fieldSpec) {
+			generationMetadata.addField(fieldSpec);
+		}
+
+		public Map<String, FieldSpec> getFields() {
+			return generationMetadata.getFields();
+		}
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
new file mode 100644
index 0000000000..1fffb7ffcb
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import org.springframework.aot.generate.GenerationContext;
+import org.springframework.data.repository.config.AotRepositoryContext;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.JavaFile;
+import org.springframework.javapoet.TypeSpec;
+
+/**
+ * @author Christoph Strobl
+ */
+public class RepositoryContributor implements AotCodeContributor {
+
+	private AotRepositoryContext repositoryContext;
+
+	public RepositoryContributor(AotRepositoryContext repositoryContext) {
+		this.repositoryContext = repositoryContext;
+	}
+
+	@Override
+	public void contribute(GenerationContext generationContext) {
+
+		RepositoryInformation repositoryInformation = repositoryContext.getRepositoryInformation();
+
+		AotRepositoryBuilder builder = AotRepositoryBuilder.forRepository(repositoryInformation);
+		builder.withFileCustomizer(this::customizeFile);
+		builder.withConstructorCustomizer(this::customizeConstructor);
+		builder.withDerivedMethodCustomizer(this::customizeDerivedMethod);
+
+		JavaFile file = builder.javaFile();
+
+		System.out.printf("------ %s.%s ------\n", file.packageName, file.typeSpec.name);
+		System.out.println(file);
+		System.out.println("-------------------");
+
+		generationContext.getGeneratedFiles().addSourceFile(file);
+	}
+
+	/**
+	 * Customization Hook for Store implementations
+	 */
+	protected void customizeConstructor(AotRepositoryConstructorBuilder constructorBuilder) {
+
+	}
+
+	protected void customizeFile(RepositoryInformation information, AotRepositoryBuilder.GenerationMetadata metadata,
+			TypeSpec.Builder builder) {
+
+	}
+
+	protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+
+	}
+}
diff --git a/src/test/java/example/UserRepository.java b/src/test/java/example/UserRepository.java
new file mode 100644
index 0000000000..d87b9237ad
--- /dev/null
+++ b/src/test/java/example/UserRepository.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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 example;
+
+import example.UserRepository.User;
+
+import java.util.List;
+
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * @author Christoph Strobl
+ */
+public interface UserRepository extends CrudRepository<User, Long> {
+
+	User findByFirstname(String firstname);
+
+	List<User> findByFirstnameIn(List<String> firstnames);
+
+	Long countAllByLastname(String lastname);
+
+	Long countAll();
+
+	void doSomething();
+
+	default Long theDefaultMethod() {
+		return countAll();
+	}
+
+	class User {
+		String firstname;
+	}
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java
new file mode 100644
index 0000000000..426944c700
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import example.UserRepository;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.aot.test.generate.TestGenerationContext;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.test.tools.ClassFile;
+import org.springframework.core.test.tools.TestCompiler;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.config.AotRepositoryContext;
+import org.springframework.data.repository.core.CrudMethods;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryComposition;
+import org.springframework.data.repository.core.support.RepositoryFragment;
+import org.springframework.data.util.Streamable;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+/**
+ * @author Christoph Strobl
+ * @since 2024/09
+ */
+// testclass needs to be public otherwise we cannot reference the repository within
+public class RepositoryBuilderUnitTests {
+
+	@Test
+	void compileInstance() {
+
+		DummyAotRepoContext aotContext = new DummyAotRepoContext(UserRepository.class, null);
+		RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
+
+			@Override
+			protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+				methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
+					if (!metadata.returnsVoid()) {
+						builder.addStatement("return null");
+					}
+				}));
+			}
+		};
+
+		TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
+		repositoryContributor.contribute(generationContext);
+		generationContext.writeGeneratedContent();
+
+		TestCompiler.forSystem().with(generationContext).withClasses(aotContext.getRequiredContextFiles())
+				.compile(compiled -> {
+					assertThat(compiled.getAllCompiledClasses()).map(Class::getName).contains("example.UserRepositoryImpl__Aot");
+				});
+	}
+
+	public static abstract class SimpleDummyRepository<T, ID> implements CrudRepository<T, ID> {}
+
+	public static class DummyAotRepoContext implements AotRepositoryContext {
+
+		private final StubRepositoryInformation repositoryInformation;
+
+		public DummyAotRepoContext(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
+			this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
+		}
+
+		@Override
+		public ConfigurableListableBeanFactory getBeanFactory() {
+			return null;
+		}
+
+		@Override
+		public TypeIntrospector introspectType(String typeName) {
+			return null;
+		}
+
+		@Override
+		public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
+			return null;
+		}
+
+		@Override
+		public String getBeanName() {
+			return "dummyRepository";
+		}
+
+		@Override
+		public Set<String> getBasePackages() {
+			return Set.of("org.springframework.data.dummy.repository.aot");
+		}
+
+		@Override
+		public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
+			return Set.of();
+		}
+
+		@Override
+		public RepositoryInformation getRepositoryInformation() {
+			return repositoryInformation;
+		}
+
+		@Override
+		public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
+			return Set.of();
+		}
+
+		@Override
+		public Set<Class<?>> getResolvedTypes() {
+			return Set.of();
+		}
+
+		public List<ClassFile> getRequiredContextFiles() {
+			return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass()));
+		}
+
+		static ClassFile classFileForType(Class<?> type) {
+
+			String name = type.getName();
+			ClassPathResource cpr = new ClassPathResource(name.replaceAll("\\.", "/") + ".class");
+
+			try {
+				return ClassFile.of(name, cpr.getContentAsByteArray());
+			} catch (IOException e) {
+				throw new IllegalArgumentException("Cannot open [%s].".formatted(cpr.getPath()));
+			}
+		}
+	}
+
+	public static class StubRepositoryInformation implements RepositoryInformation {
+
+		private final RepositoryMetadata metadata;
+		private final RepositoryComposition baseComposition;
+
+		public StubRepositoryInformation(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
+
+			this.metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
+			this.baseComposition = composition != null ? composition
+					: RepositoryComposition.of(RepositoryFragment.structural(SimpleDummyRepository.class));
+		}
+
+		@Override
+		public TypeInformation<?> getIdTypeInformation() {
+			return metadata.getIdTypeInformation();
+		}
+
+		@Override
+		public TypeInformation<?> getDomainTypeInformation() {
+			return metadata.getDomainTypeInformation();
+		}
+
+		@Override
+		public Class<?> getRepositoryInterface() {
+			return metadata.getRepositoryInterface();
+		}
+
+		@Override
+		public TypeInformation<?> getReturnType(Method method) {
+			return metadata.getReturnType(method);
+		}
+
+		@Override
+		public Class<?> getReturnedDomainClass(Method method) {
+			return metadata.getReturnedDomainClass(method);
+		}
+
+		@Override
+		public CrudMethods getCrudMethods() {
+			return metadata.getCrudMethods();
+		}
+
+		@Override
+		public boolean isPagingRepository() {
+			return false;
+		}
+
+		@Override
+		public Set<Class<?>> getAlternativeDomainTypes() {
+			return null;
+		}
+
+		@Override
+		public boolean isReactiveRepository() {
+			return false;
+		}
+
+		@Override
+		public Set<RepositoryFragment<?>> getFragments() {
+			return null;
+		}
+
+		@Override
+		public boolean isBaseClassMethod(Method method) {
+
+			return baseComposition.findMethod(method).isPresent();
+		}
+
+		@Override
+		public boolean isCustomMethod(Method method) {
+			return false;
+		}
+
+		@Override
+		public boolean isQueryMethod(Method method) {
+			return false;
+		}
+
+		@Override
+		public Streamable<Method> getQueryMethods() {
+			return null;
+		}
+
+		@Override
+		public Class<?> getRepositoryBaseClass() {
+			return SimpleDummyRepository.class;
+		}
+
+		@Override
+		public Method getTargetClassMethod(Method method) {
+			return null;
+		}
+
+	}
+}

From 88cf81ef4e10a036e6d9fd2866ee843939002541 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Mon, 23 Sep 2024 08:20:47 +0200
Subject: [PATCH 03/14] fix field name lookup for type

---
 .../repository/aot/generate/AotRepositoryBuilder.java | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index 26e112f432..07d2065ce9 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -139,9 +139,14 @@ public GenerationMetadata(ClassName className) {
 		@Nullable
 		public String fieldNameOf(Class<?> type) {
 
-			// TODO: might be more than one match
-			return fields.entrySet().stream().filter(entry -> entry.getValue().name.equals(type.getName())).map(Entry::getKey)
-					.findFirst().orElse(null);
+			TypeName lookup = TypeName.get(type).withoutAnnotations();
+			for(Entry<String, FieldSpec> field : fields.entrySet()) {
+				if(field.getValue().type.withoutAnnotations().equals(lookup)) {
+					return field.getKey();
+				}
+			}
+
+			return null;
 		}
 
 		public ClassName getTargetTypeName() {

From 04125c16b77bee0fda72ceb5b1db9209dc2889a1 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Mon, 23 Sep 2024 10:53:40 +0200
Subject: [PATCH 04/14] Add logging and feature flag

---
 .../aot/generate/RepositoryContributor.java   |  20 +-
 .../config/AotRepositoryContext.java          |   6 +
 .../DummyModuleAotRepositoryContext.java      | 105 +++++++++
 ...ModuleDefaultRepositoryImplementation.java |  28 +++
 .../generate/RepositoryBuilderUnitTests.java  | 213 +-----------------
 .../RepositoryContributorUnitTests.java       |  64 ++++++
 .../generate/StubRepositoryInformation.java   | 127 +++++++++++
 7 files changed, 349 insertions(+), 214 deletions(-)
 create mode 100644 src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java
 create mode 100644 src/test/java/org/springframework/data/repository/aot/generate/DummyModuleDefaultRepositoryImplementation.java
 create mode 100644 src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
 create mode 100644 src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index 1fffb7ffcb..3cdcaeca34 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -15,6 +15,8 @@
  */
 package org.springframework.data.repository.aot.generate;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.data.repository.config.AotRepositoryContext;
 import org.springframework.data.repository.core.RepositoryInformation;
@@ -26,6 +28,8 @@
  */
 public class RepositoryContributor implements AotCodeContributor {
 
+	private static final Log logger = LogFactory.getLog(RepositoryContributor.class);
+
 	private AotRepositoryContext repositoryContext;
 
 	public RepositoryContributor(AotRepositoryContext repositoryContext) {
@@ -44,11 +48,21 @@ public void contribute(GenerationContext generationContext) {
 
 		JavaFile file = builder.javaFile();
 
-		System.out.printf("------ %s.%s ------\n", file.packageName, file.typeSpec.name);
-		System.out.println(file);
-		System.out.println("-------------------");
+		if (logger.isTraceEnabled()) {
+			logger.trace("""
+					------ AOT Generated Repository: %s.%s ------
+					%s
+					-------------------
+					""".formatted(file.packageName, file.typeSpec.name, file));
+		}
+
 
+		// generate the file itself
 		generationContext.getGeneratedFiles().addSourceFile(file);
+
+		// register it in spring.factories
+		String registration = "%s=%s.%s".formatted(repositoryInformation.getRepositoryInterface().getName(), file.packageName, file.typeSpec.name);
+		generationContext.getGeneratedFiles().addResourceFile("META-INF/spring.factories", registration);
 	}
 
 	/**
diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
index 6e18dc727b..e38c1e5a58 100644
--- a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
+++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
@@ -18,6 +18,7 @@
 import java.lang.annotation.Annotation;
 import java.util.Set;
 
+import org.springframework.core.SpringProperties;
 import org.springframework.core.annotation.MergedAnnotation;
 import org.springframework.data.aot.AotContext;
 import org.springframework.data.repository.core.RepositoryInformation;
@@ -32,6 +33,8 @@
  */
 public interface AotRepositoryContext extends AotContext {
 
+	String GENERATED_REPOSITORIES_ENABLED = "spring.aot.repositories.enabled";
+
 	/**
 	 * @return the {@link String bean name} of the repository / factory bean.
 	 */
@@ -64,4 +67,7 @@ public interface AotRepositoryContext extends AotContext {
 	 */
 	Set<Class<?>> getResolvedTypes();
 
+	default boolean aotGeneratedRepositoriesEnabled() {
+		return SpringProperties.getFlag(GENERATED_REPOSITORIES_ENABLED);
+	}
 }
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java
new file mode 100644
index 0000000000..b873f8efe7
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleAotRepositoryContext.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.test.tools.ClassFile;
+import org.springframework.data.repository.config.AotRepositoryContext;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.core.support.RepositoryComposition;
+import org.springframework.lang.Nullable;
+
+/**
+ * Dummy {@link AotRepositoryContext} used to simulate module specific repository implementation.
+ *
+ * @author Christoph Strobl
+ */
+class DummyModuleAotRepositoryContext implements AotRepositoryContext {
+
+	private final StubRepositoryInformation repositoryInformation;
+
+	public DummyModuleAotRepositoryContext(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
+		this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
+	}
+
+	@Override
+	public ConfigurableListableBeanFactory getBeanFactory() {
+		return null;
+	}
+
+	@Override
+	public TypeIntrospector introspectType(String typeName) {
+		return null;
+	}
+
+	@Override
+	public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
+		return null;
+	}
+
+	@Override
+	public String getBeanName() {
+		return "dummyRepository";
+	}
+
+	@Override
+	public Set<String> getBasePackages() {
+		return Set.of("org.springframework.data.dummy.repository.aot");
+	}
+
+	@Override
+	public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
+		return Set.of();
+	}
+
+	@Override
+	public RepositoryInformation getRepositoryInformation() {
+		return repositoryInformation;
+	}
+
+	@Override
+	public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
+		return Set.of();
+	}
+
+	@Override
+	public Set<Class<?>> getResolvedTypes() {
+		return Set.of();
+	}
+
+	public List<ClassFile> getRequiredContextFiles() {
+		return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass()));
+	}
+
+	static ClassFile classFileForType(Class<?> type) {
+
+		String name = type.getName();
+		ClassPathResource cpr = new ClassPathResource(name.replaceAll("\\.", "/") + ".class");
+
+		try {
+			return ClassFile.of(name, cpr.getContentAsByteArray());
+		} catch (IOException e) {
+			throw new IllegalArgumentException("Cannot open [%s].".formatted(cpr.getPath()));
+		}
+	}
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleDefaultRepositoryImplementation.java b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleDefaultRepositoryImplementation.java
new file mode 100644
index 0000000000..f666b24e1a
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/aot/generate/DummyModuleDefaultRepositoryImplementation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * Dummy base class to simulate module specific repository implementation. <br>
+ * NOTE: needs to be {@literal public} to be referenced in generated sources.
+ * 
+ * @author Christoph Strobl
+ */
+public abstract class DummyModuleDefaultRepositoryImplementation<T, ID> implements CrudRepository<T, ID> {
+
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java
index 426944c700..5bf04cd6e7 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryBuilderUnitTests.java
@@ -19,228 +19,19 @@
 
 import example.UserRepository;
 
-import java.io.IOException;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Set;
-
 import org.junit.jupiter.api.Test;
 import org.springframework.aot.test.generate.TestGenerationContext;
-import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.core.annotation.MergedAnnotation;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.test.tools.ClassFile;
 import org.springframework.core.test.tools.TestCompiler;
-import org.springframework.data.repository.CrudRepository;
-import org.springframework.data.repository.config.AotRepositoryContext;
-import org.springframework.data.repository.core.CrudMethods;
-import org.springframework.data.repository.core.RepositoryInformation;
-import org.springframework.data.repository.core.RepositoryMetadata;
-import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
-import org.springframework.data.repository.core.support.RepositoryComposition;
-import org.springframework.data.repository.core.support.RepositoryFragment;
-import org.springframework.data.util.Streamable;
-import org.springframework.data.util.TypeInformation;
-import org.springframework.lang.Nullable;
 
 /**
  * @author Christoph Strobl
- * @since 2024/09
  */
 // testclass needs to be public otherwise we cannot reference the repository within
-public class RepositoryBuilderUnitTests {
+class RepositoryBuilderUnitTests {
 
 	@Test
 	void compileInstance() {
 
-		DummyAotRepoContext aotContext = new DummyAotRepoContext(UserRepository.class, null);
-		RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
-
-			@Override
-			protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
-				methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
-					if (!metadata.returnsVoid()) {
-						builder.addStatement("return null");
-					}
-				}));
-			}
-		};
-
-		TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
-		repositoryContributor.contribute(generationContext);
-		generationContext.writeGeneratedContent();
-
-		TestCompiler.forSystem().with(generationContext).withClasses(aotContext.getRequiredContextFiles())
-				.compile(compiled -> {
-					assertThat(compiled.getAllCompiledClasses()).map(Class::getName).contains("example.UserRepositoryImpl__Aot");
-				});
-	}
-
-	public static abstract class SimpleDummyRepository<T, ID> implements CrudRepository<T, ID> {}
-
-	public static class DummyAotRepoContext implements AotRepositoryContext {
-
-		private final StubRepositoryInformation repositoryInformation;
-
-		public DummyAotRepoContext(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
-			this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
-		}
-
-		@Override
-		public ConfigurableListableBeanFactory getBeanFactory() {
-			return null;
-		}
-
-		@Override
-		public TypeIntrospector introspectType(String typeName) {
-			return null;
-		}
-
-		@Override
-		public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) {
-			return null;
-		}
-
-		@Override
-		public String getBeanName() {
-			return "dummyRepository";
-		}
-
-		@Override
-		public Set<String> getBasePackages() {
-			return Set.of("org.springframework.data.dummy.repository.aot");
-		}
-
-		@Override
-		public Set<Class<? extends Annotation>> getIdentifyingAnnotations() {
-			return Set.of();
-		}
-
-		@Override
-		public RepositoryInformation getRepositoryInformation() {
-			return repositoryInformation;
-		}
-
-		@Override
-		public Set<MergedAnnotation<Annotation>> getResolvedAnnotations() {
-			return Set.of();
-		}
-
-		@Override
-		public Set<Class<?>> getResolvedTypes() {
-			return Set.of();
-		}
-
-		public List<ClassFile> getRequiredContextFiles() {
-			return List.of(classFileForType(repositoryInformation.getRepositoryBaseClass()));
-		}
-
-		static ClassFile classFileForType(Class<?> type) {
-
-			String name = type.getName();
-			ClassPathResource cpr = new ClassPathResource(name.replaceAll("\\.", "/") + ".class");
-
-			try {
-				return ClassFile.of(name, cpr.getContentAsByteArray());
-			} catch (IOException e) {
-				throw new IllegalArgumentException("Cannot open [%s].".formatted(cpr.getPath()));
-			}
-		}
-	}
-
-	public static class StubRepositoryInformation implements RepositoryInformation {
-
-		private final RepositoryMetadata metadata;
-		private final RepositoryComposition baseComposition;
-
-		public StubRepositoryInformation(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
-
-			this.metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
-			this.baseComposition = composition != null ? composition
-					: RepositoryComposition.of(RepositoryFragment.structural(SimpleDummyRepository.class));
-		}
-
-		@Override
-		public TypeInformation<?> getIdTypeInformation() {
-			return metadata.getIdTypeInformation();
-		}
-
-		@Override
-		public TypeInformation<?> getDomainTypeInformation() {
-			return metadata.getDomainTypeInformation();
-		}
-
-		@Override
-		public Class<?> getRepositoryInterface() {
-			return metadata.getRepositoryInterface();
-		}
-
-		@Override
-		public TypeInformation<?> getReturnType(Method method) {
-			return metadata.getReturnType(method);
-		}
-
-		@Override
-		public Class<?> getReturnedDomainClass(Method method) {
-			return metadata.getReturnedDomainClass(method);
-		}
-
-		@Override
-		public CrudMethods getCrudMethods() {
-			return metadata.getCrudMethods();
-		}
-
-		@Override
-		public boolean isPagingRepository() {
-			return false;
-		}
-
-		@Override
-		public Set<Class<?>> getAlternativeDomainTypes() {
-			return null;
-		}
-
-		@Override
-		public boolean isReactiveRepository() {
-			return false;
-		}
-
-		@Override
-		public Set<RepositoryFragment<?>> getFragments() {
-			return null;
-		}
-
-		@Override
-		public boolean isBaseClassMethod(Method method) {
-
-			return baseComposition.findMethod(method).isPresent();
-		}
-
-		@Override
-		public boolean isCustomMethod(Method method) {
-			return false;
-		}
-
-		@Override
-		public boolean isQueryMethod(Method method) {
-			return false;
-		}
-
-		@Override
-		public Streamable<Method> getQueryMethods() {
-			return null;
-		}
-
-		@Override
-		public Class<?> getRepositoryBaseClass() {
-			return SimpleDummyRepository.class;
-		}
-
-		@Override
-		public Method getTargetClassMethod(Method method) {
-			return null;
-		}
-
+		// moved to contributor
 	}
 }
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
new file mode 100644
index 0000000000..bf562a6d8e
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import example.UserRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.aot.test.generate.TestGenerationContext;
+import org.springframework.core.test.tools.ResourceFile;
+import org.springframework.core.test.tools.ResourceFileAssert;
+import org.springframework.core.test.tools.ResourceFiles;
+import org.springframework.core.test.tools.TestCompiler;
+
+/**
+ * @author Christoph Strobl
+ */
+class RepositoryContributorUnitTests {
+
+    @Test
+    void testCompile() {
+
+        DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
+        RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
+
+            @Override
+            protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+                methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
+                    if (!metadata.returnsVoid()) {
+                        builder.addStatement("return null");
+                    }
+                }));
+            }
+        };
+
+        TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
+        repositoryContributor.contribute(generationContext);
+        generationContext.writeGeneratedContent();
+
+        TestCompiler.forSystem().with(generationContext).compile(compiled -> {
+            assertThat(compiled.getAllCompiledClasses()).map(Class::getName).contains("example.UserRepositoryImpl__Aot");
+
+            ResourceFile springFactories = compiled.getResourceFiles().get("META-INF/spring.factories");
+            assertThat(springFactories).isNotNull();
+            springFactories.assertThat().contains("example.UserRepository=example.UserRepositoryImpl__Aot");
+        });
+
+
+    }
+
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java b/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java
new file mode 100644
index 0000000000..e526a21279
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 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.springframework.data.repository.aot.generate;
+
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import org.springframework.data.repository.core.CrudMethods;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryComposition;
+import org.springframework.data.repository.core.support.RepositoryFragment;
+import org.springframework.data.util.Streamable;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+/**
+ * Stub {@link RepositoryInformation} used for testing.
+ *
+ * @author Christoph Strobl
+ */
+class StubRepositoryInformation implements RepositoryInformation {
+
+	private final RepositoryMetadata metadata;
+	private final RepositoryComposition baseComposition;
+
+	public StubRepositoryInformation(Class<?> repositoryInterface, @Nullable RepositoryComposition composition) {
+
+		this.metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
+		this.baseComposition = composition != null ? composition
+				: RepositoryComposition.of(RepositoryFragment.structural(DummyModuleDefaultRepositoryImplementation.class));
+	}
+
+	@Override
+	public TypeInformation<?> getIdTypeInformation() {
+		return metadata.getIdTypeInformation();
+	}
+
+	@Override
+	public TypeInformation<?> getDomainTypeInformation() {
+		return metadata.getDomainTypeInformation();
+	}
+
+	@Override
+	public Class<?> getRepositoryInterface() {
+		return metadata.getRepositoryInterface();
+	}
+
+	@Override
+	public TypeInformation<?> getReturnType(Method method) {
+		return metadata.getReturnType(method);
+	}
+
+	@Override
+	public Class<?> getReturnedDomainClass(Method method) {
+		return metadata.getReturnedDomainClass(method);
+	}
+
+	@Override
+	public CrudMethods getCrudMethods() {
+		return metadata.getCrudMethods();
+	}
+
+	@Override
+	public boolean isPagingRepository() {
+		return false;
+	}
+
+	@Override
+	public Set<Class<?>> getAlternativeDomainTypes() {
+		return null;
+	}
+
+	@Override
+	public boolean isReactiveRepository() {
+		return false;
+	}
+
+	@Override
+	public Set<RepositoryFragment<?>> getFragments() {
+		return null;
+	}
+
+	@Override
+	public boolean isBaseClassMethod(Method method) {
+		return baseComposition.findMethod(method).isPresent();
+	}
+
+	@Override
+	public boolean isCustomMethod(Method method) {
+		return false;
+	}
+
+	@Override
+	public boolean isQueryMethod(Method method) {
+		return false;
+	}
+
+	@Override
+	public Streamable<Method> getQueryMethods() {
+		return null;
+	}
+
+	@Override
+	public Class<?> getRepositoryBaseClass() {
+		return DummyModuleDefaultRepositoryImplementation.class;
+	}
+
+	@Override
+	public Method getTargetClassMethod(Method method) {
+		return null;
+	}
+}

From 5a5047ac017da69b07191b4f84309f1f56555639 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Mon, 23 Sep 2024 16:13:47 +0200
Subject: [PATCH 05/14] Add logging and pass and delegate find method to
 repository composition

---
 .../aot/generate/AotRepositoryBuilder.java    | 31 +++++++++--
 .../AotRepositoryConstructorBuilder.java      |  2 +-
 .../aot/generate/RepositoryContributor.java   | 14 +++--
 .../config/AotRepositoryInformation.java      |  9 +++-
 .../data/aot/CodeContributionAssert.java      | 13 ++++-
 .../RepositoryContributorUnitTests.java       | 52 ++++++++++---------
 6 files changed, 83 insertions(+), 38 deletions(-)

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index 07d2065ce9..71bad0e214 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -15,6 +15,9 @@
  */
 package org.springframework.data.repository.aot.generate;
 
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.temporal.ChronoField;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -22,6 +25,8 @@
 
 import javax.lang.model.element.Modifier;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.springframework.aot.generate.ClassNameGenerator;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.ClassName;
@@ -30,6 +35,7 @@
 import org.springframework.javapoet.TypeName;
 import org.springframework.javapoet.TypeSpec;
 import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
 import org.springframework.util.ReflectionUtils;
 
 /**
@@ -52,14 +58,28 @@ public static AotRepositoryBuilder forRepository(RepositoryInformation repositor
 
 		this.repositoryInformation = repositoryInformation;
 		this.generationMetadata = new GenerationMetadata(className());
+		this.generationMetadata.addField(FieldSpec
+				.builder(TypeName.get(Log.class), "logger", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+				.initializer("$T.getLog($T.class)", TypeName.get(LogFactory.class), this.generationMetadata.getTargetTypeName())
+				.build());
+
 		this.customizer = (info, metadata, builder) -> {};
 	}
 
 	public JavaFile javaFile() {
 
+		YearMonth creationDate = YearMonth.now(ZoneId.of("UTC"));
+
 		// start creating the type
-		TypeSpec.Builder builder = TypeSpec.classBuilder(this.generationMetadata.getTargetTypeName())
-				.addModifiers(Modifier.PUBLIC);
+		TypeSpec.Builder builder = TypeSpec.classBuilder(this.generationMetadata.getTargetTypeName()) //
+				.addModifiers(Modifier.PUBLIC) //
+				.addAnnotation(Component.class) //
+				.addJavadoc("AOT generated repository implementation for {@link $T}.\n",
+						repositoryInformation.getRepositoryInterface()) //
+				.addJavadoc("\n") //
+				.addJavadoc("@since $L/$L\n", creationDate.get(ChronoField.YEAR), creationDate.get(ChronoField.MONTH_OF_YEAR)) //
+				.addJavadoc("@author $L", "Spring Data"); // TODO: does System.getProperty("user.name") make sense here?
+
 		// TODO: we do not need that here
 		// .addSuperinterface(repositoryInformation.getRepositoryInterface());
 
@@ -78,7 +98,8 @@ public JavaFile javaFile() {
 			derivedMethodBuilderCustomizer.accept(derivedMethodBuilder);
 			builder.addMethod(derivedMethodBuilder.buildMethod());
 		}, it -> {
-			return !repositoryInformation.isBaseClassMethod(it) && !repositoryInformation.isCustomMethod(it) && !it.isDefault();
+			return !repositoryInformation.isBaseClassMethod(it) && !repositoryInformation.isCustomMethod(it)
+					&& !it.isDefault();
 		});
 
 		// write fields at the end so we make sure to capture things added by methods
@@ -140,8 +161,8 @@ public GenerationMetadata(ClassName className) {
 		public String fieldNameOf(Class<?> type) {
 
 			TypeName lookup = TypeName.get(type).withoutAnnotations();
-			for(Entry<String, FieldSpec> field : fields.entrySet()) {
-				if(field.getValue().type.withoutAnnotations().equals(lookup)) {
+			for (Entry<String, FieldSpec> field : fields.entrySet()) {
+				if (field.getValue().type.withoutAnnotations().equals(lookup)) {
 					return field.getKey();
 				}
 			}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
index 91bd3fedb4..8f89708088 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
@@ -45,7 +45,7 @@ public AotRepositoryConstructorBuilder(RepositoryInformation repositoryInformati
 		this.repositoryInformation = repositoryInformation;
 		this.metadata = metadata;
 		this.constructorArguments = new LinkedHashMap<>(3);
-		addParameter("delegate", getDefaultStoreRepositoryImplementationType(repositoryInformation));
+		// addParameter("delegate", getDefaultStoreRepositoryImplementationType(repositoryInformation));
 	}
 
 	public void addParameter(String parameterName, Class<?> type) {
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index 3cdcaeca34..65e9220c05 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -18,6 +18,8 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.aot.generate.GenerationContext;
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.TypeReference;
 import org.springframework.data.repository.config.AotRepositoryContext;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.JavaFile;
@@ -39,6 +41,7 @@ public RepositoryContributor(AotRepositoryContext repositoryContext) {
 	@Override
 	public void contribute(GenerationContext generationContext) {
 
+		//TODO: do we need - generationContext.withName("spring-data");
 		RepositoryInformation repositoryInformation = repositoryContext.getRepositoryInformation();
 
 		AotRepositoryBuilder builder = AotRepositoryBuilder.forRepository(repositoryInformation);
@@ -47,21 +50,24 @@ public void contribute(GenerationContext generationContext) {
 		builder.withDerivedMethodCustomizer(this::customizeDerivedMethod);
 
 		JavaFile file = builder.javaFile();
+		String typeName = "%s.%s".formatted(file.packageName, file.typeSpec.name);
 
 		if (logger.isTraceEnabled()) {
 			logger.trace("""
-					------ AOT Generated Repository: %s.%s ------
+					------ AOT Generated Repository: %s ------
 					%s
 					-------------------
-					""".formatted(file.packageName, file.typeSpec.name, file));
+					""".formatted(typeName, file));
 		}
 
-
 		// generate the file itself
 		generationContext.getGeneratedFiles().addSourceFile(file);
 
+		// generate native runtime hints - needed cause we're using the repository proxy
+		generationContext.getRuntimeHints().reflection().registerType(TypeReference.of(typeName), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
+
 		// register it in spring.factories
-		String registration = "%s=%s.%s".formatted(repositoryInformation.getRepositoryInterface().getName(), file.packageName, file.typeSpec.name);
+		String registration = "%s=%s".formatted(repositoryInformation.getRepositoryInterface().getName(), typeName);
 		generationContext.getGeneratedFiles().addResourceFile("META-INF/spring.factories", registration);
 	}
 
diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryInformation.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryInformation.java
index b2faf62e4c..ee0ab7cfb2 100644
--- a/src/main/java/org/springframework/data/repository/config/AotRepositoryInformation.java
+++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryInformation.java
@@ -24,7 +24,9 @@
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.data.repository.core.RepositoryInformationSupport;
 import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryComposition;
 import org.springframework.data.repository.core.support.RepositoryFragment;
+import org.springframework.data.util.Lazy;
 
 /**
  * {@link RepositoryInformation} based on {@link RepositoryMetadata} collected at build time.
@@ -35,6 +37,9 @@
 class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
 
 	private final Supplier<Collection<RepositoryFragment<?>>> fragments;
+	private Lazy<RepositoryComposition> baseComposition = Lazy.of(() -> {
+		return RepositoryComposition.of(RepositoryFragment.structural(getRepositoryBaseClass()));
+	});
 
 	AotRepositoryInformation(Supplier<RepositoryMetadata> repositoryMetadata, Supplier<Class<?>> repositoryBaseClass,
 			Supplier<Collection<RepositoryFragment<?>>> fragments) {
@@ -60,12 +65,12 @@ public boolean isCustomMethod(Method method) {
 
 	@Override
 	public boolean isBaseClassMethod(Method method) {
-		return false;
+		return baseComposition.get().findMethod(method).isPresent();
 	}
 
 	@Override
 	public Method getTargetClassMethod(Method method) {
-		return method;
+		return baseComposition.get().findMethod(method).orElse(method);
 	}
 
 }
diff --git a/src/test/java/org/springframework/data/aot/CodeContributionAssert.java b/src/test/java/org/springframework/data/aot/CodeContributionAssert.java
index a7f7cd4a34..1bf8817bb8 100644
--- a/src/test/java/org/springframework/data/aot/CodeContributionAssert.java
+++ b/src/test/java/org/springframework/data/aot/CodeContributionAssert.java
@@ -15,7 +15,7 @@
  */
 package org.springframework.data.aot;
 
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
 
 import java.lang.reflect.Method;
 import java.util.Arrays;
@@ -24,6 +24,7 @@
 import org.assertj.core.api.AbstractAssert;
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.JdkProxyHint;
+import org.springframework.aot.hint.TypeReference;
 import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
 
 /**
@@ -51,6 +52,16 @@ public CodeContributionAssert contributesReflectionFor(Class<?>... types) {
 		return this;
 	}
 
+	public CodeContributionAssert contributesReflectionFor(String... types) {
+
+		for (String type : types) {
+			assertThat(this.actual.getRuntimeHints()).describedAs("No reflection entry found for [%s]", type)
+					.matches(RuntimeHintsPredicates.reflection().onType(TypeReference.of(type)));
+		}
+
+		return this;
+	}
+
 	public CodeContributionAssert contributesReflectionFor(Method... methods) {
 
 		for (Method method : methods) {
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
index bf562a6d8e..db00b9cd21 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
@@ -18,47 +18,49 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 import example.UserRepository;
+
 import org.junit.jupiter.api.Test;
 import org.springframework.aot.test.generate.TestGenerationContext;
 import org.springframework.core.test.tools.ResourceFile;
-import org.springframework.core.test.tools.ResourceFileAssert;
-import org.springframework.core.test.tools.ResourceFiles;
 import org.springframework.core.test.tools.TestCompiler;
+import org.springframework.data.aot.CodeContributionAssert;
 
 /**
  * @author Christoph Strobl
  */
 class RepositoryContributorUnitTests {
 
-    @Test
-    void testCompile() {
+	@Test
+	void testCompile() {
 
-        DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
-        RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
+		DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
+		RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
 
-            @Override
-            protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
-                methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
-                    if (!metadata.returnsVoid()) {
-                        builder.addStatement("return null");
-                    }
-                }));
-            }
-        };
+			@Override
+			protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+				methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
+					if (!metadata.returnsVoid()) {
+						builder.addStatement("return null");
+					}
+				}));
+			}
+		};
 
-        TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
-        repositoryContributor.contribute(generationContext);
-        generationContext.writeGeneratedContent();
+		TestGenerationContext generationContext = new TestGenerationContext(UserRepository.class);
+		repositoryContributor.contribute(generationContext);
+		generationContext.writeGeneratedContent();
 
-        TestCompiler.forSystem().with(generationContext).compile(compiled -> {
-            assertThat(compiled.getAllCompiledClasses()).map(Class::getName).contains("example.UserRepositoryImpl__Aot");
+		String expectedTypeName = "example.UserRepositoryImpl__Aot";
 
-            ResourceFile springFactories = compiled.getResourceFiles().get("META-INF/spring.factories");
-            assertThat(springFactories).isNotNull();
-            springFactories.assertThat().contains("example.UserRepository=example.UserRepositoryImpl__Aot");
-        });
+		TestCompiler.forSystem().with(generationContext).compile(compiled -> {
+			assertThat(compiled.getAllCompiledClasses()).map(Class::getName).contains(expectedTypeName);
 
+			ResourceFile springFactories = compiled.getResourceFiles().get("META-INF/spring.factories");
+			assertThat(springFactories).isNotNull();
+			springFactories.assertThat().contains("example.UserRepository=%s".formatted(expectedTypeName));
+		});
 
-    }
+		new CodeContributionAssert(generationContext).contributesReflectionFor(expectedTypeName);
+	}
 
 }

From 23758ca1000c5350f6bfa55ec5ef346488a89739 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Tue, 24 Sep 2024 10:17:11 +0200
Subject: [PATCH 06/14] move flag lookup

---
 src/main/java/org/springframework/data/aot/AotContext.java | 7 +++++++
 .../data/repository/config/AotRepositoryContext.java       | 6 ------
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/springframework/data/aot/AotContext.java b/src/main/java/org/springframework/data/aot/AotContext.java
index f6df3d1dc3..5d8073edd2 100644
--- a/src/main/java/org/springframework/data/aot/AotContext.java
+++ b/src/main/java/org/springframework/data/aot/AotContext.java
@@ -28,6 +28,7 @@
 import org.springframework.beans.factory.config.BeanReference;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.SpringProperties;
 import org.springframework.data.util.TypeScanner;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
@@ -48,6 +49,12 @@
  */
 public interface AotContext {
 
+	String GENERATED_REPOSITORIES_ENABLED = "spring.aot.repositories.enabled";
+
+	static boolean aotGeneratedRepositoriesEnabled() {
+		return SpringProperties.getFlag(GENERATED_REPOSITORIES_ENABLED);
+	}
+
 	/**
 	 * Create an {@link AotContext} backed by the given {@link BeanFactory}.
 	 *
diff --git a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
index e38c1e5a58..995aa04084 100644
--- a/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
+++ b/src/main/java/org/springframework/data/repository/config/AotRepositoryContext.java
@@ -33,8 +33,6 @@
  */
 public interface AotRepositoryContext extends AotContext {
 
-	String GENERATED_REPOSITORIES_ENABLED = "spring.aot.repositories.enabled";
-
 	/**
 	 * @return the {@link String bean name} of the repository / factory bean.
 	 */
@@ -66,8 +64,4 @@ public interface AotRepositoryContext extends AotContext {
 	 * @return all {@link Class types} reachable from the repository.
 	 */
 	Set<Class<?>> getResolvedTypes();
-
-	default boolean aotGeneratedRepositoriesEnabled() {
-		return SpringProperties.getFlag(GENERATED_REPOSITORIES_ENABLED);
-	}
 }

From 0b67b0fe97004553e66816585386346d99a3edd6 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Tue, 24 Sep 2024 14:20:34 +0200
Subject: [PATCH 07/14] fixme: fragment method lookuop

---
 .../support/DefaultRepositoryInformation.java | 23 +++++++++++++++++++
 ...DefaultRepositoryInformationUnitTests.java | 16 +++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
index f033a2023b..111a4c4177 100644
--- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
+++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java
@@ -18,6 +18,7 @@
 import static org.springframework.util.ReflectionUtils.*;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -25,7 +26,10 @@
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.data.repository.core.RepositoryInformationSupport;
 import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.MethodLookup.InvokedMethod;
+import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
 import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
 
 /**
  * Default implementation of {@link RepositoryInformation}.
@@ -100,6 +104,25 @@ public boolean isBaseClassMethod(Method method) {
 		return baseComposition.getMethod(method) != null;
 	}
 
+
+	protected boolean isQueryMethodCandidate(Method method) {
+
+		// FIXME - that should be simplified
+		boolean queryMethodCandidate = super.isQueryMethodCandidate(method);
+		if(!isQueryAnnotationPresentOn(method)) {
+			return queryMethodCandidate;
+		}
+
+		return queryMethodCandidate && !getFragments().stream().anyMatch(fragment -> {
+			if(fragment.getImplementation().isPresent()) {
+				if(ClassUtils.hasMethod(fragment.getImplementation().get().getClass(), method.getName(), method.getParameterTypes())) {
+					return true;
+				}
+			}
+			return false;
+		});
+	}
+
 	@Override
 	public Set<RepositoryFragment<?>> getFragments() {
 		return composition.getFragments().toSet();
diff --git a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
index 7ec9a2deda..e91ce12301 100755
--- a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java
@@ -198,6 +198,16 @@ void getQueryShouldNotReturnAnyBridgeMethods() {
 		assertThat(information.getQueryMethods()).allMatch(method -> !method.isBridge());
 	}
 
+	@Test // GH-???
+	void annotatedQueryMethodWithFragmentImplementationIsNotConsideredForQueryMethods() {
+
+		RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomDefaultRepositoryMethodsRepository.class);
+		RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
+			RepositoryComposition.of(RepositoryFragment.implemented(new FragmentThatImplementsFinderWithQueryAnnotation())));
+
+		assertThat(information.getQueryMethods()).allMatch(it -> !it.getName().equals("findAll"));
+	}
+
 	@Test // DATACMNS-854
 	void discoversCustomlyImplementedCrudMethodWithGenerics() throws SecurityException, NoSuchMethodException {
 
@@ -377,6 +387,12 @@ interface CustomDefaultRepositoryMethodsRepository extends CrudRepository<User,
 		List<User> findAll();
 	}
 
+	static class FragmentThatImplementsFinderWithQueryAnnotation {
+		public List<User> findAll() {
+			return null;
+		}
+	}
+
 	// DATACMNS-854, DATACMNS-912
 
 	interface GenericsSaveRepository extends CrudRepository<Sample, Long> {}

From f3e4fb7a7552bba7b43ccdd54cd830ebe805f038 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Tue, 24 Sep 2024 14:38:45 +0200
Subject: [PATCH 08/14] keep not where we could plug some bean registration
 code

---
 .../repository/config/RepositoryRegistrationAotProcessor.java    | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
index 42e57e7d26..b61d08ab8d 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotProcessor.java
@@ -126,6 +126,7 @@ protected RepositoryRegistrationAotContribution newRepositoryRegistrationAotCont
 				.forBean(repositoryBean);
 
 		BiConsumer<AotRepositoryContext, GenerationContext> moduleContribution = this::registerReflectiveForAggregateRoot;
+		//TODO: add the hook for customizing bean initialization code here!
 
 		return contribution.withModuleContribution(moduleContribution.andThen(this::contribute));
 	}

From ff735e6f1bdc1d2cb4ed78e48b7e67296422d61c Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Thu, 9 Jan 2025 13:21:00 +0100
Subject: [PATCH 09/14] Add actual return type to metadata to get the User out
 of the List<User> for example

---
 .../aot/generate/AotRepositoryBuilder.java           | 11 +++++++++++
 .../generate/AotRepositoryDerivedMethodBuilder.java  | 12 ++++++++++--
 .../aot/generate/AotRepositoryMethodBuilder.java     |  9 ++++++++-
 3 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index 71bad0e214..f7172d63f9 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -28,6 +28,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.aot.generate.ClassNameGenerator;
+import org.springframework.data.repository.CrudRepository;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.ClassName;
 import org.springframework.javapoet.FieldSpec;
@@ -98,6 +99,16 @@ public JavaFile javaFile() {
 			derivedMethodBuilderCustomizer.accept(derivedMethodBuilder);
 			builder.addMethod(derivedMethodBuilder.buildMethod());
 		}, it -> {
+
+			/*
+			the isBaseClassMethod(it) check seems to have some issues.
+			need to hard code it here
+			 */
+
+			if(ReflectionUtils.findMethod(CrudRepository.class, it.getName(), it.getParameterTypes()) != null) {
+				return false;
+			}
+
 			return !repositoryInformation.isBaseClassMethod(it) && !repositoryInformation.isCustomMethod(it)
 					&& !it.isDefault();
 		});
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
index 187b931a08..5cb2bbd19d 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
@@ -65,10 +65,18 @@ private void initReturnType(Method method, RepositoryInformation repositoryInfor
 				repositoryInformation.getRepositoryInterface());
 
 		TypeName returnTypeName = TypeName.get(returnType.resolve());
+		TypeName actualReturnTypeName = null;
 		if (returnType.hasGenerics()) {
-			returnTypeName = ParameterizedTypeName.get(returnType.resolve(), returnType.resolveGenerics());
+			Class<?>[] generics = returnType.resolveGenerics();
+			returnTypeName = ParameterizedTypeName.get(returnType.resolve(), generics);
+
+			if(generics.length == 1) {
+				actualReturnTypeName = TypeName.get(generics[0]);
+			}
 		}
 
-		setReturnType(returnTypeName);
+		setReturnType(returnTypeName, actualReturnTypeName);
 	}
+
+
 }
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
index 90ef94bebc..ab46ee6d92 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -73,8 +73,9 @@ public void addParameter(ParameterSpec parameter) {
 		this.metadata.methodArguments.put(parameter.name, parameter);
 	}
 
-	public void setReturnType(@Nullable TypeName returnType) {
+	public void setReturnType(@Nullable TypeName returnType, @Nullable TypeName actualReturnType) {
 		this.metadata.returnType = returnType;
+		this.metadata.actualReturnType = actualReturnType;
 	}
 
 	public void customize(RepositoryMethodCustomizer customizer) {
@@ -106,6 +107,7 @@ public static class MethodGenerationMetadata {
 		private final AotRepositoryBuilder.GenerationMetadata generationMetadata;
 		private final Method repositoryMethod;
 		private final Map<String, ParameterSpec> methodArguments;
+		@Nullable public TypeName actualReturnType;
 		@Nullable private TypeName returnType;
 
 		public MethodGenerationMetadata(AotRepositoryBuilder.GenerationMetadata generationMetadata,
@@ -138,6 +140,11 @@ public TypeName getReturnType() {
 			return returnType;
 		}
 
+		@Nullable
+		public TypeName getActualReturnType() {
+			return actualReturnType;
+		}
+
 		@Nullable
 		public String getSortParameterName() {
 			return getParameterNameOf(Sort.class);

From 11fba5e85257e0115dcce620e55b8851fb56a705 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Mon, 13 Jan 2025 12:00:32 +0100
Subject: [PATCH 10/14] Modules should be able to report back if code could be
 generated for certain method

---
 .../aot/generate/AotRepositoryBuilder.java    | 11 ++--
 .../generate/AotRepositoryMethodBuilder.java  |  6 ++
 .../repository/aot/generate/CodeBlocks.java   | 64 +++++++++++++++++++
 .../repository/aot/generate/Contribution.java | 40 ++++++++++++
 .../aot/generate/RepositoryContributor.java   |  4 +-
 .../RepositoryContributorUnitTests.java       |  3 +-
 6 files changed, 121 insertions(+), 7 deletions(-)
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/Contribution.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index f7172d63f9..5899d000e1 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -22,6 +22,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 import javax.lang.model.element.Modifier;
 
@@ -48,7 +49,7 @@ public class AotRepositoryBuilder {
 	private final GenerationMetadata generationMetadata;
 
 	private Consumer<AotRepositoryConstructorBuilder> constructorBuilderCustomizer;
-	private Consumer<AotRepositoryMethodBuilder> derivedMethodBuilderCustomizer;
+	private Function<AotRepositoryMethodBuilder, Contribution> derivedMethodBuilderCustomizer;
 	private RepositoryCustomizer customizer;
 
 	public static AotRepositoryBuilder forRepository(RepositoryInformation repositoryInformation) {
@@ -96,8 +97,10 @@ public JavaFile javaFile() {
 
 			AotRepositoryDerivedMethodBuilder derivedMethodBuilder = new AotRepositoryDerivedMethodBuilder(method,
 					repositoryInformation, generationMetadata);
-			derivedMethodBuilderCustomizer.accept(derivedMethodBuilder);
-			builder.addMethod(derivedMethodBuilder.buildMethod());
+			switch (derivedMethodBuilderCustomizer.apply(derivedMethodBuilder)) {
+				case CODE -> builder.addMethod(derivedMethodBuilder.buildMethod());
+				// todo other cases, not sure which ones right now
+			}
 		}, it -> {
 
 			/*
@@ -127,7 +130,7 @@ AotRepositoryBuilder withConstructorCustomizer(Consumer<AotRepositoryConstructor
 		return this;
 	}
 
-	AotRepositoryBuilder withDerivedMethodCustomizer(Consumer<AotRepositoryMethodBuilder> methodBuilder) {
+	AotRepositoryBuilder withDerivedMethodCustomizer(Function<AotRepositoryMethodBuilder, Contribution> methodBuilder) {
 		this.derivedMethodBuilderCustomizer = methodBuilder;
 		return this;
 	}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
index ab46ee6d92..edcfaa25f1 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -46,6 +46,7 @@ public class AotRepositoryMethodBuilder {
 	private final MethodGenerationMetadata metadata;
 
 	private RepositoryMethodCustomizer customizer = (info, md, builder) -> {};
+	private CodeBlocks codeBlocks;
 
 	public AotRepositoryMethodBuilder(Method method, RepositoryInformation repositoryInformation,
 			AotRepositoryBuilder.GenerationMetadata metadata) {
@@ -53,6 +54,7 @@ public AotRepositoryMethodBuilder(Method method, RepositoryInformation repositor
 		this.method = method;
 		this.repositoryInformation = repositoryInformation;
 		this.metadata = new MethodGenerationMetadata(metadata, method);
+		this.codeBlocks = new CodeBlocks(metadata);
 	}
 
 	public void addParameter(String parameterName, Class<?> type) {
@@ -96,6 +98,10 @@ MethodSpec buildMethod() {
 		return builder.build();
 	}
 
+	public CodeBlocks codeBlocks() {
+		return codeBlocks;
+	}
+
 	public interface RepositoryMethodCustomizer {
 
 		void customize(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata,
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
new file mode 100644
index 0000000000..680cf7c2db
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2025. 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.
+ */
+
+/*
+ * Copyright 2025 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.springframework.data.repository.aot.generate;
+
+import org.apache.commons.logging.Log;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.GenerationMetadata;
+import org.springframework.javapoet.CodeBlock;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public class CodeBlocks {
+
+	private final GenerationMetadata metadata;
+
+	public CodeBlocks(GenerationMetadata metadata) {
+		this.metadata = metadata;
+	}
+
+	private CodeBlock log(String level, String message) {
+
+		CodeBlock.Builder builder = CodeBlock.builder();
+		builder.beginControlFlow("if($L.is$LEnabled())", metadata.fieldNameOf(Log.class), StringUtils.capitalize(level));
+		builder.addStatement("$L.$L($S)", metadata.fieldNameOf(Log.class), level, message);
+		builder.endControlFlow();
+		return builder.build();
+	}
+
+	public CodeBlock logDebug(String message) {
+		return log("debug", message);
+	}
+
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java b/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java
new file mode 100644
index 0000000000..acf39b525d
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2025. 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.
+ */
+
+/*
+ * Copyright 2025 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.springframework.data.repository.aot.generate;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public enum Contribution {
+    CODE, SKIP
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index 65e9220c05..0cd80f119e 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -83,7 +83,7 @@ protected void customizeFile(RepositoryInformation information, AotRepositoryBui
 
 	}
 
-	protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
-
+	protected Contribution customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+		return Contribution.SKIP;
 	}
 }
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
index db00b9cd21..6889e5ed97 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
@@ -37,12 +37,13 @@ void testCompile() {
 		RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
 
 			@Override
-			protected void customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
+			protected Contribution customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
 				methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
 					if (!metadata.returnsVoid()) {
 						builder.addStatement("return null");
 					}
 				}));
+				return Contribution.CODE;
 			}
 		};
 

From 0fa4ed17ffdf420a79d161a1e2abdb237428bc6c Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Mon, 13 Jan 2025 15:36:22 +0100
Subject: [PATCH 11/14] add some return type checks for generating page and
 slice code stuff

---
 .../AotRepositoryDerivedMethodBuilder.java    |  2 +
 .../generate/AotRepositoryMethodBuilder.java  | 38 +++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
index 5cb2bbd19d..e08b60beb8 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
@@ -55,7 +55,9 @@ private void initParameters(Method method, RepositoryInformation repositoryInfor
 							resolvableParameterType.resolveGenerics());
 				}
 				addParameter(parameter.getName(), parameterType);
+				index++;
 			}
+
 		}
 	}
 
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
index edcfaa25f1..dd28b16ae5 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -15,25 +15,37 @@
  */
 package org.springframework.data.repository.aot.generate;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
+import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 import javax.lang.model.element.Modifier;
 
 import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
 import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.query.Parameters;
+import org.springframework.data.repository.query.ParametersSource;
+import org.springframework.data.repository.query.ReturnedType;
 import org.springframework.javapoet.FieldSpec;
 import org.springframework.javapoet.MethodSpec;
 import org.springframework.javapoet.ParameterSpec;
 import org.springframework.javapoet.ParameterizedTypeName;
 import org.springframework.javapoet.TypeName;
 import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 
 /**
@@ -141,6 +153,26 @@ public boolean returnsVoid() {
 			return repositoryMethod.getReturnType().equals(Void.TYPE);
 		}
 
+		public boolean returnsPage() {
+			return ClassUtils.isAssignable(Page.class, repositoryMethod.getReturnType());
+		}
+
+		public boolean returnsSlice() {
+			return ClassUtils.isAssignable(Slice.class, repositoryMethod.getReturnType());
+		}
+
+		public boolean returnsCollection() {
+			return ClassUtils.isAssignable(Collection.class, repositoryMethod.getReturnType());
+		}
+
+		public boolean returnsSingleValue() {
+			return !returnsPage() && !returnsSlice() && !returnsCollection();
+		}
+
+		public boolean returnsOptionalValue() {
+			return ClassUtils.isAssignable(Optional.class, repositoryMethod.getReturnType());
+		}
+
 		@Nullable
 		public TypeName getReturnType() {
 			return returnType;
@@ -190,5 +222,11 @@ public void addField(FieldSpec fieldSpec) {
 		public Map<String, FieldSpec> getFields() {
 			return generationMetadata.getFields();
 		}
+
+		@Nullable
+		public <T> T annotationValue(Class<? extends Annotation> annotation, String attribute) {
+			AnnotationAttributes values = AnnotatedElementUtils.getMergedAnnotationAttributes(this.repositoryMethod, annotation);
+			return values != null ? (T) values.get(attribute) : null;
+		}
 	}
 }

From 8c76b40985954ddad36804738598b3c6a760c90b Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Tue, 14 Jan 2025 12:27:22 +0100
Subject: [PATCH 12/14] Reduce number of callbacks and merge context with
 metadata

---
 .../aot/generate/AotRepositoryBuilder.java    |  30 +--
 .../AotRepositoryConstructorBuilder.java      |   5 +-
 .../AotRepositoryDerivedMethodBuilder.java    |  84 --------
 .../generate/AotRepositoryMethodBuilder.java  | 184 ++++++------------
 .../AotRepositoryMethodGenerationContext.java | 178 +++++++++++++++++
 .../repository/aot/generate/CodeBlocks.java   |  52 ++---
 .../aot/generate/RepositoryContributor.java   |   9 +-
 .../RepositoryContributorUnitTests.java       |   9 +-
 8 files changed, 297 insertions(+), 254 deletions(-)
 delete mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index 5899d000e1..95d86e5bcc 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -46,10 +46,10 @@
 public class AotRepositoryBuilder {
 
 	private final RepositoryInformation repositoryInformation;
-	private final GenerationMetadata generationMetadata;
+	private final TargetAotRepositoryImplementationMetadata generationMetadata;
 
 	private Consumer<AotRepositoryConstructorBuilder> constructorBuilderCustomizer;
-	private Function<AotRepositoryMethodBuilder, Contribution> derivedMethodBuilderCustomizer;
+	private Function<AotRepositoryMethodGenerationContext, AotRepositoryMethodBuilder> methodContextFunction;
 	private RepositoryCustomizer customizer;
 
 	public static AotRepositoryBuilder forRepository(RepositoryInformation repositoryInformation) {
@@ -59,7 +59,7 @@ public static AotRepositoryBuilder forRepository(RepositoryInformation repositor
 	AotRepositoryBuilder(RepositoryInformation repositoryInformation) {
 
 		this.repositoryInformation = repositoryInformation;
-		this.generationMetadata = new GenerationMetadata(className());
+		this.generationMetadata = new TargetAotRepositoryImplementationMetadata(className());
 		this.generationMetadata.addField(FieldSpec
 				.builder(TypeName.get(Log.class), "logger", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
 				.initializer("$T.getLog($T.class)", TypeName.get(LogFactory.class), this.generationMetadata.getTargetTypeName())
@@ -95,12 +95,14 @@ public JavaFile javaFile() {
 		// start with the derived ones
 		ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), method -> {
 
-			AotRepositoryDerivedMethodBuilder derivedMethodBuilder = new AotRepositoryDerivedMethodBuilder(method,
-					repositoryInformation, generationMetadata);
-			switch (derivedMethodBuilderCustomizer.apply(derivedMethodBuilder)) {
-				case CODE -> builder.addMethod(derivedMethodBuilder.buildMethod());
-				// todo other cases, not sure which ones right now
+//			AotRepositoryDerivedMethodBuilder derivedMethodBuilder = new AotRepositoryDerivedMethodBuilder(method,
+//					repositoryInformation, generationMetadata);
+			AotRepositoryMethodGenerationContext context = new AotRepositoryMethodGenerationContext(method, repositoryInformation, generationMetadata);
+			AotRepositoryMethodBuilder methodBuilder = methodContextFunction.apply(context);
+			if(methodBuilder != null) {
+				builder.addMethod(methodBuilder.buildMethod());
 			}
+
 		}, it -> {
 
 			/*
@@ -130,8 +132,8 @@ AotRepositoryBuilder withConstructorCustomizer(Consumer<AotRepositoryConstructor
 		return this;
 	}
 
-	AotRepositoryBuilder withDerivedMethodCustomizer(Function<AotRepositoryMethodBuilder, Contribution> methodBuilder) {
-		this.derivedMethodBuilderCustomizer = methodBuilder;
+	AotRepositoryBuilder withDerivedMethodFunction(Function<AotRepositoryMethodGenerationContext, AotRepositoryMethodBuilder> methodContextFunction) {
+		this.methodContextFunction = methodContextFunction;
 		return this;
 	}
 
@@ -141,7 +143,7 @@ AotRepositoryBuilder withFileCustomizer(RepositoryCustomizer repositoryCustomize
 		return this;
 	}
 
-	GenerationMetadata getGenerationMetadata() {
+	TargetAotRepositoryImplementationMetadata getGenerationMetadata() {
 		return generationMetadata;
 	}
 
@@ -159,15 +161,15 @@ private String typeName() {
 
 	public interface RepositoryCustomizer {
 
-		void customize(RepositoryInformation repositoryInformation, GenerationMetadata metadata, TypeSpec.Builder builder);
+		void customize(RepositoryInformation repositoryInformation, TargetAotRepositoryImplementationMetadata metadata, TypeSpec.Builder builder);
 	}
 
-	public class GenerationMetadata {
+	public class TargetAotRepositoryImplementationMetadata {
 
 		private ClassName className;
 		private Map<String, FieldSpec> fields = new HashMap<>();
 
-		public GenerationMetadata(ClassName className) {
+		public TargetAotRepositoryImplementationMetadata(ClassName className) {
 			this.className = className;
 		}
 
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
index 8f89708088..2b929b6e35 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
@@ -23,6 +23,7 @@
 import javax.lang.model.element.Modifier;
 
 import org.springframework.core.ResolvableType;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.MethodSpec;
 import org.springframework.javapoet.ParameterizedTypeName;
@@ -34,13 +35,13 @@
 public class AotRepositoryConstructorBuilder {
 
 	private final RepositoryInformation repositoryInformation;
-	private final AotRepositoryBuilder.GenerationMetadata metadata;
+	private final TargetAotRepositoryImplementationMetadata metadata;
 	private final Map<String, TypeName> constructorArguments;
 
 	private ConstructorCustomizer customizer = (info, builder) -> {};
 
 	public AotRepositoryConstructorBuilder(RepositoryInformation repositoryInformation,
-			AotRepositoryBuilder.GenerationMetadata metadata) {
+			TargetAotRepositoryImplementationMetadata metadata) {
 
 		this.repositoryInformation = repositoryInformation;
 		this.metadata = metadata;
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
deleted file mode 100644
index e08b60beb8..0000000000
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryDerivedMethodBuilder.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 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.springframework.data.repository.aot.generate;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-
-import org.springframework.core.MethodParameter;
-import org.springframework.core.ResolvableType;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.GenerationMetadata;
-import org.springframework.data.repository.core.RepositoryInformation;
-import org.springframework.javapoet.ParameterizedTypeName;
-import org.springframework.javapoet.TypeName;
-
-/**
- * @author Christoph Strobl
- */
-public class AotRepositoryDerivedMethodBuilder extends AotRepositoryMethodBuilder {
-
-	public AotRepositoryDerivedMethodBuilder(Method method, RepositoryInformation repositoryInformation,
-			GenerationMetadata metadata) {
-
-		super(method, repositoryInformation, metadata);
-
-		initReturnType(method, repositoryInformation);
-		initParameters(method, repositoryInformation);
-	}
-
-	private void initParameters(Method method, RepositoryInformation repositoryInformation) {
-
-		ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
-		if (method.getParameterCount() > 0) {
-			int index = 0;
-			for (Parameter parameter : method.getParameters()) {
-
-				ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(new MethodParameter(method, index),
-						repositoryInterface);
-
-				TypeName parameterType = TypeName.get(resolvableParameterType.resolve());
-				if (resolvableParameterType.hasGenerics()) {
-					parameterType = ParameterizedTypeName.get(resolvableParameterType.resolve(),
-							resolvableParameterType.resolveGenerics());
-				}
-				addParameter(parameter.getName(), parameterType);
-				index++;
-			}
-
-		}
-	}
-
-	private void initReturnType(Method method, RepositoryInformation repositoryInformation) {
-
-		ResolvableType returnType = ResolvableType.forMethodReturnType(method,
-				repositoryInformation.getRepositoryInterface());
-
-		TypeName returnTypeName = TypeName.get(returnType.resolve());
-		TypeName actualReturnTypeName = null;
-		if (returnType.hasGenerics()) {
-			Class<?>[] generics = returnType.resolveGenerics();
-			returnTypeName = ParameterizedTypeName.get(returnType.resolve(), generics);
-
-			if(generics.length == 1) {
-				actualReturnTypeName = TypeName.get(generics[0]);
-			}
-		}
-
-		setReturnType(returnTypeName, actualReturnTypeName);
-	}
-
-
-}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
index dd28b16ae5..0f313b7c5a 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -15,37 +15,23 @@
  */
 package org.springframework.data.repository.aot.generate;
 
-import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
-import java.util.Collection;
+import java.lang.reflect.Parameter;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 import javax.lang.model.element.Modifier;
 
+import org.springframework.core.MethodParameter;
 import org.springframework.core.ResolvableType;
-import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.data.domain.Limit;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Slice;
-import org.springframework.data.domain.Sort;
 import org.springframework.data.repository.core.RepositoryInformation;
-import org.springframework.data.repository.query.Parameters;
-import org.springframework.data.repository.query.ParametersSource;
-import org.springframework.data.repository.query.ReturnedType;
-import org.springframework.javapoet.FieldSpec;
 import org.springframework.javapoet.MethodSpec;
 import org.springframework.javapoet.ParameterSpec;
 import org.springframework.javapoet.ParameterizedTypeName;
 import org.springframework.javapoet.TypeName;
 import org.springframework.lang.Nullable;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 
 /**
@@ -53,20 +39,15 @@
  */
 public class AotRepositoryMethodBuilder {
 
-	private final Method method;
-	private final RepositoryInformation repositoryInformation;
-	private final MethodGenerationMetadata metadata;
+	private final AotRepositoryMethodGenerationContext context;
 
-	private RepositoryMethodCustomizer customizer = (info, md, builder) -> {};
-	private CodeBlocks codeBlocks;
+	private RepositoryMethodCustomizer customizer = (context, body) -> {};
 
-	public AotRepositoryMethodBuilder(Method method, RepositoryInformation repositoryInformation,
-			AotRepositoryBuilder.GenerationMetadata metadata) {
+	public AotRepositoryMethodBuilder(AotRepositoryMethodGenerationContext context) {
 
-		this.method = method;
-		this.repositoryInformation = repositoryInformation;
-		this.metadata = new MethodGenerationMetadata(metadata, method);
-		this.codeBlocks = new CodeBlocks(metadata);
+		this.context = context;
+		initReturnType(context.getMethod(), context.getRepositoryInformation());
+		initParameters(context.getMethod(), context.getRepositoryInformation());
 	}
 
 	public void addParameter(String parameterName, Class<?> type) {
@@ -84,61 +65,89 @@ public void addParameter(String parameterName, TypeName type) {
 	}
 
 	public void addParameter(ParameterSpec parameter) {
-		this.metadata.methodArguments.put(parameter.name, parameter);
+		this.context.addParameter(parameter);
 	}
 
 	public void setReturnType(@Nullable TypeName returnType, @Nullable TypeName actualReturnType) {
-		this.metadata.returnType = returnType;
-		this.metadata.actualReturnType = actualReturnType;
+		this.context.getTargetMethodMetadata().returnType = returnType;
+		this.context.getTargetMethodMetadata().actualReturnType = actualReturnType;
 	}
 
-	public void customize(RepositoryMethodCustomizer customizer) {
+	public AotRepositoryMethodBuilder customize(RepositoryMethodCustomizer customizer) {
 		this.customizer = customizer;
+		return this;
 	}
 
 	MethodSpec buildMethod() {
 
-		MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getName()).addModifiers(Modifier.PUBLIC);
-		if (!metadata.returnsVoid()) {
-			builder.returns(metadata.getReturnType());
+		MethodSpec.Builder builder = MethodSpec.methodBuilder(context.getMethod().getName()).addModifiers(Modifier.PUBLIC);
+		if (!context.returnsVoid()) {
+			builder.returns(context.getReturnType());
 		}
-		builder.addJavadoc("AOT generated implementation of {@link $T#$L($L)}.", method.getDeclaringClass(),
-				method.getName(), StringUtils.collectionToCommaDelimitedString(
-						metadata.methodArguments.values().stream().map(it -> it.type.toString()).collect(Collectors.toList())));
-		metadata.methodArguments.forEach((name, spec) -> builder.addParameter(spec));
-		customizer.customize(repositoryInformation, metadata, builder);
+		builder.addJavadoc("AOT generated implementation of {@link $T#$L($L)}.", context.getMethod().getDeclaringClass(),
+				context.getMethod().getName(),
+				StringUtils.collectionToCommaDelimitedString(context.getTargetMethodMetadata().methodArguments.values().stream()
+						.map(it -> it.type.toString()).collect(Collectors.toList())));
+		context.getTargetMethodMetadata().methodArguments.forEach((name, spec) -> builder.addParameter(spec));
+		customizer.customize(context, builder);
 		return builder.build();
 	}
 
-	public CodeBlocks codeBlocks() {
-		return codeBlocks;
+	private void initParameters(Method method, RepositoryInformation repositoryInformation) {
+
+		ResolvableType repositoryInterface = ResolvableType.forClass(repositoryInformation.getRepositoryInterface());
+		if (method.getParameterCount() > 0) {
+			int index = 0;
+			for (Parameter parameter : method.getParameters()) {
+
+				ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(new MethodParameter(method, index),
+						repositoryInterface);
+
+				TypeName parameterType = TypeName.get(resolvableParameterType.resolve());
+				if (resolvableParameterType.hasGenerics()) {
+					parameterType = ParameterizedTypeName.get(resolvableParameterType.resolve(),
+							resolvableParameterType.resolveGenerics());
+				}
+				addParameter(parameter.getName(), parameterType);
+				index++;
+			}
+
+		}
 	}
 
-	public interface RepositoryMethodCustomizer {
+	private void initReturnType(Method method, RepositoryInformation repositoryInformation) {
+
+		ResolvableType returnType = ResolvableType.forMethodReturnType(method,
+				repositoryInformation.getRepositoryInterface());
+
+		TypeName returnTypeName = TypeName.get(returnType.resolve());
+		TypeName actualReturnTypeName = null;
+		if (returnType.hasGenerics()) {
+			Class<?>[] generics = returnType.resolveGenerics();
+			returnTypeName = ParameterizedTypeName.get(returnType.resolve(), generics);
+
+			if (generics.length == 1) {
+				actualReturnTypeName = TypeName.get(generics[0]);
+			}
+		}
 
-		void customize(RepositoryInformation repositoryInformation, MethodGenerationMetadata metadata,
-				MethodSpec.Builder builder);
+		setReturnType(returnTypeName, actualReturnTypeName);
 	}
 
-	public static class MethodGenerationMetadata {
+	public interface RepositoryMethodCustomizer {
+		void customize(AotRepositoryMethodGenerationContext context, MethodSpec.Builder builder);
+	}
+
+	public static class TargetAotRepositoryMethodImplementationMetadata {
 
-		private final AotRepositoryBuilder.GenerationMetadata generationMetadata;
-		private final Method repositoryMethod;
 		private final Map<String, ParameterSpec> methodArguments;
 		@Nullable public TypeName actualReturnType;
 		@Nullable private TypeName returnType;
 
-		public MethodGenerationMetadata(AotRepositoryBuilder.GenerationMetadata generationMetadata,
-				Method repositoryMethod) {
-			this.generationMetadata = generationMetadata;
-			this.repositoryMethod = repositoryMethod;
+		public TargetAotRepositoryMethodImplementationMetadata() {
 			this.methodArguments = new LinkedHashMap<>();
 		}
 
-		public Method getRepositoryMethod() {
-			return repositoryMethod;
-		}
-
 		@Nullable
 		public String getParameterNameOf(Class<?> type) {
 			for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
@@ -149,30 +158,6 @@ public String getParameterNameOf(Class<?> type) {
 			return null;
 		}
 
-		public boolean returnsVoid() {
-			return repositoryMethod.getReturnType().equals(Void.TYPE);
-		}
-
-		public boolean returnsPage() {
-			return ClassUtils.isAssignable(Page.class, repositoryMethod.getReturnType());
-		}
-
-		public boolean returnsSlice() {
-			return ClassUtils.isAssignable(Slice.class, repositoryMethod.getReturnType());
-		}
-
-		public boolean returnsCollection() {
-			return ClassUtils.isAssignable(Collection.class, repositoryMethod.getReturnType());
-		}
-
-		public boolean returnsSingleValue() {
-			return !returnsPage() && !returnsSlice() && !returnsCollection();
-		}
-
-		public boolean returnsOptionalValue() {
-			return ClassUtils.isAssignable(Optional.class, repositoryMethod.getReturnType());
-		}
-
 		@Nullable
 		public TypeName getReturnType() {
 			return returnType;
@@ -183,50 +168,9 @@ public TypeName getActualReturnType() {
 			return actualReturnType;
 		}
 
-		@Nullable
-		public String getSortParameterName() {
-			return getParameterNameOf(Sort.class);
-		}
-
-		@Nullable
-		public String getPageableParameterName() {
-			return getParameterNameOf(Pageable.class);
-		}
-
-		@Nullable
-		public String getLimitParameterName() {
-			return getParameterNameOf(Limit.class);
-		}
-
 		public void addParameter(ParameterSpec parameterSpec) {
 			this.methodArguments.put(parameterSpec.name, parameterSpec);
 		}
 
-		@Nullable
-		public String fieldNameOf(Class<?> type) {
-			return generationMetadata.fieldNameOf(type);
-		}
-
-		public boolean hasField(String fieldName) {
-			return generationMetadata.hasField(fieldName);
-		}
-
-		public void addField(String fieldName, TypeName type, Modifier... modifiers) {
-			generationMetadata.addField(fieldName, type, modifiers);
-		}
-
-		public void addField(FieldSpec fieldSpec) {
-			generationMetadata.addField(fieldSpec);
-		}
-
-		public Map<String, FieldSpec> getFields() {
-			return generationMetadata.getFields();
-		}
-
-		@Nullable
-		public <T> T annotationValue(Class<? extends Annotation> annotation, String attribute) {
-			AnnotationAttributes values = AnnotatedElementUtils.getMergedAnnotationAttributes(this.repositoryMethod, annotation);
-			return values != null ? (T) values.get(attribute) : null;
-		}
 	}
 }
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java
new file mode 100644
index 0000000000..546a377708
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2025. 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.
+ */
+
+/*
+ * Copyright 2025 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.springframework.data.repository.aot.generate;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Optional;
+
+import javax.lang.model.element.Modifier;
+
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
+import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder.TargetAotRepositoryMethodImplementationMetadata;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.javapoet.FieldSpec;
+import org.springframework.javapoet.ParameterSpec;
+import org.springframework.javapoet.TypeName;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public class AotRepositoryMethodGenerationContext {
+
+	private final Method method;
+	private final RepositoryInformation repositoryInformation;
+	private final TargetAotRepositoryImplementationMetadata targetTypeMetadata;
+	private final TargetAotRepositoryMethodImplementationMetadata targetMethodMetadata;
+	private final CodeBlocks codeBlocks;
+
+	public AotRepositoryMethodGenerationContext(Method method, RepositoryInformation repositoryInformation,
+			TargetAotRepositoryImplementationMetadata targetTypeMetadata) {
+
+		this.method = method;
+		this.repositoryInformation = repositoryInformation;
+		this.targetTypeMetadata = targetTypeMetadata;
+		this.targetMethodMetadata = new TargetAotRepositoryMethodImplementationMetadata();
+		this.codeBlocks = new CodeBlocks(targetTypeMetadata);
+	}
+
+	public boolean hasField(String fieldName) {
+		return targetTypeMetadata.hasField(fieldName);
+	}
+
+	public void addField(String fieldName, TypeName type, Modifier... modifiers) {
+		targetTypeMetadata.addField(fieldName, type, modifiers);
+	}
+
+	public void addField(FieldSpec fieldSpec) {
+		targetTypeMetadata.addField(fieldSpec);
+	}
+
+	public String fieldNameOf(Class<?> type) {
+		return targetTypeMetadata.fieldNameOf(type);
+	}
+
+	public RepositoryInformation getRepositoryInformation() {
+		return repositoryInformation;
+	}
+
+	public Method getMethod() {
+		return method;
+	}
+
+	TargetAotRepositoryImplementationMetadata getTargetTypeMetadata() {
+		return targetTypeMetadata;
+	}
+
+	@Nullable
+	public String getParameterNameOf(Class<?> type) {
+		return targetMethodMetadata.getParameterNameOf(type);
+	}
+
+	public void addParameter(ParameterSpec parameter) {
+		this.targetMethodMetadata.addParameter(parameter);
+	}
+
+	public boolean returnsVoid() {
+		return getMethod().getReturnType().equals(Void.TYPE);
+	}
+
+	public boolean returnsPage() {
+		return ClassUtils.isAssignable(Page.class, getMethod().getReturnType());
+	}
+
+	public boolean returnsSlice() {
+		return ClassUtils.isAssignable(Slice.class, getMethod().getReturnType());
+	}
+
+	public boolean returnsCollection() {
+		return ClassUtils.isAssignable(Collection.class, getMethod().getReturnType());
+	}
+
+	public boolean returnsSingleValue() {
+		return !returnsPage() && !returnsSlice() && !returnsCollection();
+	}
+
+	public boolean returnsOptionalValue() {
+		return ClassUtils.isAssignable(Optional.class, getMethod().getReturnType());
+	}
+
+	@Nullable
+	public TypeName getActualReturnType() {
+		return targetMethodMetadata.actualReturnType;
+	}
+
+	@Nullable
+	public String getSortParameterName() {
+		return getParameterNameOf(Sort.class);
+	}
+
+	@Nullable
+	public String getPageableParameterName() {
+		return getParameterNameOf(Pageable.class);
+	}
+
+	@Nullable
+	public String getLimitParameterName() {
+		return getParameterNameOf(Limit.class);
+	}
+
+	@Nullable
+	public <T> T annotationValue(Class<? extends Annotation> annotation, String attribute) {
+		AnnotationAttributes values = AnnotatedElementUtils.getMergedAnnotationAttributes(getMethod(), annotation);
+		return values != null ? (T) values.get(attribute) : null;
+	}
+
+	@Nullable
+	public TypeName getReturnType() {
+		return targetMethodMetadata.getReturnType();
+	}
+
+	TargetAotRepositoryMethodImplementationMetadata getTargetMethodMetadata() {
+		return targetMethodMetadata;
+	}
+
+	public CodeBlocks codeBlocks() {
+		return codeBlocks;
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
index 680cf7c2db..2554a29fa2 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
@@ -1,19 +1,3 @@
-/*
- * Copyright 2025. 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.
- */
-
 /*
  * Copyright 2025 the original author or authors.
  *
@@ -21,7 +5,7 @@
  * 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
+ *      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,
@@ -32,33 +16,51 @@
 package org.springframework.data.repository.aot.generate;
 
 import org.apache.commons.logging.Log;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.GenerationMetadata;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.javapoet.CodeBlock;
+import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 
 /**
+ * Helper to write contextual pieces of code during code generation.
+ *
  * @author Christoph Strobl
- * @since 2025/01
  */
 public class CodeBlocks {
 
-	private final GenerationMetadata metadata;
+	private final TargetAotRepositoryImplementationMetadata metadata;
 
-	public CodeBlocks(GenerationMetadata metadata) {
+	public CodeBlocks(TargetAotRepositoryImplementationMetadata metadata) {
 		this.metadata = metadata;
 	}
 
-	private CodeBlock log(String level, String message) {
+	/**
+	 * @param level the log level eg. `debug`.
+	 * @param message the message to print/
+	 * @param args optional args to be applied to the message.
+	 * @return a {@link CodeBlock} containing a level guarded logging statement.
+	 */
+	private CodeBlock log(String level, String message, Object... args) {
 
 		CodeBlock.Builder builder = CodeBlock.builder();
 		builder.beginControlFlow("if($L.is$LEnabled())", metadata.fieldNameOf(Log.class), StringUtils.capitalize(level));
-		builder.addStatement("$L.$L($S)", metadata.fieldNameOf(Log.class), level, message);
+		if (ObjectUtils.isEmpty(args)) {
+			builder.addStatement("$L.$L($S)", metadata.fieldNameOf(Log.class), level, message);
+		} else {
+			builder.addStatement("$L.$L($S.formatted($L))", metadata.fieldNameOf(Log.class), level, message,
+					StringUtils.arrayToCommaDelimitedString(args));
+		}
 		builder.endControlFlow();
 		return builder.build();
 	}
 
-	public CodeBlock logDebug(String message) {
-		return log("debug", message);
+	/**
+	 * @param message the logging message.
+	 * @param args optional args to apply to the message.
+	 * @return a {@link CodeBlock} containing a debug level guarded logging statement.
+	 */
+	public CodeBlock logDebug(String message, Object... args) {
+		return log("debug", message, args);
 	}
 
 }
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index 0cd80f119e..e788b81eab 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -20,6 +20,7 @@
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.MemberCategory;
 import org.springframework.aot.hint.TypeReference;
+import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.data.repository.config.AotRepositoryContext;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.JavaFile;
@@ -47,7 +48,7 @@ public void contribute(GenerationContext generationContext) {
 		AotRepositoryBuilder builder = AotRepositoryBuilder.forRepository(repositoryInformation);
 		builder.withFileCustomizer(this::customizeFile);
 		builder.withConstructorCustomizer(this::customizeConstructor);
-		builder.withDerivedMethodCustomizer(this::customizeDerivedMethod);
+		builder.withDerivedMethodFunction(this::contributeRepositoryMethod);
 
 		JavaFile file = builder.javaFile();
 		String typeName = "%s.%s".formatted(file.packageName, file.typeSpec.name);
@@ -78,12 +79,12 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder constructorB
 
 	}
 
-	protected void customizeFile(RepositoryInformation information, AotRepositoryBuilder.GenerationMetadata metadata,
+	protected void customizeFile(RepositoryInformation information, TargetAotRepositoryImplementationMetadata metadata,
 			TypeSpec.Builder builder) {
 
 	}
 
-	protected Contribution customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
-		return Contribution.SKIP;
+	protected AotRepositoryMethodBuilder contributeRepositoryMethod(AotRepositoryMethodGenerationContext context) {
+		return null;
 	}
 }
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
index 6889e5ed97..19ab041fe7 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
@@ -35,15 +35,14 @@ void testCompile() {
 
 		DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
 		RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
-
 			@Override
-			protected Contribution customizeDerivedMethod(AotRepositoryMethodBuilder methodBuilder) {
-				methodBuilder.customize(((repositoryInformation, metadata, builder) -> {
-					if (!metadata.returnsVoid()) {
+			protected AotRepositoryMethodBuilder contributeRepositoryMethod(AotRepositoryMethodGenerationContext context) {
+
+				return new AotRepositoryMethodBuilder(context).customize(((ctx, builder) -> {
+					if (!ctx.returnsVoid()) {
 						builder.addStatement("return null");
 					}
 				}));
-				return Contribution.CODE;
 			}
 		};
 

From a11576888e2a6dd1d8a1316cbe6d34f652984958 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Wed, 15 Jan 2025 07:43:32 +0100
Subject: [PATCH 13/14] Remove code no longer needed

---
 .../aot/generate/AotRepositoryBuilder.java    | 17 ++++----
 .../repository/aot/generate/Contribution.java | 40 -------------------
 2 files changed, 10 insertions(+), 47 deletions(-)
 delete mode 100644 src/main/java/org/springframework/data/repository/aot/generate/Contribution.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index 95d86e5bcc..fc83c65a6a 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -29,6 +29,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.aot.generate.ClassNameGenerator;
+import org.springframework.aot.generate.Generated;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.ClassName;
@@ -75,6 +76,7 @@ public JavaFile javaFile() {
 		// start creating the type
 		TypeSpec.Builder builder = TypeSpec.classBuilder(this.generationMetadata.getTargetTypeName()) //
 				.addModifiers(Modifier.PUBLIC) //
+				.addAnnotation(Generated.class) //
 				.addAnnotation(Component.class) //
 				.addJavadoc("AOT generated repository implementation for {@link $T}.\n",
 						repositoryInformation.getRepositoryInterface()) //
@@ -95,11 +97,10 @@ public JavaFile javaFile() {
 		// start with the derived ones
 		ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), method -> {
 
-//			AotRepositoryDerivedMethodBuilder derivedMethodBuilder = new AotRepositoryDerivedMethodBuilder(method,
-//					repositoryInformation, generationMetadata);
-			AotRepositoryMethodGenerationContext context = new AotRepositoryMethodGenerationContext(method, repositoryInformation, generationMetadata);
+			AotRepositoryMethodGenerationContext context = new AotRepositoryMethodGenerationContext(method,
+					repositoryInformation, generationMetadata);
 			AotRepositoryMethodBuilder methodBuilder = methodContextFunction.apply(context);
-			if(methodBuilder != null) {
+			if (methodBuilder != null) {
 				builder.addMethod(methodBuilder.buildMethod());
 			}
 
@@ -110,7 +111,7 @@ the isBaseClassMethod(it) check seems to have some issues.
 			need to hard code it here
 			 */
 
-			if(ReflectionUtils.findMethod(CrudRepository.class, it.getName(), it.getParameterTypes()) != null) {
+			if (ReflectionUtils.findMethod(CrudRepository.class, it.getName(), it.getParameterTypes()) != null) {
 				return false;
 			}
 
@@ -132,7 +133,8 @@ AotRepositoryBuilder withConstructorCustomizer(Consumer<AotRepositoryConstructor
 		return this;
 	}
 
-	AotRepositoryBuilder withDerivedMethodFunction(Function<AotRepositoryMethodGenerationContext, AotRepositoryMethodBuilder> methodContextFunction) {
+	AotRepositoryBuilder withDerivedMethodFunction(
+			Function<AotRepositoryMethodGenerationContext, AotRepositoryMethodBuilder> methodContextFunction) {
 		this.methodContextFunction = methodContextFunction;
 		return this;
 	}
@@ -161,7 +163,8 @@ private String typeName() {
 
 	public interface RepositoryCustomizer {
 
-		void customize(RepositoryInformation repositoryInformation, TargetAotRepositoryImplementationMetadata metadata, TypeSpec.Builder builder);
+		void customize(RepositoryInformation repositoryInformation, TargetAotRepositoryImplementationMetadata metadata,
+				TypeSpec.Builder builder);
 	}
 
 	public class TargetAotRepositoryImplementationMetadata {
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java b/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java
deleted file mode 100644
index acf39b525d..0000000000
--- a/src/main/java/org/springframework/data/repository/aot/generate/Contribution.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2025. 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.
- */
-
-/*
- * Copyright 2025 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.springframework.data.repository.aot.generate;
-
-/**
- * @author Christoph Strobl
- * @since 2025/01
- */
-public enum Contribution {
-    CODE, SKIP
-}

From 464a761ba34c38a7a9aa8983c1c9f39b7806cf95 Mon Sep 17 00:00:00 2001
From: Christoph Strobl <christoph.strobl@broadcom.com>
Date: Wed, 15 Jan 2025 13:52:53 +0100
Subject: [PATCH 14/14] Use part tree in method context to determine
 count/delete/...

---
 .../aot/generate/AotRepositoryBuilder.java    | 63 +--------------
 .../AotRepositoryConstructorBuilder.java      |  5 +-
 .../AotRepositoryImplementationMetadata.java  | 81 +++++++++++++++++++
 .../generate/AotRepositoryMethodBuilder.java  | 44 +---------
 .../AotRepositoryMethodGenerationContext.java | 35 +++++---
 ...epositoryMethodImplementationMetadata.java | 74 +++++++++++++++++
 .../repository/aot/generate/CodeBlocks.java   |  5 +-
 .../aot/generate/RepositoryContributor.java   |  3 +-
 8 files changed, 194 insertions(+), 116 deletions(-)
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryImplementationMetadata.java
 create mode 100644 src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodImplementationMetadata.java

diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
index fc83c65a6a..f830f108b0 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
@@ -18,9 +18,6 @@
 import java.time.YearMonth;
 import java.time.ZoneId;
 import java.time.temporal.ChronoField;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -37,7 +34,6 @@
 import org.springframework.javapoet.JavaFile;
 import org.springframework.javapoet.TypeName;
 import org.springframework.javapoet.TypeSpec;
-import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ReflectionUtils;
 
@@ -47,7 +43,7 @@
 public class AotRepositoryBuilder {
 
 	private final RepositoryInformation repositoryInformation;
-	private final TargetAotRepositoryImplementationMetadata generationMetadata;
+	private final AotRepositoryImplementationMetadata generationMetadata;
 
 	private Consumer<AotRepositoryConstructorBuilder> constructorBuilderCustomizer;
 	private Function<AotRepositoryMethodGenerationContext, AotRepositoryMethodBuilder> methodContextFunction;
@@ -60,7 +56,7 @@ public static AotRepositoryBuilder forRepository(RepositoryInformation repositor
 	AotRepositoryBuilder(RepositoryInformation repositoryInformation) {
 
 		this.repositoryInformation = repositoryInformation;
-		this.generationMetadata = new TargetAotRepositoryImplementationMetadata(className());
+		this.generationMetadata = new AotRepositoryImplementationMetadata(className());
 		this.generationMetadata.addField(FieldSpec
 				.builder(TypeName.get(Log.class), "logger", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
 				.initializer("$T.getLog($T.class)", TypeName.get(LogFactory.class), this.generationMetadata.getTargetTypeName())
@@ -145,7 +141,7 @@ AotRepositoryBuilder withFileCustomizer(RepositoryCustomizer repositoryCustomize
 		return this;
 	}
 
-	TargetAotRepositoryImplementationMetadata getGenerationMetadata() {
+	AotRepositoryImplementationMetadata getGenerationMetadata() {
 		return generationMetadata;
 	}
 
@@ -163,58 +159,7 @@ private String typeName() {
 
 	public interface RepositoryCustomizer {
 
-		void customize(RepositoryInformation repositoryInformation, TargetAotRepositoryImplementationMetadata metadata,
+		void customize(RepositoryInformation repositoryInformation, AotRepositoryImplementationMetadata metadata,
 				TypeSpec.Builder builder);
 	}
-
-	public class TargetAotRepositoryImplementationMetadata {
-
-		private ClassName className;
-		private Map<String, FieldSpec> fields = new HashMap<>();
-
-		public TargetAotRepositoryImplementationMetadata(ClassName className) {
-			this.className = className;
-		}
-
-		@Nullable
-		public String fieldNameOf(Class<?> type) {
-
-			TypeName lookup = TypeName.get(type).withoutAnnotations();
-			for (Entry<String, FieldSpec> field : fields.entrySet()) {
-				if (field.getValue().type.withoutAnnotations().equals(lookup)) {
-					return field.getKey();
-				}
-			}
-
-			return null;
-		}
-
-		public ClassName getTargetTypeName() {
-			return className;
-		}
-
-		public String getTargetTypeSimpleName() {
-			return className.simpleName();
-		}
-
-		public String getTargetTypePackageName() {
-			return className.packageName();
-		}
-
-		public boolean hasField(String fieldName) {
-			return fields.containsKey(fieldName);
-		}
-
-		public void addField(String fieldName, TypeName type, Modifier... modifiers) {
-			fields.put(fieldName, FieldSpec.builder(type, fieldName, modifiers).build());
-		}
-
-		public void addField(FieldSpec fieldSpec) {
-			fields.put(fieldSpec.name, fieldSpec);
-		}
-
-		Map<String, FieldSpec> getFields() {
-			return fields;
-		}
-	}
 }
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
index 2b929b6e35..d5c6d60ce4 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
@@ -23,7 +23,6 @@
 import javax.lang.model.element.Modifier;
 
 import org.springframework.core.ResolvableType;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.MethodSpec;
 import org.springframework.javapoet.ParameterizedTypeName;
@@ -35,13 +34,13 @@
 public class AotRepositoryConstructorBuilder {
 
 	private final RepositoryInformation repositoryInformation;
-	private final TargetAotRepositoryImplementationMetadata metadata;
+	private final AotRepositoryImplementationMetadata metadata;
 	private final Map<String, TypeName> constructorArguments;
 
 	private ConstructorCustomizer customizer = (info, builder) -> {};
 
 	public AotRepositoryConstructorBuilder(RepositoryInformation repositoryInformation,
-			TargetAotRepositoryImplementationMetadata metadata) {
+			AotRepositoryImplementationMetadata metadata) {
 
 		this.repositoryInformation = repositoryInformation;
 		this.metadata = metadata;
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryImplementationMetadata.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryImplementationMetadata.java
new file mode 100644
index 0000000000..ed7dc2f2db
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryImplementationMetadata.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 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.data.repository.aot.generate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.lang.model.element.Modifier;
+
+import org.springframework.javapoet.ClassName;
+import org.springframework.javapoet.FieldSpec;
+import org.springframework.javapoet.TypeName;
+import org.springframework.lang.Nullable;
+
+/**
+ * @author Christoph Strobl
+ */
+class AotRepositoryImplementationMetadata {
+
+	private ClassName className;
+	private Map<String, FieldSpec> fields = new HashMap<>();
+
+	public AotRepositoryImplementationMetadata(ClassName className) {
+		this.className = className;
+	}
+
+	@Nullable
+	public String fieldNameOf(Class<?> type) {
+
+		TypeName lookup = TypeName.get(type).withoutAnnotations();
+		for (Entry<String, FieldSpec> field : fields.entrySet()) {
+			if (field.getValue().type.withoutAnnotations().equals(lookup)) {
+				return field.getKey();
+			}
+		}
+
+		return null;
+	}
+
+	public ClassName getTargetTypeName() {
+		return className;
+	}
+
+	public String getTargetTypeSimpleName() {
+		return className.simpleName();
+	}
+
+	public String getTargetTypePackageName() {
+		return className.packageName();
+	}
+
+	public boolean hasField(String fieldName) {
+		return fields.containsKey(fieldName);
+	}
+
+	public void addField(String fieldName, TypeName type, Modifier... modifiers) {
+		fields.put(fieldName, FieldSpec.builder(type, fieldName, modifiers).build());
+	}
+
+	public void addField(FieldSpec fieldSpec) {
+		fields.put(fieldSpec.name, fieldSpec);
+	}
+
+	Map<String, FieldSpec> getFields() {
+		return fields;
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
index 0f313b7c5a..f4ca542646 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
@@ -69,8 +69,8 @@ public void addParameter(ParameterSpec parameter) {
 	}
 
 	public void setReturnType(@Nullable TypeName returnType, @Nullable TypeName actualReturnType) {
-		this.context.getTargetMethodMetadata().returnType = returnType;
-		this.context.getTargetMethodMetadata().actualReturnType = actualReturnType;
+		this.context.getTargetMethodMetadata().setReturnType(returnType);
+		this.context.getTargetMethodMetadata().setActualReturnType(actualReturnType);
 	}
 
 	public AotRepositoryMethodBuilder customize(RepositoryMethodCustomizer customizer) {
@@ -86,9 +86,9 @@ MethodSpec buildMethod() {
 		}
 		builder.addJavadoc("AOT generated implementation of {@link $T#$L($L)}.", context.getMethod().getDeclaringClass(),
 				context.getMethod().getName(),
-				StringUtils.collectionToCommaDelimitedString(context.getTargetMethodMetadata().methodArguments.values().stream()
+				StringUtils.collectionToCommaDelimitedString(context.getTargetMethodMetadata().getMethodArguments().values().stream()
 						.map(it -> it.type.toString()).collect(Collectors.toList())));
-		context.getTargetMethodMetadata().methodArguments.forEach((name, spec) -> builder.addParameter(spec));
+		context.getTargetMethodMetadata().getMethodArguments().forEach((name, spec) -> builder.addParameter(spec));
 		customizer.customize(context, builder);
 		return builder.build();
 	}
@@ -137,40 +137,4 @@ private void initReturnType(Method method, RepositoryInformation repositoryInfor
 	public interface RepositoryMethodCustomizer {
 		void customize(AotRepositoryMethodGenerationContext context, MethodSpec.Builder builder);
 	}
-
-	public static class TargetAotRepositoryMethodImplementationMetadata {
-
-		private final Map<String, ParameterSpec> methodArguments;
-		@Nullable public TypeName actualReturnType;
-		@Nullable private TypeName returnType;
-
-		public TargetAotRepositoryMethodImplementationMetadata() {
-			this.methodArguments = new LinkedHashMap<>();
-		}
-
-		@Nullable
-		public String getParameterNameOf(Class<?> type) {
-			for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
-				if (entry.getValue().type.equals(TypeName.get(type))) {
-					return entry.getKey();
-				}
-			}
-			return null;
-		}
-
-		@Nullable
-		public TypeName getReturnType() {
-			return returnType;
-		}
-
-		@Nullable
-		public TypeName getActualReturnType() {
-			return actualReturnType;
-		}
-
-		public void addParameter(ParameterSpec parameterSpec) {
-			this.methodArguments.put(parameterSpec.name, parameterSpec);
-		}
-
-	}
 }
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java
index 546a377708..99dedcffe4 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodGenerationContext.java
@@ -45,9 +45,8 @@
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Slice;
 import org.springframework.data.domain.Sort;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
-import org.springframework.data.repository.aot.generate.AotRepositoryMethodBuilder.TargetAotRepositoryMethodImplementationMetadata;
 import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.query.parser.PartTree;
 import org.springframework.javapoet.FieldSpec;
 import org.springframework.javapoet.ParameterSpec;
 import org.springframework.javapoet.TypeName;
@@ -62,18 +61,24 @@ public class AotRepositoryMethodGenerationContext {
 
 	private final Method method;
 	private final RepositoryInformation repositoryInformation;
-	private final TargetAotRepositoryImplementationMetadata targetTypeMetadata;
-	private final TargetAotRepositoryMethodImplementationMetadata targetMethodMetadata;
+	private final AotRepositoryImplementationMetadata targetTypeMetadata;
+	private final AotRepositoryMethodImplementationMetadata targetMethodMetadata;
 	private final CodeBlocks codeBlocks;
+	@Nullable PartTree partTree;
 
 	public AotRepositoryMethodGenerationContext(Method method, RepositoryInformation repositoryInformation,
-			TargetAotRepositoryImplementationMetadata targetTypeMetadata) {
+			AotRepositoryImplementationMetadata targetTypeMetadata) {
 
 		this.method = method;
 		this.repositoryInformation = repositoryInformation;
 		this.targetTypeMetadata = targetTypeMetadata;
-		this.targetMethodMetadata = new TargetAotRepositoryMethodImplementationMetadata();
+		this.targetMethodMetadata = new AotRepositoryMethodImplementationMetadata();
 		this.codeBlocks = new CodeBlocks(targetTypeMetadata);
+		try {
+			this.partTree = new PartTree(method.getName(), repositoryInformation.getDomainType());
+		} catch (Exception e) {
+			// not a part tree quer
+		}
 	}
 
 	public boolean hasField(String fieldName) {
@@ -100,7 +105,7 @@ public Method getMethod() {
 		return method;
 	}
 
-	TargetAotRepositoryImplementationMetadata getTargetTypeMetadata() {
+	AotRepositoryImplementationMetadata getTargetTypeMetadata() {
 		return targetTypeMetadata;
 	}
 
@@ -137,9 +142,21 @@ public boolean returnsOptionalValue() {
 		return ClassUtils.isAssignable(Optional.class, getMethod().getReturnType());
 	}
 
+	public boolean isCountMethod() {
+		return partTree != null ? partTree.isCountProjection() : method.getName().startsWith("count");
+	}
+
+	public boolean isExistsMethod() {
+		return partTree != null ? partTree.isExistsProjection() : method.getName().startsWith("exists");
+	}
+
+	public boolean isDeleteMethod() {
+		return partTree != null ? partTree.isDelete() : method.getName().startsWith("delete");
+	}
+
 	@Nullable
 	public TypeName getActualReturnType() {
-		return targetMethodMetadata.actualReturnType;
+		return targetMethodMetadata.getActualReturnType();
 	}
 
 	@Nullable
@@ -168,7 +185,7 @@ public TypeName getReturnType() {
 		return targetMethodMetadata.getReturnType();
 	}
 
-	TargetAotRepositoryMethodImplementationMetadata getTargetMethodMetadata() {
+	AotRepositoryMethodImplementationMetadata getTargetMethodMetadata() {
 		return targetMethodMetadata;
 	}
 
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodImplementationMetadata.java b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodImplementationMetadata.java
new file mode 100644
index 0000000000..791c217fb1
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodImplementationMetadata.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2025 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.data.repository.aot.generate;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.springframework.javapoet.ParameterSpec;
+import org.springframework.javapoet.TypeName;
+import org.springframework.lang.Nullable;
+
+/**
+ * @author Christoph Strobl
+ */
+class AotRepositoryMethodImplementationMetadata {
+
+	private final Map<String, ParameterSpec> methodArguments;
+	@Nullable private TypeName actualReturnType;
+	@Nullable private TypeName returnType;
+
+	public AotRepositoryMethodImplementationMetadata() {
+		this.methodArguments = new LinkedHashMap<>();
+	}
+
+	@Nullable
+	public String getParameterNameOf(Class<?> type) {
+		for (Entry<String, ParameterSpec> entry : methodArguments.entrySet()) {
+			if (entry.getValue().type.equals(TypeName.get(type))) {
+				return entry.getKey();
+			}
+		}
+		return null;
+	}
+
+	@Nullable
+	public TypeName getReturnType() {
+		return returnType;
+	}
+
+	@Nullable
+	public TypeName getActualReturnType() {
+		return actualReturnType;
+	}
+
+	public void addParameter(ParameterSpec parameterSpec) {
+		this.methodArguments.put(parameterSpec.name, parameterSpec);
+	}
+
+	Map<String, ParameterSpec> getMethodArguments() {
+		return methodArguments;
+	}
+
+	void setActualReturnType(@Nullable TypeName actualReturnType) {
+		this.actualReturnType = actualReturnType;
+	}
+
+	void setReturnType(@Nullable TypeName returnType) {
+		this.returnType = returnType;
+	}
+}
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
index 2554a29fa2..742984c6a8 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/CodeBlocks.java
@@ -16,7 +16,6 @@
 package org.springframework.data.repository.aot.generate;
 
 import org.apache.commons.logging.Log;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.javapoet.CodeBlock;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
@@ -28,9 +27,9 @@
  */
 public class CodeBlocks {
 
-	private final TargetAotRepositoryImplementationMetadata metadata;
+	private final AotRepositoryImplementationMetadata metadata;
 
-	public CodeBlocks(TargetAotRepositoryImplementationMetadata metadata) {
+	public CodeBlocks(AotRepositoryImplementationMetadata metadata) {
 		this.metadata = metadata;
 	}
 
diff --git a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
index e788b81eab..6bcdb34464 100644
--- a/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
+++ b/src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
@@ -20,7 +20,6 @@
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.MemberCategory;
 import org.springframework.aot.hint.TypeReference;
-import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.TargetAotRepositoryImplementationMetadata;
 import org.springframework.data.repository.config.AotRepositoryContext;
 import org.springframework.data.repository.core.RepositoryInformation;
 import org.springframework.javapoet.JavaFile;
@@ -79,7 +78,7 @@ protected void customizeConstructor(AotRepositoryConstructorBuilder constructorB
 
 	}
 
-	protected void customizeFile(RepositoryInformation information, TargetAotRepositoryImplementationMetadata metadata,
+	protected void customizeFile(RepositoryInformation information, AotRepositoryImplementationMetadata metadata,
 			TypeSpec.Builder builder) {
 
 	}