DATACMNS-810 - Refactor Query by Example API.

Split Example and ExampleSpec to create reusable components. Refactor builder pattern to a fluent API that creates immutable instances. Split user and framework API, Example and ExampleSpec are user API, created ExampleSpecAccessor for modules to access example spec configuration. Create static methods in GenericPropertyMatchers to ease creation of matchers in a readable style. Convert PropertySpecifier to inner class  and move PropertySpecifiers to ExampleSpec.

Related tickets: DATAJPA-218, DATAMONGO-1245.
Original pull request: #153.
This commit is contained in:
Mark Paluch
2016-02-25 09:31:24 +01:00
committed by Oliver Gierke
parent 4092f0716f
commit b0ce7009e8
12 changed files with 2792 additions and 907 deletions

View File

@@ -15,480 +15,126 @@
*/
package org.springframework.data.domain;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.data.domain.Example.NullHandler;
import org.springframework.data.domain.PropertySpecifier.NoOpPropertyValueTransformer;
import org.springframework.data.domain.PropertySpecifier.PropertyValueTransformer;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Support for query by example (QBE).
* Support for query by example (QBE). An {@link Example} takes a {@code probe} to define the example. Matching options
* and type safety can be tuned using {@link ExampleSpec}. {@link Example} uses {@link ExampleSpec#untyped()}
* {@link ExampleSpec} by default.
* <p>
* This class is immutable.
*
* @author Christoph Strobl
* @author Mark Paluch
* @param <T>
* @since 1.12
*/
public class Example<T> {
private final T sampleObject;
private NullHandler nullHandler = NullHandler.IGNORE;
private StringMatcher defaultStringMatcher = StringMatcher.DEFAULT;
private PropertySpecifiers propertySpecifiers = new PropertySpecifiers();
private Set<String> ignoredPaths = new LinkedHashSet<String>();
private boolean ignoreCase = false;
private final T probe;
private final ExampleSpec exampleSpec;
/**
* Create a new {@link Example} including all non-null properties by default.
*
* @param sampleObject The example to use. Must not be {@literal null}.
*
* @param probe The probe to use. Must not be {@literal null}.
*/
public Example(T sampleObject) {
@SuppressWarnings("unchecked")
private Example(T probe) {
Assert.notNull(sampleObject, "SampleObject must not be null!");
this.sampleObject = sampleObject;
Assert.notNull(probe, "Probe must not be null!");
this.probe = probe;
this.exampleSpec = ExampleSpec.untyped();
}
/**
* Create a new {@link Example} including all non-null properties by default.
*
* @param probe The probe to use. Must not be {@literal null}.
* @param exampleSpec The example specification to use. Must not be {@literal null}.
*/
private Example(T probe, ExampleSpec exampleSpec) {
Assert.notNull(probe, "Probe must not be null!");
this.probe = probe;
this.exampleSpec = exampleSpec;
}
/**
* Create a new {@link Example} including all non-null properties by default using an untyped {@link ExampleSpec}.
*
* @param probe must not be {@literal null}.
* @return
*/
public static <T> Example<T> of(T probe) {
return new Example<T>(probe);
}
/**
* Create a new {@link Example} with a configured untyped {@link ExampleSpec}.
*
* @param probe must not be {@literal null}.
* @param exampleSpec must not be {@literal null}.
* @return
*/
public static <T> Example<T> of(T probe, ExampleSpec exampleSpec) {
return new Example<T>(probe, exampleSpec);
}
/**
* Create a new {@link Example} with a configured {@link TypedExampleSpec}.
*
* @param probe must not be {@literal null}.
* @param exampleSpec must not be {@literal null}.
* @return
*/
public static <T, S extends T> Example<S> of(S probe, TypedExampleSpec<T> exampleSpec) {
return new Example<S>(probe, exampleSpec);
}
/**
* Get the example used.
*
*
* @return never {@literal null}.
*/
public T getSampleObject() {
return sampleObject;
public T getProbe() {
return probe;
}
/**
* Get defined null handling.
*
* @return never {@literal null}
*/
public NullHandler getNullHandler() {
return nullHandler;
}
/**
* Get defined {@link StringMatcher}.
*
* Get the {@link ExampleSpec} used.
*
* @return never {@literal null}.
*/
public StringMatcher getDefaultStringMatcher() {
return defaultStringMatcher;
public ExampleSpec getExampleSpec() {
return exampleSpec;
}
/**
* @return {@literal true} if {@link String} should be matched with ignore case option.
*/
public boolean isIngnoreCaseEnabled() {
return this.ignoreCase;
}
/**
* @param path
* @return return {@literal true} if path was set to be ignored.
*/
public boolean isIgnoredPath(String path) {
return this.ignoredPaths.contains(path);
}
/**
* @return unmodifiable {@link Set} of ignored paths.
*/
public Set<String> getIgnoredPaths() {
return Collections.unmodifiableSet(ignoredPaths);
}
/**
* @return unmodifiable {@link Collection} of {@link PropertySpecifier}s.
*/
public Collection<PropertySpecifier> getPropertySpecifiers() {
return propertySpecifiers.getSpecifiers();
}
/**
* @param path Dot-Path to property.
* @return {@literal true} in case {@link PropertySpecifier} defined for given path.
*/
public boolean hasPropertySpecifier(String path) {
return propertySpecifiers.hasSpecifierForPath(path);
}
/**
* Get the {@link PropertySpecifier} for given path. <br />
* Please check if {@link #hasPropertySpecifier(String)} to avoid running into {@literal null} values.
*
* @param path Dot-Path to property.
* @return {@literal null} when no {@link PropertySpecifier} defined for path.
*/
public PropertySpecifier getPropertySpecifier(String path) {
return propertySpecifiers.getForPath(path);
}
/**
* @return true if at least one {@link PropertySpecifier} defined.
*/
public boolean hasPropertySpecifiers() {
return this.propertySpecifiers.hasValues();
}
/**
* Get the {@link StringMatcher} for a given path or return the default one if none defined.
*
* @param path
* @return never {@literal null}.
*/
public StringMatcher getStringMatcherForPath(String path) {
if (!hasPropertySpecifier(path)) {
return getDefaultStringMatcher();
}
PropertySpecifier specifier = getPropertySpecifier(path);
return specifier.getStringMatcher() != null ? specifier.getStringMatcher() : getDefaultStringMatcher();
}
/**
* Get the ignore case flag for a given path or return the default one if none defined.
*
* @param path
* @return never {@literal null}.
*/
public boolean isIgnoreCaseForPath(String path) {
if (!hasPropertySpecifier(path)) {
return isIngnoreCaseEnabled();
}
PropertySpecifier specifier = getPropertySpecifier(path);
return specifier.getIgnoreCase() != null ? specifier.getIgnoreCase().booleanValue() : isIngnoreCaseEnabled();
}
/**
* Get the ignore case flag for a given path or return {@link NoOpPropertyValueTransformer} if none defined.
*
* @param path
* @return never {@literal null}.
*/
public PropertyValueTransformer getValueTransformerForPath(String path) {
if (!hasPropertySpecifier(path)) {
return NoOpPropertyValueTransformer.INSTANCE;
}
return getPropertySpecifier(path).getPropertyValueTransformer();
}
/**
* Get the actual type for the example used. This is usually the given class, but the original class in case of a
* Get the actual type for the probe used. This is usually the given class, but the original class in case of a
* CGLIB-generated subclass.
*
*
* @return
* @see ClassUtils#getUserClass(Class)
*/
@SuppressWarnings("unchecked")
public Class<T> getSampleType() {
return (Class<T>) ClassUtils.getUserClass(sampleObject.getClass());
public Class<T> getProbeType() {
return (Class<T>) ClassUtils.getUserClass(probe.getClass());
}
/**
* Create a new {@link Example} including all non-null properties by default.
*
* @param sampleObject must not be {@literal null}.
* Get the actual result type for the query. The result type can be different when using {@link TypedExampleSpec}.
*
* @return
* @see ClassUtils#getUserClass(Class)
*/
public static <T> Example<T> exampleOf(T sampleObject) {
return new Example<T>(sampleObject);
}
/**
* Create a new {@link Example} including all non-null properties, excluding explicitly named properties to ignore.
*
* @param sampleObject must not be {@literal null}.
* @return
*/
public static <T> Example<T> exampleOf(T value, String... ignoredProperties) {
return new Builder<T>(value).ignore(ignoredProperties).get();
}
/**
* Create new {@link Builder} for specifying {@link Example}.
*
* @param sampleObject must not be {@literal null}.
* @return
* @see Builder
*/
public static <T> Builder<T> newExampleOf(T sampleObject) {
return new Builder<T>(sampleObject);
}
/**
* Builder for specifying desired behavior of {@link Example}.
*
* @author Christoph Strobl
* @param <T>
*/
public static class Builder<T> {
private Example<T> example;
Builder(T sampleObject) {
example = new Example<T>(sampleObject);
}
/**
* Sets {@link NullHandler} used for {@link Example}.
*
* @param nullHandling
* @return
* @see Builder#nullHandling(NullHandler)
*/
public Builder<T> withNullHandler(NullHandler nullHandling) {
return handleNullValues(nullHandling);
}
/**
* Sets default {@link StringMatcher} used for {@link Example}.
*
* @param stringMatcher
* @return
* @see Builder#matchStrings(StringMatcher)
*/
public Builder<T> withStringMatcher(StringMatcher stringMatcher) {
return matchStrings(stringMatcher);
}
/**
* Adds {@link PropertySpecifier} used for {@link Example}.
*
* @param specifier
* @return
* @see Builder#specify(PropertySpecifier...)
*/
public Builder<T> withPropertySpecifier(PropertySpecifier... specifiers) {
return specify(specifiers);
}
/**
* Sets {@link NullHandler} used for {@link Example}.
*
* @param nullHandling Defaulted to {@link NullHandler#INCLUDE} in case of {@literal null}.
* @return
*/
public Builder<T> handleNullValues(NullHandler nullHandling) {
example.nullHandler = nullHandling == null ? NullHandler.IGNORE : nullHandling;
return this;
}
/**
* Sets treatment of {@literal null} values to {@link NullHandler#INCLUDE}
*
* @return
*/
public Builder<T> includeNullValues() {
return handleNullValues(NullHandler.INCLUDE);
}
/**
* Sets treatment of {@literal null} values to {@link NullHandler#IGNORE}
*
* @return
*/
public Builder<T> ignoreNullValues() {
return handleNullValues(NullHandler.IGNORE);
}
/**
* Sets the default {@link StringMatcher} used for {@link Example}.
*
* @param stringMatcher Defaulted to {@link StringMatcher#DEFAULT} in case of {@literal null}.
* @return
*/
public Builder<T> matchStrings(StringMatcher stringMatcher) {
return matchStrings(stringMatcher, example.ignoreCase);
}
/**
* Sets the default {@link StringMatcher} used for {@link Example}.
*
* @param stringMatcher Defaulted to {@link StringMatcher#DEFAULT} in case of {@literal null}.
* @param ignoreCase
* @return
*/
public Builder<T> matchStrings(StringMatcher stringMatching, boolean ignoreCase) {
example.defaultStringMatcher = stringMatching == null ? StringMatcher.DEFAULT : stringMatching;
example.ignoreCase = ignoreCase;
return this;
}
/**
* Enable case ignoring string matching.
*
* @return
*/
public Builder<T> matchStringsWithIgnoreCase() {
example.ignoreCase = true;
return this;
}
/**
* Set string matching to {@link StringMatcher#STARTING}
*
* @return
*/
public Builder<T> matchStringsStartingWith() {
return matchStrings(StringMatcher.STARTING);
}
/**
* Set string matching to {@link StringMatcher#ENDING}
*
* @return
*/
public Builder<T> matchStringsEndingWith() {
return matchStrings(StringMatcher.ENDING);
}
/**
* Set string matching to {@link StringMatcher#CONTAINING}
*
* @return
*/
public Builder<T> matchStringsContaining() {
return matchStrings(StringMatcher.CONTAINING);
}
/**
* Define specific property handling.
*
* @param specifiers
* @return
*/
public Builder<T> specify(PropertySpecifier... specifiers) {
for (PropertySpecifier specifier : specifiers) {
example.propertySpecifiers.add(specifier);
}
return this;
}
/**
* Ignore given properties.
*
* @param ignoredProperties
* @return
*/
public Builder<T> ignore(String... ignoredProperties) {
for (String ignoredProperty : ignoredProperties) {
example.ignoredPaths.add(ignoredProperty);
}
return this;
}
/**
* @return {@link Example} as defined.
*/
public Example<T> get() {
return this.example;
}
}
/**
* Null handling for creating criterion out of an {@link Example}.
*
* @author Christoph Strobl
*/
public static enum NullHandler {
INCLUDE, IGNORE
}
/**
* Match modes for treatment of {@link String} values.
*
* @author Christoph Strobl
*/
public static enum StringMatcher {
/**
* Store specific default.
*/
DEFAULT(null),
/**
* Matches the exact string
*/
EXACT(Type.SIMPLE_PROPERTY),
/**
* Matches string starting with pattern
*/
STARTING(Type.STARTING_WITH),
/**
* Matches string ending with pattern
*/
ENDING(Type.ENDING_WITH),
/**
* Matches string containing pattern
*/
CONTAINING(Type.CONTAINING),
/**
* Treats strings as regular expression patterns
*/
REGEX(Type.REGEX);
private Type type;
private StringMatcher(Type type) {
this.type = type;
}
/**
* Get the according {@link Part.Type}.
*
* @return {@literal null} for {@link StringMatcher#DEFAULT}.
*/
public Type getPartType() {
return type;
}
}
private static class PropertySpecifiers {
private Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<String, PropertySpecifier>();
public void add(PropertySpecifier specifier) {
Assert.notNull(specifier, "PropertySpecifier must not be null!");
propertySpecifiers.put(specifier.getPath(), specifier);
}
public boolean hasSpecifierForPath(String path) {
return propertySpecifiers.containsKey(path);
}
public PropertySpecifier getForPath(String path) {
return propertySpecifiers.get(path);
}
public boolean hasValues() {
return !propertySpecifiers.isEmpty();
}
public Collection<PropertySpecifier> getSpecifiers() {
return propertySpecifiers.values();
}
@SuppressWarnings("unchecked")
public <S extends T> Class<S> getResultType() {
return (Class<S>) (exampleSpec instanceof TypedExampleSpec<?> ? ((TypedExampleSpec<T>) exampleSpec).getType()
: getProbeType());
}
}

View File

@@ -0,0 +1,845 @@
/*
* Copyright 2016 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.domain;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
/**
* Specification for property path matching to use in query by example (QBE). An {@link ExampleSpec} can be created for
* a {@link Class object type}. Instances of {@link ExampleSpec} can be either {@link #untyped()} or
* {@link #typed(Class)} and settings can be tuned {@code with...} methods in a fluent style. {@code with...} methods
* return a copy of the {@link ExampleSpec} instance with the specified setting. Null-handling defaults to
* {@link NullHandler#IGNORE} and case-sensitive {@link StringMatcher#DEFAULT} string matching.
* <p>
* This class is immutable.
*
* @author Christoph Strobl
* @author Mark Paluch
* @param <T>
* @since 1.12
*/
public class ExampleSpec {
protected final NullHandler nullHandler;
protected final StringMatcher defaultStringMatcher;
protected final boolean defaultIgnoreCase;
protected final PropertySpecifiers propertySpecifiers;
protected final Set<String> ignoredPaths;
ExampleSpec() {
this.nullHandler = NullHandler.IGNORE;
this.defaultStringMatcher = StringMatcher.DEFAULT;
this.propertySpecifiers = new PropertySpecifiers();
this.defaultIgnoreCase = false;
this.ignoredPaths = Collections.emptySet();
}
ExampleSpec(NullHandler nullHandler, StringMatcher defaultStringMatcher, PropertySpecifiers propertySpecifiers,
Set<String> ignoredPaths, boolean defaultIgnoreCase) {
this.nullHandler = nullHandler;
this.defaultStringMatcher = defaultStringMatcher;
this.propertySpecifiers = propertySpecifiers;
this.ignoredPaths = Collections.unmodifiableSet(ignoredPaths);
this.defaultIgnoreCase = defaultIgnoreCase;
}
/**
* Create a new untyped {@link ExampleSpec} including all non-null properties by default.
*
* @param type must not be {@literal null}.
* @return
*/
public static ExampleSpec untyped() {
return new ExampleSpec();
}
/**
* Create a new {@link TypedExampleSpec} including all non-null properties by default.
*
* @param type must not be {@literal null}.
* @return
*/
public static <T> TypedExampleSpec<T> typed(Class<T> type) {
return new TypedExampleSpec<T>(type);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code propertyPaths}. This instance is immutable and
* unaffected by this method call.
*
* @param ignoredPaths must not be {@literal null} and not empty.
* @return
*/
public ExampleSpec withIgnorePaths(String... ignoredPaths) {
Assert.notEmpty(ignoredPaths, "IgnoredPaths must not be empty!");
Assert.noNullElements(ignoredPaths, "IgnoredPaths must not contain null elements!");
Set<String> newIgnoredPaths = new LinkedHashSet<String>(this.ignoredPaths);
newIgnoredPaths.addAll(Arrays.asList(ignoredPaths));
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#STARTING}.
* This instance is immutable and unaffected by this method call.
*
* @return
*/
public ExampleSpec withStringMatcherStarting() {
return new ExampleSpec(nullHandler, StringMatcher.STARTING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#ENDING}. This
* instance is immutable and unaffected by this method call.
*
* @return
*/
public ExampleSpec withStringMatcherEnding() {
return new ExampleSpec(nullHandler, StringMatcher.ENDING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#CONTAINING}.
* This instance is immutable and unaffected by this method call.
*
* @return
*/
public ExampleSpec withStringMatcherContaining() {
return new ExampleSpec(nullHandler, StringMatcher.CONTAINING, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified string matching of {@code defaultStringMatcher}. This
* instance is immutable and unaffected by this method call.
*
* @param defaultStringMatcher must not be {@literal null}.
* @return
*/
public ExampleSpec withStringMatcher(StringMatcher defaultStringMatcher) {
Assert.notNull(ignoredPaths, "DefaultStringMatcher must not be empty!");
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with ignoring case sensitivity by default. This instance is immutable
* and unaffected by this method call.
*
* @return
*/
public ExampleSpec withIgnoreCase() {
return withIgnoreCase(true);
}
/**
* Returns a copy of this {@link ExampleSpec} with {@code defaultIgnoreCase}. This instance is immutable and
* unaffected by this method call.
*
* @param defaultIgnoreCase
* @return
*/
public ExampleSpec withIgnoreCase(boolean defaultIgnoreCase) {
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* {@code propertyPath}. This instance is immutable and unaffected by this method call.
*
* @param propertyPath must not be {@literal null}.
* @param matcherConfigurer callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
* @return
*/
public ExampleSpec withMatcher(String propertyPath, MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!");
GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher();
matcherConfigurer.configureMatcher(genericPropertyMatcher);
return withMatcher(propertyPath, genericPropertyMatcher);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* {@code propertyPath}. This instance is immutable and unaffected by this method call.
*
* @param propertyPath must not be {@literal null}.
* @param genericPropertyMatcher callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
* @return
*/
public ExampleSpec withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(genericPropertyMatcher, "GenericPropertyMatcher must not be empty!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
PropertySpecifier propertySpecifier = new PropertySpecifier(propertyPath);
if (genericPropertyMatcher.ignoreCase != null) {
propertySpecifier = propertySpecifier.withIgnoreCase(genericPropertyMatcher.ignoreCase);
}
if (genericPropertyMatcher.stringMatcher != null) {
propertySpecifier = propertySpecifier.withStringMatcher(genericPropertyMatcher.stringMatcher);
}
if (genericPropertyMatcher.valueTransformer != null) {
propertySpecifier = propertySpecifier.withValueTransformer(genericPropertyMatcher.valueTransformer);
}
propertySpecifiers.add(propertySpecifier);
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code PropertyValueTransformer} for the
* {@code propertyPath}.
*
* @param propertyPath must not be {@literal null}.
* @param propertyValueTransformer must not be {@literal null}.
* @return
*/
public ExampleSpec withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be empty!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
propertySpecifiers.add(propertySpecifier.withValueTransformer(propertyValueTransformer));
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with ignore case sensitivity for the {@code propertyPaths}. This
* instance is immutable and unaffected by this method call.
*
* @param propertyPaths must not be {@literal null} and not empty.
* @return
*/
public ExampleSpec withIgnoreCase(String... propertyPaths) {
Assert.notEmpty(propertyPaths, "PropertyPaths must not be empty!");
Assert.noNullElements(propertyPaths, "PropertyPaths must not contain null elements!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
for (String propertyPath : propertyPaths) {
PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
propertySpecifiers.add(propertySpecifier.withIgnoreCase(true));
}
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
protected PropertySpecifier getOrCreatePropertySpecifier(String propertyPath, PropertySpecifiers propertySpecifiers) {
if (propertySpecifiers.hasSpecifierForPath(propertyPath)) {
return propertySpecifiers.getForPath(propertyPath);
}
return new PropertySpecifier(propertyPath);
}
/**
* Returns a copy of this {@link ExampleSpec} with treatment for {@literal null} values of {@link NullHandler#INCLUDE}
* . This instance is immutable and unaffected by this method call.
*
* @return
*/
public ExampleSpec withIncludeNullValues() {
return new ExampleSpec(NullHandler.INCLUDE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with treatment for {@literal null} values of {@link NullHandler#IGNORE}.
* This instance is immutable and unaffected by this method call.
*
* @return
*/
public ExampleSpec withIgnoreNullValues() {
return new ExampleSpec(NullHandler.IGNORE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code nullHandler}. This instance is immutable and
* unaffected by this method call.
*
* @param nullHandler must not be {@literal null}.
* @return
*/
public ExampleSpec withNullHandler(NullHandler nullHandler) {
Assert.notNull(nullHandler, "NullHandler must not be null!");
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Get defined null handling.
*
* @return never {@literal null}
*/
public ExampleSpec.NullHandler getNullHandler() {
return nullHandler;
}
/**
* Get defined {@link ExampleSpec.StringMatcher}.
*
* @return never {@literal null}.
*/
public ExampleSpec.StringMatcher getDefaultStringMatcher() {
return defaultStringMatcher;
}
/**
* @return {@literal true} if {@link String} should be matched with ignore case option.
*/
public boolean isIgnoreCaseEnabled() {
return this.defaultIgnoreCase;
}
/**
* @param path
* @return return {@literal true} if path was set to be ignored.
*/
public boolean isIgnoredPath(String path) {
return this.ignoredPaths.contains(path);
}
/**
* @return unmodifiable {@link Set} of ignored paths.
*/
public Set<String> getIgnoredPaths() {
return ignoredPaths;
}
/**
* @return the {@link PropertySpecifiers} within the {@link ExampleSpec}.
*/
public PropertySpecifiers getPropertySpecifiers() {
return propertySpecifiers;
}
/**
* Null handling for creating criterion out of an {@link Example}.
*
* @author Christoph Strobl
*/
public static enum NullHandler {
INCLUDE, IGNORE
}
/**
* Callback to configure a matcher.
*
* @author Mark Paluch
* @param <T>
*/
public static interface MatcherConfigurer<T> {
void configureMatcher(T matcher);
}
/**
* A generic property matcher that specifies {@link StringMatcher string matching} and case sensitivity.
*
* @author Mark Paluch
*/
public static class GenericPropertyMatcher {
StringMatcher stringMatcher = null;
Boolean ignoreCase = null;
PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
/**
* Creates an unconfigured {@link GenericPropertyMatcher}.
*/
public GenericPropertyMatcher() {}
/**
* Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
*
* @param stringMatcher must not be {@literal null}.
* @param ignoreCase
* @return
*/
public static GenericPropertyMatcher of(StringMatcher stringMatcher, boolean ignoreCase) {
return new GenericPropertyMatcher().stringMatcher(stringMatcher).ignoreCase(ignoreCase);
}
/**
* Creates a new {@link GenericPropertyMatcher} with a {@link StringMatcher} and {@code ignoreCase}.
*
* @param stringMatcher must not be {@literal null}.
* @return
*/
public static GenericPropertyMatcher of(StringMatcher stringMatcher) {
return new GenericPropertyMatcher().stringMatcher(stringMatcher);
}
/**
* Sets ignores case to {@literal true}.
*
* @return
*/
public GenericPropertyMatcher ignoreCase() {
this.ignoreCase = true;
return this;
}
/**
* Sets ignores case to {@code ignoreCase}.
*
* @param ignoreCase
* @return
*/
public GenericPropertyMatcher ignoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Sets ignores case to {@literal false}.
*
* @return
*/
public GenericPropertyMatcher caseSensitive() {
this.ignoreCase = false;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#CONTAINING}.
*
* @return
*/
public GenericPropertyMatcher contains() {
this.stringMatcher = StringMatcher.CONTAINING;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#ENDING}.
*
* @return
*/
public GenericPropertyMatcher endsWith() {
this.stringMatcher = StringMatcher.ENDING;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#STARTING}.
*
* @return
*/
public GenericPropertyMatcher startsWith() {
this.stringMatcher = StringMatcher.STARTING;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#EXACT}.
*
* @return
*/
public GenericPropertyMatcher exact() {
this.stringMatcher = StringMatcher.EXACT;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#DEFAULT}.
*
* @return
*/
public GenericPropertyMatcher storeDefaultMatching() {
this.stringMatcher = StringMatcher.DEFAULT;
return this;
}
/**
* Sets string matcher to {@link StringMatcher#REGEX}.
*
* @return
*/
public GenericPropertyMatcher regex() {
this.stringMatcher = StringMatcher.REGEX;
return this;
}
/**
* Sets string matcher to {@code stringMatcher}.
*
* @param stringMatcher must not be {@literal null}.
* @return
*/
public GenericPropertyMatcher stringMatcher(StringMatcher stringMatcher) {
Assert.notNull(stringMatcher, "StringMatcher must not be null!");
this.stringMatcher = stringMatcher;
return this;
}
/**
* Sets the {@link PropertyValueTransformer} to {@code propertyValueTransformer}.
*
* @param propertyValueTransformer must not be {@literal null}.
* @return
*/
public GenericPropertyMatcher transform(PropertyValueTransformer propertyValueTransformer) {
Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be null!");
this.valueTransformer = propertyValueTransformer;
return this;
}
}
/**
* Predefined property matchers to create a {@link GenericPropertyMatcher}.
*
* @author Mark Paluch
*/
public static class GenericPropertyMatchers {
/**
* Creates a {@link GenericPropertyMatcher} that matches string case insensitive.
*
* @return
*/
public static GenericPropertyMatcher ignoreCase() {
return new GenericPropertyMatcher().ignoreCase();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string case sensitive.
*
* @return
*/
public static GenericPropertyMatcher caseSensitive() {
return new GenericPropertyMatcher().caseSensitive();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#CONTAINING}.
*
* @return
*/
public static GenericPropertyMatcher contains() {
return new GenericPropertyMatcher().contains();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#ENDING}.
*
* @return
*/
public static GenericPropertyMatcher endsWith() {
return new GenericPropertyMatcher().endsWith();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#STARTING}.
*
* @return
*/
public static GenericPropertyMatcher startsWith() {
return new GenericPropertyMatcher().startsWith();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#EXACT}.
*
* @return
*/
public static GenericPropertyMatcher exact() {
return new GenericPropertyMatcher().startsWith();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#DEFAULT}.
*
* @return
*/
public static GenericPropertyMatcher storeDefaultMatching() {
return new GenericPropertyMatcher().storeDefaultMatching();
}
/**
* Creates a {@link GenericPropertyMatcher} that matches string using {@link StringMatcher#REGEX}.
*
* @return
*/
public static GenericPropertyMatcher regex() {
return new GenericPropertyMatcher().regex();
}
}
/**
* Match modes for treatment of {@link String} values.
*
* @author Christoph Strobl
*/
public static enum StringMatcher {
/**
* Store specific default.
*/
DEFAULT,
/**
* Matches the exact string
*/
EXACT,
/**
* Matches string starting with pattern
*/
STARTING,
/**
* Matches string ending with pattern
*/
ENDING,
/**
* Matches string containing pattern
*/
CONTAINING,
/**
* Treats strings as regular expression patterns
*/
REGEX;
}
/**
* Allows to transform the property value before it is used in the query.
*/
public static interface PropertyValueTransformer extends Converter<Object, Object> {
// TODO: should we use the converter interface directly or not at all?
}
/**
* @author Christoph Strobl
* @since 1.12
*/
public static enum NoOpPropertyValueTransformer implements ExampleSpec.PropertyValueTransformer {
INSTANCE;
@Override
public Object convert(Object source) {
return source;
}
}
/**
* Define specific property handling for a Dot-Path.
*
* @author Christoph Strobl
* @since 1.12
*/
public static class PropertySpecifier {
private final String path;
private final StringMatcher stringMatcher;
private final Boolean ignoreCase;
private final PropertyValueTransformer valueTransformer;
/**
* Creates new {@link PropertySpecifier} for given path.
*
* @param path Dot-Path to the property. Must not be {@literal null}.
*/
PropertySpecifier(String path) {
Assert.hasText(path, "Path must not be null/empty!");
this.path = path;
this.stringMatcher = null;
this.ignoreCase = null;
this.valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
}
private PropertySpecifier(String path, StringMatcher stringMatcher, Boolean ignoreCase,
PropertyValueTransformer valueTransformer) {
this.path = path;
this.stringMatcher = stringMatcher;
this.ignoreCase = ignoreCase;
this.valueTransformer = valueTransformer;
}
/**
* Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
* {@link StringMatcher} in the returned instance.
*
* @param stringMatcher must not be {@literal null}.
* @return
*/
public PropertySpecifier withStringMatcher(StringMatcher stringMatcher) {
Assert.notNull(stringMatcher, "StringMatcher must not be null!");
return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer);
}
/**
* Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
* {@code ignoreCase}.
*
* @param ignoreCase must not be {@literal null}.
* @return
*/
public PropertySpecifier withIgnoreCase(boolean ignoreCase) {
return new PropertySpecifier(this.path, this.stringMatcher, ignoreCase, this.valueTransformer);
}
/**
* Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
* {@link PropertyValueTransformer} in the returned instance.
*
* @param valueTransformer must not be {@literal null}.
* @return
*/
public PropertySpecifier withValueTransformer(PropertyValueTransformer valueTransformer) {
Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!");
return new PropertySpecifier(this.path, this.stringMatcher, this.ignoreCase, valueTransformer);
}
/**
* Get the properties Dot-Path.
*
* @return never {@literal null}.
*/
public String getPath() {
return path;
}
/**
* Get the {@link StringMatcher}.
*
* @return can be {@literal null}.
*/
public StringMatcher getStringMatcher() {
return stringMatcher;
}
/**
* @return {@literal null} if not set.
*/
public Boolean getIgnoreCase() {
return ignoreCase;
}
/**
* Get the property transformer to be applied.
*
* @return never {@literal null}.
*/
public PropertyValueTransformer getPropertyValueTransformer() {
return valueTransformer == null ? NoOpPropertyValueTransformer.INSTANCE : valueTransformer;
}
/**
* Transforms a given source using the {@link PropertyValueTransformer}.
*
* @param source
* @return
*/
public Object transformValue(Object source) {
return getPropertyValueTransformer().convert(source);
}
}
/**
* Define specific property handling for Dot-Paths.
*
* @author Christoph Strobl
* @since 1.12
*/
public static class PropertySpecifiers {
private final Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<String, PropertySpecifier>();
PropertySpecifiers() {
}
PropertySpecifiers(PropertySpecifiers propertySpecifiers) {
this.propertySpecifiers.putAll(propertySpecifiers.propertySpecifiers);
}
public void add(PropertySpecifier specifier) {
Assert.notNull(specifier, "PropertySpecifier must not be null!");
propertySpecifiers.put(specifier.getPath(), specifier);
}
public boolean hasSpecifierForPath(String path) {
return propertySpecifiers.containsKey(path);
}
public PropertySpecifier getForPath(String path) {
return propertySpecifiers.get(path);
}
public boolean hasValues() {
return !propertySpecifiers.isEmpty();
}
public Collection<PropertySpecifier> getSpecifiers() {
return propertySpecifiers.values();
}
}
}

View File

@@ -1,267 +0,0 @@
/*
* Copyright 2016 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.domain;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example.StringMatcher;
import org.springframework.data.domain.PropertySpecifier.PropertyValueTransformer;
import org.springframework.util.Assert;
/**
* Define specific property handling for a Dot-Path.
*
* @author Christoph Strobl
* @since 1.12
*/
public class PropertySpecifier {
private final String path;
private StringMatcher stringMatcher;
private Boolean ignoreCase;
private PropertyValueTransformer valueTransformer;
/**
* Creates new {@link PropertySpecifier} for given path.
*
* @param path Dot-Path to the property. Must not be {@literal null}.
*/
PropertySpecifier(String path) {
Assert.hasText(path, "Path must not be null/empty!");
this.path = path;
}
/**
* Get the properties Dot-Path.
*
* @return never {@literal null}.
*/
public String getPath() {
return path;
}
/**
* Get the {@link StringMatcher}.
*
* @return can be {@literal null}.
*/
public StringMatcher getStringMatcher() {
return stringMatcher;
}
/**
* @return {literal true} in case {@link StringMatcher} defined.
*/
public boolean hasStringMatcher() {
return this.stringMatcher != null;
}
/**
* @return {@literal null} if not set.
*/
public Boolean getIgnoreCase() {
return ignoreCase;
}
/**
* Get the property transformer to be applied.
*
* @return never {@literal null}.
*/
public PropertyValueTransformer getPropertyValueTransformer() {
return valueTransformer == null ? NoOpPropertyValueTransformer.INSTANCE : valueTransformer;
}
/**
* Transforms a given source using the {@link PropertyValueTransformer}.
*
* @param source
* @return
*/
public Object transformValue(Object source) {
return getPropertyValueTransformer().convert(source);
}
/**
* Creates new case ignoring {@link PropertySpecifier} for given path.
*
* @param propertyPath must not be {@literal null}.
* @return
*/
public static PropertySpecifier ignoreCase(String propertyPath) {
return new Builder(propertyPath).matchStringsWithIgnoreCase().get();
}
/**
* Creates new {@link PropertySpecifier} using given {@link PropertyValueTransformer}.
*
* @param propertyPath must not be {@literal null}.
* @param valueTransformer should not be {@literal null}, will be defaulted to {@link NoOpPropertyValueTransformer}.
* @return
*/
public static PropertySpecifier transform(String propertyPath, PropertyValueTransformer valueTransformer) {
return new Builder(propertyPath).withValueTransformer(
valueTransformer != null ? valueTransformer : NoOpPropertyValueTransformer.INSTANCE).get();
}
/**
* Create new {@link Builder} for specifying {@link PropertySpecifier}.
*
* @param propertyPath must not be {@literal null}.
* @return
*/
public static Builder newPropertySpecifier(String propertyPath) {
return new Builder(propertyPath);
}
/**
* Builder for specifying desired behavior of {@link PropertySpecifier}.
*
* @author Christoph Strobl
*/
public static class Builder {
private PropertySpecifier specifier;
Builder(String path) {
specifier = new PropertySpecifier(path);
}
/**
* Sets the {@link StringMatcher} used for {@link PropertySpecifier}.
*
* @param stringMatcher
* @return
* @see Builder#stringMatcher(StringMatcher)
*/
public Builder withStringMatcher(StringMatcher stringMatcher) {
return matchString(stringMatcher);
}
/**
* Sets the {@link PropertyValueTransformer} used for {@link PropertySpecifier}.
*
* @param valueTransformer
* @return
* @see Builder#valueTransformer(PropertyValueTransformer)
*/
public Builder withValueTransformer(PropertyValueTransformer valueTransformer) {
specifier.valueTransformer = valueTransformer;
return this;
}
/**
* Sets the {@link PropertyValueTransformer} used for {@link PropertySpecifier}.
*
* @param valueTransformer
* @return
* @see Builder#valueTransformer(PropertyValueTransformer)
*/
public Builder transforming(PropertyValueTransformer valueTransformer) {
return withValueTransformer(valueTransformer);
}
/**
* Sets the {@link StringMatcher} used for {@link PropertySpecifier}.
*
* @param stringMatcher
* @return
*/
public Builder matchString(StringMatcher stringMatcher) {
return matchString(stringMatcher, specifier.ignoreCase);
}
/**
* Sets the {@link StringMatcher} used for {@link PropertySpecifier}.
*
* @param stringMatcher
* @param ignoreCase
* @return
*/
public Builder matchString(StringMatcher stringMatcher, Boolean ignoreCase) {
specifier.stringMatcher = stringMatcher;
specifier.ignoreCase = ignoreCase;
return this;
}
/**
* Enable case ignoring string matching.
*
* @return
*/
public Builder matchStringsWithIgnoreCase() {
specifier.ignoreCase = true;
return this;
}
/**
* Set string matching to {@link StringMatcher#STARTING}
*
* @return
*/
public Builder matchStringStartingWith() {
return matchString(StringMatcher.STARTING);
}
/**
* Set string matching to {@link StringMatcher#ENDING}
*
* @return
*/
public Builder matchStringEndingWith() {
return matchString(StringMatcher.ENDING);
}
/**
* Set string matching to {@link StringMatcher#CONTAINING}
*
* @return
*/
public Builder matchStringContaining() {
return matchString(StringMatcher.CONTAINING);
}
/**
* @return {@link PropertySpecifier} as defined.
*/
public PropertySpecifier get() {
return this.specifier;
}
}
public static interface PropertyValueTransformer extends Converter<Object, Object> {
// TODO: should we use the converter interface directly or not at all?
}
/**
* @author Christoph Strobl
* @since 1.12
*/
static enum NoOpPropertyValueTransformer implements PropertyValueTransformer {
INSTANCE;
@Override
public Object convert(Object source) {
return source;
}
}
}

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2016 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.domain;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A {@code TypedExampleSpec} is a special {@link ExampleSpec} that holds information of the type to query for.
*
* @author Mark Paluch
* @since 1.12
*/
public class TypedExampleSpec<T> extends ExampleSpec {
protected final Class<T> type;
TypedExampleSpec(Class<T> type) {
super();
Assert.notNull(type, "Type must not be null!");
this.type = type;
}
TypedExampleSpec(Class<T> type, NullHandler nullHandler, StringMatcher defaultStringMatcher,
PropertySpecifiers propertySpecifiers, Set<String> ignoredPaths, boolean defaultIgnoreCase) {
super(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
Assert.notNull(type, "Type must not be null!");
this.type = type;
}
/**
* Create a new {@link TypedExampleSpec} for the {@code type} including all non-null properties by default.
*
* @param type must not be {@literal null}.
* @return
*/
public static <T> TypedExampleSpec<T> of(Class<T> type) {
return new TypedExampleSpec<T>(type);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified {@code propertyPaths}. This instance is
* immutable and unaffected by this method call.
*
* @param ignoredPaths must not be {@literal null} and not empty.
* @return
*/
@Override
public TypedExampleSpec<T> withIgnorePaths(String... ignoredPaths) {
Assert.notEmpty(ignoredPaths, "IgnoredPaths must not be empty!");
Assert.noNullElements(ignoredPaths, "IgnoredPaths must not contain null elements!");
Set<String> newIgnoredPaths = new LinkedHashSet<String>(this.ignoredPaths);
newIgnoredPaths.addAll(Arrays.asList(ignoredPaths));
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified string matching of
* {@link StringMatcher#STARTING}. This instance is immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withStringMatcherStarting() {
return new TypedExampleSpec<T>(type, nullHandler, StringMatcher.STARTING, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified string matching of {@link StringMatcher#ENDING}.
* This instance is immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withStringMatcherEnding() {
return new TypedExampleSpec<T>(type, nullHandler, StringMatcher.ENDING, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified string matching of
* {@link StringMatcher#CONTAINING}. This instance is immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withStringMatcherContaining() {
return new TypedExampleSpec<T>(type, nullHandler, StringMatcher.CONTAINING, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified string matching of {@code defaultStringMatcher}.
* This instance is immutable and unaffected by this method call.
*
* @param defaultStringMatcher must not be {@literal null}.
* @return
*/
@Override
public TypedExampleSpec<T> withStringMatcher(StringMatcher defaultStringMatcher) {
Assert.notNull(ignoredPaths, "DefaultStringMatcher must not be empty!");
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with ignoring case sensitivity by default. This instance is
* immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withIgnoreCase() {
return withIgnoreCase(true);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with {@code defaultIgnoreCase}. This instance is immutable and
* unaffected by this method call.
*
* @param defaultIgnoreCase
* @return
*/
@Override
public TypedExampleSpec<T> withIgnoreCase(boolean defaultIgnoreCase) {
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* {@code propertyPath}. This instance is immutable and unaffected by this method call.
*
* @param propertyPath must not be {@literal null}.
* @param matcherConfigurer callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
* @return
*/
@Override
public TypedExampleSpec<T> withMatcher(String propertyPath,
MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!");
GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher();
matcherConfigurer.configureMatcher(genericPropertyMatcher);
return withMatcher(propertyPath, genericPropertyMatcher);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* {@code propertyPath}. This instance is immutable and unaffected by this method call.
*
* @param propertyPath must not be {@literal null}.
* @param genericPropertyMatcher callback to configure a {@link GenericPropertyMatcher}, must not be {@literal null}.
* @return
*/
@Override
public TypedExampleSpec<T> withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(genericPropertyMatcher, "GenericPropertyMatcher must not be empty!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
PropertySpecifier propertySpecifier = new PropertySpecifier(propertyPath);
if (genericPropertyMatcher.ignoreCase != null) {
propertySpecifier = propertySpecifier.withIgnoreCase(genericPropertyMatcher.ignoreCase);
}
if (genericPropertyMatcher.stringMatcher != null) {
propertySpecifier = propertySpecifier.withStringMatcher(genericPropertyMatcher.stringMatcher);
}
if (genericPropertyMatcher.valueTransformer != null) {
propertySpecifier = propertySpecifier.withValueTransformer(genericPropertyMatcher.valueTransformer);
}
propertySpecifiers.add(propertySpecifier);
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified {@code PropertyValueTransformer} for the
* {@code propertyPath}.
*
* @param propertyPath must not be {@literal null}.
* @param propertyValueTransformer must not be {@literal null}.
* @return
*/
@Override
public TypedExampleSpec<T> withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be empty!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
propertySpecifiers.add(propertySpecifier.withValueTransformer(propertyValueTransformer));
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with ignore case sensitivity for the {@code propertyPaths}. This
* instance is immutable and unaffected by this method call.
*
* @param propertyPaths must not be {@literal null} and not empty.
* @return
*/
@Override
public TypedExampleSpec<T> withIgnoreCase(String... propertyPaths) {
Assert.notEmpty(propertyPaths, "PropertyPaths must not be empty!");
Assert.noNullElements(propertyPaths, "PropertyPaths must not contain null elements!");
PropertySpecifiers propertySpecifiers = new PropertySpecifiers(this.propertySpecifiers);
for (String propertyPath : propertyPaths) {
PropertySpecifier propertySpecifier = getOrCreatePropertySpecifier(propertyPath, propertySpecifiers);
propertySpecifiers.add(propertySpecifier.withIgnoreCase(true));
}
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with treatment for {@literal null} values of
* {@link NullHandler#INCLUDE} . This instance is immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withIncludeNullValues() {
return new TypedExampleSpec<T>(type, NullHandler.INCLUDE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with treatment for {@literal null} values of
* {@link NullHandler#IGNORE}. This instance is immutable and unaffected by this method call.
*
* @return
*/
@Override
public TypedExampleSpec<T> withIgnoreNullValues() {
return new TypedExampleSpec<T>(type, NullHandler.IGNORE, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link TypedExampleSpec} with the specified {@code nullHandler}. This instance is immutable
* and unaffected by this method call.
*
* @param nullHandler must not be {@literal null}.
* @return
*/
@Override
public TypedExampleSpec<T> withNullHandler(NullHandler nullHandler) {
Assert.notNull(nullHandler, "NullHandler must not be null!");
return new TypedExampleSpec<T>(type, nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths,
defaultIgnoreCase);
}
/**
* Get the actual type for the {@link TypedExampleSpec} used. This is usually the given class, but the original class
* in case of a CGLIB-generated subclass.
*
* @return
* @see ClassUtils#getUserClass(Class)
*/
@SuppressWarnings("unchecked")
public Class<T> getType() {
return (Class<T>) ClassUtils.getUserClass(type);
}
}