diff --git a/src/main/java/org/springframework/data/domain/Range.java b/src/main/java/org/springframework/data/domain/Range.java new file mode 100644 index 000000000..5b71a8f4b --- /dev/null +++ b/src/main/java/org/springframework/data/domain/Range.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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.util.Assert; + +/** + * Simple value object to work with ranges. + * + * @author Oliver Gierke + * @since 1.10 + */ +public class Range> { + + private final T lowerBound; + private final T upperBound; + private final boolean lowerInclusive; + private final boolean upperInclusive; + + /** + * Creates a new {@link Range} with the given lower and upper bound. Treats the given values as inclusive bounds. Use + * {@link #Range(Comparable, Comparable, boolean, boolean)} to configure different bound behavior. + * + * @see #Range(Comparable, Comparable, boolean, boolean) + * @param lowerBound can be {@literal null} in case upperBound is not {@literal null}. + * @param upperBound can be {@literal null} in case lowerBound is not {@literal null}. + */ + public Range(T lowerBound, T upperBound) { + this(lowerBound, upperBound, true, true); + } + + /** + * Createsa new {@link Range} with the given lower and upper bound as well as the given inclusive/exclusive semantics. + * + * @param lowerBound can be {@literal null}. + * @param upperBound can be {@literal null}. + * @param lowerInclusive + * @param upperInclusive + */ + public Range(T lowerBound, T upperBound, boolean lowerInclusive, boolean upperInclusive) { + + this.lowerBound = lowerBound; + this.upperBound = upperBound; + this.lowerInclusive = lowerInclusive; + this.upperInclusive = upperInclusive; + } + + /** + * Returns the lower bound of the range. + * + * @return can be {@literal null}. + */ + public T getLowerBound() { + return lowerBound; + } + + /** + * Returns the upper bound of the range. + * + * @return can be {@literal null}. + */ + public T getUpperBound() { + return upperBound; + } + + /** + * Returns whether the {@link Range} contains the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public boolean contains(T value) { + + Assert.notNull(value, "Reference value must not be null!"); + + boolean greaterThanLowerBound = lowerBound == null ? true : lowerInclusive ? lowerBound.compareTo(value) <= 0 + : lowerBound.compareTo(value) < 0; + boolean lessThanUpperBound = upperBound == null ? true : upperInclusive ? upperBound.compareTo(value) >= 0 + : upperBound.compareTo(value) > 0; + + return greaterThanLowerBound && lessThanUpperBound; + } +} diff --git a/src/main/java/org/springframework/data/geo/Distance.java b/src/main/java/org/springframework/data/geo/Distance.java index a2095998f..fdbed6bae 100644 --- a/src/main/java/org/springframework/data/geo/Distance.java +++ b/src/main/java/org/springframework/data/geo/Distance.java @@ -17,6 +17,7 @@ package org.springframework.data.geo; import java.io.Serializable; +import org.springframework.data.domain.Range; import org.springframework.util.Assert; /** @@ -26,7 +27,7 @@ import org.springframework.util.Assert; * @author Thomas Darimont * @since 1.8 */ -public class Distance implements Serializable { +public class Distance implements Serializable, Comparable { private static final long serialVersionUID = 2460886201934027744L; @@ -54,6 +55,30 @@ public class Distance implements Serializable { this.metric = metric == null ? Metrics.NEUTRAL : metric; } + /** + * Creates a {@link Range} between the given {@link Distance}. + * + * @param min can be {@literal null}. + * @param max can be {@literal null}. + * @return will never be {@literal null}. + */ + public static Range between(Distance min, Distance max) { + return new Range(min, max); + } + + /** + * Creates a new {@link Range} by creating minimum and maximum {@link Distance} from the given values. + * + * @param minValue + * @param minMetric can be {@literal null}. + * @param maxValue + * @param maxMetric can be {@literal null}. + * @return + */ + public static Range between(double minValue, Metric minMetric, double maxValue, Metric maxMetric) { + return between(new Distance(minValue, minMetric), new Distance(maxValue, maxMetric)); + } + /** * Returns the distance value in the current {@link Metric}. * @@ -138,6 +163,18 @@ public class Distance implements Serializable { return this.metric.equals(metric) ? this : new Distance(getNormalizedValue() * metric.getMultiplier(), metric); } + /* + * (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(Distance o) { + + double difference = this.getNormalizedValue() - o.getNormalizedValue(); + + return difference == 0 ? 0 : difference > 0 ? 1 : -1; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java index ddffff193..060535ee0 100644 --- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java @@ -17,6 +17,7 @@ package org.springframework.data.util; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; @@ -156,14 +157,8 @@ class TypeDiscoverer implements TypeInformation { */ public List> getParameterTypes(Constructor constructor) { - Assert.notNull(constructor); - List> result = new ArrayList>(); - - for (Type parameterType : constructor.getGenericParameterTypes()) { - result.add(createInfo(parameterType)); - } - - return result; + Assert.notNull(constructor, "Constructor must not be null!"); + return getParameterTypes((Executable) constructor); } /* @@ -412,16 +407,8 @@ class TypeDiscoverer implements TypeInformation { */ public List> getParameterTypes(Method method) { - Assert.notNull(method); - - Type[] parameterTypes = method.getGenericParameterTypes(); - List> result = new ArrayList>(parameterTypes.length); - - for (Type parameterType : parameterTypes) { - result.add(createInfo(parameterType)); - } - - return result; + Assert.notNull(method, "Method most not be null!"); + return getParameterTypes((Executable) method); } /* @@ -492,6 +479,18 @@ class TypeDiscoverer implements TypeInformation { return createInfo(arguments[index]); } + private List> getParameterTypes(Executable executable) { + + Type[] types = executable.getGenericParameterTypes(); + List> result = new ArrayList>(types.length); + + for (Type parameterType : types) { + result.add(createInfo(parameterType)); + } + + return result; + } + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) diff --git a/src/test/java/org/springframework/data/domain/RangeUnitTests.java b/src/test/java/org/springframework/data/domain/RangeUnitTests.java new file mode 100644 index 000000000..086f09f9c --- /dev/null +++ b/src/test/java/org/springframework/data/domain/RangeUnitTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 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.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Unit tests for {@link Range}. + * + * @author Oliver Gierke + * @since 1.10 + */ +public class RangeUnitTests { + + /** + * @see DATACMNS-651 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullReferenceValuesForContains() { + new Range(10L, 20L).contains(null); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void usesBoundsInclusivelyByDefault() { + + Range range = new Range(10L, 20L); + + assertThat(range.contains(10L), is(true)); + assertThat(range.contains(20L), is(true)); + assertThat(range.contains(15L), is(true)); + assertThat(range.contains(5L), is(false)); + assertThat(range.contains(25L), is(false)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void excludesLowerBoundIfConfigured() { + + Range range = new Range(10L, 20L, false, true); + + assertThat(range.contains(10L), is(false)); + assertThat(range.contains(20L), is(true)); + assertThat(range.contains(15L), is(true)); + assertThat(range.contains(5L), is(false)); + assertThat(range.contains(25L), is(false)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void excludesUpperBoundIfConfigured() { + + Range range = new Range(10L, 20L, true, false); + + assertThat(range.contains(10L), is(true)); + assertThat(range.contains(20L), is(false)); + assertThat(range.contains(15L), is(true)); + assertThat(range.contains(5L), is(false)); + assertThat(range.contains(25L), is(false)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void handlesOpenUpperBoundCorrectly() { + + Range range = new Range(10L, null); + + assertThat(range.contains(10L), is(true)); + assertThat(range.contains(20L), is(true)); + assertThat(range.contains(15L), is(true)); + assertThat(range.contains(5L), is(false)); + assertThat(range.contains(25L), is(true)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void handlesOpenLowerBoundCorrectly() { + + Range range = new Range(null, 20L); + + assertThat(range.contains(10L), is(true)); + assertThat(range.contains(20L), is(true)); + assertThat(range.contains(15L), is(true)); + assertThat(range.contains(5L), is(true)); + assertThat(range.contains(25L), is(false)); + } +} diff --git a/src/test/java/org/springframework/data/geo/DistanceUnitTests.java b/src/test/java/org/springframework/data/geo/DistanceUnitTests.java index 7c1f9c583..f22037d57 100644 --- a/src/test/java/org/springframework/data/geo/DistanceUnitTests.java +++ b/src/test/java/org/springframework/data/geo/DistanceUnitTests.java @@ -15,12 +15,12 @@ */ package org.springframework.data.geo; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.number.IsCloseTo.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.data.geo.Metrics.*; import org.junit.Test; +import org.springframework.data.domain.Range; import org.springframework.util.SerializationUtils; /** @@ -145,4 +145,54 @@ public class DistanceUnitTests { public void returnsMetricsAbbreviationAsUnit() { assertThat(new Distance(10, KILOMETERS).getUnit(), is("km")); } + + /** + * @see DATACMNS-651 + */ + @Test + public void createsARangeCorrectly() { + + Distance twoKilometers = new Distance(2, KILOMETERS); + Distance tenKilometers = new Distance(10, KILOMETERS); + + Range range = Distance.between(twoKilometers, tenKilometers); + + assertThat(range, is(notNullValue())); + assertThat(range.getLowerBound(), is(twoKilometers)); + assertThat(range.getUpperBound(), is(tenKilometers)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void createsARangeFromPiecesCorrectly() { + + Distance twoKilometers = new Distance(2, KILOMETERS); + Distance tenKilometers = new Distance(10, KILOMETERS); + + Range range = Distance.between(2, KILOMETERS, 10, KILOMETERS); + + assertThat(range, is(notNullValue())); + assertThat(range.getLowerBound(), is(twoKilometers)); + assertThat(range.getUpperBound(), is(tenKilometers)); + } + + /** + * @see DATACMNS-651 + */ + @Test + public void implementsComparableCorrectly() { + + Distance twoKilometers = new Distance(2, KILOMETERS); + Distance tenKilometers = new Distance(10, KILOMETERS); + Distance tenKilometersInMiles = new Distance(6.21371256214785, MILES); + + assertThat(tenKilometers.compareTo(tenKilometers), is(0)); + assertThat(tenKilometers.compareTo(tenKilometersInMiles), is(0)); + assertThat(tenKilometersInMiles.compareTo(tenKilometers), is(0)); + + assertThat(twoKilometers.compareTo(tenKilometers), is(lessThan(0))); + assertThat(tenKilometers.compareTo(twoKilometers), is(greaterThan(0))); + } }