Convert immutable classes to Java records - #88
Conversation
…Metric, JobID and SelectedComponentInfo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ole-v-v
left a comment
There was a problem hiding this comment.
I love how much terser the class declarations have become.
We have lost some Javadoc comments, many of which I tend to find useful and helpful. Please see if you can fit the information into new Javadoc comments on the record classes except where self-evident and/or only written for the sake of there being Javadoc. Also repeated information is often needed only once.
You have made sure to retain some getters with name starting with get (for example SelectedComponentInfo#getID). Fine.
And I am sorry, we need these in all cases. This is for backward compatibility.
I suggest:
- In our own code use the new getters as you are generally doing already.
For example inGeneralConversationState#startyou calldelay.count()instead ofdelay.getCount().
Also in the last line of theGettingFileconstructor I prefer usingpillar.componentID()overpillar.getID(). - Insert (reinsert)
getXx()methods in all new record classes,
and at the same time deprecate them for removal.
Nice work. This will ease reading our code in the future.
| */ | ||
| package org.bitrepository.client.conversation.selector; | ||
|
|
||
| import org.jetbrains.annotations.NotNull; |
There was a problem hiding this comment.
I’m in favour of not using Jetbrains-specific annotations. Also when we already use them in a few places (probably by accident).
There was a problem hiding this comment.
Changed it to jspecify
| @Override | ||
| public String toString() { | ||
| return getClass().getSimpleName() + ": componentID=" + componentID + ", componentTopic=" + componentTopic; | ||
| public @NotNull String toString() { |
There was a problem hiding this comment.
Annotation/tag is not necessary.
| @Override | ||
| public String toString() { | ||
| return getClass().getSimpleName() + ": componentID=" + componentID + ", componentTopic=" + componentTopic; | ||
| public @NotNull String toString() { |
There was a problem hiding this comment.
I am not perfectly sure whether we want the explicit toString() method at all. The default one yields somthing like SelectedComponentInfo[componentID=some-id, componentTopic=some-topic]. The difference is basically the colon vs. the square brackets. I tend to think it’s close enough that we don’t want to maintain our own implementation any more?
To me it counts that the toString() method never was documented to yields a specific format and that parsing the result from toString() is always discouraged and in this case unnecessary since we got the getters. toString() is there for readable log and trace output, and the default implementation serves that purpose just fine.
| * @param outputHandler The {@link OutputHandler} for handling outputting results | ||
| */ | ||
| public CompleteEventAwaiter(Settings settings, OutputHandler outputHandler) { | ||
| protected CompleteEventAwaiter(Settings settings, OutputHandler outputHandler) { |
There was a problem hiding this comment.
Since the class is abstract, I guess it’s OK to declare the constructor protected.
| /** | ||
| * @return The ID of the pillar chosen by this selector if finished. If unfinished null is returned | ||
| */ | ||
| public String getID() { |
There was a problem hiding this comment.
Yes, unfortunately we need to keep the getters named get-something for backward compatibility (we might have deprecated them, even for removal).
There was a problem hiding this comment.
Done it for every changed class to record
| IntegrityModel store, | ||
| IntegrityAlerter alerter, | ||
| AuditTrailManager auditManager | ||
| ) implements WorkflowContext {} |
There was a problem hiding this comment.
I agree to leave out the toString() method. Unfortunately we need the old get methods with names starting with get (may deprecate for removal).
There was a problem hiding this comment.
Reinserted getters for for every changed class
| protected final String fileID; | ||
| protected final String checksum; | ||
| protected final Instant calculationDate; | ||
| public record ChecksumEntry(String fileID, String checksum, Instant calculationInstant) { |
| Assertions.assertEquals(CE_DATE, ce.getCalculationInstant()); | ||
| } | ||
|
|
||
| @Test |
There was a problem hiding this comment.
It’s good to think of new tests.
| @Tag("pillartest") | ||
| void compactConstructorRejectsNullFileID() { | ||
| addDescription("The compact constructor must reject a null fileID"); | ||
| Assertions.assertThrows(Exception.class, () -> new ChecksumEntry(null, CE_CHECKSUM, CE_DATE)); |
There was a problem hiding this comment.
Since the constructor is not declared to throw any checked exception, I would prefer to strengthen the assertion to require a RuntimeException (may even require that it is either NullPointerException or IllegalArgumentException, but this would require more code lines and probably isn’t worth it).
There was a problem hiding this comment.
Introduced IllegalArgumentException assertions.assertThrow
| @Test | ||
| @Tag("regressiontest") | ||
| @Tag("pillartest") | ||
| void equalityIsComponentBased() { |
There was a problem hiding this comment.
I tend to think of this test as unneeded and not really one that we want. Does it test anything else than the default, auto-generated equals() and hashCode() methods of a record behave correctly? It’s not our job to test the Java language.
…ue to backward compatibility issues.
| * The topic for communication with the selected pillar | ||
| */ | ||
| protected final String componentTopic; | ||
| public record SelectedComponentInfo(@NonNull String componentID, @NonNull String componentTopic) { |
There was a problem hiding this comment.
I wish I knew whether componentTopic was the right name for this field.
- The name used in some places in the old class and agrees with some of the Javadoc comments.
- On the other hand
ComponentSelectorfeeds a “reply to” value into the field, andGettingFileuses the value as a destination. Also the name of the gettergetDestination()supports this interpretation.
Let it be or add the alternative interpretation to the Javadoc comment, for example “The topic or destination for comm...”.
| Assertions.assertThrows(IllegalArgumentException.class, () -> new ChecksumEntry(null, CE_CHECKSUM, CE_DATE)); | ||
| } |
There was a problem hiding this comment.
I wasn’t being clear. The current implementation throws IllegalArgumentException, not documented. I would find an implementation throwing NullPointerException in this place perfectly fine too. For example if we replace the use of out own ArgumentValidator with Java’s Objects.requireNonNull(). So IMHO the test should accept either.
Easy fix:
Assertions.assertThrows(RuntimeException.class, () -> new ChecksumEntry(null, CE_CHECKSUM, CE_DATE));
(since both ``IllegalArgumentExceptionandNullPointerException` are subclasses of `RuntimeException`).
Gold-plated test, maybe a bit too complicated and/or long-winded:
RuntimeException exception = Assertions.assertThrows(
RuntimeException.class, () -> new ChecksumEntry(null, CE_CHECKSUM, CE_DATE));
Assertions.assertTrue(exception instanceof NullPointerException || exception instanceof IllegalArgumentException,
"Expected: NullPointerException or IllegalArgumentException. Actual: " + exception);
There was a problem hiding this comment.
Changed for both of the methods that has IllegalArgumentException in them
There was a problem hiding this comment.
We don’t need to change compactConstructorRejectsEmptyFileID(). For an empty string IllegalArgumentException is the right exception type. NullPointerException would not make sense.
| } | ||
|
|
||
| @Test | ||
| void equalityIsComponentBased() { |
| RuntimeException exception = Assertions.assertThrows( | ||
| RuntimeException.class, () -> new ChecksumEntry("", CE_CHECKSUM, CE_DATE)); | ||
| Assertions.assertTrue(exception instanceof NullPointerException || exception instanceof IllegalArgumentException, | ||
| "Expected: NullPointerException or IllegalArgumentException. Actual: " + exception); |
There was a problem hiding this comment.
As I said elsewhere, the change in this method is not needed. We prefer it as it was before. It was simpler and a more precise test.
There was a problem hiding this comment.
Changed it back to the former
Converted 10 immutable classes to Java
Deleted equals(), hashCode(), and toString() implementations where Java's auto-generated record versions are equivalent
Updated all call sites to use record accessor names
Added dedicated unit tests for record-specific behaviour: compact constructor validation, non-canonical constructor null-coalescing, custom toString format, and component-based equality