-
Notifications
You must be signed in to change notification settings - Fork 41
DestinationSet should implement java.util.Set #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Thanks for submitting this! I always meant to give it a more thorough review and suggest a few things. Just a heads up, this probably will not be a top priority until after we get our first release out the door. |
|
Can this be revisited now? @tonygermano |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements the java.util.Set<Integer> interface for the DestinationSet class, addressing a long-standing feature request. The implementation adds standard Set methods (add, contains, iterator, etc.) and modernizes the code with Java 8 streams and Optional.
Key Changes:
DestinationSetnow implementsSet<Integer>, enabling use as a standard Java collection- Refactored existing methods to use streams and Optional for cleaner code
- Added comprehensive test suite with 20+ test cases covering Set interface methods
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 21 comments.
| File | Description |
|---|---|
| server/src/com/mirth/connect/server/userutil/DestinationSet.java | Implements Set interface with all required methods, refactors internal logic to use streams and Optional |
| server/test/com/mirth/connect/userutil/DestinationSetTest.java | Adds comprehensive test suite for Set interface implementation and existing functionality |
Critical Issues Found:
- NullPointerException bug: The constructor leaves
metaDataIdsnull when the source map doesn't contain the required key, causing NPEs in all Set methods - Test reliability: Multiple tests assume HashSet iteration order, which is not guaranteed and could cause intermittent test failures
- Missing test coverage: No test for the scenario where the source map lacks the
DESTINATION_SET_KEY
Code Quality Issues:
- Inconsistent spacing in control flow statements (
if,for) - Unchecked casts that will generate compiler warnings
- Comment formatting inconsistencies
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| @Override | ||
| public boolean retainAll(Collection<?> metaDataIdOrConnectorNames) { | ||
| return removeAllExcept((Collection<Object>)metaDataIdOrConnectorNames); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unchecked cast from Collection<?> to Collection<Object>. While this works, it will generate a compiler warning. Consider adding @SuppressWarnings("unchecked") to this method or refactoring to avoid the cast.
|
|
||
| @Override | ||
| public boolean removeAll(Collection<?> metaDataIdOrConnectorNames) { | ||
| return remove((Collection<Object>)metaDataIdOrConnectorNames); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unchecked cast from Collection<?> to Collection<Object>. While this works, it will generate a compiler warning. Consider adding @SuppressWarnings("unchecked") to this method or refactoring to avoid the cast.
| @Test | ||
| public void testIsEmptyWithoutSourceMapKey() throws Exception { | ||
| DestinationSet ds = getNewTestDestinationSetUsing(new Integer[0]); | ||
|
|
||
| assertTrue(ds.isEmpty()); | ||
| assertEquals(0, ds.size()); | ||
| } |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test name testIsEmptyWithoutSourceMapKey is misleading. The test actually creates a source map WITH the key (line 47 in getNewTestDestinationSetUsing), just with an empty set.
There's no test coverage for the scenario where the source map does NOT contain Constants.DESTINATION_SET_KEY at all (i.e., when line 45 in DestinationSet.java returns false). This is a critical gap because that scenario will cause a NullPointerException with the current implementation.
Add a test like:
@Test
public void testWithoutSourceMapKey() throws Exception {
ConnectorMessage cm = new ConnectorMessage();
Map<String, Object> sm = new HashMap<>();
// Don't put DESTINATION_SET_KEY in the map
cm.setSourceMap(sm);
ImmutableConnectorMessage icm = new ImmutableConnectorMessage(cm);
DestinationSet ds = new DestinationSet(icm);
assertTrue(ds.isEmpty());
assertEquals(0, ds.size());
}| public void testToArrayGenericProperlySized() throws Exception { | ||
| DestinationSet ds = getNewTestDestinationSet(); | ||
|
|
||
| Integer[] arr = new Integer[ds.size()]; | ||
| //the input and response should be the same array | ||
| Integer[] resp = ds.toArray(arr); | ||
| assertEquals(3, resp.length); | ||
| assertTrue(Arrays.equals(arr, resp)); | ||
| assertEquals(Integer.valueOf(1), arr[0]); | ||
| assertEquals(Integer.valueOf(2), arr[1]); | ||
| assertEquals(Integer.valueOf(3), arr[2]); | ||
| } |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test assumes a specific array order (1, 2, 3), but DestinationSet uses a HashSet internally, which does not guarantee any specific order. The test is flawed and could fail intermittently.
The test should verify that all expected elements are present without assuming order.
| DestinationSet ds = getNewTestDestinationSet(); | ||
|
|
||
| Integer[] arr = new Integer[ds.size()]; | ||
| //the input and response should be the same array |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment formatting should follow convention with a space after //. Should be // the input and response should be the same array.
| //the input and response should be the same array | |
| // the input and response should be the same array |
|
|
||
| assertFalse(ds.contains(0)); | ||
| assertTrue(ds.contains(1)); | ||
| assertTrue(ds.contains("Map to 2")); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actual argument type 'String' is incompatible with expected argument type 'Integer'.
| assertTrue(ds.contains("Map to 2")); | |
| assertTrue(ds.contains(2)); |
| assertTrue(ds.contains("Map to 2")); | ||
| assertTrue(ds.contains(3)); | ||
| assertFalse(ds.contains(4)); | ||
| assertFalse(ds.contains("Invalid")); |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actual argument type 'String' is incompatible with expected argument type 'Integer'.
| * @return A boolean indicating whether at least one destination connector was actually removed | ||
| * from processing for this message. | ||
| */ | ||
| public boolean removeAllExcept(Object metaDataIdOrConnectorName) { |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method DestinationSet.removeAllExcept(..) could be confused with overloaded method removeAllExcept, since dispatch depends on static types.
| * @return A boolean indicating whether at least one destination connector was actually removed | ||
| * from processing for this message. | ||
| */ | ||
| public boolean remove(Object metaDataIdOrConnectorName) { |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method DestinationSet.remove(..) could be confused with overloaded method remove, since dispatch depends on static types.
| * destination connector name. | ||
| * @return A boolean indicating whether at least one destination connector was actually removed | ||
| * from processing for this message. | ||
| */ |
Copilot
AI
Dec 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method overrides Set.remove; it is advisable to add an Override annotation.
| */ | |
| */ | |
| @Override |
| public void testToArray() throws Exception { | ||
| DestinationSet ds = getNewTestDestinationSet(); | ||
|
|
||
| Object[] arr = ds.toArray(); | ||
| assertEquals(3, arr.length); | ||
| assertEquals(Integer.valueOf(1), arr[0]); | ||
| assertEquals(Integer.valueOf(2), arr[1]); | ||
| assertEquals(Integer.valueOf(3), arr[2]); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true. Tests that assume toArray to be deterministic could be flaky.
| try { | ||
| if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) { | ||
| this.destinationIdMap = connectorMessage.getDestinationIdMap(); | ||
| this.metaDataIds = (Set<Integer>) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY); | ||
| } | ||
| } catch (Exception e) { | ||
| metaDataIds = new HashSet<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also a good point and probable cause for future NPEs.
pacmano1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs more review.
"add" should be in it own PR. We should try to avoid adding features in the context of a limited scope issue.
|
I'm impressed by the copilot results. I'd like to resolve the findings this weekend. |
|
Impressive work. |
Signed-off-by: Richard Ogin <rogin@users.noreply.github.com>
3e60899
2b23e77 to
3e60899
Compare
tonygermano
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the comments below, I pushed a change to your branch with the following:
- I fixed the line endings on DestinationSet so that you were no longer rewriting the entire file.
- I changed the header on DestinationSet and added one to DestinationSetTest in SPDX format and adding your name to the copyright.
- I rebased to resolve the merge conflict with previous PR #133 (keeps the changes in this PR, which negate the changes from the previous PR.)
The previous PR also added tests, but to file server/test/com/mirth/connect/server/userutil/DestinationSetTest.java instead of server/test/com/mirth/connect/userutil/DestinationSetTest.java. I think the other PR was correct, and this PR should add to the other tests. I think this is also what is currently causing the build to fail.
| } | ||
|
|
||
| return false; | ||
| return remove(Collections.singleton(metaDataIdOrConnectorName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: annotate with @Override
This is a Set operation
suggestion: keep this method simple and don't wrap and delegate
If metaDataIds is made final and never null above, then the first null check can be removed.
| } | ||
|
|
||
| return removed; | ||
| if(metaDataIdOrConnectorNames == null) { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: follow copilot recommendation to add space in if (
suggestion: move this implementation to Set operation removeAll and deprecate this method
This encourages the use of the Set operations remove for a single object and removeAll for collections. The overloaded remove has been a source of confusion in the past.
issue: should throw a NPE instead of return false when Collection is null
This matches the previous behavior and is specified by Set.removeAll
| .filter(Boolean::booleanValue) | ||
| .count() > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .filter(Boolean::booleanValue) | |
| .count() > 0; | |
| .anyMatch(Boolean::booleanValue) |
| } | ||
|
|
||
| return false; | ||
| return removeAllExcept(Collections.singleton(metaDataIdOrConnectorName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: make this a call to Set operation retainAll
| } | ||
|
|
||
| return false; | ||
| if(metaDataIdOrConnectorNames == null) { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: follow copilot recommendation to add space in if (
suggestion: move this implementation to Set operation retainAll and deprecate this method
This encourages the use of the Set operations and reduces confusion about two methods which do the same thing. I think removeAllExcept(Object) can remain since Set does not provide a retainOnly method for a single Object.
issue: should throw a NPE instead of return false when Collection is null
This matches the previous behavior and is specified by Set.retainAll
Suggestion: remove null check for metaDataIds
It should not be null if following other recommendations in this review
| } | ||
|
|
||
| @Override | ||
| public boolean addAll(Collection<? extends Integer> metaDataIdOrConnectorNames) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: addAll should throw UnsupportedOperationException
DestinationSets do not support adding to the Set
| } | ||
|
|
||
| @Override | ||
| public boolean retainAll(Collection<?> metaDataIdOrConnectorNames) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: deprecate removeAllExcept(Collection<Object>) in favor of this method
Also mentioned above. The Set operations should be the primary methods to reduce confusion and we can eventually remove the old methods.
| } | ||
|
|
||
| @Override | ||
| public boolean removeAll(Collection<?> metaDataIdOrConnectorNames) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: deprecate remove(Collection<Object>) in favor of this method
Also mentioned above. The Set operations should be the primary methods to reduce confusion and we can eventually remove the old methods.
| Optional<Integer> m = convertToMetaDataId(metaDataIdOrConnectorName); | ||
|
|
||
| return m.isPresent() && metaDataIds.contains(m.get()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if convertToMetaDataId is changed back to returning nulls and metaDataIds is always non-null as suggested elsewhere, I think this can be simplified to just
return metaDataIds.contains(convertToMetaDataId(metaDataIdOrConnectorName));|
|
||
| @Override | ||
| public boolean containsAll(Collection<?> metaDataIdOrConnectorNames) { | ||
| if(metaDataIdOrConnectorNames == null) { return false; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: follow copilot recommendation to add space in if (
issue: Set.containsAll should throw a NPE instead of returning false when Collection is null
This was an old feature, requested by several people, that NextGen chose to ignore. Prior code review comments are here. I compiled it with Java 8, and the test ran successfully. No further testing on my part.