Skip to content

Commit 3095be0

Browse files
committed
Added a SET_PRIVATE_FIELDS_CONVENTION
Some existing JSON libraries set field values directly. While we respect field access modifiers by default, this commit adds a `SET_PRIVATE_FIELDS_CONVENTION` that can be used to directly set private fields, thus mimicing other libraries. This convention is not part of the default conventions and must be explicitly set. JAVA-2648
1 parent 5d4c2c4 commit 3095be0

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
17+
package org.bson.codecs.pojo;
18+
19+
import org.bson.codecs.configuration.CodecConfigurationException;
20+
21+
import java.lang.reflect.Field;
22+
23+
import static java.lang.String.format;
24+
import static java.lang.reflect.Modifier.isPrivate;
25+
26+
final class ConventionSetPrivateFieldImpl implements Convention {
27+
28+
@Override
29+
public void apply(final ClassModelBuilder<?> classModelBuilder) {
30+
for (PropertyModelBuilder<?> propertyModelBuilder : classModelBuilder.getPropertyModelBuilders()) {
31+
if (propertyModelBuilder.getPropertyAccessor() instanceof PropertyAccessorImpl) {
32+
PropertyAccessorImpl<?> defaultAccessor = (PropertyAccessorImpl<?>) propertyModelBuilder.getPropertyAccessor();
33+
PropertyMetadata<?> propertyMetaData = defaultAccessor.getPropertyMetadata();
34+
if (!propertyMetaData.isDeserializable() && isPrivate(propertyMetaData.getField().getModifiers())) {
35+
setPropertyAccessor(propertyModelBuilder);
36+
}
37+
}
38+
}
39+
}
40+
41+
@SuppressWarnings("unchecked")
42+
private <T> void setPropertyAccessor(final PropertyModelBuilder<T> propertyModelBuilder) {
43+
propertyModelBuilder.propertyAccessor(new PrivateProperyAccessor<T>(
44+
(PropertyAccessorImpl<T>) propertyModelBuilder.getPropertyAccessor()));
45+
}
46+
47+
private static final class PrivateProperyAccessor<T> implements PropertyAccessor<T> {
48+
private final PropertyAccessorImpl<T> wrapped;
49+
50+
private PrivateProperyAccessor(final PropertyAccessorImpl<T> wrapped) {
51+
this.wrapped = wrapped;
52+
}
53+
54+
@Override
55+
public <S> T get(final S instance) {
56+
return wrapped.get(instance);
57+
}
58+
59+
@Override
60+
public <S> void set(final S instance, final T value) {
61+
try {
62+
Field field = wrapped.getPropertyMetadata().getField();
63+
field.setAccessible(true);
64+
field.set(instance, value);
65+
} catch (Exception e) {
66+
throw new CodecConfigurationException(format("Unable to set value for property '%s' in %s",
67+
wrapped.getPropertyMetadata().getName(),
68+
wrapped.getPropertyMetadata().getDeclaringClassName()), e);
69+
}
70+
}
71+
}
72+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ public final class Conventions {
4949
*/
5050
public static final Convention ANNOTATION_CONVENTION = new ConventionAnnotationImpl();
5151

52+
/**
53+
* A convention that enables private fields to be set using reflection.
54+
*
55+
* <p>This convention, mimics how some other JSON libraries directly set a private field when there is no setter.</p>
56+
* <p>Note: This convention is not part of the {@code DEFAULT_CONVENTIONS} list and must explicitly be set.</p>
57+
*
58+
* @since 3.6
59+
*/
60+
public static final Convention SET_PRIVATE_FIELDS_CONVENTION = new ConventionSetPrivateFieldImpl();
61+
5262
/**
5363
* The default conventions list
5464
*/

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public <S> void set(final S instance, final T value) {
6161
}
6262
}
6363

64+
PropertyMetadata<T> getPropertyMetadata() {
65+
return propertyMetadata;
66+
}
67+
6468
private CodecConfigurationException getError(final Exception cause) {
6569
return new CodecConfigurationException(format("Unable to get value for property '%s' in %s", propertyMetadata.getName(),
6670
propertyMetadata.getDeclaringClassName()), cause);

bson/src/test/unit/org/bson/codecs/pojo/PojoCustomTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.bson.codecs.pojo.entities.Optional;
3737
import org.bson.codecs.pojo.entities.OptionalPropertyCodecProvider;
3838
import org.bson.codecs.pojo.entities.PrimitivesModel;
39+
import org.bson.codecs.pojo.entities.PrivateSetterFieldModel;
3940
import org.bson.codecs.pojo.entities.SimpleEnum;
4041
import org.bson.codecs.pojo.entities.SimpleEnumModel;
4142
import org.bson.codecs.pojo.entities.SimpleModel;
@@ -48,6 +49,7 @@
4849
import org.bson.types.ObjectId;
4950
import org.junit.Test;
5051

52+
import java.util.ArrayList;
5153
import java.util.Collection;
5254
import java.util.Collections;
5355
import java.util.List;
@@ -58,7 +60,9 @@
5860
import static org.bson.codecs.configuration.CodecRegistries.fromCodecs;
5961
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
6062
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
63+
import static org.bson.codecs.pojo.Conventions.DEFAULT_CONVENTIONS;
6164
import static org.bson.codecs.pojo.Conventions.NO_CONVENTIONS;
65+
import static org.bson.codecs.pojo.Conventions.SET_PRIVATE_FIELDS_CONVENTION;
6266

6367
public final class PojoCustomTest extends PojoTestCase {
6468

@@ -153,6 +157,17 @@ public void apply(final ClassModelBuilder<?> classModelBuilder) {
153157
+ " 'simple_model': {'integer_field': 42, 'string_field': 'myString' } } }");
154158
}
155159

160+
@Test
161+
public void testSetPrivateFieldConvention() {
162+
PojoCodecProvider.Builder builder = getPojoCodecProviderBuilder(PrivateSetterFieldModel.class);
163+
ArrayList<Convention> conventions = new ArrayList<Convention>(DEFAULT_CONVENTIONS);
164+
conventions.add(SET_PRIVATE_FIELDS_CONVENTION);
165+
builder.conventions(conventions);
166+
167+
roundTrip(builder, new PrivateSetterFieldModel(1, "2", asList("a", "b")),
168+
"{'integerField': 1, 'stringField': '2', listField: ['a', 'b']}");
169+
}
170+
156171
@Test
157172
public void testEnumSupport() {
158173
roundTrip(getPojoCodecProviderBuilder(SimpleEnumModel.class), new SimpleEnumModel(SimpleEnum.BRAVO), "{ 'myEnum': 'BRAVO' }");
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
17+
package org.bson.codecs.pojo.entities;
18+
19+
import java.util.List;
20+
21+
public final class PrivateSetterFieldModel {
22+
23+
private Integer integerField;
24+
private String stringField;
25+
private List<String> listField;
26+
27+
public PrivateSetterFieldModel(){
28+
}
29+
30+
public PrivateSetterFieldModel(final Integer integerField, final String stringField, final List<String> listField) {
31+
this.integerField = integerField;
32+
this.stringField = stringField;
33+
this.listField = listField;
34+
}
35+
36+
public Integer getIntegerField() {
37+
return integerField;
38+
}
39+
40+
public String getStringField() {
41+
return stringField;
42+
}
43+
44+
public List<String> getListField() {
45+
return listField;
46+
}
47+
48+
@Override
49+
public boolean equals(final Object o) {
50+
if (this == o) {
51+
return true;
52+
}
53+
if (o == null || getClass() != o.getClass()) {
54+
return false;
55+
}
56+
57+
PrivateSetterFieldModel that = (PrivateSetterFieldModel) o;
58+
59+
if (getIntegerField() != null ? !getIntegerField().equals(that.getIntegerField()) : that.getIntegerField() != null) {
60+
return false;
61+
}
62+
if (getStringField() != null ? !getStringField().equals(that.getStringField()) : that.getStringField() != null) {
63+
return false;
64+
}
65+
return getListField() != null ? getListField().equals(that.getListField()) : that.getListField() == null;
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
int result = getIntegerField() != null ? getIntegerField().hashCode() : 0;
71+
result = 31 * result + (getStringField() != null ? getStringField().hashCode() : 0);
72+
result = 31 * result + (getListField() != null ? getListField().hashCode() : 0);
73+
return result;
74+
}
75+
}

0 commit comments

Comments
 (0)