Skip to content

Commit c7a53c5

Browse files
committed
Added MapCodec
JAVA-2448
1 parent 1bd198e commit c7a53c5

File tree

3 files changed

+585
-0
lines changed

3 files changed

+585
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright (c) 2008-2014 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+
17+
package org.bson.codecs;
18+
19+
import org.bson.BsonBinarySubType;
20+
import org.bson.BsonDocument;
21+
import org.bson.BsonDocumentWriter;
22+
import org.bson.BsonReader;
23+
import org.bson.BsonType;
24+
import org.bson.BsonValue;
25+
import org.bson.BsonWriter;
26+
import org.bson.Document;
27+
import org.bson.Transformer;
28+
import org.bson.codecs.configuration.CodecRegistry;
29+
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.UUID;
34+
35+
import static java.util.Arrays.asList;
36+
import static org.bson.assertions.Assertions.notNull;
37+
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
38+
39+
/**
40+
* A Codec for Document instances.
41+
*
42+
* @see org.bson.Document
43+
* @since 3.0
44+
*/
45+
public class DocumentCodec implements CollectibleCodec<Document> {
46+
47+
private static final String ID_FIELD_NAME = "_id";
48+
private static final CodecRegistry DEFAULT_REGISTRY = fromProviders(asList(new ValueCodecProvider(),
49+
new BsonValueCodecProvider(),
50+
new DocumentCodecProvider()));
51+
private static final BsonTypeClassMap DEFAULT_BSON_TYPE_CLASS_MAP = new BsonTypeClassMap();
52+
53+
private final BsonTypeCodecMap bsonTypeCodecMap;
54+
private final CodecRegistry registry;
55+
private final IdGenerator idGenerator;
56+
private final Transformer valueTransformer;
57+
58+
/**
59+
* Construct a new instance with a default {@code CodecRegistry} and
60+
*/
61+
public DocumentCodec() {
62+
this(DEFAULT_REGISTRY, DEFAULT_BSON_TYPE_CLASS_MAP);
63+
}
64+
65+
/**
66+
* Construct a new instance with the given registry and BSON type class map.
67+
*
68+
* @param registry the registry
69+
* @param bsonTypeClassMap the BSON type class map
70+
*/
71+
public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap) {
72+
this(registry, bsonTypeClassMap, null);
73+
}
74+
75+
/**
76+
* Construct a new instance with the given registry and BSON type class map. The transformer is applied as a last step when decoding
77+
* values, which allows users of this codec to control the decoding process. For example, a user of this class could substitute a
78+
* value decoded as a Document with an instance of a special purpose class (e.g., one representing a DBRef in MongoDB).
79+
*
80+
* @param registry the registry
81+
* @param bsonTypeClassMap the BSON type class map
82+
* @param valueTransformer the value transformer to use as a final step when decoding the value of any field in the document
83+
*/
84+
public DocumentCodec(final CodecRegistry registry, final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) {
85+
this.registry = notNull("registry", registry);
86+
this.bsonTypeCodecMap = new BsonTypeCodecMap(notNull("bsonTypeClassMap", bsonTypeClassMap), registry);
87+
this.idGenerator = new ObjectIdGenerator();
88+
this.valueTransformer = valueTransformer != null ? valueTransformer : new Transformer() {
89+
@Override
90+
public Object transform(final Object value) {
91+
return value;
92+
}
93+
};
94+
}
95+
96+
@Override
97+
public boolean documentHasId(final Document document) {
98+
return document.containsKey(ID_FIELD_NAME);
99+
}
100+
101+
@Override
102+
public BsonValue getDocumentId(final Document document) {
103+
if (!documentHasId(document)) {
104+
throw new IllegalStateException("The document does not contain an _id");
105+
}
106+
107+
Object id = document.get(ID_FIELD_NAME);
108+
if (id instanceof BsonValue) {
109+
return (BsonValue) id;
110+
}
111+
112+
BsonDocument idHoldingDocument = new BsonDocument();
113+
BsonWriter writer = new BsonDocumentWriter(idHoldingDocument);
114+
writer.writeStartDocument();
115+
writer.writeName(ID_FIELD_NAME);
116+
writeValue(writer, EncoderContext.builder().build(), id);
117+
writer.writeEndDocument();
118+
return idHoldingDocument.get(ID_FIELD_NAME);
119+
}
120+
121+
@Override
122+
public Document generateIdIfAbsentFromDocument(final Document document) {
123+
if (!documentHasId(document)) {
124+
document.put(ID_FIELD_NAME, idGenerator.generate());
125+
}
126+
return document;
127+
}
128+
129+
@Override
130+
public void encode(final BsonWriter writer, final Document document, final EncoderContext encoderContext) {
131+
writeMap(writer, document, encoderContext);
132+
}
133+
134+
@Override
135+
public Document decode(final BsonReader reader, final DecoderContext decoderContext) {
136+
Document document = new Document();
137+
138+
reader.readStartDocument();
139+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
140+
String fieldName = reader.readName();
141+
document.put(fieldName, readValue(reader, decoderContext));
142+
}
143+
144+
reader.readEndDocument();
145+
146+
return document;
147+
}
148+
149+
@Override
150+
public Class<Document> getEncoderClass() {
151+
return Document.class;
152+
}
153+
154+
private void beforeFields(final BsonWriter bsonWriter, final EncoderContext encoderContext, final Map<String, Object> document) {
155+
if (encoderContext.isEncodingCollectibleDocument() && document.containsKey(ID_FIELD_NAME)) {
156+
bsonWriter.writeName(ID_FIELD_NAME);
157+
writeValue(bsonWriter, encoderContext, document.get(ID_FIELD_NAME));
158+
}
159+
}
160+
161+
private boolean skipField(final EncoderContext encoderContext, final String key) {
162+
return encoderContext.isEncodingCollectibleDocument() && key.equals(ID_FIELD_NAME);
163+
}
164+
165+
@SuppressWarnings({"unchecked", "rawtypes"})
166+
private void writeValue(final BsonWriter writer, final EncoderContext encoderContext, final Object value) {
167+
if (value == null) {
168+
writer.writeNull();
169+
} else if (value instanceof Iterable) {
170+
writeIterable(writer, (Iterable<Object>) value, encoderContext.getChildContext());
171+
} else if (value instanceof Map) {
172+
writeMap(writer, (Map<String, Object>) value, encoderContext.getChildContext());
173+
} else {
174+
Codec codec = registry.get(value.getClass());
175+
encoderContext.encodeWithChildContext(codec, writer, value);
176+
}
177+
}
178+
179+
private void writeMap(final BsonWriter writer, final Map<String, Object> map, final EncoderContext encoderContext) {
180+
writer.writeStartDocument();
181+
182+
beforeFields(writer, encoderContext, map);
183+
184+
for (final Map.Entry<String, Object> entry : map.entrySet()) {
185+
if (skipField(encoderContext, entry.getKey())) {
186+
continue;
187+
}
188+
writer.writeName(entry.getKey());
189+
writeValue(writer, encoderContext, entry.getValue());
190+
}
191+
writer.writeEndDocument();
192+
}
193+
194+
private void writeIterable(final BsonWriter writer, final Iterable<Object> list, final EncoderContext encoderContext) {
195+
writer.writeStartArray();
196+
for (final Object value : list) {
197+
writeValue(writer, encoderContext, value);
198+
}
199+
writer.writeEndArray();
200+
}
201+
202+
private Object readValue(final BsonReader reader, final DecoderContext decoderContext) {
203+
BsonType bsonType = reader.getCurrentBsonType();
204+
if (bsonType == BsonType.NULL) {
205+
reader.readNull();
206+
return null;
207+
} else if (bsonType == BsonType.ARRAY) {
208+
return readList(reader, decoderContext);
209+
} else if (bsonType == BsonType.BINARY && BsonBinarySubType.isUuid(reader.peekBinarySubType()) && reader.peekBinarySize() == 16) {
210+
return registry.get(UUID.class).decode(reader, decoderContext);
211+
}
212+
return valueTransformer.transform(bsonTypeCodecMap.get(bsonType).decode(reader, decoderContext));
213+
}
214+
215+
private List<Object> readList(final BsonReader reader, final DecoderContext decoderContext) {
216+
reader.readStartArray();
217+
List<Object> list = new ArrayList<Object>();
218+
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
219+
list.add(readValue(reader, decoderContext));
220+
}
221+
reader.readEndArray();
222+
return list;
223+
}
224+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright (c) 2008-2014 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+
17+
package org.bson.codecs;
18+
19+
import org.bson.Document;
20+
import org.bson.Transformer;
21+
import org.bson.codecs.configuration.CodecProvider;
22+
import org.bson.codecs.configuration.CodecRegistry;
23+
import org.bson.types.CodeWithScope;
24+
25+
import static org.bson.assertions.Assertions.notNull;
26+
27+
/**
28+
* A {@code CodecProvider} for the Document class and all the default Codec implementations on which it depends.
29+
*
30+
* @since 3.0
31+
*/
32+
public class DocumentCodecProvider implements CodecProvider {
33+
private final BsonTypeClassMap bsonTypeClassMap;
34+
private final Transformer valueTransformer;
35+
36+
/**
37+
* Construct a new instance with a default {@code BsonTypeClassMap}.
38+
*/
39+
public DocumentCodecProvider() {
40+
this(new BsonTypeClassMap());
41+
}
42+
43+
/**
44+
* Construct a new instance with a default {@code BsonTypeClassMap} and the given {@code Transformer}. The transformer is used by the
45+
* DocumentCodec as a last step when decoding values.
46+
*
47+
* @param valueTransformer the value transformer for decoded values
48+
* @see org.bson.codecs.DocumentCodec#DocumentCodec(org.bson.codecs.configuration.CodecRegistry, BsonTypeClassMap, org.bson.Transformer)
49+
*/
50+
public DocumentCodecProvider(final Transformer valueTransformer) {
51+
this(new BsonTypeClassMap(), valueTransformer);
52+
}
53+
54+
/**
55+
* Construct a new instance with the given instance of {@code BsonTypeClassMap}.
56+
*
57+
* @param bsonTypeClassMap the non-null {@code BsonTypeClassMap} with which to construct instances of {@code DocumentCodec} and {@code
58+
* ListCodec}
59+
*/
60+
public DocumentCodecProvider(final BsonTypeClassMap bsonTypeClassMap) {
61+
this(bsonTypeClassMap, null);
62+
}
63+
64+
/**
65+
* Construct a new instance with the given instance of {@code BsonTypeClassMap}.
66+
*
67+
* @param bsonTypeClassMap the non-null {@code BsonTypeClassMap} with which to construct instances of {@code DocumentCodec} and {@code
68+
* ListCodec}.
69+
* @param valueTransformer the value transformer for decoded values
70+
*/
71+
public DocumentCodecProvider(final BsonTypeClassMap bsonTypeClassMap, final Transformer valueTransformer) {
72+
this.bsonTypeClassMap = notNull("bsonTypeClassMap", bsonTypeClassMap);
73+
this.valueTransformer = valueTransformer;
74+
}
75+
76+
@Override
77+
@SuppressWarnings("unchecked")
78+
public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
79+
if (clazz == CodeWithScope.class) {
80+
return (Codec<T>) new CodeWithScopeCodec(registry.get(Document.class));
81+
}
82+
83+
if (clazz == Document.class) {
84+
return (Codec<T>) new DocumentCodec(registry, bsonTypeClassMap, valueTransformer);
85+
}
86+
87+
return null;
88+
}
89+
90+
@Override
91+
public boolean equals(final Object o) {
92+
if (this == o) {
93+
return true;
94+
}
95+
if (o == null || getClass() != o.getClass()) {
96+
return false;
97+
}
98+
99+
DocumentCodecProvider that = (DocumentCodecProvider) o;
100+
101+
if (!bsonTypeClassMap.equals(that.bsonTypeClassMap)) {
102+
return false;
103+
}
104+
if (valueTransformer != null ? !valueTransformer.equals(that.valueTransformer) : that.valueTransformer != null) {
105+
return false;
106+
}
107+
108+
return true;
109+
}
110+
111+
@Override
112+
public int hashCode() {
113+
int result = bsonTypeClassMap.hashCode();
114+
result = 31 * result + (valueTransformer != null ? valueTransformer.hashCode() : 0);
115+
return result;
116+
}
117+
}

0 commit comments

Comments
 (0)