GH-3003 - Add vector type support.

This commit combines Spring Data Commons' Vector type
with the Neo4j vector functionality.
Fields defined as Spring Data Commons `Vector` will get
persisted through the `setNodeVectorProperty` procedure.

Closes #3003

Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
This commit is contained in:
Gerrit Meier
2025-04-16 16:59:38 +02:00
parent a86b0a3dc3
commit bbf261e750
12 changed files with 211 additions and 43 deletions

View File

@@ -188,6 +188,10 @@ If you require the time zone, use a type that supports it (i.e. `ZoneDateTime`)
|Point with CRS 4326 and x/y corresponding to lat/long
|
|`org.springframework.data.domain.Vector`
|persisted through `setNodeVectorProperty`
|
|Instances of `Enum`
|String (The name value of the enum)
|
@@ -210,6 +214,17 @@ If you require the time zone, use a type that supports it (i.e. `ZoneDateTime`)
|===
[[build-in.conversions.vector]]
=== Vector type
Spring Data has its own type for vector representation `org.springframework.data.domain.Vector`.
While this can be used as a wrapper around a `float` or `double` array, Spring Data Neo4j supports only the `double` variant right now.
From a user perspective, it is possible to only define the `Vector` interface on the property definition and use either `double` or `float`.
Neo4j will store both `double` and `float` variants as a 64-bit Cypher `FLOAT` value, which is consistent with values persisted through Cypher and the dedicated `setNodeVectorProperty` function that Spring Data Neo4j uses to persist the property.
NOTE: Spring Data Neo4j only allows one `Vector` property to be present in an entity definition.
NOTE: Please be aware that a persisted `float` value differs from a read back value due to the nature of floating numbers.
[[custom.conversions]]
== Custom conversions

View File

@@ -50,6 +50,7 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.data.convert.ConverterBuilder;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.domain.Vector;
import org.springframework.data.mapping.MappingException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -104,7 +105,7 @@ final class AdditionalTypes {
hlp.add(ConverterBuilder.reading(Value.class, Node.class, Value::asNode));
hlp.add(ConverterBuilder.reading(Value.class, Relationship.class, Value::asRelationship));
hlp.add(ConverterBuilder.reading(Value.class, Map.class, Value::asMap).andWriting(AdditionalTypes::value));
hlp.add(ConverterBuilder.reading(Value.class, Vector.class, AdditionalTypes::asVector).andWriting(AdditionalTypes::value));
CONVERTERS = Collections.unmodifiableList(hlp);
}
@@ -112,6 +113,10 @@ final class AdditionalTypes {
return Values.value(map);
}
static Value value(Vector vector) {
return Values.value(vector.toDoubleArray());
}
static TimeZone asTimeZone(Value value) {
return TimeZone.getTimeZone(value.asString());
}
@@ -462,6 +467,11 @@ final class AdditionalTypes {
return array;
}
static Vector asVector(Value value) {
double[] array = asDoubleArray(value);
return Vector.of(array);
}
static Value value(short[] aShortArray) {
if (aShortArray == null) {
return Values.NULL;

View File

@@ -58,6 +58,8 @@ public final class Constants {
public static final String NAME_OF_ID = "__id__";
public static final String NAME_OF_VERSION_PARAM = "__version__";
public static final String NAME_OF_PROPERTIES_PARAM = "__properties__";
public static final String NAME_OF_VECTOR_PROPERTY = "__vectorProperty__";
public static final String NAME_OF_VECTOR_VALUE = "__vectorValue__";
/**
* Indicates the parameter that contains the static labels which are required to correctly compute the difference
* in the list of dynamic labels when saving a node.

View File

@@ -308,6 +308,17 @@ public enum CypherGenerator {
Assert.notNull(idDescription, "Cannot save individual nodes without an id attribute");
Parameter<?> idParameter = parameter(Constants.NAME_OF_ID);
Function<StatementBuilder.OngoingMatchAndUpdate, Statement> vectorProcedureCall = (bs) -> {
if (((Neo4jPersistentEntity<?>) nodeDescription).hasVectorProperty()) {
return bs.with(rootNode)
.call("db.create.setNodeVectorProperty")
.withArgs(rootNode.getRequiredSymbolicName(), parameter(Constants.NAME_OF_VECTOR_PROPERTY), parameter(Constants.NAME_OF_VECTOR_VALUE))
.withoutResults()
.returning(rootNode).build();
}
return bs.returning(rootNode).build();
};
if (!idDescription.isInternallyGeneratedId()) {
GraphPropertyDescription idPropertyDescription = ((Neo4jPersistentEntity<?>) nodeDescription).getRequiredIdProperty();
@@ -316,92 +327,79 @@ public enum CypherGenerator {
String nameOfPossibleExistingNode = "hlp";
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
Statement createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
Statement createIfNew = vectorProcedureCall.apply(updateDecorator.apply(optionalMatch(possibleExistingNode)
.where(createCompositePropertyCondition(idPropertyDescription, possibleExistingNode.getRequiredSymbolicName(), idParameter))
.with(possibleExistingNode)
.where(possibleExistingNode.isNull())
.create(rootNode.withProperties(versionProperty, literalOf(0)))
.with(rootNode)
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode)
.build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
Statement updateIfExists = updateDecorator.apply(match(rootNode)
Statement updateIfExists = vectorProcedureCall.apply(updateDecorator.apply(match(rootNode)
.where(createCompositePropertyCondition(idPropertyDescription, rootNode.getRequiredSymbolicName(), idParameter))
.and(versionProperty.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))) // Initial check
.set(versionProperty.to(versionProperty.add(literalOf(1)))) // Acquire lock
.with(rootNode)
.where(versionProperty.isEqualTo(coalesce(parameter(Constants.NAME_OF_VERSION_PARAM), literalOf(0)).add(
literalOf(1))))
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
.returning(rootNode)
.build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
return Cypher.union(createIfNew, updateIfExists);
} else {
String nameOfPossibleExistingNode = "hlp";
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
Statement createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
Statement createIfNew = vectorProcedureCall.apply(updateDecorator.apply(optionalMatch(possibleExistingNode)
.where(createCompositePropertyCondition(idPropertyDescription, possibleExistingNode.getRequiredSymbolicName(), idParameter))
.with(possibleExistingNode)
.where(possibleExistingNode.isNull())
.create(rootNode)
.with(rootNode)
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode)
.build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
Statement updateIfExists = updateDecorator.apply(match(rootNode)
Statement updateIfExists = vectorProcedureCall.apply(updateDecorator.apply(match(rootNode)
.where(createCompositePropertyCondition(idPropertyDescription, rootNode.getRequiredSymbolicName(), idParameter))
.with(rootNode)
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
.returning(rootNode)
.build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
return Cypher.union(createIfNew, updateIfExists);
}
} else {
String nameOfPossibleExistingNode = "hlp";
Node possibleExistingNode = node(primaryLabel, additionalLabels).named(nameOfPossibleExistingNode);
Statement createIfNew;
Statement updateIfExists;
var neo4jPersistentEntity = (Neo4jPersistentEntity<?>) nodeDescription;
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity, canUseElementId);
if (neo4jPersistentEntity.hasVersionProperty()) {
Property versionProperty = rootNode.property(neo4jPersistentEntity.getRequiredVersionProperty().getName());
createIfNew = updateDecorator.apply(optionalMatch(possibleExistingNode)
var createIfNew = vectorProcedureCall.apply(updateDecorator.apply(optionalMatch(possibleExistingNode)
.where(nodeIdFunction.apply(possibleExistingNode).isEqualTo(idParameter))
.with(possibleExistingNode)
.where(possibleExistingNode.isNull())
.create(rootNode.withProperties(versionProperty, literalOf(0)))
.with(rootNode)
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
.returning(rootNode)
.build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
updateIfExists = updateDecorator.apply(match(rootNode)
var updateIfExists = vectorProcedureCall.apply(updateDecorator.apply(match(rootNode)
.where(nodeIdFunction.apply(rootNode).isEqualTo(idParameter))
.and(versionProperty.isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM))) // Initial check
.set(versionProperty.to(versionProperty.add(literalOf(1)))) // Acquire lock
.with(rootNode)
.where(versionProperty.isEqualTo(coalesce(parameter(Constants.NAME_OF_VERSION_PARAM), literalOf(0)).add(
literalOf(1))))
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
.returning(rootNode).build();
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
return Cypher.union(createIfNew, updateIfExists);
} else {
createIfNew = updateDecorator
.apply(optionalMatch(possibleExistingNode).where(nodeIdFunction.apply(possibleExistingNode).isEqualTo(idParameter))
var createStatement = vectorProcedureCall.apply(updateDecorator.apply(optionalMatch(possibleExistingNode).where(nodeIdFunction.apply(possibleExistingNode).isEqualTo(idParameter))
.with(possibleExistingNode).where(possibleExistingNode.isNull()).create(rootNode)
.set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM)))
.returning(rootNode).build();
.set(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
var updateStatement = vectorProcedureCall.apply(updateDecorator.apply(match(rootNode).where(nodeIdFunction.apply(rootNode).isEqualTo(idParameter))
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))));
updateIfExists = updateDecorator.apply(match(rootNode).where(nodeIdFunction.apply(rootNode).isEqualTo(idParameter))
.mutate(rootNode, parameter(Constants.NAME_OF_PROPERTIES_PARAM))).returning(rootNode).build();
return Cypher.union(createStatement, updateStatement);
}
return Cypher.union(createIfNew, updateIfExists);
}
}

View File

@@ -242,14 +242,13 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
PropertyHandlerSupport.of(nodeDescription).doWithProperties((Neo4jPersistentProperty p) -> {
// Skip the internal properties, we don't want them to end up stored as properties
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty() || p.isReadOnly()) {
if (p.isInternalIdProperty() || p.isDynamicLabels() || p.isEntity() || p.isVersionProperty() || p.isReadOnly() || p.isVectorProperty()) {
return;
}
final Value value = conversionService.writeValue(propertyAccessor.getProperty(p), p.getTypeInformation(), p.getOptionalConverter());
if (p.isComposite()) {
properties.put(p.getPropertyName(), new MapValueWrapper(value));
//value.keys().forEach(k -> properties.put(k, value.get(k)));
} else {
properties.put(p.getPropertyName(), value);
}
@@ -270,6 +269,14 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
// we incremented this upfront the persist operation so the matching version would be one "before"
parameters.put(Constants.NAME_OF_VERSION_PARAM, versionProperty);
}
// special handling for vector property to provide the needed procedure information
if (nodeDescription.hasVectorProperty()) {
Neo4jPersistentProperty vectorProperty = nodeDescription.getRequiredVectorProperty();
parameters.put(Constants.NAME_OF_VECTOR_PROPERTY, vectorProperty.getPropertyName());
parameters.put(Constants.NAME_OF_VECTOR_VALUE, conversionService.writeValue(propertyAccessor.getProperty(vectorProperty), vectorProperty.getTypeInformation(), vectorProperty.getOptionalConverter()));
return;
}
}
/**

View File

@@ -35,6 +35,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.log.LogAccessor;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.domain.Vector;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.neo4j.core.schema.DynamicLabels;
@@ -88,6 +89,8 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
private List<NodeDescription<?>> childNodeDescriptionsInHierarchy;
private final Lazy<Neo4jPersistentProperty> vectorProperty;
DefaultNeo4jPersistentEntity(TypeInformation<T> information) {
super(information);
@@ -99,6 +102,8 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
this.isRelationshipPropertiesEntity = Lazy.of(() -> isAnnotationPresent(RelationshipProperties.class));
this.idDescription = Lazy.of(this::computeIdDescription);
this.childNodeDescriptionsInHierarchy = computeChildNodeDescriptionInHierarchy();
this.vectorProperty = Lazy.of(() -> getGraphProperties().stream().map(Neo4jPersistentProperty.class::cast)
.filter(Neo4jPersistentProperty::isVectorProperty).findFirst().orElse(null));
}
/*
@@ -212,6 +217,7 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
verifyDynamicAssociations();
verifyAssociationsWithProperties();
verifyDynamicLabels();
verifyAtMostOneVectorDefinition();
}
private void verifyIdDescription() {
@@ -301,6 +307,18 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
DynamicLabels.class.getSimpleName(), namesOfPropertiesWithDynamicLabels));
}
private void verifyAtMostOneVectorDefinition() {
List<Neo4jPersistentProperty> foundVectorDefinition = new ArrayList<>();
PropertyHandlerSupport.of(this).doWithProperties(persistentProperty -> {
if (persistentProperty.getType().isAssignableFrom(Vector.class)) {
foundVectorDefinition.add(persistentProperty);
}
});
Assert.state(foundVectorDefinition.size() <= 1, () -> String.format("There are multiple fields of type %s in entity %s: %s",
Vector.class.toString(), this.getName(), foundVectorDefinition.stream().map(p -> p.getPropertyName()).toList()));
}
/**
* The primary label will get computed and returned by following rules:<br>
* 1. If there is no {@link Node} annotation, use the class name.<br>
@@ -410,6 +428,23 @@ final class DefaultNeo4jPersistentEntity<T> extends BasicPersistentEntity<T, Neo
return this.getTypeInformation().getRawTypeInformation().getType().isInterface();
}
@Override
public boolean hasVectorProperty() {
return Optional.ofNullable(getVectorProperty()).map(v -> true).orElse(false);
}
public Neo4jPersistentProperty getVectorProperty() {
return this.vectorProperty.getNullable();
}
public Neo4jPersistentProperty getRequiredVectorProperty() {
Neo4jPersistentProperty property = getVectorProperty();
if (property != null) {
return property;
}
throw new IllegalStateException(String.format("Required vector property not found for %s", this.getType()));
}
private static boolean hasEmptyLabelInformation(Node nodeAnnotation) {
return nodeAnnotation.labels().length < 1 && !StringUtils.hasText(nodeAnnotation.primaryLabel());
}

View File

@@ -75,4 +75,9 @@ public interface Neo4jPersistentEntity<T>
}
return isUsingInternalIds() && Neo4jPersistentEntity.DEPRECATED_GENERATED_ID_TYPES.contains(getRequiredIdProperty().getType());
}
boolean hasVectorProperty();
Neo4jPersistentProperty getVectorProperty();
Neo4jPersistentProperty getRequiredVectorProperty();
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.core.mapping;
import java.util.Optional;
import org.apiguardian.api.API;
import org.springframework.data.domain.Vector;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyConverter;
import org.springframework.data.neo4j.core.schema.CompositeProperty;
@@ -67,6 +68,10 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
return this.isAnnotationPresent(DynamicLabels.class) && this.isCollectionLike();
}
default boolean isVectorProperty() {
return this.getType().isAssignableFrom(Vector.class);
}
@Nullable
Neo4jPersistentPropertyConverter<?> getOptionalConverter();

View File

@@ -32,6 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Vector;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.neo4j.core.convert.ConvertWith;
@@ -378,6 +379,27 @@ class DefaultNeo4jPersistentEntityTest {
}
}
@Nested
class VectorType {
@Test
void validVectorProperties() {
Neo4jPersistentEntity<?> persistentEntity = new Neo4jMappingContext()
.getPersistentEntity(VectorValid.class);
assertThat(persistentEntity.getPersistentProperty("vectorProperty").isVectorProperty());
}
@Test
void invalidVectorProperties() {
assertThatIllegalStateException()
.isThrownBy(() -> new Neo4jMappingContext().getPersistentEntity(VectorInvalid.class))
.withMessageContaining("There are multiple fields of type interface org.springframework.data.domain.Vector in entity org.springframework.data.neo4j.core.mapping.DefaultNeo4jPersistentEntityTest$VectorInvalid:")
// the order of properties might be not the same all the time
.withMessageContaining("vectorProperty1")
.withMessageContaining("vectorProperty2");
}
}
@Node
private static class SomeOtherNode {
@Id Long id;
@@ -751,4 +773,21 @@ class DefaultNeo4jPersistentEntityTest {
static class IWillBeConverted {
}
@Node
static class VectorValid {
@Id @GeneratedValue
private Long id;
Vector vectorProperty;
}
@Node
static class VectorInvalid {
@Id @GeneratedValue
private Long id;
Vector vectorProperty1;
Vector vectorProperty2;
}
}

View File

@@ -210,11 +210,34 @@ class TypeConversionIT extends Neo4jConversionsITBase {
Map<String, Object> parameters = new HashMap<>();
parameters.put("id", id);
parameters.put("attribute", fieldName);
parameters.put("v", driverValue);
long cnt = session
.run("MATCH (n) WHERE id(n) = $id AND n[$attribute] = $v RETURN COUNT(n) AS cnt", parameters)
.single().get("cnt").asLong();
long cnt = 0L;
// the procedure will convert the value eventually and thus the equals check
// cannot be applied anymore
if (fieldName.equals("aVector")) {
var doubleList = driverValue.asList(v -> v.asDouble());
parameters.put("v1_lower", doubleList.get(0) - 0.000001d);
parameters.put("v2_lower", doubleList.get(1) - 0.000001d);
parameters.put("v1_upper", doubleList.get(0) + 0.000001d);
parameters.put("v2_upper", doubleList.get(1) + 0.000001d);
cnt = session
.run("""
MATCH (n) WHERE id(n) = $id
AND n[$attribute][0] > $v1_lower
AND n[$attribute][1] > $v2_lower
AND n[$attribute][0] < $v1_upper
AND n[$attribute][1] < $v2_upper
RETURN COUNT(n) AS cnt
""",
parameters)
.single().get("cnt").asLong();
} else {
parameters.put("v", driverValue);
cnt = session
.run("MATCH (n) WHERE id(n) = $id AND n[$attribute] = $v RETURN COUNT(n) AS cnt", parameters)
.single().get("cnt").asLong();
}
assertThat(cnt).isEqualTo(1L);
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.integration.shared.conversion;
import org.junit.jupiter.api.BeforeAll;
import org.neo4j.driver.Session;
import org.neo4j.driver.Values;
import org.springframework.data.domain.Vector;
import org.springframework.data.geo.Point;
import org.springframework.data.neo4j.integration.shared.conversion.ThingWithAllAdditionalTypes.SomeEnum;
import org.springframework.data.neo4j.test.BookmarkCapture;
@@ -126,6 +127,7 @@ public abstract class Neo4jConversionsITBase {
hlp.put("aZoneId", ZoneId.of("America/New_York"));
hlp.put("aZeroPeriod", Period.of(0, 0, 0));
hlp.put("aZeroDuration", Duration.ZERO);
hlp.put("aVector", Vector.of(0.1d, 0.2d));
ADDITIONAL_TYPES = Collections.unmodifiableMap(hlp);
}
@@ -278,7 +280,8 @@ public abstract class Neo4jConversionsITBase {
n.anEnum = 'TheUsualMisfit', n.anArrayOfEnums = ['ValueA', 'ValueB'],
n.aCollectionOfEnums = ['ValueC', 'TheUsualMisfit'],
n.aTimeZone = 'America/Los_Angeles',\s
n.aZoneId = 'America/New_York', n.aZeroPeriod = duration('PT0S'), n.aZeroDuration = duration('PT0S')
n.aZoneId = 'America/New_York', n.aZeroPeriod = duration('PT0S'), n.aZeroDuration = duration('PT0S'),
n.aVector = [0.1, 0.2]
RETURN id(n) AS id
""", parameters).single().get("id").asLong();

View File

@@ -30,6 +30,7 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import org.springframework.data.domain.Vector;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
@@ -43,7 +44,7 @@ import org.springframework.data.neo4j.core.schema.Node;
@Node("AdditionalTypes")
public class ThingWithAllAdditionalTypes {
private ThingWithAllAdditionalTypes(Long id, boolean[] booleanArray, byte aByte, char aChar, char[] charArray, Date aDate, BigDecimal aBigDecimal, BigInteger aBigInteger, double[] doubleArray, float aFloat, float[] floatArray, int anInt, int[] intArray, Locale aLocale, long[] longArray, short aShort, short[] shortArray, Period aPeriod, Duration aDuration, String[] stringArray, List<String> listOfStrings, Set<String> setOfStrings, Instant anInstant, UUID aUUID, URL aURL, URI aURI, SomeEnum anEnum, SomeEnum[] anArrayOfEnums, List<Double> listOfDoubles, List<SomeEnum> aCollectionOfEnums, TimeZone aTimeZone, ZoneId aZoneId, Period aZeroPeriod, Duration aZeroDuration) {
private ThingWithAllAdditionalTypes(Long id, boolean[] booleanArray, byte aByte, char aChar, char[] charArray, Date aDate, BigDecimal aBigDecimal, BigInteger aBigInteger, double[] doubleArray, float aFloat, float[] floatArray, int anInt, int[] intArray, Locale aLocale, long[] longArray, short aShort, short[] shortArray, Period aPeriod, Duration aDuration, String[] stringArray, List<String> listOfStrings, Set<String> setOfStrings, Instant anInstant, UUID aUUID, URL aURL, URI aURI, SomeEnum anEnum, SomeEnum[] anArrayOfEnums, List<Double> listOfDoubles, List<SomeEnum> aCollectionOfEnums, TimeZone aTimeZone, ZoneId aZoneId, Period aZeroPeriod, Duration aZeroDuration, Vector aVector) {
this.id = id;
this.booleanArray = booleanArray;
this.aByte = aByte;
@@ -78,6 +79,7 @@ public class ThingWithAllAdditionalTypes {
this.aZoneId = aZoneId;
this.aZeroPeriod = aZeroPeriod;
this.aZeroDuration = aZeroDuration;
this.aVector = aVector;
}
public static ThingWithAllAdditionalTypesBuilder builder() {
@@ -220,6 +222,10 @@ public class ThingWithAllAdditionalTypes {
return this.aZeroDuration;
}
public Vector getAVector() {
return this.aVector;
}
public void setBooleanArray(boolean[] booleanArray) {
this.booleanArray = booleanArray;
}
@@ -352,6 +358,10 @@ public class ThingWithAllAdditionalTypes {
this.aZeroDuration = aZeroDuration;
}
public void setAVector(Vector aVector) {
this.aVector = aVector;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
@@ -505,6 +515,12 @@ public class ThingWithAllAdditionalTypes {
if (this$aZeroDuration == null ? other$aZeroDuration != null : !this$aZeroDuration.equals(other$aZeroDuration)) {
return false;
}
final Object this$aVector = this.getAVector();
final Object other$aVector = other.getAVector();
if (this$aVector == null ? other$aVector != null : !this$aVector.equals(other$aVector)) {
return false;
}
return true;
}
@@ -569,6 +585,8 @@ public class ThingWithAllAdditionalTypes {
result = result * PRIME + ($aZeroPeriod == null ? 43 : $aZeroPeriod.hashCode());
final Object $aZeroDuration = this.getAZeroDuration();
result = result * PRIME + ($aZeroDuration == null ? 43 : $aZeroDuration.hashCode());
final Object $aVector = this.getAVector();
result = result * PRIME + ($aVector == null ? 43 : $aVector.hashCode());
return result;
}
@@ -577,7 +595,7 @@ public class ThingWithAllAdditionalTypes {
}
public ThingWithAllAdditionalTypes withId(Long id) {
return this.id == id ? this : new ThingWithAllAdditionalTypes(id, this.booleanArray, this.aByte, this.aChar, this.charArray, this.aDate, this.aBigDecimal, this.aBigInteger, this.doubleArray, this.aFloat, this.floatArray, this.anInt, this.intArray, this.aLocale, this.longArray, this.aShort, this.shortArray, this.aPeriod, this.aDuration, this.stringArray, this.listOfStrings, this.setOfStrings, this.anInstant, this.aUUID, this.aURL, this.aURI, this.anEnum, this.anArrayOfEnums, this.listOfDoubles, this.aCollectionOfEnums, this.aTimeZone, this.aZoneId, this.aZeroPeriod, this.aZeroDuration);
return this.id == id ? this : new ThingWithAllAdditionalTypes(id, this.booleanArray, this.aByte, this.aChar, this.charArray, this.aDate, this.aBigDecimal, this.aBigInteger, this.doubleArray, this.aFloat, this.floatArray, this.anInt, this.intArray, this.aLocale, this.longArray, this.aShort, this.shortArray, this.aPeriod, this.aDuration, this.stringArray, this.listOfStrings, this.setOfStrings, this.anInstant, this.aUUID, this.aURL, this.aURI, this.anEnum, this.anArrayOfEnums, this.listOfDoubles, this.aCollectionOfEnums, this.aTimeZone, this.aZoneId, this.aZeroPeriod, this.aZeroDuration, this.aVector);
}
enum SomeEnum {
@@ -654,6 +672,8 @@ public class ThingWithAllAdditionalTypes {
private Duration aZeroDuration;
private Vector aVector;
/**
* the builder
*/
@@ -692,6 +712,7 @@ public class ThingWithAllAdditionalTypes {
private ZoneId aZoneId;
private Period aZeroPeriod;
private Duration aZeroDuration;
private Vector aVector;
ThingWithAllAdditionalTypesBuilder() {
}
@@ -866,12 +887,17 @@ public class ThingWithAllAdditionalTypes {
return this;
}
public ThingWithAllAdditionalTypesBuilder aVector(Vector vector) {
this.aVector = aVector;
return this;
}
public ThingWithAllAdditionalTypes build() {
return new ThingWithAllAdditionalTypes(this.id, this.booleanArray, this.aByte, this.aChar, this.charArray, this.aDate, this.aBigDecimal, this.aBigInteger, this.doubleArray, this.aFloat, this.floatArray, this.anInt, this.intArray, this.aLocale, this.longArray, this.aShort, this.shortArray, this.aPeriod, this.aDuration, this.stringArray, this.listOfStrings, this.setOfStrings, this.anInstant, this.aUUID, this.aURL, this.aURI, this.anEnum, this.anArrayOfEnums, this.listOfDoubles, this.aCollectionOfEnums, this.aTimeZone, this.aZoneId, this.aZeroPeriod, this.aZeroDuration);
return new ThingWithAllAdditionalTypes(this.id, this.booleanArray, this.aByte, this.aChar, this.charArray, this.aDate, this.aBigDecimal, this.aBigInteger, this.doubleArray, this.aFloat, this.floatArray, this.anInt, this.intArray, this.aLocale, this.longArray, this.aShort, this.shortArray, this.aPeriod, this.aDuration, this.stringArray, this.listOfStrings, this.setOfStrings, this.anInstant, this.aUUID, this.aURL, this.aURI, this.anEnum, this.anArrayOfEnums, this.listOfDoubles, this.aCollectionOfEnums, this.aTimeZone, this.aZoneId, this.aZeroPeriod, this.aZeroDuration, this.aVector);
}
public String toString() {
return "ThingWithAllAdditionalTypes.ThingWithAllAdditionalTypesBuilder(id=" + this.id + ", booleanArray=" + java.util.Arrays.toString(this.booleanArray) + ", aByte=" + this.aByte + ", aChar=" + this.aChar + ", charArray=" + java.util.Arrays.toString(this.charArray) + ", aDate=" + this.aDate + ", aBigDecimal=" + this.aBigDecimal + ", aBigInteger=" + this.aBigInteger + ", doubleArray=" + java.util.Arrays.toString(this.doubleArray) + ", aFloat=" + this.aFloat + ", floatArray=" + java.util.Arrays.toString(this.floatArray) + ", anInt=" + this.anInt + ", intArray=" + java.util.Arrays.toString(this.intArray) + ", aLocale=" + this.aLocale + ", longArray=" + java.util.Arrays.toString(this.longArray) + ", aShort=" + this.aShort + ", shortArray=" + java.util.Arrays.toString(this.shortArray) + ", aPeriod=" + this.aPeriod + ", aDuration=" + this.aDuration + ", stringArray=" + java.util.Arrays.deepToString(this.stringArray) + ", listOfStrings=" + this.listOfStrings + ", setOfStrings=" + this.setOfStrings + ", anInstant=" + this.anInstant + ", aUUID=" + this.aUUID + ", aURL=" + this.aURL + ", aURI=" + this.aURI + ", anEnum=" + this.anEnum + ", anArrayOfEnums=" + java.util.Arrays.deepToString(this.anArrayOfEnums) + ", listOfDoubles=" + this.listOfDoubles + ", aCollectionOfEnums=" + this.aCollectionOfEnums + ", aTimeZone=" + this.aTimeZone + ", aZoneId=" + this.aZoneId + ", aZeroPeriod=" + this.aZeroPeriod + ", aZeroDuration=" + this.aZeroDuration + ")";
return "ThingWithAllAdditionalTypes.ThingWithAllAdditionalTypesBuilder(id=" + this.id + ", booleanArray=" + java.util.Arrays.toString(this.booleanArray) + ", aByte=" + this.aByte + ", aChar=" + this.aChar + ", charArray=" + java.util.Arrays.toString(this.charArray) + ", aDate=" + this.aDate + ", aBigDecimal=" + this.aBigDecimal + ", aBigInteger=" + this.aBigInteger + ", doubleArray=" + java.util.Arrays.toString(this.doubleArray) + ", aFloat=" + this.aFloat + ", floatArray=" + java.util.Arrays.toString(this.floatArray) + ", anInt=" + this.anInt + ", intArray=" + java.util.Arrays.toString(this.intArray) + ", aLocale=" + this.aLocale + ", longArray=" + java.util.Arrays.toString(this.longArray) + ", aShort=" + this.aShort + ", shortArray=" + java.util.Arrays.toString(this.shortArray) + ", aPeriod=" + this.aPeriod + ", aDuration=" + this.aDuration + ", stringArray=" + java.util.Arrays.deepToString(this.stringArray) + ", listOfStrings=" + this.listOfStrings + ", setOfStrings=" + this.setOfStrings + ", anInstant=" + this.anInstant + ", aUUID=" + this.aUUID + ", aURL=" + this.aURL + ", aURI=" + this.aURI + ", anEnum=" + this.anEnum + ", anArrayOfEnums=" + java.util.Arrays.deepToString(this.anArrayOfEnums) + ", listOfDoubles=" + this.listOfDoubles + ", aCollectionOfEnums=" + this.aCollectionOfEnums + ", aTimeZone=" + this.aTimeZone + ", aZoneId=" + this.aZoneId + ", aZeroPeriod=" + this.aZeroPeriod + ", aZeroDuration=" + this.aZeroDuration + ", aVector=" + this.aVector + ")";
}
}
}