DATACMNS-764 - Java 8 polishing.

Use Streamable in configuration APIs for more efficient, lazy traversal of source lists. Introduced SelectionSet.of(…) to move away from constructors. Avoid the use of null in SelectionSet.

Original pull request: #201.
This commit is contained in:
Oliver Gierke
2017-06-09 13:46:17 +02:00
parent eec63cb11d
commit 9462a5ba47
9 changed files with 118 additions and 132 deletions

View File

@@ -18,7 +18,6 @@ package org.springframework.data.repository.config;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -37,6 +36,7 @@ import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -113,7 +113,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages()
*/
public Iterable<String> getBasePackages() {
public Streamable<String> getBasePackages() {
String[] value = attributes.getStringArray("value");
String[] basePackages = attributes.getStringArray(BASE_PACKAGES);
@@ -121,19 +121,20 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
// Default configuration - return package of annotated class
if (value.length == 0 && basePackages.length == 0 && basePackageClasses.length == 0) {
String className = configMetadata.getClassName();
return Collections.singleton(ClassUtils.getPackageName(className));
return Streamable.of(ClassUtils.getPackageName(className));
}
Set<String> packages = new HashSet<>();
packages.addAll(Arrays.asList(value));
packages.addAll(Arrays.asList(basePackages));
for (Class<?> typeName : basePackageClasses) {
packages.add(ClassUtils.getPackageName(typeName));
}
Arrays.stream(basePackageClasses)//
.map(ClassUtils::getPackageName)//
.forEach(it -> packages.add(it));
return packages;
return Streamable.of(packages);
}
/*
@@ -182,20 +183,15 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
* @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters()
*/
@Override
public Iterable<TypeFilter> getExcludeFilters() {
public Streamable<TypeFilter> getExcludeFilters() {
return parseFilters("excludeFilters");
}
private Set<TypeFilter> parseFilters(String attributeName) {
private Streamable<TypeFilter> parseFilters(String attributeName) {
Set<TypeFilter> result = new HashSet<>();
AnnotationAttributes[] filters = attributes.getAnnotationArray(attributeName);
for (AnnotationAttributes filter : filters) {
result.addAll(typeFiltersFor(filter));
}
return result;
return Streamable.of(() -> Arrays.stream(filters).flatMap(it -> typeFiltersFor(it).stream()));
}
/**

View File

@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.config;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@@ -37,6 +33,7 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
/**
@@ -52,6 +49,7 @@ import org.springframework.util.Assert;
public class CustomRepositoryImplementationDetector {
private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class";
private static final String AMBIGUOUS_CUSTOM_IMPLEMENTATIONS = "Ambiguous custom implementations detected! Found %s but expected a single implementation!";
private final @NonNull MetadataReaderFactory metadataReaderFactory;
private final @NonNull Environment environment;
@@ -94,12 +92,10 @@ public class CustomRepositoryImplementationDetector {
Set<BeanDefinition> definitions = findCandidateBeanDefinitions(className, basePackages, excludeFilters);
SelectionSet<BeanDefinition> selection = new SelectionSet<>( //
definitions, //
c -> c.isEmpty() ? null : throwAmbiguousCustomImplementationException(c) //
).filterIfNecessary(bd -> beanName != null && beanName.equals(beanNameGenerator.apply(bd)));
return Optional.ofNullable((AbstractBeanDefinition) selection.uniqueResult());
return SelectionSet //
.of(definitions, c -> c.isEmpty() ? Optional.empty() : throwAmbiguousCustomImplementationException(c)) //
.filterIfNecessary(bd -> beanName != null && beanName.equals(beanNameGenerator.apply(bd)))//
.uniqueResult().map(it -> AbstractBeanDefinition.class.cast(it));
}
Set<BeanDefinition> findCandidateBeanDefinitions(String className, Iterable<String> basePackages,
@@ -116,31 +112,20 @@ public class CustomRepositoryImplementationDetector {
provider.setMetadataReaderFactory(metadataReaderFactory);
provider.addIncludeFilter(new RegexPatternTypeFilter(pattern));
for (TypeFilter excludeFilter : excludeFilters) {
provider.addExcludeFilter(excludeFilter);
}
excludeFilters.forEach(it -> provider.addExcludeFilter(it));
Set<BeanDefinition> definitions = new HashSet<>();
for (String basePackage : basePackages) {
definitions.addAll(provider.findCandidateComponents(basePackage));
}
return definitions;
return Streamable.of(basePackages).stream()//
.flatMap(it -> provider.findCandidateComponents(it).stream())//
.collect(Collectors.toSet());
}
private static AbstractBeanDefinition throwAmbiguousCustomImplementationException(
private static Optional<BeanDefinition> throwAmbiguousCustomImplementationException(
Collection<BeanDefinition> definitions) {
List<String> implementationClassNames = new ArrayList<String>();
for (BeanDefinition bean : definitions) {
implementationClassNames.add(bean.getBeanClassName());
}
String implementationNames = definitions.stream()//
.map(BeanDefinition::getBeanClassName)//
.collect(Collectors.joining(", "));
throw new IllegalStateException(
String.format("Ambiguous custom implementations detected! Found %s but expected a single implementation!", //
definitions.stream()//
.map(BeanDefinition::getBeanClassName)//
.collect(Collectors.joining(", "))));
throw new IllegalStateException(String.format(AMBIGUOUS_CUSTOM_IMPLEMENTATIONS, implementationNames));
}
}

View File

@@ -15,10 +15,11 @@
*/
package org.springframework.data.repository.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
@@ -32,16 +33,13 @@ import org.springframework.util.ClassUtils;
* @author Oliver Gierke
* @author Jens Schauder
*/
@RequiredArgsConstructor
public class RepositoryBeanNameGenerator {
private static final BeanNameGenerator DELEGATE = new AnnotationBeanNameGenerator();
private final ClassLoader beanClassLoader;
public RepositoryBeanNameGenerator(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
}
/**
* Generate a bean name for the given bean definition.
*
@@ -66,11 +64,13 @@ public class RepositoryBeanNameGenerator {
private Class<?> getRepositoryInterfaceFrom(BeanDefinition beanDefinition) {
if (beanDefinition instanceof ScannedGenericBeanDefinition) {
try {
return ((ScannedGenericBeanDefinition) beanDefinition).resolveBeanClass(beanClassLoader);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Could not resolve bean class.", e);
}
} else {
return getRepositoryInterfaceFromFactory(beanDefinition);
}
@@ -81,8 +81,11 @@ public class RepositoryBeanNameGenerator {
Object value = beanDefinition.getConstructorArgumentValues().getArgumentValue(0, Class.class).getValue();
if (value instanceof Class<?>) {
return (Class<?>) value;
} else {
try {
return ClassUtils.forName(value.toString(), beanClassLoader);
} catch (Exception o_O) {

View File

@@ -15,13 +15,13 @@
*/
package org.springframework.data.repository.config;
import java.util.Collection;
import java.util.Optional;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.util.Streamable;
/**
* Interface containing the configurable options for the Spring Data repository subsystem.
@@ -46,7 +46,7 @@ public interface RepositoryConfigurationSource {
*
* @return must not be {@literal null}.
*/
Iterable<String> getBasePackages();
Streamable<String> getBasePackages();
/**
* Returns the {@link QueryLookupStrategy.Key} to define how query methods shall be resolved.
@@ -89,7 +89,7 @@ public interface RepositoryConfigurationSource {
* @param loader
* @return
*/
Collection<BeanDefinition> getCandidates(ResourceLoader loader);
Streamable<BeanDefinition> getCandidates(ResourceLoader loader);
/**
* Returns the value for the {@link String} attribute with the given name. The name is expected to be handed in
@@ -115,12 +115,12 @@ public interface RepositoryConfigurationSource {
*
* @return must not be {@literal null}.
*/
Iterable<TypeFilter> getExcludeFilters();
Streamable<TypeFilter> getExcludeFilters();
/**
* Returns a name for the beanDefinition.
*
* @param beanDefinition Must not be {@literal null}.
* @param beanDefinition must not be {@literal null}.
* @return
* @since 2.0
*/

View File

@@ -15,15 +15,13 @@
*/
package org.springframework.data.repository.config;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
/**
@@ -58,27 +56,20 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfiguration#getCandidates(org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getCandidates(org.springframework.core.io.ResourceLoader)
*/
public Collection<BeanDefinition> getCandidates(ResourceLoader loader) {
@Override
public Streamable<BeanDefinition> getCandidates(ResourceLoader loader) {
RepositoryComponentProvider scanner = new RepositoryComponentProvider(getIncludeFilters());
scanner.setConsiderNestedRepositoryInterfaces(shouldConsiderNestedRepositories());
scanner.setResourceLoader(loader);
scanner.setEnvironment(environment);
for (TypeFilter filter : getExcludeFilters()) {
scanner.addExcludeFilter(filter);
}
getExcludeFilters().forEach(it -> scanner.addExcludeFilter(it));
Set<BeanDefinition> result = new HashSet<>();
for (String basePackage : getBasePackages()) {
Set<BeanDefinition> candidate = scanner.findCandidateComponents(basePackage);
result.addAll(candidate);
}
return result;
return Streamable.of(() -> getBasePackages().stream()//
.flatMap(it -> scanner.findCandidateComponents(it).stream()));
}
/**
@@ -87,8 +78,18 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository
*
* @return must not be {@literal null}.
*/
public Iterable<TypeFilter> getExcludeFilters() {
return Collections.emptySet();
@Override
public Streamable<TypeFilter> getExcludeFilters() {
return Streamable.empty();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBeanNameGenerator()
*/
@Override
public String generateBeanName(BeanDefinition beanDefinition) {
return beanNameGenerator.generateBeanName(beanDefinition);
}
/**
@@ -110,13 +111,4 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository
public boolean shouldConsiderNestedRepositories() {
return false;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBeanNameGenerator()
*/
@Override
public String generateBeanName(BeanDefinition beanDefinition) {
return beanNameGenerator.generateBeanName(beanDefinition);
}
}

View File

@@ -15,8 +15,10 @@
*/
package org.springframework.data.repository.config;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -26,41 +28,35 @@ import java.util.stream.Collectors;
* filters are ignored.
*
* @author Jens Schauder
* @author Oliver Gierke
* @since 2.0
*/
@RequiredArgsConstructor(staticName = "of")
class SelectionSet<T> {
private final Collection<T> collection;
private final Function<Collection<T>, T> fallback;
private final Function<Collection<T>, Optional<T>> fallback;
/**
* creates a {@link SelectionSet} with a default fallback of {@literal null}, when no element is found and an
* {@link IllegalStateException} when no element is found.
*/
SelectionSet(Collection<T> collection) {
this(collection, defaultFallback());
static <T> SelectionSet<T> of(Collection<T> collection) {
return new SelectionSet<>(collection, defaultFallback());
}
/**
* @param collection The collection from which a unique element will get picked.
* @param fallback Will get called once {@literal #uniqueResult} gets called if no unique result can be determined.
*/
SelectionSet(Collection<T> collection, Function<Collection<T>, T> fallback) {
this.collection = collection;
this.fallback = fallback;
}
/**
* If this <code>SelectionSet</code> contains exactly one element it gets returned. If no unique result can
* be identified the fallback function passed in at the constructor gets called and its return value becomes
* the return value of this method.
* If this {@code SelectionSet} contains exactly one element it gets returned. If no unique result can be identified
* the fallback function passed in at the constructor gets called and its return value becomes the return value of
* this method.
*
* @return a unique result, or the result of the callback provided in the constructor.
*/
T uniqueResult() {
T uniqueResult = findUniqueResult();
Optional<T> uniqueResult() {
return uniqueResult != null ? uniqueResult : fallback.apply(collection);
Optional<T> uniqueResult = findUniqueResult();
return uniqueResult.isPresent() ? uniqueResult : fallback.apply(collection);
}
/**
@@ -70,26 +66,22 @@ class SelectionSet<T> {
*/
SelectionSet<T> filterIfNecessary(Predicate<T> predicate) {
if (findUniqueResult() != null) {
return this;
}
List<T> fillteredList = collection.stream().filter(predicate).collect(Collectors.toList());
return new SelectionSet<T>(fillteredList, fallback);
return findUniqueResult().map(it -> this).orElseGet(
() -> new SelectionSet<T>(collection.stream().filter(predicate).collect(Collectors.toList()), fallback));
}
private static <S> Function<Collection<S>, S> defaultFallback() {
private static <S> Function<Collection<S>, Optional<S>> defaultFallback() {
return c -> {
if (c.isEmpty()) {
return null;
return Optional.empty();
} else {
throw new IllegalStateException("More then one element in collection.");
}
};
}
private T findUniqueResult() {
return collection.size() == 1 ? collection.iterator().next() : null;
private Optional<T> findUniqueResult() {
return Optional.ofNullable(collection.size() == 1 ? collection.iterator().next() : null);
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.repository.config;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
@@ -26,6 +25,7 @@ import org.springframework.data.config.TypeFilterParser;
import org.springframework.data.config.TypeFilterParser.Type;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.util.ParsingUtils;
import org.springframework.data.util.Streamable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
@@ -89,10 +89,11 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBasePackages()
*/
public Iterable<String> getBasePackages() {
public Streamable<String> getBasePackages() {
String attribute = element.getAttribute(BASE_PACKAGE);
return Arrays.asList(StringUtils.delimitedListToStringArray(attribute, ",", " "));
return Streamable.of(StringUtils.delimitedListToStringArray(attribute, ",", " "));
}
/*
@@ -125,8 +126,8 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
* @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#getExcludeFilters()
*/
@Override
public Iterable<TypeFilter> getExcludeFilters() {
return excludeFilters;
public Streamable<TypeFilter> getExcludeFilters() {
return Streamable.of(excludeFilters);
}
/*