DATACMNS-810 - Polishing.

Renamed ExampleSpecification to ExampleMatcher and introduced a matching() factory method, dropping the dedicated override mechanism for types for now. Updated documentation accordingly.

Used Lombok for constructor creation, field defaults, toString() as well as equals(…) and hashCode() implementations.

Tweaked imports in unit tests. Renamed methods to express expected behavior.

Original pull request: #153.
This commit is contained in:
Oliver Gierke
2016-03-02 12:00:37 +01:00
parent b0ce7009e8
commit 26f725be0d
14 changed files with 806 additions and 1869 deletions

View File

@@ -15,84 +15,51 @@
*/
package org.springframework.data.domain;
import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.util.ClassUtils;
/**
* 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.
* and type safety can be tuned using {@link ExampleMatcher}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @param <T>
* @author Oliver Gierke
* @param <T> the type of the probe.
* @since 1.12
*/
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Example<T> {
private final T probe;
private final ExampleSpec exampleSpec;
private final @NonNull T probe;
private final @NonNull ExampleMatcher matcher;
/**
* Create a new {@link Example} including all non-null properties by default.
*
* @param probe The probe to use. Must not be {@literal null}.
*/
@SuppressWarnings("unchecked")
private Example(T probe) {
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);
return new Example<T>(probe, ExampleMatcher.matching());
}
/**
* Create a new {@link Example} with a configured untyped {@link ExampleSpec}.
* Create a new {@link Example} using the given {@link ExampleMatcher}.
*
* @param probe must not be {@literal null}.
* @param exampleSpec must not be {@literal null}.
* @param matcher 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);
public static <T> Example<T> of(T probe, ExampleMatcher matcher) {
return new Example<T>(probe, matcher);
}
/**
@@ -105,12 +72,12 @@ public class Example<T> {
}
/**
* Get the {@link ExampleSpec} used.
* Get the {@link ExampleMatcher} used.
*
* @return never {@literal null}.
*/
public ExampleSpec getExampleSpec() {
return exampleSpec;
public ExampleMatcher getMatcher() {
return matcher;
}
/**
@@ -124,17 +91,4 @@ public class Example<T> {
public Class<T> getProbeType() {
return (Class<T>) ClassUtils.getUserClass(probe.getClass());
}
/**
* Get the actual result type for the query. The result type can be different when using {@link TypedExampleSpec}.
*
* @return
* @see ClassUtils#getUserClass(Class)
*/
@SuppressWarnings("unchecked")
public <S extends T> Class<S> getResultType() {
return (Class<S>) (exampleSpec instanceof TypedExampleSpec<?> ? ((TypedExampleSpec<T>) exampleSpec).getType()
: getProbeType());
}
}

View File

@@ -15,6 +15,12 @@
*/
package org.springframework.data.domain;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -27,10 +33,10 @@ 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
* Specification for property path matching to use in query by example (QBE). An {@link ExampleMatcher} can be created
* for a {@link Class object type}. Instances of {@link ExampleMatcher} can be either {@link #matching()} 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
* return a copy of the {@link ExampleMatcher} 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.
@@ -40,61 +46,40 @@ import org.springframework.util.Assert;
* @param <T>
* @since 1.12
*/
public class ExampleSpec {
@ToString
@EqualsAndHashCode
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class ExampleMatcher {
protected final NullHandler nullHandler;
protected final StringMatcher defaultStringMatcher;
protected final boolean defaultIgnoreCase;
protected final PropertySpecifiers propertySpecifiers;
protected final Set<String> ignoredPaths;
NullHandler nullHandler;
StringMatcher defaultStringMatcher;
PropertySpecifiers propertySpecifiers;
Set<String> ignoredPaths;
boolean defaultIgnoreCase;
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;
private ExampleMatcher() {
this(NullHandler.IGNORE, StringMatcher.DEFAULT, new PropertySpecifiers(), Collections.<String> emptySet(), false);
}
/**
* Create a new untyped {@link ExampleSpec} including all non-null properties by default.
* Create a new untyped {@link ExampleMatcher} including all non-null properties by default.
*
* @param type must not be {@literal null}.
* @return
*/
public static ExampleSpec untyped() {
return new ExampleSpec();
public static ExampleMatcher matching() {
return new ExampleMatcher();
}
/**
* 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.
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withIgnorePaths(String... ignoredPaths) {
Assert.notEmpty(ignoredPaths, "IgnoredPaths must not be empty!");
Assert.noNullElements(ignoredPaths, "IgnoredPaths must not contain null elements!");
@@ -102,83 +87,54 @@ public class ExampleSpec {
Set<String> newIgnoredPaths = new LinkedHashSet<String>(this.ignoredPaths);
newIgnoredPaths.addAll(Arrays.asList(ignoredPaths));
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, newIgnoredPaths,
defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified string matching of {@link StringMatcher#STARTING}.
* Returns a copy of this {@link ExampleMatcher} with the specified string matching of {@code defaultStringMatcher}.
* 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) {
public ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher) {
Assert.notNull(ignoredPaths, "DefaultStringMatcher must not be empty!");
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with ignoring case sensitivity by default. This instance is immutable
* Returns a copy of this {@link ExampleMatcher} with ignoring case sensitivity by default. This instance is immutable
* and unaffected by this method call.
*
* @return
*/
public ExampleSpec withIgnoreCase() {
public ExampleMatcher withIgnoreCase() {
return withIgnoreCase(true);
}
/**
* Returns a copy of this {@link ExampleSpec} with {@code defaultIgnoreCase}. This instance is immutable and
* Returns a copy of this {@link ExampleMatcher} 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);
public ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase) {
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withMatcher(String propertyPath, MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!");
@@ -190,14 +146,14 @@ public class ExampleSpec {
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code GenericPropertyMatcher} for the
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(genericPropertyMatcher, "GenericPropertyMatcher must not be empty!");
@@ -219,18 +175,18 @@ public class ExampleSpec {
propertySpecifiers.add(propertySpecifier);
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with the specified {@code PropertyValueTransformer} for the
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer) {
Assert.hasText(propertyPath, "PropertyPath must not be empty!");
Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be empty!");
@@ -240,17 +196,17 @@ public class ExampleSpec {
propertySpecifiers.add(propertySpecifier.withValueTransformer(propertyValueTransformer));
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
* Returns a copy of this {@link ExampleSpec} with ignore case sensitivity for the {@code propertyPaths}. This
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withIgnoreCase(String... propertyPaths) {
Assert.notEmpty(propertyPaths, "PropertyPaths must not be empty!");
Assert.noNullElements(propertyPaths, "PropertyPaths must not contain null elements!");
@@ -262,10 +218,10 @@ public class ExampleSpec {
propertySpecifiers.add(propertySpecifier.withIgnoreCase(true));
}
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
protected PropertySpecifier getOrCreatePropertySpecifier(String propertyPath, PropertySpecifiers propertySpecifiers) {
private PropertySpecifier getOrCreatePropertySpecifier(String propertyPath, PropertySpecifiers propertySpecifiers) {
if (propertySpecifiers.hasSpecifierForPath(propertyPath)) {
return propertySpecifiers.getForPath(propertyPath);
@@ -275,38 +231,38 @@ public class ExampleSpec {
}
/**
* 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.
* Returns a copy of this {@link ExampleMatcher} 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,
public ExampleMatcher withIncludeNullValues() {
return new ExampleMatcher(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.
* Returns a copy of this {@link ExampleMatcher} 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,
public ExampleMatcher withIgnoreNullValues() {
return new ExampleMatcher(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.
* Returns a copy of this {@link ExampleMatcher} 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) {
public ExampleMatcher withNullHandler(NullHandler nullHandler) {
Assert.notNull(nullHandler, "NullHandler must not be null!");
return new ExampleSpec(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
return new ExampleMatcher(nullHandler, defaultStringMatcher, propertySpecifiers, ignoredPaths, defaultIgnoreCase);
}
/**
@@ -314,16 +270,16 @@ public class ExampleSpec {
*
* @return never {@literal null}
*/
public ExampleSpec.NullHandler getNullHandler() {
public ExampleMatcher.NullHandler getNullHandler() {
return nullHandler;
}
/**
* Get defined {@link ExampleSpec.StringMatcher}.
* Get defined {@link ExampleMatcher.StringMatcher}.
*
* @return never {@literal null}.
*/
public ExampleSpec.StringMatcher getDefaultStringMatcher() {
public ExampleMatcher.StringMatcher getDefaultStringMatcher() {
return defaultStringMatcher;
}
@@ -350,7 +306,7 @@ public class ExampleSpec {
}
/**
* @return the {@link PropertySpecifiers} within the {@link ExampleSpec}.
* @return the {@link PropertySpecifiers} within the {@link ExampleMatcher}.
*/
public PropertySpecifiers getPropertySpecifiers() {
return propertySpecifiers;
@@ -538,7 +494,6 @@ public class ExampleSpec {
this.valueTransformer = propertyValueTransformer;
return this;
}
}
/**
@@ -620,7 +575,6 @@ public class ExampleSpec {
public static GenericPropertyMatcher regex() {
return new GenericPropertyMatcher().regex();
}
}
/**
@@ -660,15 +614,13 @@ public class ExampleSpec {
/**
* 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?
}
public static interface PropertyValueTransformer extends Converter<Object, Object> {}
/**
* @author Christoph Strobl
* @since 1.12
*/
public static enum NoOpPropertyValueTransformer implements ExampleSpec.PropertyValueTransformer {
public static enum NoOpPropertyValueTransformer implements ExampleMatcher.PropertyValueTransformer {
INSTANCE;
@@ -676,7 +628,6 @@ public class ExampleSpec {
public Object convert(Object source) {
return source;
}
}
/**
@@ -685,13 +636,14 @@ public class ExampleSpec {
* @author Christoph Strobl
* @since 1.12
*/
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class PropertySpecifier {
private final String path;
private final StringMatcher stringMatcher;
private final Boolean ignoreCase;
private final PropertyValueTransformer valueTransformer;
String path;
StringMatcher stringMatcher;
Boolean ignoreCase;
PropertyValueTransformer valueTransformer;
/**
* Creates new {@link PropertySpecifier} for given path.
@@ -708,15 +660,6 @@ public class ExampleSpec {
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.
@@ -797,7 +740,6 @@ public class ExampleSpec {
public Object transformValue(Object source) {
return getPropertyValueTransformer().convert(source);
}
}
/**
@@ -810,9 +752,7 @@ public class ExampleSpec {
private final Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<String, PropertySpecifier>();
PropertySpecifiers() {
}
PropertySpecifiers() {}
PropertySpecifiers(PropertySpecifiers propertySpecifiers) {
this.propertySpecifiers.putAll(propertySpecifiers.propertySpecifiers);
@@ -839,7 +779,5 @@ public class ExampleSpec {
public Collection<PropertySpecifier> getSpecifiers() {
return propertySpecifiers.values();
}
}
}

View File

@@ -1,313 +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 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);
}
}