Skip to content

Commit b83a114

Browse files
committed
Add Java Bean support to the PojoCodec
JAVA-2555
1 parent 775f75f commit b83a114

File tree

93 files changed

+4492
-1376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+4492
-1376
lines changed

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

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@
1616

1717
package org.bson.codecs.pojo;
1818

19-
import java.util.HashMap;
2019
import java.util.List;
2120
import java.util.Map;
2221

2322
/**
24-
* This model represents the metadata for a class and all its fields.
23+
* This model represents the metadata for a class and all its properties.
2524
*
2625
* @param <T> The type of the class the ClassModel represents
2726
* @since 3.5
@@ -34,25 +33,23 @@ public final class ClassModel<T> {
3433
private final boolean discriminatorEnabled;
3534
private final String discriminatorKey;
3635
private final String discriminator;
37-
private final FieldModel<?> idField;
38-
private final List<FieldModel<?>> fieldModels;
39-
private final Map<String, TypeParameterMap> fieldNameToTypeParameterMap;
40-
private final Map<String, FieldModel<?>> fieldMap;
36+
private final PropertyModel<?> idProperty;
37+
private final List<PropertyModel<?>> propertyModels;
38+
private final Map<String, TypeParameterMap> propertyNameToTypeParameterMap;
4139

42-
ClassModel(final Class<T> clazz, final Map<String, TypeParameterMap> fieldNameToTypeParameterMap,
40+
ClassModel(final Class<T> clazz, final Map<String, TypeParameterMap> propertyNameToTypeParameterMap,
4341
final InstanceCreatorFactory<T> instanceCreatorFactory, final Boolean discriminatorEnabled, final String discriminatorKey,
44-
final String discriminator, final FieldModel<?> idField, final List<FieldModel<?>> fieldModels) {
42+
final String discriminator, final PropertyModel<?> idProperty, final List<PropertyModel<?>> propertyModels) {
4543
this.name = clazz.getSimpleName();
4644
this.type = clazz;
4745
this.hasTypeParameters = clazz.getTypeParameters().length > 0;
48-
this.fieldNameToTypeParameterMap = fieldNameToTypeParameterMap;
46+
this.propertyNameToTypeParameterMap = propertyNameToTypeParameterMap;
4947
this.instanceCreatorFactory = instanceCreatorFactory;
5048
this.discriminatorEnabled = discriminatorEnabled;
5149
this.discriminatorKey = discriminatorKey;
5250
this.discriminator = discriminator;
53-
this.idField = idField;
54-
this.fieldModels = fieldModels;
55-
this.fieldMap = generateFieldMap(fieldModels);
51+
this.idProperty = idProperty;
52+
this.propertyModels = propertyModels;
5653
}
5754

5855
/**
@@ -113,31 +110,36 @@ public String getDiscriminator() {
113110
}
114111

115112
/**
116-
* Gets a field by the document field name.
113+
* Gets a {@link PropertyModel} by the property name.
117114
*
118-
* @param documentFieldName the fieldModel's document name
119-
* @return the field or null if the field is not found
115+
* @param propertyName the PropertyModel's property name
116+
* @return the PropertyModel or null if the property is not found
120117
*/
121-
public FieldModel<?> getFieldModel(final String documentFieldName) {
122-
return fieldMap.get(documentFieldName);
118+
public PropertyModel<?> getPropertyModel(final String propertyName) {
119+
for (PropertyModel<?> propertyModel : propertyModels) {
120+
if (propertyModel.getName().equals(propertyName)) {
121+
return propertyModel;
122+
}
123+
}
124+
return null;
123125
}
124126

125127
/**
126-
* Returns all the fields on this model
128+
* Returns all the properties on this model
127129
*
128-
* @return the list of fields
130+
* @return the list of properties
129131
*/
130-
public List<FieldModel<?>> getFieldModels() {
131-
return fieldModels;
132+
public List<PropertyModel<?>> getPropertyModels() {
133+
return propertyModels;
132134
}
133135

134136
/**
135-
* Returns the {@link FieldModel} mapped as the id field for this ClassModel
137+
* Returns the {@link PropertyModel} mapped as the id property for this ClassModel
136138
*
137-
* @return the FieldModel for the id
139+
* @return the PropertyModel for the id
138140
*/
139-
public FieldModel<?> getIdFieldModel() {
140-
return idField;
141+
public PropertyModel<?> getIdPropertyModel() {
142+
return idProperty;
141143
}
142144

143145
/**
@@ -183,13 +185,13 @@ public boolean equals(final Object o) {
183185
if (getDiscriminator() != null ? !getDiscriminator().equals(that.getDiscriminator()) : that.getDiscriminator() != null) {
184186
return false;
185187
}
186-
if (idField != null ? !idField.equals(that.idField) : that.idField != null) {
188+
if (idProperty != null ? !idProperty.equals(that.idProperty) : that.idProperty != null) {
187189
return false;
188190
}
189-
if (!getFieldModels().equals(that.getFieldModels())) {
191+
if (!getPropertyModels().equals(that.getPropertyModels())) {
190192
return false;
191193
}
192-
if (!getFieldNameToTypeParameterMap().equals(that.getFieldNameToTypeParameterMap())) {
194+
if (!getPropertyNameToTypeParameterMap().equals(that.getPropertyNameToTypeParameterMap())) {
193195
return false;
194196
}
195197

@@ -203,26 +205,18 @@ public int hashCode() {
203205
result = 31 * result + (discriminatorEnabled ? 1 : 0);
204206
result = 31 * result + (getDiscriminatorKey() != null ? getDiscriminatorKey().hashCode() : 0);
205207
result = 31 * result + (getDiscriminator() != null ? getDiscriminator().hashCode() : 0);
206-
result = 31 * result + (idField != null ? idField.hashCode() : 0);
207-
result = 31 * result + getFieldModels().hashCode();
208-
result = 31 * result + getFieldNameToTypeParameterMap().hashCode();
208+
result = 31 * result + (idProperty != null ? idProperty.hashCode() : 0);
209+
result = 31 * result + getPropertyModels().hashCode();
210+
result = 31 * result + getPropertyNameToTypeParameterMap().hashCode();
209211
return result;
210212
}
211213

212214
InstanceCreatorFactory<T> getInstanceCreatorFactory() {
213215
return instanceCreatorFactory;
214216
}
215217

216-
Map<String, TypeParameterMap> getFieldNameToTypeParameterMap() {
217-
return fieldNameToTypeParameterMap;
218-
}
219-
220-
private static Map<String, FieldModel<?>> generateFieldMap(final List<FieldModel<?>> fieldModels) {
221-
Map<String, FieldModel<?>> fieldMap = new HashMap<String, FieldModel<?>>();
222-
for (FieldModel<?> fieldModel : fieldModels) {
223-
fieldMap.put(fieldModel.getDocumentFieldName(), fieldModel);
224-
}
225-
return fieldMap;
218+
Map<String, TypeParameterMap> getPropertyNameToTypeParameterMap() {
219+
return propertyNameToTypeParameterMap;
226220
}
227221

228222
}

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

Lines changed: 73 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.bson.codecs.pojo;
1818

19+
import org.bson.codecs.configuration.CodecConfigurationException;
20+
1921
import java.lang.annotation.Annotation;
2022
import java.util.ArrayList;
2123
import java.util.Collections;
@@ -41,17 +43,17 @@
4143
* @see ClassModel
4244
*/
4345
public class ClassModelBuilder<T> {
44-
private static final String ID_FIELD_NAME = "_id";
45-
private final List<FieldModelBuilder<?>> fields = new ArrayList<FieldModelBuilder<?>>();
46+
private static final String ID_PROPERTY_NAME = "_id";
47+
private final List<PropertyModelBuilder<?>> propertyModelBuilders = new ArrayList<PropertyModelBuilder<?>>();
4648
private InstanceCreatorFactory<T> instanceCreatorFactory;
4749
private Class<T> type;
48-
private Map<String, TypeParameterMap> fieldNameToTypeParameterMap = emptyMap();
50+
private Map<String, TypeParameterMap> propertyNameToTypeParameterMap = emptyMap();
4951
private List<Convention> conventions = DEFAULT_CONVENTIONS;
5052
private List<Annotation> annotations = emptyList();
5153
private boolean discriminatorEnabled;
5254
private String discriminator;
5355
private String discriminatorKey;
54-
private String idField;
56+
private String idPropertyName;
5557

5658
ClassModelBuilder(final Class<T> type) {
5759
configureClassModelBuilder(this, notNull("type", type));
@@ -184,59 +186,56 @@ public Boolean useDiscriminator() {
184186
}
185187

186188
/**
187-
* Designates a field as the {@code _id} field for this type. If another field is currently marked as the {@code _id} field,
188-
* that setting is cleared in favor of the named field.
189+
* Designates a property as the {@code _id} property for this type. If another property is currently marked as the {@code _id}
190+
* property, that setting is cleared in favor of the named property.
191+
*
192+
* @param idPropertyName the property name to use for the {@code _id} property
189193
*
190-
* @param idField the FieldModel field name to use for the {@code _id} field
191194
* @return this
192195
*/
193-
public ClassModelBuilder<T> idField(final String idField) {
194-
this.idField = notNull("idField", idField);
196+
public ClassModelBuilder<T> idPropertyName(final String idPropertyName) {
197+
this.idPropertyName = notNull("idPropertyName", idPropertyName);
195198
return this;
196199
}
197200

198201
/**
199-
* @return the designated {@code _id} field for this type or null if not set
202+
* @return the designated {@code _id} property name for this type or null if not set
200203
*/
201-
public String getIdField() {
202-
return idField;
204+
public String getIdPropertyName() {
205+
return idPropertyName;
203206
}
204207

205208
/**
206-
* Remove a field from the builder
209+
* Remove a property from the builder
207210
*
208-
* @param name the actual field name in the POJO and not the {@code documentFieldName}.
209-
* @return returns true if the field matched and was removed
211+
* @param propertyName the actual property name in the POJO and not the {@code documentPropertyName}.
212+
* @return returns true if the property matched and was removed
210213
*/
211-
public boolean removeField(final String name) {
212-
return fields.remove(getField(notNull("name", name)));
214+
public boolean removeProperty(final String propertyName) {
215+
return propertyModelBuilders.remove(getProperty(notNull("propertyName", propertyName)));
213216
}
214217

215218
/**
216-
* Gets a field by the given name.
217-
*
218-
* <p>
219-
* Note: Searches against the actual field name in the POJO and not the {@code documentFieldName}.
220-
* </p>
219+
* Gets a property by the property name.
221220
*
222-
* @param name the name of the field to find.
223-
* @return the field or null if the field is not found
221+
* @param propertyName the name of the property to find.
222+
* @return the property or null if the property is not found
224223
*/
225-
public FieldModelBuilder<?> getField(final String name) {
226-
notNull("name", name);
227-
for (FieldModelBuilder<?> fieldModelBuilder : fields) {
228-
if (fieldModelBuilder.getFieldName().equals(name)) {
229-
return fieldModelBuilder;
224+
public PropertyModelBuilder<?> getProperty(final String propertyName) {
225+
notNull("propertyName", propertyName);
226+
for (PropertyModelBuilder<?> propertyModelBuilder : propertyModelBuilders) {
227+
if (propertyModelBuilder.getName().equals(propertyName)) {
228+
return propertyModelBuilder;
230229
}
231230
}
232231
return null;
233232
}
234233

235234
/**
236-
* @return the fields on the modeled type
235+
* @return the properties on the modeled type
237236
*/
238-
public List<FieldModelBuilder<?>> getFields() {
239-
return Collections.unmodifiableList(fields);
237+
public List<PropertyModelBuilder<?>> getPropertyModelBuilders() {
238+
return Collections.unmodifiableList(propertyModelBuilders);
240239
}
241240

242241
/**
@@ -245,8 +244,8 @@ public List<FieldModelBuilder<?>> getFields() {
245244
* @return the new instance
246245
*/
247246
public ClassModel<T> build() {
248-
List<FieldModel<?>> fieldModels = new ArrayList<FieldModel<?>>();
249-
FieldModel<?> idFieldModel = null;
247+
List<PropertyModel<?>> propertyModels = new ArrayList<PropertyModel<?>>();
248+
PropertyModel<?> idPropertyModel = null;
250249

251250
stateNotNull("type", type);
252251
for (Convention convention : conventions) {
@@ -259,72 +258,71 @@ public ClassModel<T> build() {
259258
stateNotNull("discriminator", discriminator);
260259
}
261260

262-
for (FieldModelBuilder<?> fieldModelBuilder : fields) {
263-
boolean isIdField = fieldModelBuilder.getFieldName().equals(idField);
264-
if (isIdField) {
265-
fieldModelBuilder.documentFieldName(ID_FIELD_NAME);
261+
for (PropertyModelBuilder<?> propertyModelBuilder : propertyModelBuilders) {
262+
boolean isIdProperty = propertyModelBuilder.getName().equals(idPropertyName);
263+
if (isIdProperty) {
264+
propertyModelBuilder.readName(ID_PROPERTY_NAME).writeName(ID_PROPERTY_NAME);
266265
}
267266

268-
FieldModel<?> model = fieldModelBuilder.build();
269-
fieldModels.add(model);
270-
if (isIdField) {
271-
idFieldModel = model;
267+
PropertyModel<?> model = propertyModelBuilder.build();
268+
propertyModels.add(model);
269+
if (isIdProperty) {
270+
idPropertyModel = model;
272271
}
273272
}
274-
validateFieldModels(fieldModels);
273+
validatePropertyModels(type.getSimpleName(), propertyModels);
275274

276275

277-
return new ClassModel<T>(type, fieldNameToTypeParameterMap, instanceCreatorFactory, discriminatorEnabled, discriminatorKey,
278-
discriminator, idFieldModel, unmodifiableList(fieldModels));
276+
return new ClassModel<T>(type, propertyNameToTypeParameterMap, instanceCreatorFactory, discriminatorEnabled, discriminatorKey,
277+
discriminator, idPropertyModel, unmodifiableList(propertyModels));
279278
}
280279

281280
@Override
282281
public String toString() {
283282
return format("ClassModelBuilder{type=%s}", type);
284283
}
285284

286-
Map<String, TypeParameterMap> getFieldNameToTypeParameterMap() {
287-
return fieldNameToTypeParameterMap;
285+
Map<String, TypeParameterMap> getPropertyNameToTypeParameterMap() {
286+
return propertyNameToTypeParameterMap;
288287
}
289288

290-
ClassModelBuilder<T> fieldNameToTypeParameterMap(final Map<String, TypeParameterMap> fieldNameToTypeParameterMap) {
291-
this.fieldNameToTypeParameterMap = unmodifiableMap(new HashMap<String, TypeParameterMap>(fieldNameToTypeParameterMap));
289+
ClassModelBuilder<T> propertyNameToTypeParameterMap(final Map<String, TypeParameterMap> propertyNameToTypeParameterMap) {
290+
this.propertyNameToTypeParameterMap = unmodifiableMap(new HashMap<String, TypeParameterMap>(propertyNameToTypeParameterMap));
292291
return this;
293292
}
294293

295-
ClassModelBuilder<T> addField(final FieldModelBuilder<?> fieldModelBuilder) {
296-
fields.add(notNull("fieldModelBuilder", fieldModelBuilder));
294+
ClassModelBuilder<T> addProperty(final PropertyModelBuilder<?> propertyModelBuilder) {
295+
propertyModelBuilders.add(notNull("propertyModelBuilder", propertyModelBuilder));
297296
return this;
298297
}
299298

300-
private void validateFieldModels(final List<FieldModel<?>> fieldModels) {
301-
Map<String, Integer> fieldNameMap = new HashMap<String, Integer>();
302-
Map<String, Integer> fieldDocumentNameMap = new HashMap<String, Integer>();
303-
String duplicateFieldName = null;
304-
String duplicateDocumentFieldName = null;
305-
306-
for (FieldModel<?> fieldModel : fieldModels) {
307-
String fieldName = fieldModel.getFieldName();
308-
if (fieldNameMap.containsKey(fieldName)) {
309-
duplicateFieldName = fieldName;
310-
break;
311-
}
312-
fieldNameMap.put(fieldName, 1);
299+
private void validatePropertyModels(final String declaringClass, final List<PropertyModel<?>> propertyModels) {
300+
Map<String, Integer> propertyNameMap = new HashMap<String, Integer>();
301+
Map<String, Integer> propertyReadNameMap = new HashMap<String, Integer>();
302+
Map<String, Integer> propertyWriteNameMap = new HashMap<String, Integer>();
313303

314-
String documentFieldName = fieldModel.getDocumentFieldName();
315-
if (fieldDocumentNameMap.containsKey(documentFieldName)) {
316-
duplicateDocumentFieldName = documentFieldName;
317-
break;
304+
for (PropertyModel<?> propertyModel : propertyModels) {
305+
checkForDuplicates("property", propertyModel.getName(), propertyNameMap, declaringClass);
306+
if (propertyModel.isReadable()) {
307+
checkForDuplicates("read property", propertyModel.getReadName(), propertyReadNameMap, declaringClass);
308+
}
309+
if (propertyModel.isWritable()) {
310+
checkForDuplicates("write property", propertyModel.getWriteName(), propertyWriteNameMap, declaringClass);
318311
}
319-
fieldDocumentNameMap.put(documentFieldName, 1);
320312
}
321-
if (idField != null && !fieldNameMap.containsKey(idField)) {
322-
throw new IllegalStateException(format("Invalid id field, field named field '%s' can not be found.", idField));
323-
} else if (duplicateFieldName != null) {
324-
throw new IllegalStateException(format("Duplicate field named '%s' found.", duplicateFieldName));
325-
} else if (duplicateDocumentFieldName != null) {
326-
throw new IllegalStateException(format("Duplicate document field named '%s' found.", duplicateDocumentFieldName));
313+
314+
if (idPropertyName != null && !propertyNameMap.containsKey(idPropertyName)) {
315+
throw new CodecConfigurationException(format("Invalid id property, property named '%s' can not be found.", idPropertyName));
316+
}
317+
}
318+
319+
private void checkForDuplicates(final String propertyType, final String propertyName, final Map<String, Integer> propertyNameMap,
320+
final String declaringClass) {
321+
if (propertyNameMap.containsKey(propertyName)) {
322+
throw new CodecConfigurationException(format("Duplicate %s named '%s' found in %s.", propertyType, propertyName,
323+
declaringClass));
327324
}
325+
propertyNameMap.put(propertyName, 1);
328326
}
329327

330328
}

0 commit comments

Comments
 (0)