[JAVA-2612] Improve POJO Codec - support sub-classes with the bson creator Created: 25/Sep/17  Updated: 29/Oct/23  Resolved: 04/Dec/17

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: None
Fix Version/s: 3.6.0

Type: Improvement Priority: Major - P3
Reporter: Ross Lawley Assignee: Ross Lawley
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

PR: https://github.com/mongodb/mongo-java-driver/pull/417



 Comments   
Comment by Githook User [ 04/Dec/17 ]

Author:

{'username': 'jflorencio', 'email': 'joe@deal.com', 'name': 'Joseph Florencio'}

Message: PojoCodec improvents for sub-classes

Pojo Codec fixes with sub-classes and bson creator

This commit tries to fix a few issues:

1. It was not possible to overload a getter in a sub-class and make the
type more specific (covariant overload). For example, attempting to
serialize Bar would fail:

```
// Assume there are setters here as well...
class Foo {
public Number getNumber()

{ /*...*/ }
}

class Bar extends Foo {
@Override
public Long getNumber() { /*...*/ }

}
```

This was due to bridge methods not getting picked up, so even though
Method#getDeclaredMethods is documented to return methods only from the
super class, the Java compiler inserts a bridge method in the sub-class
(Bar in this case) for these covariant overrides for legacy interop.

2.a. It was not possible to use a more specific type in a getter, but a
more general type in the setter/constructor/static factory method. For example:

```
class Foo {
public ImmutableList<String> getNames()

{ /*...*/ }
// Mutable class
public void setNames(List<String> names) { /*...*/ }

// or immutable class (both didn't work)
@BsonCreator
public Foo(@BsonProperty("names") List<String> names)

{ /*...*/ }

}
```

The first issue was that strict type equality was enforced. This is a
pretty common pattern that should be allowed though - it's typically a
bad idea to accept such a specific class in your constructor/setter.
In my case, I'm creating my domain models with Immutables, so the
generated immutable class will override my getter that returns
`List<T>` to return `ImmutableList<T>`.

The second issue was that various collections were boxed to specific
implementations like ArrayList/HashMap/HashSet. Doing this in TypeData
caused quite a few problems. The main being it was no longer possible to
understand the type hierarchy (ImmutableList does not extend ArrayList,
but it safely implements List). The ArrayList/HashMap/etc selection
happens in collection codec as an implementation detail. I think this is
a bit cleaner.

3. It was not possible to use immutable collections like Guava's
ImmutableList. By fixing the above issues, it works now. Note that we
still can't use ImmutableList as an argument that's accepted, but that's
a case where it's reasonable to provide a custom codec implementation
since there's no way we can do that automatically.

4. @BsonCreator annotations were only searched for in the target class,
but not any super classes. It's relatively common to have an abstract
base class host all of the static factory methods for the concrete
subtypes, so this allows for that pattern. I do the above, but in
addition, some of my domain models are generated through Immutables
where I don't have the ability to inject annotated static factory
methods.


I moved code around a bit for better locality and some re-usability. In
particular with PropertyReflectionUtils, and TypeData having a few
methods of extracting the types out of methods/fields/etc.

I tried to add test cases for all of the above things - they all failed
prior to these modifications. All other existing bson tests pass.

JAVA-2612
Branch: 3.6.x
https://github.com/mongodb/mongo-java-driver/commit/275513a0ae355039aad81664c23d0c1d5ceb7aa5

Comment by Ross Lawley [ 04/Dec/17 ]

A massive thank you to Joseph Florencio for the excellent pull request.

Comment by Githook User [ 04/Dec/17 ]

Author:

{'username': 'jflorencio', 'email': 'joe@deal.com', 'name': 'Joseph Florencio'}

Message: PojoCodec improvents for sub-classes

Pojo Codec fixes with sub-classes and bson creator

This commit tries to fix a few issues:

1. It was not possible to overload a getter in a sub-class and make the
type more specific (covariant overload). For example, attempting to
serialize Bar would fail:

```
// Assume there are setters here as well...
class Foo {
public Number getNumber()

{ /*...*/ }
}

class Bar extends Foo {
@Override
public Long getNumber() { /*...*/ }

}
```

This was due to bridge methods not getting picked up, so even though
Method#getDeclaredMethods is documented to return methods only from the
super class, the Java compiler inserts a bridge method in the sub-class
(Bar in this case) for these covariant overrides for legacy interop.

2.a. It was not possible to use a more specific type in a getter, but a
more general type in the setter/constructor/static factory method. For example:

```
class Foo {
public ImmutableList<String> getNames()

{ /*...*/ }
// Mutable class
public void setNames(List<String> names) { /*...*/ }

// or immutable class (both didn't work)
@BsonCreator
public Foo(@BsonProperty("names") List<String> names)

{ /*...*/ }

}
```

The first issue was that strict type equality was enforced. This is a
pretty common pattern that should be allowed though - it's typically a
bad idea to accept such a specific class in your constructor/setter.
In my case, I'm creating my domain models with Immutables, so the
generated immutable class will override my getter that returns
`List<T>` to return `ImmutableList<T>`.

The second issue was that various collections were boxed to specific
implementations like ArrayList/HashMap/HashSet. Doing this in TypeData
caused quite a few problems. The main being it was no longer possible to
understand the type hierarchy (ImmutableList does not extend ArrayList,
but it safely implements List). The ArrayList/HashMap/etc selection
happens in collection codec as an implementation detail. I think this is
a bit cleaner.

3. It was not possible to use immutable collections like Guava's
ImmutableList. By fixing the above issues, it works now. Note that we
still can't use ImmutableList as an argument that's accepted, but that's
a case where it's reasonable to provide a custom codec implementation
since there's no way we can do that automatically.

4. @BsonCreator annotations were only searched for in the target class,
but not any super classes. It's relatively common to have an abstract
base class host all of the static factory methods for the concrete
subtypes, so this allows for that pattern. I do the above, but in
addition, some of my domain models are generated through Immutables
where I don't have the ability to inject annotated static factory
methods.


I moved code around a bit for better locality and some re-usability. In
particular with PropertyReflectionUtils, and TypeData having a few
methods of extracting the types out of methods/fields/etc.

I tried to add test cases for all of the above things - they all failed
prior to these modifications. All other existing bson tests pass.

JAVA-2612
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/275513a0ae355039aad81664c23d0c1d5ceb7aa5

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