Skip to content

Commit 15bfd48

Browse files
committed
HV-2139 Introduce an RandomAccessPath as an extension of the org.hibernate.validator.path.Path
Signed-off-by: marko-bekhta <[email protected]>
1 parent ca8c8ae commit 15bfd48

File tree

5 files changed

+142
-8
lines changed

5 files changed

+142
-8
lines changed

engine/src/main/java/org/hibernate/validator/internal/engine/path/MaterializedPath.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@
66

77
import java.io.Serial;
88
import java.io.Serializable;
9+
import java.lang.invoke.MethodHandles;
910
import java.util.Iterator;
1011

11-
import org.hibernate.validator.path.Path;
12+
import org.hibernate.validator.internal.util.logging.Log;
13+
import org.hibernate.validator.internal.util.logging.LoggerFactory;
14+
import org.hibernate.validator.path.RandomAccessPath;
1215

1316

14-
final class MaterializedPath implements Path, Serializable {
17+
final class MaterializedPath implements RandomAccessPath, Serializable {
18+
19+
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
1520

1621
@Serial
17-
private static final long serialVersionUID = 1264131890253015968L;
22+
private static final long serialVersionUID = -329465327521818082L;
1823

1924
private static final String PROPERTY_PATH_SEPARATOR = ".";
2025

@@ -26,6 +31,29 @@ final class MaterializedPath implements Path, Serializable {
2631
this.leafNode = nodes[nodes.length - 1];
2732
}
2833

34+
@Override
35+
public Node getLeafNode() {
36+
return leafNode;
37+
}
38+
39+
@Override
40+
public Node getRootNode() {
41+
return nodes[0];
42+
}
43+
44+
@Override
45+
public Node getNode(int index) {
46+
if ( index < 0 || index >= nodes.length ) {
47+
throw LOG.pathIndexOutOfBounds( index, nodes.length );
48+
}
49+
return nodes[index];
50+
}
51+
52+
@Override
53+
public int length() {
54+
return nodes.length;
55+
}
56+
2957
@Override
3058
public Iterator<Node> iterator() {
3159
return new MaterializedNode.NodeIterator( nodes );
@@ -72,9 +100,4 @@ static String asString(MaterializedNode currentLeafNode) {
72100
}
73101
return builder.toString();
74102
}
75-
76-
@Override
77-
public Node getLeafNode() {
78-
return leafNode;
79-
}
80103
}

engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,4 +959,7 @@ ConstraintDefinitionException getConstraintValidatorDefinitionConstraintMismatch
959959
@Message(id = 272,
960960
value = "Using `@Valid` on a container is deprecated. You should apply the annotation on the type argument(s). (%1$s) can potentially be a container at runtime. Affected element: %2$s")
961961
void potentiallyDeprecatedUseOfValidOnContainer(@FormatWith(ClassObjectFormatter.class) Class<?> valueType, Object context);
962+
963+
@Message(id = 273, value = "Index %1$d is out of bounds for the path of length %2$d.")
964+
IndexOutOfBoundsException pathIndexOutOfBounds(int index, int length);
962965
}

engine/src/main/java/org/hibernate/validator/path/Path.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/**
1010
* An extended representation of the validation path, provides Hibernate Validator specific functionality.
11+
*
12+
* @since 9.1
1113
*/
1214
@Incubating
1315
public interface Path extends jakarta.validation.Path {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.validator.path;
6+
7+
import java.util.RandomAccess;
8+
9+
import org.hibernate.validator.Incubating;
10+
11+
/**
12+
* An extended representation of the validation path, provides Hibernate Validator specific functionality.
13+
* Represents a path with access to the nodes by their index.
14+
*
15+
* @since 9.1
16+
*/
17+
@Incubating
18+
public interface RandomAccessPath extends Path, RandomAccess {
19+
20+
/**
21+
* @return The first node in the path, i.e. {@code path.iterator().next()}.
22+
*/
23+
jakarta.validation.Path.Node getRootNode();
24+
25+
/**
26+
* @param index The index of the node to return.
27+
* @return The node in the path for a given index.
28+
* @throws IndexOutOfBoundsException if the index is out of range, i.e. {@code index < 0 || index >= length() }
29+
*/
30+
jakarta.validation.Path.Node getNode(int index);
31+
32+
/**
33+
* @return The length of the path, i.e. the number of nodes this path contains.
34+
*/
35+
int length();
36+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.validator.test.internal.engine.path;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
10+
import jakarta.validation.Path;
11+
12+
import org.hibernate.validator.internal.engine.path.MutablePath;
13+
import org.hibernate.validator.path.RandomAccessPath;
14+
15+
import org.assertj.core.api.Assertions;
16+
import org.testng.annotations.Test;
17+
18+
public class RandomAccessPathTest {
19+
20+
@Test
21+
public void testParsing() {
22+
String property = "orders[3].deliveryAddress.addressline[1]";
23+
Path path = MutablePath.createPathFromString( property ).materialize();
24+
assertThat( path ).isInstanceOf( RandomAccessPath.class );
25+
26+
if ( path instanceof RandomAccessPath randomAccessPath ) {
27+
assertThat( randomAccessPath.length() ).isEqualTo( 4 );
28+
29+
// Get the root node and assert its properties
30+
Path.Node elem = randomAccessPath.getRootNode();
31+
assertThat( elem ).isNotNull();
32+
assertThat( elem.getName() ).isEqualTo( "orders" );
33+
assertThat( elem.isInIterable() ).isFalse();
34+
35+
// Get the node by index 0 and assert its properties
36+
elem = randomAccessPath.getNode( 0 );
37+
assertThat( elem ).isNotNull();
38+
assertThat( elem.getName() ).isEqualTo( "orders" );
39+
assertThat( elem.isInIterable() ).isFalse();
40+
41+
// Get the node by index 1 and assert its properties
42+
elem = randomAccessPath.getNode( 1 );
43+
assertThat( elem ).isNotNull();
44+
assertThat( elem.getName() ).isEqualTo( "deliveryAddress" );
45+
assertThat( elem.isInIterable() ).isTrue();
46+
assertThat( elem.getIndex() ).isEqualTo( 3 );
47+
48+
// Get the node by index 2 and assert its properties
49+
elem = randomAccessPath.getNode( 2 );
50+
assertThat( elem ).isNotNull();
51+
assertThat( elem.getName() ).isEqualTo( "addressline" );
52+
assertThat( elem.isInIterable() ).isFalse();
53+
54+
// Get the node by index 3 and assert its properties
55+
elem = randomAccessPath.getNode( 3 );
56+
assertThat( elem ).isNotNull();
57+
assertThat( elem.getName() ).isNull();
58+
assertThat( elem.isInIterable() ).isTrue();
59+
assertThat( elem.getIndex() ).isEqualTo( 1 );
60+
61+
assertThatThrownBy( () -> randomAccessPath.getNode( 4 ) )
62+
.isInstanceOf( IndexOutOfBoundsException.class );
63+
64+
assertThat( path.toString() ).isEqualTo( property );
65+
}
66+
else {
67+
Assertions.fail( "Path is not IndexedPath" );
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)