Enforce non-null contract on vector elements.

Also add shortcuts for empty sources and simplify copy calls that do not transform source values.

See #3193
Original pull request: #3194
This commit is contained in:
Christoph Strobl
2025-01-28 09:51:46 +01:00
committed by Mark Paluch
parent 059d09a224
commit db382681a8
7 changed files with 130 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.
@@ -30,7 +30,7 @@ class DoubleVector implements Vector {
private final double[] v;
public DoubleVector(double[] v) {
DoubleVector(double[] v) {
this.v = v;
}
@@ -39,10 +39,11 @@ class DoubleVector implements Vector {
*/
static Vector copy(double[] v) {
double[] copy = new double[v.length];
System.arraycopy(v, 0, copy, 0, copy.length);
if (v.length == 0) {
return new DoubleVector(new double[0]);
}
return new DoubleVector(copy);
return new DoubleVector(Arrays.copyOf(v, v.length));
}
/**
@@ -50,6 +51,10 @@ class DoubleVector implements Vector {
*/
static Vector copy(Collection<? extends Number> v) {
if (v.isEmpty()) {
return new DoubleVector(new double[0]);
}
double[] copy = new double[v.size()];
int i = 0;
for (Number number : v) {
@@ -87,11 +92,7 @@ class DoubleVector implements Vector {
@Override
public double[] toDoubleArray() {
double[] copy = new double[this.v.length];
System.arraycopy(this.v, 0, copy, 0, copy.length);
return copy;
return Arrays.copyOf(this.v, this.v.length);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.
@@ -30,7 +30,7 @@ class FloatVector implements Vector {
private final float[] v;
public FloatVector(float[] v) {
FloatVector(float[] v) {
this.v = v;
}
@@ -39,10 +39,11 @@ class FloatVector implements Vector {
*/
static Vector copy(float[] v) {
float[] copy = new float[v.length];
System.arraycopy(v, 0, copy, 0, copy.length);
if (v.length == 0) {
return new FloatVector(new float[0]);
}
return new FloatVector(copy);
return new FloatVector(Arrays.copyOf(v, v.length));
}
/**
@@ -50,6 +51,10 @@ class FloatVector implements Vector {
*/
static Vector copy(Collection<? extends Number> v) {
if (v.isEmpty()) {
return new FloatVector(new float[0]);
}
float[] copy = new float[v.size()];
int i = 0;
for (Number number : v) {
@@ -76,11 +81,7 @@ class FloatVector implements Vector {
@Override
public float[] toFloatArray() {
float[] copy = new float[this.v.length];
System.arraycopy(this.v, 0, copy, 0, copy.length);
return copy;
return Arrays.copyOf(this.v, this.v.length);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.
@@ -18,6 +18,7 @@ package org.springframework.data.domain;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
@@ -30,7 +31,9 @@ class NumberVector implements Vector {
private final Number[] v;
public NumberVector(Number[] v) {
NumberVector(Number[] v) {
Assert.noNullElements(v, "Vector [v] must not contain null elements");
this.v = v;
}
@@ -39,41 +42,39 @@ class NumberVector implements Vector {
*/
static Vector copy(Number[] v) {
Number[] copy = new Number[v.length];
System.arraycopy(v, 0, copy, 0, copy.length);
if (v.length == 0) {
return new NumberVector(new Number[0]);
}
return new NumberVector(copy);
return new NumberVector(Arrays.copyOf(v, v.length));
}
/**
* Copy the given {@link Number} and wrap it within a Vector.
*/
static Vector copy(Collection<? extends Number> numbers) {
static Vector copy(Collection<? extends Number> v) {
Number[] copy = new Number[numbers.size()];
int i = 0;
for (Number number : numbers) {
copy[i++] = number;
if (v.isEmpty()) {
return new NumberVector(new Number[0]);
}
return new NumberVector(copy);
return new NumberVector(v.toArray(Number[]::new));
}
@Override
public Class<? extends Number> getType() {
Class<?> candidate = null;
for (Object val : v) {
if (val != null) {
if (candidate == null) {
candidate = val.getClass();
} else if (candidate != val.getClass()) {
return Number.class;
}
if (this.v.length == 0) {
return Number.class;
}
Class<? extends Number> candidate = this.v[0].getClass();
for (int i = 1; i < this.v.length; i++) {
if (candidate != this.v[i].getClass()) {
return Number.class;
}
}
return (Class<? extends Number>) candidate;
return candidate;
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.
@@ -75,6 +75,9 @@ public interface Vector {
static Vector of(Collection<? extends Number> values) {
Assert.notNull(values, "Vector values must not be null");
if(values.isEmpty()) {
return NumberVector.copy(new Number[0]);
}
Class<?> cet = CollectionUtils.findCommonElementType(values);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* Copyright 2025 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.
@@ -15,9 +15,12 @@
*/
package org.springframework.data.domain;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -25,12 +28,40 @@ import org.junit.jupiter.api.Test;
* Unit tests for {@link NumberVector}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
class NumberVectorUnitTests {
Number[] values = new Number[] { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6f };
Number[] floats = new Number[] { (float) 1.1d, (float) 2.2d, (float) 3.3d, (float) 4.4d, (float) 5.5, 6.6 };
@Test // GH-3193
void shouldErrorOnNullElements() {
List<Long> source = new ArrayList<>(3);
source.add(1L);
source.add(null);
source.add(3L);
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> NumberVector.copy(source));
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> NumberVector.copy(new Number[] { 1L, null, 3L }));
}
@Test // GH-3193
void shouldAcceptEmptySource() {
Vector vector = NumberVector.copy(List.of());
assertThat(vector.size()).isEqualTo(0);
assertThat(vector.getType()).isEqualTo(Number.class);
vector = NumberVector.copy(new Number[] {});
assertThat(vector.size()).isEqualTo(0);
assertThat(vector.getType()).isEqualTo(Number.class);
}
@Test // GH-3193
void shouldCreateVector() {
@@ -48,6 +79,17 @@ class NumberVectorUnitTests {
assertThat(vector.getSource()).isNotSameAs(vector).isEqualTo(values);
}
@Test // GH-3193
void shouldFigureOutCommonType() {
assertThat(NumberVector.copy(List.of()).getType()).isEqualTo(Number.class);
assertThat(NumberVector.copy(List.of(1)).getType()).isEqualTo(Integer.class);
assertThat(NumberVector.copy(List.of(1L, 2L)).getType()).isEqualTo(Long.class);
assertThat(NumberVector.copy(List.of(1F, 2F)).getType()).isEqualTo(Float.class);
assertThat(NumberVector.copy(List.of(1D, 2D)).getType()).isEqualTo(Double.class);
assertThat(NumberVector.copy(List.of(1D, 2F, 3F)).getType()).isEqualTo(Number.class);
}
@Test // GH-3193
void shouldRenderToString() {
@@ -66,7 +108,7 @@ class NumberVectorUnitTests {
}
@Test // GH-3193
void sourceShouldReturnSource() {
void sourceShouldReturnSource() { // this one is questionable
Vector vector = new NumberVector(values);

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 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
*
* https://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.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.Collection;
import org.junit.jupiter.api.Test;
/**
* @author Christoph Strobl
*/
public class VectorUnitTests {
@Test // GH-3193
void staticInitializersErrorOnNull() {
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> Vector.of((double[]) null));
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> Vector.of((float[]) null));
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> Vector.of((Collection<Number>) null));
}
}