From 4092f0716fb91b075f0c2339df9829870cfae059 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 27 Jan 2016 13:55:58 +0100 Subject: [PATCH] DATACMNS-810 - Add core types for Query by Example support. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added required types for Query by Example which should be used by the individual store implementations. Generally the Example type captures a sample object and allow various settings concerning the mapping into an actual query. So there’s configuration options for handling null values, string matching, property paths to ignore, Related tickets: DATAJPA-218, DATAMONGO-1245. Original pull request: #153. --- .../springframework/data/domain/Example.java | 494 ++++++++++++++++++ .../data/domain/PropertySpecifier.java | 267 ++++++++++ .../data/domain/ExampleUnitTests.java | 279 ++++++++++ 3 files changed, 1040 insertions(+) create mode 100644 src/main/java/org/springframework/data/domain/Example.java create mode 100644 src/main/java/org/springframework/data/domain/PropertySpecifier.java create mode 100644 src/test/java/org/springframework/data/domain/ExampleUnitTests.java diff --git a/src/main/java/org/springframework/data/domain/Example.java b/src/main/java/org/springframework/data/domain/Example.java new file mode 100644 index 000000000..6b8f6d76d --- /dev/null +++ b/src/main/java/org/springframework/data/domain/Example.java @@ -0,0 +1,494 @@ +/* + * 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.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). + * + * @author Christoph Strobl + * @param + * @since 1.12 + */ +public class Example { + + private final T sampleObject; + + private NullHandler nullHandler = NullHandler.IGNORE; + private StringMatcher defaultStringMatcher = StringMatcher.DEFAULT; + private PropertySpecifiers propertySpecifiers = new PropertySpecifiers(); + private Set ignoredPaths = new LinkedHashSet(); + + private boolean ignoreCase = false; + + /** + * Create a new {@link Example} including all non-null properties by default. + * + * @param sampleObject The example to use. Must not be {@literal null}. + */ + public Example(T sampleObject) { + + Assert.notNull(sampleObject, "SampleObject must not be null!"); + this.sampleObject = sampleObject; + } + + /** + * Get the example used. + * + * @return never {@literal null}. + */ + public T getSampleObject() { + return sampleObject; + } + + /** + * Get defined null handling. + * + * @return never {@literal null} + */ + public NullHandler getNullHandler() { + return nullHandler; + } + + /** + * Get defined {@link StringMatcher}. + * + * @return never {@literal null}. + */ + public StringMatcher getDefaultStringMatcher() { + return defaultStringMatcher; + } + + /** + * @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 getIgnoredPaths() { + return Collections.unmodifiableSet(ignoredPaths); + } + + /** + * @return unmodifiable {@link Collection} of {@link PropertySpecifier}s. + */ + public Collection 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.
+ * 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 + * CGLIB-generated subclass. + * + * @return + * @see ClassUtils#getUserClass(Class) + */ + @SuppressWarnings("unchecked") + public Class getSampleType() { + return (Class) ClassUtils.getUserClass(sampleObject.getClass()); + } + + /** + * Create a new {@link Example} including all non-null properties by default. + * + * @param sampleObject must not be {@literal null}. + * @return + */ + public static Example exampleOf(T sampleObject) { + return new Example(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 Example exampleOf(T value, String... ignoredProperties) { + return new Builder(value).ignore(ignoredProperties).get(); + } + + /** + * Create new {@link Builder} for specifying {@link Example}. + * + * @param sampleObject must not be {@literal null}. + * @return + * @see Builder + */ + public static Builder newExampleOf(T sampleObject) { + return new Builder(sampleObject); + } + + /** + * Builder for specifying desired behavior of {@link Example}. + * + * @author Christoph Strobl + * @param + */ + public static class Builder { + + private Example example; + + Builder(T sampleObject) { + example = new Example(sampleObject); + } + + /** + * Sets {@link NullHandler} used for {@link Example}. + * + * @param nullHandling + * @return + * @see Builder#nullHandling(NullHandler) + */ + public Builder withNullHandler(NullHandler nullHandling) { + return handleNullValues(nullHandling); + } + + /** + * Sets default {@link StringMatcher} used for {@link Example}. + * + * @param stringMatcher + * @return + * @see Builder#matchStrings(StringMatcher) + */ + public Builder withStringMatcher(StringMatcher stringMatcher) { + return matchStrings(stringMatcher); + } + + /** + * Adds {@link PropertySpecifier} used for {@link Example}. + * + * @param specifier + * @return + * @see Builder#specify(PropertySpecifier...) + */ + public Builder 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 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 includeNullValues() { + return handleNullValues(NullHandler.INCLUDE); + } + + /** + * Sets treatment of {@literal null} values to {@link NullHandler#IGNORE} + * + * @return + */ + public Builder 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 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 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 matchStringsWithIgnoreCase() { + example.ignoreCase = true; + return this; + } + + /** + * Set string matching to {@link StringMatcher#STARTING} + * + * @return + */ + public Builder matchStringsStartingWith() { + return matchStrings(StringMatcher.STARTING); + } + + /** + * Set string matching to {@link StringMatcher#ENDING} + * + * @return + */ + public Builder matchStringsEndingWith() { + return matchStrings(StringMatcher.ENDING); + } + + /** + * Set string matching to {@link StringMatcher#CONTAINING} + * + * @return + */ + public Builder matchStringsContaining() { + return matchStrings(StringMatcher.CONTAINING); + } + + /** + * Define specific property handling. + * + * @param specifiers + * @return + */ + public Builder specify(PropertySpecifier... specifiers) { + + for (PropertySpecifier specifier : specifiers) { + example.propertySpecifiers.add(specifier); + } + return this; + } + + /** + * Ignore given properties. + * + * @param ignoredProperties + * @return + */ + public Builder ignore(String... ignoredProperties) { + + for (String ignoredProperty : ignoredProperties) { + example.ignoredPaths.add(ignoredProperty); + } + return this; + } + + /** + * @return {@link Example} as defined. + */ + public Example 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 propertySpecifiers = new LinkedHashMap(); + + 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 getSpecifiers() { + return propertySpecifiers.values(); + } + } + +} diff --git a/src/main/java/org/springframework/data/domain/PropertySpecifier.java b/src/main/java/org/springframework/data/domain/PropertySpecifier.java new file mode 100644 index 000000000..8ff51c54b --- /dev/null +++ b/src/main/java/org/springframework/data/domain/PropertySpecifier.java @@ -0,0 +1,267 @@ +/* + * 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 { + // 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; + } + + } +} diff --git a/src/test/java/org/springframework/data/domain/ExampleUnitTests.java b/src/test/java/org/springframework/data/domain/ExampleUnitTests.java new file mode 100644 index 000000000..223864dfa --- /dev/null +++ b/src/test/java/org/springframework/data/domain/ExampleUnitTests.java @@ -0,0 +1,279 @@ +/* + * 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 static org.hamcrest.collection.IsEmptyCollection.*; +import static org.hamcrest.core.Is.*; +import static org.hamcrest.core.IsCollectionContaining.*; +import static org.hamcrest.core.IsEqual.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.junit.Assert.*; +import static org.springframework.data.domain.Example.*; +import static org.springframework.data.domain.PropertySpecifier.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Example.NullHandler; +import org.springframework.data.domain.Example.StringMatcher; +import org.springframework.data.domain.PropertySpecifier.NoOpPropertyValueTransformer; +import org.springframework.data.domain.PropertySpecifier.PropertyValueTransformer; + +/** + * @author Christoph Strobl + */ +public class ExampleUnitTests { + + private Person person; + private Example example; + + @Before + public void setUp() { + + person = new Person(); + person.firstname = "rand"; + + example = exampleOf(person); + } + + /** + * @see DATACMNS-810 + */ + @Test(expected = IllegalArgumentException.class) + public void exampleOfNullThrowsException() { + new Example(null); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldUseDefaultStringMatcher() { + assertThat(example.getDefaultStringMatcher(), equalTo(StringMatcher.DEFAULT)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldUseDefaultStringMatcherForPathThatDoesNotHavePropertySpecifier() { + assertThat(example.getStringMatcherForPath("firstname"), equalTo(example.getDefaultStringMatcher())); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldUseConfiguredStringMatcherAsDefaultForPathThatDoesNotHavePropertySpecifier() { + + example = newExampleOf(person).withStringMatcher(StringMatcher.CONTAINING).get(); + + assertThat(example.getDefaultStringMatcher(), equalTo(StringMatcher.CONTAINING)); + assertThat(example.getStringMatcherForPath("firstname"), equalTo(StringMatcher.CONTAINING)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldUseDefinedStringMatcherForPathThatDoesHavePropertySpecifierWithStringMatcher() { + + example = newExampleOf(person).withPropertySpecifier( + PropertySpecifier.newPropertySpecifier("firstname").matchString(StringMatcher.CONTAINING).get()).get(); + + assertThat(example.getStringMatcherForPath("firstname"), equalTo(StringMatcher.CONTAINING)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldFavorStringMatcherDefinedForPathOverConfiguredDefaultStringMatcher() { + + example = newExampleOf(person) + .withStringMatcher(StringMatcher.STARTING) + .withPropertySpecifier( + PropertySpecifier.newPropertySpecifier("firstname").matchString(StringMatcher.CONTAINING).get()).get(); + + assertThat(example.getDefaultStringMatcher(), equalTo(StringMatcher.STARTING)); + assertThat(example.getStringMatcherForPath("firstname"), equalTo(StringMatcher.CONTAINING)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void exampleShouldUseDefaultStringMatcherForPathThatHasPropertySpecifierWithoutStringMatcher() { + + example = newExampleOf(person).withStringMatcher(StringMatcher.STARTING) + .withPropertySpecifier(ignoreCase("firstname")).get(); + + assertThat(example.getStringMatcherForPath("firstname"), equalTo(StringMatcher.STARTING)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void isIgnoredPathShouldReturnFalseWhenNoPathsIgnored() { + + assertThat(example.getIgnoredPaths(), is(empty())); + assertThat(example.isIgnoredPath("firstname"), is(false)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void isIgnoredPathShouldReturnTrueWhenNoPathsIgnored() { + + example = newExampleOf(person).ignore("firstname").get(); + + assertThat(example.getIgnoredPaths(), hasItem("firstname")); + assertThat(example.isIgnoredPath("firstname"), is(true)); + } + + /** + * @see DATACMNS-810 + */ + @Test(expected = UnsupportedOperationException.class) + public void ignoredPathsShouldNotAllowModification() { + example.getIgnoredPaths().add("o_O"); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void ignoreCaseShouldReturnFalseByDefault() { + + assertThat(example.isIngnoreCaseEnabled(), is(false)); + assertThat(example.isIgnoreCaseForPath("firstname"), is(false)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void ignoreCaseShouldReturnTrueWhenIgnoreCaseIsEnabled() { + + example = newExampleOf(person).matchStringsWithIgnoreCase().get(); + + assertThat(example.isIngnoreCaseEnabled(), is(true)); + assertThat(example.isIgnoreCaseForPath("firstname"), is(true)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void ignoreCaseShouldFavorPathSpecificSettings() { + + example = newExampleOf(person).withPropertySpecifier(ignoreCase("firstname")).get(); + + assertThat(example.isIngnoreCaseEnabled(), is(false)); + assertThat(example.isIgnoreCaseForPath("firstname"), is(true)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void getValueTransformerForPathReturnsNoOpValueTransformerByDefault() { + assertThat(example.getValueTransformerForPath("firstname"), instanceOf(NoOpPropertyValueTransformer.class)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void getValueTransformerForPathReturnsConfigurtedTransformerForPath() { + + PropertyValueTransformer transformer = new PropertyValueTransformer() { + + @Override + public Object convert(Object source) { + return source.toString(); + } + }; + + example = newExampleOf(person).withPropertySpecifier( + newPropertySpecifier("firstname").withValueTransformer(transformer).get()).get(); + + assertThat(example.getValueTransformerForPath("firstname"), equalTo(transformer)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void hasPropertySpecifiersReturnsFalseIfNoneDefined() { + assertThat(example.hasPropertySpecifiers(), is(false)); + } + + /** + * @see DATACMNS-810 + */ + @Test(expected = UnsupportedOperationException.class) + public void getPropertiesSpecifiersShouldNotAllowAddingSpecifiers() { + example.getPropertySpecifiers().add(ignoreCase("firstname")); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void hasPropertySpecifiersReturnsTrueWhenAtLeastOneIsSet() { + + example = newExampleOf(person) + .withStringMatcher(StringMatcher.STARTING) + .withPropertySpecifier( + PropertySpecifier.newPropertySpecifier("firstname").matchString(StringMatcher.CONTAINING).get()).get(); + + assertThat(example.hasPropertySpecifiers(), is(true)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void getSampleTypeRetunsSampleObjectsClass() { + assertThat(example.getSampleType(), equalTo(Person.class)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void getNullHandlerShouldReturnIgnoreByDefault() { + assertThat(example.getNullHandler(), is(NullHandler.IGNORE)); + } + + /** + * @see DATACMNS-810 + */ + @Test + public void getNullHandlerShouldReturnConfiguredHandler() { + + example = newExampleOf(person).handleNullValues(NullHandler.INCLUDE).get(); + assertThat(example.getNullHandler(), is(NullHandler.INCLUDE)); + } + + static class Person { + + String firstname; + } + +}