[JAVA-2758] ClassCastException in method Document#get(final Object key, final T defaultValue) when defaultValue is subType of T Created: 29/Jan/18  Updated: 28/Oct/23  Resolved: 22/May/18

Status: Closed
Project: Java Driver
Component/s: API
Affects Version/s: None
Fix Version/s: 3.8.0

Type: Bug Priority: Major - P3
Reporter: Daniel Fesenmeyer [X] Assignee: Jeffrey Yemin
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

The following code throws an unexpected ClassCastException when a value is found, i.e. the default value is not used:

final Collection<String> myCollection = myDocument.get("myKey", Collections.emptySet());
// throws: java.lang.ClassCastException: Cannot cast java.util.Collections$UnmodifiableSet to java.util.Collections$EmptySet

The following complicated code works:

final Collection<String> myCollection = document.get("myKey", Collections.unmodifiableSet(Collections.emptySet()))

In my opinion, this method is useless (and dangerous) in the public API. If you want to provide such a functionality, you should consider to provide a method which also requires the class to cast to: Document#get(Object key, Class<T> clazz, T defaultValue)
This method should cast to clazz instead of the defaultValue.getClass().



 Comments   
Comment by Githook User [ 22/May/18 ]

Author:

{'username': 'jyemin', 'name': 'Jeff Yemin', 'email': 'jeff.yemin@10gen.com'}

Message: JAVA-2758: Cast to T instead of using Class#cast

In org.bson.Document#get(Object key, T defaultValue), just cast to T
instead of calling Class#cast on defaultValue's class.
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/847d648db06498520fdd2427460ec53924fd2e37

Comment by Daniel Fesenmeyer [X] [ 30/Jan/18 ]

I think the second approach is more convenient, because method getObject(final Object key) would be a generic alternative to getInteger(final Object key), ...
An unsafe cast would be needed for both implementations, thus i would vote for the second approach

Comment by Jeffrey Yemin [ 29/Jan/18 ]

One other idea: change the implementation of the existing method to:

    @SuppressWarnings("unchecked")
    public <T> T get(final Object key, final T defaultValue) {
        Object value = documentAsMap.get(key);
        return value == null ? defaultValue : (T) value;   // just cast to T
    }

Comment by Jeffrey Yemin [ 29/Jan/18 ]

The current API is convenient for final (or should be final) classes like Integer, Boolean, Date, but I see how it's difficult to use properly for collections and maps, so thank you for bringing it up. Thinking about the API that you propose, one issue is that

Collection<String> myCollection = myDocument.get("myKey", Collection.class, Collections.emptySet())

generates a compiler warning for unsafe operations, because myCollection is generic. We could add a method like this to Document

public <T> List<T> getList(final Document document, final Object key, final Class<T> clazz, final List<T> defaultValue)

but it would have to contain an unsafe cast to List<T>.

Generated at Thu Feb 08 08:58:00 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.