Skip to content

Commit f525edf

Browse files
jflorenciorozza
authored andcommitted
Support for custom container types
While PojoCodec accounts for generic containers, it only accounts for it when the PojoCodec is the one that encodes and decodes it. This doesn't work if you want to use Java 8 Optionals, or other containers classes you don't have source code access to, and can't be automatically serialized/deserialized. To make this possible I introduced 4 changes to the public API: - `TypeWithTypeParameters`: This is an interface that `TypeData` now implements that exposes the captured type parameter data. The intention here was to reduce the surface area of the API exposed since TypeData has quite a bit going on, and I don't think we want to commit to such an API. - `PropertyCodecRegistry`: This is a new interface that's analogous to `CodecRegistry`, except instead of accepting a `Class<T>`, it accepts a `TypeWithTypeParameters<T>`. This is strictly for consumption by clients of the API - there is no expectation clients would implement it since it's provided through the property codec provider. - `PropertyCodecProvider`: Similar to above, this is anologous to `CodecProvider` except that it accepts a `TypeWithTypeParameters<T>` and a `PropertyCodecRegistry`. This is the primary interface that's implemented to support implementing custom containers like collections, maps, options, etc. - `PojoCodecProvider.Builder#register(PropertyCodecProvider...)`: How clients register the providers. Note that they cannot register a registry - this isn't as permissive as codec providers so it's intentionally constrained here. Internally I shifted the implementation of enums, maps and collections to use this new setup as well. You can see `CollectionPropertyCodecProvider` and `MapPropertyCodecProvider` for how this looks - they're just implicitly added to the same registration list clients use (see `PojoCodecImpl.PropertyCodecRegistryImpl`). In the tests I implemented `OptionalPropertyCodecProviderForTest` which is a provider for a custom optional class, which gives an example for people who would like to do the same, or support Java 8 optionals. JAVA-2554
1 parent 41f8cf0 commit f525edf

15 files changed

+656
-197
lines changed

bson/src/main/org/bson/codecs/pojo/CollectionCodec.java

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.bson.codecs.pojo;
17+
18+
import org.bson.BsonReader;
19+
import org.bson.BsonType;
20+
import org.bson.BsonWriter;
21+
import org.bson.codecs.Codec;
22+
import org.bson.codecs.DecoderContext;
23+
import org.bson.codecs.EncoderContext;
24+
import org.bson.codecs.configuration.CodecConfigurationException;
25+
26+
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.HashSet;
29+
30+
import static java.lang.String.format;
31+
32+
final class CollectionPropertyCodecProvider implements PropertyCodecProvider {
33+
@SuppressWarnings({"rawtypes", "unchecked"})
34+
@Override
35+
public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
36+
if (Collection.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 1) {
37+
return new CollectionCodec(type.getType(), registry.get(type.getTypeParameters().get(0)));
38+
} else {
39+
return null;
40+
}
41+
}
42+
43+
private static class CollectionCodec<T> implements Codec<Collection<T>> {
44+
private final Class<Collection<T>> encoderClass;
45+
private final Codec<T> codec;
46+
47+
CollectionCodec(final Class<Collection<T>> encoderClass, final Codec<T> codec) {
48+
this.encoderClass = encoderClass;
49+
this.codec = codec;
50+
}
51+
52+
@Override
53+
public void encode(final BsonWriter writer, final Collection<T> collection, final EncoderContext encoderContext) {
54+
writer.writeStartArray();
55+
for (final T value : collection) {
56+
codec.encode(writer, value, encoderContext);
57+
}
58+
writer.writeEndArray();
59+
}
60+
61+
@Override
62+
public Collection<T> decode(final BsonReader reader, final DecoderContext context) {
63+
Collection<T> collection = getInstance();
64+
reader.readStartArray();
65+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
66+
collection.add(codec.decode(reader, context));
67+
}
68+
reader.readEndArray();
69+
return collection;
70+
}
71+
72+
@Override
73+
public Class<Collection<T>> getEncoderClass() {
74+
return encoderClass;
75+
}
76+
77+
private Collection<T> getInstance() {
78+
if (encoderClass.isInterface()) {
79+
if (encoderClass.isAssignableFrom(ArrayList.class)) {
80+
return new ArrayList<T>();
81+
} else if (encoderClass.isAssignableFrom(HashSet.class)) {
82+
return new HashSet<T>();
83+
} else {
84+
throw new CodecConfigurationException(format("Unsupported Collection interface of %s!", encoderClass.getName()));
85+
}
86+
}
87+
88+
try {
89+
return encoderClass.newInstance();
90+
} catch (final Exception e) {
91+
throw new CodecConfigurationException(e.getMessage(), e);
92+
}
93+
}
94+
}
95+
}

bson/src/main/org/bson/codecs/pojo/LazyPojoCodec.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@
2828
class LazyPojoCodec<T> extends PojoCodec<T> {
2929
private final ClassModel<T> classModel;
3030
private final CodecRegistry registry;
31+
private final PropertyCodecRegistry propertyCodecRegistry;
3132
private final DiscriminatorLookup discriminatorLookup;
3233
private final ConcurrentMap<ClassModel<?>, Codec<?>> codecCache;
3334
private volatile PojoCodecImpl<T> pojoCodec;
3435

35-
LazyPojoCodec(final ClassModel<T> classModel, final CodecRegistry registry, final DiscriminatorLookup discriminatorLookup,
36-
final ConcurrentMap<ClassModel<?>, Codec<?>> codecCache) {
36+
LazyPojoCodec(final ClassModel<T> classModel, final CodecRegistry registry, final PropertyCodecRegistry propertyCodecRegistry,
37+
final DiscriminatorLookup discriminatorLookup, final ConcurrentMap<ClassModel<?>, Codec<?>> codecCache) {
3738
this.classModel = classModel;
3839
this.registry = registry;
40+
this.propertyCodecRegistry = propertyCodecRegistry;
3941
this.discriminatorLookup = discriminatorLookup;
4042
this.codecCache = codecCache;
4143
}
@@ -57,7 +59,7 @@ public T decode(final BsonReader reader, final DecoderContext decoderContext) {
5759

5860
private Codec<T> getPojoCodec() {
5961
if (pojoCodec == null) {
60-
pojoCodec = new PojoCodecImpl<T>(classModel, registry, discriminatorLookup, codecCache, true);
62+
pojoCodec = new PojoCodecImpl<T>(classModel, registry, null, propertyCodecRegistry, discriminatorLookup, codecCache, true);
6163
}
6264
return pojoCodec;
6365
}

bson/src/main/org/bson/codecs/pojo/MapCodec.java

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2017 MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.bson.codecs.pojo;
17+
18+
import org.bson.BsonReader;
19+
import org.bson.BsonType;
20+
import org.bson.BsonWriter;
21+
import org.bson.codecs.Codec;
22+
import org.bson.codecs.DecoderContext;
23+
import org.bson.codecs.EncoderContext;
24+
import org.bson.codecs.configuration.CodecConfigurationException;
25+
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
import java.util.Map.Entry;
29+
30+
final class MapPropertyCodecProvider implements PropertyCodecProvider {
31+
@SuppressWarnings({"rawtypes", "unchecked"})
32+
@Override
33+
public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
34+
if (Map.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 2) {
35+
return new MapCodec(type.getType(), registry.get(type.getTypeParameters().get(1)));
36+
} else {
37+
return null;
38+
}
39+
}
40+
41+
private static class MapCodec<T> implements Codec<Map<String, T>> {
42+
private final Class<Map<String, T>> encoderClass;
43+
private final Codec<T> codec;
44+
45+
MapCodec(final Class<Map<String, T>> encoderClass, final Codec<T> codec) {
46+
this.encoderClass = encoderClass;
47+
this.codec = codec;
48+
}
49+
50+
@Override
51+
public void encode(final BsonWriter writer, final Map<String, T> map, final EncoderContext encoderContext) {
52+
writer.writeStartDocument();
53+
for (final Entry<String, T> entry : map.entrySet()) {
54+
writer.writeName(entry.getKey());
55+
codec.encode(writer, entry.getValue(), encoderContext);
56+
}
57+
writer.writeEndDocument();
58+
}
59+
60+
@Override
61+
public Map<String, T> decode(final BsonReader reader, final DecoderContext context) {
62+
reader.readStartDocument();
63+
Map<String, T> map = getInstance();
64+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
65+
map.put(reader.readName(), codec.decode(reader, context));
66+
}
67+
reader.readEndDocument();
68+
return map;
69+
}
70+
71+
@Override
72+
public Class<Map<String, T>> getEncoderClass() {
73+
return encoderClass;
74+
}
75+
76+
private Map<String, T> getInstance() {
77+
if (encoderClass.isInterface()) {
78+
return new HashMap<String, T>();
79+
}
80+
try {
81+
return encoderClass.newInstance();
82+
} catch (final Exception e) {
83+
throw new CodecConfigurationException(e.getMessage(), e);
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)