Skip to content

Commit 4d1f036

Browse files
authored
java: Add comparators for comparable objects (#312)
Duration, Location and timestamp are comparable. To help sorting these messages a comparator is needed. The alternative would be implementing the `Comparable` interface, but with generated code that is quite tricky.
1 parent a38cd48 commit 4d1f036

11 files changed

+281
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
9+
### Added
10+
- [java] Add comparators for comparable objects ([#312](https://github.com/cucumber/messages/pull/312))
911

1012
## [28.0.0] - 2025-07-07
1113
### Changed

java/pom.xml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@
4242
<type>pom</type>
4343
<scope>import</scope>
4444
</dependency>
45+
<dependency>
46+
<groupId>org.assertj</groupId>
47+
<artifactId>assertj-bom</artifactId>
48+
<version>3.27.3</version>
49+
<type>pom</type>
50+
<scope>import</scope>
51+
</dependency>
4552
</dependencies>
4653
</dependencyManagement>
4754

@@ -71,9 +78,8 @@
7178
</dependency>
7279

7380
<dependency>
74-
<groupId>org.hamcrest</groupId>
75-
<artifactId>hamcrest-core</artifactId>
76-
<version>3.0</version>
81+
<groupId>org.assertj</groupId>
82+
<artifactId>assertj-core</artifactId>
7783
<scope>test</scope>
7884
</dependency>
7985

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.Duration;
4+
5+
import java.util.Comparator;
6+
7+
/**
8+
* Orders durations by their natural order.
9+
*/
10+
public final class DurationComparator implements Comparator<Duration> {
11+
@Override
12+
public int compare(Duration a, Duration b) {
13+
int c = a.getSeconds().compareTo(b.getSeconds());
14+
if (c != 0) {
15+
return c;
16+
}
17+
return a.getNanos().compareTo(b.getNanos());
18+
}
19+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.Location;
4+
5+
import java.util.Comparator;
6+
7+
/**
8+
* Orders locations by their natural order.
9+
* <p>
10+
* Locations on the same line, but with a missing column come before locations
11+
* with a column.
12+
*/
13+
public final class LocationComparator implements Comparator<Location> {
14+
@Override
15+
public int compare(Location a, Location b) {
16+
int c = a.getLine().compareTo(b.getLine());
17+
if (c != 0) {
18+
return c;
19+
}
20+
Long aColumn = a.getColumn().orElse(null);
21+
Long bColumn = b.getColumn().orElse(null);
22+
// null first.
23+
return aColumn == null ? bColumn == null ? 0 : -1 : bColumn == null ? 1 : aColumn.compareTo(bColumn);
24+
}
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.TestStepResultStatus;
4+
5+
import java.util.Comparator;
6+
7+
/**
8+
* Orders test step results from least to most severe.
9+
*/
10+
public final class TestStepResultStatusComparator implements Comparator<TestStepResultStatus> {
11+
@Override
12+
public int compare(TestStepResultStatus a, TestStepResultStatus b) {
13+
return Integer.compare(a.ordinal(), b.ordinal());
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.Timestamp;
4+
5+
import java.util.Comparator;
6+
7+
/**
8+
* Orders timestamps by their natural order.
9+
*/
10+
public final class TimestampComparator implements Comparator<Timestamp> {
11+
@Override
12+
public int compare(Timestamp a, Timestamp b) {
13+
int c = a.getSeconds().compareTo(b.getSeconds());
14+
if (c != 0) {
15+
return c;
16+
}
17+
return a.getNanos().compareTo(b.getNanos());
18+
}
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.Duration;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class DurationComparatorTest {
9+
10+
final DurationComparator comparator = new DurationComparator();
11+
12+
@Test
13+
void isEqual(){
14+
Duration a = new Duration(3L, 14L);
15+
Duration b = new Duration(3L, 14L);
16+
assertThat(comparator.compare(a, b)).isZero();
17+
}
18+
19+
@Test
20+
void isSmallerOnSeconds(){
21+
Duration a = new Duration(2L, 14L);
22+
Duration b = new Duration(3L, 14L);
23+
assertThat(comparator.compare(a, b)).isNegative();
24+
}
25+
26+
@Test
27+
void isSmallerOnNanos(){
28+
Duration a = new Duration(3L, 13L);
29+
Duration b = new Duration(3L, 14L);
30+
assertThat(comparator.compare(a, b)).isNegative();
31+
}
32+
33+
@Test
34+
void isLargerOnSeconds(){
35+
Duration a = new Duration(4L, 14L);
36+
Duration b = new Duration(3L, 14L);
37+
assertThat(comparator.compare(a, b)).isPositive();
38+
}
39+
40+
@Test
41+
void isLargerOnNanos(){
42+
Duration a = new Duration(3L, 15L);
43+
Duration b = new Duration(3L, 14L);
44+
assertThat(comparator.compare(a, b)).isPositive();
45+
}
46+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.Location;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
class LocationComparatorTest {
9+
10+
final LocationComparator comparator = new LocationComparator();
11+
12+
@Test
13+
void isEqual(){
14+
Location a = new Location(3L, 14L);
15+
Location b = new Location(3L, 14L);
16+
assertThat(comparator.compare(a, b)).isZero();
17+
}
18+
19+
@Test
20+
void isSmallerOnLine(){
21+
Location a = new Location(2L, 14L);
22+
Location b = new Location(3L, 14L);
23+
assertThat(comparator.compare(a, b)).isNegative();
24+
}
25+
26+
@Test
27+
void isSmallerOnColum(){
28+
Location a = new Location(3L, 13L);
29+
Location b = new Location(3L, 14L);
30+
assertThat(comparator.compare(a, b)).isNegative();
31+
}
32+
@Test
33+
void isSmallerAbsentColumn(){
34+
Location a = new Location(3L, null);
35+
Location b = new Location(3L, 14L);
36+
assertThat(comparator.compare(a, b)).isNegative();
37+
}
38+
39+
@Test
40+
void isLargerOnLine(){
41+
Location a = new Location(4L, 14L);
42+
Location b = new Location(3L, 14L);
43+
assertThat(comparator.compare(a, b)).isPositive();
44+
}
45+
46+
@Test
47+
void isLargerOnColumn(){
48+
Location a = new Location(3L, 15L);
49+
Location b = new Location(3L, 14L);
50+
assertThat(comparator.compare(a, b)).isPositive();
51+
}
52+
53+
@Test
54+
void isLargerAbsentColumn(){
55+
Location a = new Location(3L, 15L);
56+
Location b = new Location(3L, null);
57+
assertThat(comparator.compare(a, b)).isPositive();
58+
}
59+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package io.cucumber.messages;
22

3-
import org.hamcrest.Matchers;
43
import org.junit.jupiter.api.Test;
54

6-
import static org.hamcrest.MatcherAssert.assertThat;
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
77

88
class ProtocolVersionTest {
99

1010
@Test
1111
void should_have_a_resource_bundle_version() {
1212
String version = ProtocolVersion.getVersion();
13-
assertThat(version, Matchers.matchesPattern("\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?"));
13+
assertThat(version).matches("\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?");
1414
}
1515

1616
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.cucumber.messages;
2+
3+
import io.cucumber.messages.types.TestStepResultStatus;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Arrays;
7+
import java.util.Collections;
8+
import java.util.List;
9+
10+
import static io.cucumber.messages.types.TestStepResultStatus.AMBIGUOUS;
11+
import static io.cucumber.messages.types.TestStepResultStatus.FAILED;
12+
import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
13+
import static io.cucumber.messages.types.TestStepResultStatus.PENDING;
14+
import static io.cucumber.messages.types.TestStepResultStatus.SKIPPED;
15+
import static io.cucumber.messages.types.TestStepResultStatus.UNDEFINED;
16+
import static io.cucumber.messages.types.TestStepResultStatus.UNKNOWN;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
class TestStepResultStatusComparatorTest {
20+
21+
final TestStepResultStatusComparator comparator = new TestStepResultStatusComparator();
22+
23+
@Test
24+
void test(){
25+
List<TestStepResultStatus> values = Arrays.asList(TestStepResultStatus.values());
26+
Collections.shuffle(values);
27+
values.sort(comparator);
28+
assertThat(values).containsExactly(
29+
UNKNOWN,
30+
PASSED,
31+
SKIPPED,
32+
PENDING,
33+
UNDEFINED,
34+
AMBIGUOUS,
35+
FAILED
36+
);
37+
}
38+
}

0 commit comments

Comments
 (0)