Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/main/java/org/springframework/data/core/CustomCollections.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand All @@ -57,6 +58,7 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.data.util.TypeCollector.TypeCollectorFilters;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
Expand Down Expand Up @@ -648,4 +650,28 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {

}

/**
* {@link TypeCollectorFilters} to exclude custom collection and map types from AOT type introspection while still
* considering their component types.
*
* @since 4.2
*/
public static class AotTypeCollectorFilters implements TypeCollectorFilters {

private static final Predicate<Class<?>> CUSTOM_COLLECTION_TYPE_FILTER = type -> !isCollection(type) && !isMap(type)
&& !isCustomCollectionLibraryType(type);

@Override
public Predicate<Class<?>> classPredicate() {
return CUSTOM_COLLECTION_TYPE_FILTER;
}

private static boolean isCustomCollectionLibraryType(Class<?> type) {

String packageName = type.getPackageName();

return packageName.startsWith("io.vavr") || packageName.startsWith("org.eclipse.collections");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.aot.AotContext;
import org.springframework.data.aot.AotTypeConfiguration;
import org.springframework.data.core.CustomCollections;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
Expand Down Expand Up @@ -234,6 +237,36 @@ protected void configureTypeContributions(AotRepositoryContext repositoryContext
repositoryContext.getResolvedAnnotations().stream()
.filter(RepositoryRegistrationAotProcessor::isSpringDataManagedAnnotation).map(MergedAnnotation::getType)
.forEach(it -> contributeType(it, generationContext));

registerCustomCollectionTypeHints(information, generationContext);
}

private void registerCustomCollectionTypeHints(RepositoryInformation information,
GenerationContext generationContext) {

Class<?> repositoryInterface = information.getRepositoryInterface();

information.getQueryMethods().forEach(method -> {

contributeCustomCollectionTypeIfNecessary(
ResolvableType.forMethodReturnType(method, repositoryInterface), generationContext);

for (int i = 0; i < method.getParameterCount(); i++) {
contributeCustomCollectionTypeIfNecessary(
ResolvableType.forMethodParameter(MethodParameter.forParameter(method.getParameters()[i])
.withContainingClass(repositoryInterface)),
generationContext);
}
});
}

private void contributeCustomCollectionTypeIfNecessary(ResolvableType type, GenerationContext generationContext) {

Class<?> rawType = type.toClass();

if (CustomCollections.isCollection(rawType) || CustomCollections.isMap(rawType)) {
contributeType(rawType, generationContext);
}
}

/**
Expand Down
30 changes: 24 additions & 6 deletions src/main/java/org/springframework/data/util/TypeCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

import org.springframework.beans.factory.aot.AotServices;
import org.springframework.core.ResolvableType;
import org.springframework.data.core.CustomCollections;
import org.springframework.lang.Contract;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
Expand Down Expand Up @@ -215,20 +216,37 @@ private void processType(ResolvableType type, InspectionCache cache, Consumer<Re
}

Set<Type> additionalTypes = new LinkedHashSet<>();
additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
additionalTypes.addAll(visitConstructorsOfType(type));
additionalTypes.addAll(visitMethodsOfType(type));
additionalTypes.addAll(visitFieldsOfType(type));

if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
if (typeFilter.test(type.toClass())) {

additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
additionalTypes.addAll(visitConstructorsOfType(type));
additionalTypes.addAll(visitMethodsOfType(type));
additionalTypes.addAll(visitFieldsOfType(type));

if (!ObjectUtils.isEmpty(type.toClass().getDeclaredClasses())) {
additionalTypes.addAll(Arrays.asList(type.toClass().getDeclaredClasses()));
}
}
else if (isCustomCollectionOrMapType(type.toClass())) {

for (ResolvableType generic : type.getGenerics()) {
additionalTypes.add(generic.getType());
}
}
else {
additionalTypes.addAll(TypeUtils.resolveTypesInSignature(type));
}

for (Type discoveredType : additionalTypes) {
processType(ResolvableType.forType(discoveredType, type), cache, callback);
}
}

private static boolean isCustomCollectionOrMapType(Class<?> type) {
return CustomCollections.isCollection(type) || CustomCollections.isMap(type);
}

private Set<Type> visitConstructorsOfType(ResolvableType type) {

if (!typeFilter.test(type.toClass())) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/META-INF/spring/aot.factories
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.data.aot.AuditingBeanRegistrationAotProcessor

org.springframework.data.util.TypeCollector$TypeCollectorFilters=\
org.springframework.data.util.TypeCollector$DefaultTypeCollectorFilters
org.springframework.data.util.TypeCollector$DefaultTypeCollectorFilters, \
org.springframework.data.core.CustomCollections$AotTypeCollectorFilters
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

import org.junit.jupiter.api.Test;

import org.springframework.data.aot.types.EntityWithEclipseCollections;
import org.springframework.data.aot.types.EntityWithVavrCollections;
import org.springframework.data.aot.types.EntityWithVavrDomain;
import org.springframework.data.aot.types.*;
import org.springframework.data.util.TypeCollector;

Expand Down Expand Up @@ -99,4 +102,29 @@ void typeFilterReflectsAdditionalTypeFilters() {
assertThat(TypeCollector.inspect(c -> c.filterTypes(cls -> false), FieldsAndMethods.class).list()).isEmpty();
}

@Test // GH-3416
void filtersCustomCollectionLibraryTypes() {
assertThat(TypeCollector.create(tc -> {}).getTypeFilter().test(io.vavr.collection.Traversable.class)).isFalse();
assertThat(TypeCollector.create(tc -> {}).getTypeFilter().test(org.eclipse.collections.api.list.ImmutableList.class))
.isFalse();
}

@Test // GH-3416
void skipsVavrCollectionIntrospection() {
assertThat(TypeCollector.inspect(EntityWithVavrCollections.class).list())
.containsExactly(EntityWithVavrCollections.class);
}

@Test // GH-3416
void collectsDomainTypesFromVavrCollectionProperties() {
assertThat(TypeCollector.inspect(EntityWithVavrDomain.class).list()).containsExactlyInAnyOrder(
EntityWithVavrDomain.class, EntityWithVavrDomain.SamplePerson.class);
}

@Test // GH-3416
void collectsDomainTypesFromEclipseCollectionProperties() {
assertThat(TypeCollector.inspect(EntityWithEclipseCollections.class).list()).containsExactlyInAnyOrder(
EntityWithEclipseCollections.class, EntityWithEclipseCollections.SampleItem.class);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2025-present 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.aot.sample;

import org.eclipse.collections.api.list.ImmutableList;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.config.EnableRepositories;

import io.vavr.collection.List;
import io.vavr.collection.Seq;

/**
* @author hippi345
*/
@Configuration
@EnableRepositories(considerNestedRepositories = true, includeFilters = {
@Filter(type = FilterType.REGEX, pattern = ".*CustomCollectionReturnTypesRepository") })
public class ConfigWithCustomCollectionReturnTypes {

public interface CustomCollectionReturnTypesRepository extends PagingAndSortingRepository<Person, Long> {

List<Person> findByFirstname(String firstname);

Seq<Person> findAllByLastname(String lastname);

ImmutableList<Person> findAllEclipse();
}

public static class Person {
String firstname;
String lastname;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022-present 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.aot.types;

import org.eclipse.collections.api.list.ImmutableList;

/**
* @author hippi345
*/
public class EntityWithEclipseCollections {

ImmutableList<SampleItem> items;

public static class SampleItem {
String label;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022-present 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.aot.types;

import io.vavr.collection.List;
import io.vavr.collection.Map;

/**
* @author hippi345
*/
public class EntityWithVavrCollections {

List<String> names;
Map<String, Integer> scores;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022-present 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.aot.types;

import io.vavr.collection.List;

/**
* @author hippi345
*/
public class EntityWithVavrDomain {

List<SamplePerson> people;

public static class SamplePerson {
String name;
}
}
Loading
Loading