Skip to content

Commit c2f18b7

Browse files
committed
Allow custom Map wrapper to handle keys with dots or breackets
It's now possible to write a custom Map wrapper that allows keys that contain period (dot) `.` or brackets `[]`. Also, it's now possible to write a custom Map wrapper by extending the built-in `MapWrapper`. Should fix mybatis#13 mybatis#2298 mybatis#3062
1 parent e34bb50 commit c2f18b7

File tree

5 files changed

+103
-41
lines changed

5 files changed

+103
-41
lines changed

src/main/java/org/apache/ibatis/reflection/MetaObject.java

+3-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -112,31 +112,11 @@ public boolean hasGetter(String name) {
112112

113113
public Object getValue(String name) {
114114
PropertyTokenizer prop = new PropertyTokenizer(name);
115-
if (!prop.hasNext()) {
116-
return objectWrapper.get(prop);
117-
}
118-
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
119-
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
120-
return null;
121-
}
122-
return metaValue.getValue(prop.getChildren());
115+
return objectWrapper.get(prop);
123116
}
124117

125118
public void setValue(String name, Object value) {
126-
PropertyTokenizer prop = new PropertyTokenizer(name);
127-
if (prop.hasNext()) {
128-
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
129-
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
130-
if (value == null) {
131-
// don't instantiate child path if value is null
132-
return;
133-
}
134-
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
135-
}
136-
metaValue.setValue(prop.getChildren(), value);
137-
} else {
138-
objectWrapper.set(prop, value);
139-
}
119+
objectWrapper.set(new PropertyTokenizer(name), value);
140120
}
141121

142122
public MetaObject metaObjectForProperty(String name) {

src/main/java/org/apache/ibatis/reflection/wrapper/BaseWrapper.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020

2121
import org.apache.ibatis.reflection.MetaObject;
2222
import org.apache.ibatis.reflection.ReflectionException;
23+
import org.apache.ibatis.reflection.SystemMetaObject;
2324
import org.apache.ibatis.reflection.property.PropertyTokenizer;
2425

2526
/**
@@ -104,4 +105,23 @@ protected void setCollectionValue(PropertyTokenizer prop, Object collection, Obj
104105
}
105106
}
106107

108+
protected Object getChildValue(PropertyTokenizer prop) {
109+
MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
110+
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
111+
return null;
112+
}
113+
return metaValue.getValue(prop.getChildren());
114+
}
115+
116+
protected void setChildValue(PropertyTokenizer prop, Object value) {
117+
MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
118+
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
119+
if (value == null) {
120+
// don't instantiate child path if value is null
121+
return;
122+
}
123+
metaValue = instantiatePropertyValue(null, new PropertyTokenizer(prop.getName()), metaObject.getObjectFactory());
124+
}
125+
metaValue.setValue(prop.getChildren(), value);
126+
}
107127
}

src/main/java/org/apache/ibatis/reflection/wrapper/BeanWrapper.java

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,18 +42,21 @@ public BeanWrapper(MetaObject metaObject, Object object) {
4242

4343
@Override
4444
public Object get(PropertyTokenizer prop) {
45-
if (prop.getIndex() != null) {
46-
Object collection = resolveCollection(prop, object);
47-
return getCollectionValue(prop, collection);
45+
if (prop.hasNext()) {
46+
return getChildValue(prop);
47+
} else if (prop.getIndex() != null) {
48+
return getCollectionValue(prop, resolveCollection(prop, object));
49+
} else {
50+
return getBeanProperty(prop, object);
4851
}
49-
return getBeanProperty(prop, object);
5052
}
5153

5254
@Override
5355
public void set(PropertyTokenizer prop, Object value) {
54-
if (prop.getIndex() != null) {
55-
Object collection = resolveCollection(prop, object);
56-
setCollectionValue(prop, collection, value);
56+
if (prop.hasNext()) {
57+
setChildValue(prop, value);
58+
} else if (prop.getIndex() != null) {
59+
setCollectionValue(prop, resolveCollection(prop, object), value);
5760
} else {
5861
setBeanProperty(prop, object, value);
5962
}

src/main/java/org/apache/ibatis/reflection/wrapper/MapWrapper.java

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
2929
*/
3030
public class MapWrapper extends BaseWrapper {
3131

32-
private final Map<String, Object> map;
32+
protected final Map<String, Object> map;
3333

3434
public MapWrapper(MetaObject metaObject, Map<String, Object> map) {
3535
super(metaObject);
@@ -38,18 +38,21 @@ public MapWrapper(MetaObject metaObject, Map<String, Object> map) {
3838

3939
@Override
4040
public Object get(PropertyTokenizer prop) {
41-
if (prop.getIndex() != null) {
42-
Object collection = resolveCollection(prop, map);
43-
return getCollectionValue(prop, collection);
41+
if (prop.hasNext()) {
42+
return getChildValue(prop);
43+
} else if (prop.getIndex() != null) {
44+
return getCollectionValue(prop, resolveCollection(prop, map));
45+
} else {
46+
return map.get(prop.getName());
4447
}
45-
return map.get(prop.getName());
4648
}
4749

4850
@Override
4951
public void set(PropertyTokenizer prop, Object value) {
50-
if (prop.getIndex() != null) {
51-
Object collection = resolveCollection(prop, map);
52-
setCollectionValue(prop, collection, value);
52+
if (prop.hasNext()) {
53+
setChildValue(prop, value);
54+
} else if (prop.getIndex() != null) {
55+
setCollectionValue(prop, resolveCollection(prop, map), value);
5356
} else {
5457
map.put(prop.getName(), value);
5558
}

src/test/java/org/apache/ibatis/reflection/wrapper/MapWrapperTest.java

+56
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@
2727
import org.apache.ibatis.reflection.DefaultReflectorFactory;
2828
import org.apache.ibatis.reflection.MetaObject;
2929
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
30+
import org.apache.ibatis.reflection.factory.ObjectFactory;
31+
import org.apache.ibatis.reflection.property.PropertyTokenizer;
3032
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.params.ParameterizedTest;
34+
import org.junit.jupiter.params.provider.CsvSource;
3135

3236
class MapWrapperTest {
3337

@@ -192,4 +196,56 @@ void accessIndexedMap() {
192196
assertTrue(metaObj.hasSetter("submap[anykey]"));
193197
}
194198

199+
@ParameterizedTest
200+
@CsvSource({ "abc[def]", "abc.def", "abc.def.ghi", "abc[d.ef].ghi" })
201+
void testCustomMapWrapper(String key) {
202+
Map<String, Object> map = new HashMap<>();
203+
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new FlatMapWrapperFactory(),
204+
new DefaultReflectorFactory());
205+
metaObj.setValue(key, "1");
206+
assertEquals("1", map.get(key));
207+
assertEquals("1", metaObj.getValue(key));
208+
}
209+
210+
static class FlatMapWrapperFactory implements ObjectWrapperFactory {
211+
@Override
212+
public boolean hasWrapperFor(Object object) {
213+
return object instanceof Map;
214+
}
215+
216+
@SuppressWarnings("unchecked")
217+
@Override
218+
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
219+
return new FlatMapWrapper(metaObject, (Map<String, Object>) object, metaObject.getObjectFactory());
220+
}
221+
}
222+
223+
static class FlatMapWrapper extends MapWrapper {
224+
public FlatMapWrapper(MetaObject metaObject, Map<String, Object> map, ObjectFactory objectFactory) {
225+
super(metaObject, map);
226+
}
227+
228+
@Override
229+
public Object get(PropertyTokenizer prop) {
230+
String key;
231+
if (prop.getChildren() == null) {
232+
key = prop.getIndexedName();
233+
} else {
234+
key = prop.getIndexedName() + "." + prop.getChildren();
235+
}
236+
return map.get(key);
237+
}
238+
239+
@Override
240+
public void set(PropertyTokenizer prop, Object value) {
241+
String key;
242+
if (prop.getChildren() == null) {
243+
key = prop.getIndexedName();
244+
} else {
245+
key = prop.getIndexedName() + "." + prop.getChildren();
246+
}
247+
map.put(key, value);
248+
}
249+
}
250+
195251
}

0 commit comments

Comments
 (0)