[ 
https://issues.apache.org/jira/browse/COLLECTIONS-891?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18091105#comment-18091105
 ] 

Ruiqi Dong commented on COLLECTIONS-891:
----------------------------------------

Thanks, I agree this is a contract/compatibility question and not just an 
implementation detail.

The reason I read the current behavior as a bug is the contains(Object) 
method's own Javadoc (lines 150-155 in IndexexCollection.java):

 
{code:java}
{@inheritDoc}
Note: uses the index for fast lookup.{code}
 

To me, \{@inheritDoc} means this method is inheriting the 
Collection.contains(Object) contract: membership by object equality. The index 
note then describes the implementation strategy, not a semantic change. The 
patch keeps that property: it still uses the index to narrow the lookup to the 
transformed-key bucket, and then applies the Collection membership check inside 
that bucket.

The old behavior is problematic because contains(Object) is used by standard 
Collection operations and callers that expect Collection semantics. With a 
non-injective transformer, contains("1") can return true even when only "01" is 
present, just because both map to the same key.

For the key-oriented lookup, IndexedCollection already exposes key-typed 
methods: get(K) and values(K). In particular, values(key) != null answers 
whether the index has values for that key. I also noticed the values(K) Javadoc 
currently says "null if contains(key) == false"; that wording is probably part 
of the ambiguity here, since it informally describes a key-based contains even 
though contains(Object) inherits Collection semantics. I would be happy to 
tighten that Javadoc to "null if the key is absent" if we keep the 
Collection.contains direction.

That said, I understand the backward-compatibility concern. If the intended 
contract is that contains(Object) is key-based for this specialized collection, 
then I agree the implementation should probably stay as-is and the 
contains(Object) Javadoc should explicitly document that it intentionally 
differs from Collection.contains(Object). My preference is to keep Collection 
semantics for contains(Object) and leave key lookup to get(K)/values(K), but I 
am happy to follow the direction you prefer.

> IndexedCollection.contains() can return true for objects that were never added
> ------------------------------------------------------------------------------
>
>                 Key: COLLECTIONS-891
>                 URL: https://issues.apache.org/jira/browse/COLLECTIONS-891
>             Project: Commons Collections
>          Issue Type: Bug
>          Components: Collection
>            Reporter: Ruiqi Dong
>            Priority: Minor
>
> *Summary*
> `IndexedCollection` is still a `Collection`, so `contains( x )` should answer 
> whether an equal object is present in the collection.
>  
> The current implementation answers a different question: whether the 
> collection contains *any* element whose transformed key matches 
> `keyTransformer.apply( x )`.
> That produces false positives whenever two different objects map to the same 
> key.
>  
> *Affected code*
> File: 
> `src/main/java/org/apache/commons/collections4/collection/IndexedCollection.java`
> {code:java}
> @SuppressWarnings("unchecked")
> @Override
> public boolean contains(final Object object) {
>     return index.containsKey(keyTransformer.apply((C) object));
> } {code}
> *Reproducer*
> Add the following test to 
> `src/test/java/org/apache/commons/collections4/collection/IndexedCollectionTest.java`:
> {code:java}
> @Test
> void testContainsUsesObjectEqualityNotOnlyTransformedKey() {
>     final Collection<String> coll = makeUniqueTestCollection();
>     coll.add("01");
>     assertFalse(coll.contains("1"));
> } {code}
> This uses the existing `IntegerTransformer`, so both `"01"` and `"1"` map to 
> the same key `1`, but the objects are not equal.
>  
> Run:
> {code:java}
> mvn -q 
> -Dtest=org.apache.commons.collections4.collection.IndexedCollectionTest#testContainsUsesObjectEqualityNotOnlyTransformedKey
>  test {code}
> *Observed behavior*
> {code:java}
> expected: <false> but was: <true> {code}
> *Expected behavior*
> `contains("1")` should be `false`, because only `"01"` was added to the 
> collection.
>  
> The class is a `Collection` decorator, not just a key index. Its `contains` 
> method must preserve collection membership semantics. Matching on transformed 
> keys instead of object equality breaks the `Collection.contains` contract 
> whenever the transformer is not injective.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to