Skip to content
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

Bugfix: Nested collection replacment #970

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

lhy-hoyin
Copy link

@lhy-hoyin lhy-hoyin commented Oct 16, 2024

This commit

(1) fix #961 , where DefaultCollectionMutator.add(E e) does not overrides existing

add(E e) will check for Identifiable with the same ID in collection. If there is, an in-place replacement (order not changed) is done and calls changed(). Otherwise, it doAdd and calls changed().

Nothing is done if e is the same object as the existing element.

(2) Implement DefaultMacAlgorithm.equals

All attributes need to be equals

  • ID (inherited from CryptoAlgorithm)
  • jcaName (inherited from CryptoAlgorithm)
  • minKeyBitLength

New unit tests are also added.

Fixes: #961

by combining remove and add into a single method.

Handle null and empty by throwing NullPointerException and IllegalArgumentException respectively.

Throws NoSuchElementException if the element to replace cannot be found.

Method will rollback to previous element (before replacement) if either the remove step or add step fails.
Test happy path.
Test null handling.
Test empty handling.
Test missing handling.
@lhy-hoyin
Copy link
Author

I had considered (but eventually decided against) an alternative behavior when e cannot be identified to replace something.

Currently, it throws NoSuchElementException to indicate that there is nothing "similar" to replace.

As an alternative, when no "similar" element is found, replace could behave the same as doAdd. Essentially this means "replace if found, otherwise add as new".

I would appreciate thoughts on this alternative.

Copy link
Member

@bdemers bdemers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening the PR @lhy-hoyin!

I've left a few overly verbose comments on the your PR, hopefully you find them helpful.

I know you proposed a new replace method, and there still might be value in that, I'd suggest starting with a test that described the original problem in #961.

Basically, the docs say that .add() should replace the any existing item, most importantly with Identifiable objects. (e.g. where a user wants to replace an algorithm with a different implementation)

Again, thanks for taking the time to look into this issue!

Overloaded with a message and no-message variants.
@lhy-hoyin
Copy link
Author

lhy-hoyin commented Oct 17, 2024

.add() should replace the any existing item

In the case of .add(item) where the item is Identifiable, I suppose an existing item would be one with the same id.

Otherwise, when item is not Identifiable, I suppose that an existing item would be determined through the use of .equals().

In other words, the pseudocode would be like

add(E e) {
    for (item : collection):
        break when {
            item is Identifiable and item.id == e.id, or
            item.equals(e)
        } -> assign item to old

    if old is empty:
        doAdd(e)
    else:
        replace(old, e)
}

Did I interpret correctly?

@bdemers
Copy link
Member

bdemers commented Oct 18, 2024

add(E e) {
    for (item : collection):
        break when {
            item is Identifiable and item.id == e.id, or
            item.equals(e)
        } -> assign item to old

    if old is empty:
        doAdd(e)
    else:
        replace(old, e)
}

Did I interpret correctly?

Yes, possibly with the minor issue of equality. if item.equals(e) there is nothing to do, as e is already contained in the collection, and we would not want to trigger the changed() method

@lhy-hoyin
Copy link
Author

I noticed that DefaultMacAlgorithm did not override equals(), hence its using CryptoAlgorithm's implementation of equals().

In CryptoAlgorithm, it has ID and jacName as attributes.
In DefaultMacAlgorithm, apart from inheriting ID and jacName, it also has it own attribute minKeyBitLength.

I would like to implement equals in DefaultMacAlgorithm (as it simplifies the logic in replace), but is unsure if that would be a breaking change (for end users).

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof DefaultMacAlgorithm) {
            DefaultMacAlgorithm other = (DefaultMacAlgorithm) obj;
            return super.equals(obj) && this.minKeyBitLength == other.minKeyBitLength;
        }
        return false;
    }

With the above implementation, I am able to pass all tests.

to also check equality for minKeyBitLength, apart from ID and jcaName (both inherited from CryptoAlgorithm).
Add tests addIdentifiableWithSameIdEvictsExisting and replaceSameObject
add() is used instead of the previous replace(), but behaviour should still be the same.
Bug was that newElement is always added to the back of the collection regardless of the position of existingElement.

Add unit test to check ordering after replace
@lhy-hoyin
Copy link
Author

@bdemers Would you be able to take a look and approve the PR before the end of the month? I am hoping for this PR to contribute to my hacktoberfest progress (which cuts off by the end of the month).

Copy link
Member

@bdemers bdemers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about the delay. We can try to wrap this up this week!

The current add flow looks like it needs to iterate over the list twice (once to check if an item matches, and then again to do the replacement.
My suggestion would be to remove the public replace method (use a private method if needed), since the main problem is the add(Identifiable) use case.

That should reduce the complexity of the method/logic

to reduce code complexity.

All tests passed.
@lhy-hoyin
Copy link
Author

lhy-hoyin commented Oct 29, 2024

@bdemers I have integrated the logic for replacement directly into add. This reduced the code complexity. I have also adjusted the tests accordingly.

replace is fully removed.

Copy link
Member

@bdemers bdemers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good!
Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JwtParserBuilder NestedCollection overrides
2 participants