MapWrapperTest.java
/*
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.reflection.wrapper;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class MapWrapperTest {
@Test
void assertBasicOperations() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", "100");
map.put("b", null);
map.put("my_name", Integer.valueOf(200));
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
assertFalse(metaObj.isCollection());
assertTrue(metaObj.hasGetter("a"));
assertTrue(metaObj.hasSetter("a"));
assertTrue(metaObj.hasGetter("b.anykey"));
assertEquals("a", metaObj.findProperty("a", false));
assertEquals("b", metaObj.findProperty("b", false));
assertEquals("my_name", metaObj.findProperty("my_name", false));
assertEquals("my_name", metaObj.findProperty("my_name", true));
assertArrayEquals(new String[] { "a", "b", "my_name" }, metaObj.getGetterNames());
assertArrayEquals(new String[] { "a", "b", "my_name" }, metaObj.getSetterNames());
assertEquals(String.class, metaObj.getGetterType("a"));
assertEquals(Object.class, metaObj.getGetterType("b"));
assertEquals(Integer.class, metaObj.getGetterType("my_name"));
assertEquals(String.class, metaObj.getSetterType("a"));
assertEquals(Object.class, metaObj.getSetterType("b"));
assertEquals(Integer.class, metaObj.getSetterType("my_name"));
assertEquals("100", metaObj.getValue("a"));
assertNull(metaObj.getValue("b"));
assertEquals(Integer.valueOf(200), metaObj.getValue("my_name"));
try {
metaObj.add("x");
fail();
} catch (UnsupportedOperationException e) {
// pass
}
try {
metaObj.addAll(Arrays.asList("x", "y"));
fail();
} catch (UnsupportedOperationException e) {
// pass
}
metaObj.setValue("a", Long.valueOf(900L));
assertEquals(Long.valueOf(900L), map.get("a"));
}
@Test
void assertNonExistentKey() {
Map<String, Object> map = new HashMap<>();
map.put("a", "100");
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
assertEquals("anykey", metaObj.findProperty("anykey", false));
assertFalse(metaObj.hasGetter("anykey"));
assertFalse(metaObj.hasGetter("child.anykey"));
assertEquals(Object.class, metaObj.getGetterType("anykey"));
assertEquals(Object.class, metaObj.getGetterType("child.anykey"));
assertTrue(metaObj.hasSetter("anykey"));
assertTrue(metaObj.hasSetter("child.anykey"));
assertEquals(Object.class, metaObj.getSetterType("anykey"));
assertEquals(Object.class, metaObj.getSetterType("child.anykey"));
assertNull(metaObj.getValue("anykey"));
metaObj.setValue("anykey", Integer.valueOf(200));
metaObj.setValue("child.anykey", Integer.valueOf(300));
assertEquals(3, map.size());
assertEquals(Integer.valueOf(200), map.get("anykey"));
@SuppressWarnings("unchecked")
Map<String, Object> childMap = (Map<String, Object>) map.get("child");
assertEquals(Integer.valueOf(300), childMap.get("anykey"));
}
@Test
void assertChildBean() {
Map<String, Object> map = new HashMap<>();
TestBean bean = new TestBean();
bean.setPropA("aaa");
map.put("bean", bean);
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
assertTrue(metaObj.hasGetter("bean.propA"));
assertEquals(String.class, metaObj.getGetterType("bean.propA"));
assertEquals(String.class, metaObj.getSetterType("bean.propA"));
assertEquals("aaa", metaObj.getValue("bean.propA"));
assertNull(metaObj.getValue("bean.propB"));
metaObj.setValue("bean.propB", "bbb");
assertEquals("bbb", bean.getPropB());
metaObj.setValue("bean.propA", "ccc");
assertEquals("ccc", bean.getPropA());
}
static class TestBean {
private String propA;
private String propB;
public String getPropA() {
return propA;
}
public void setPropA(String propA) {
this.propA = propA;
}
public String getPropB() {
return propB;
}
public void setPropB(String propB) {
this.propB = propB;
}
}
@Test
void accessIndexedList() {
Map<String, Object> map = new HashMap<>();
List<String> list = Arrays.asList("a", "b", "c");
map.put("list", list);
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
assertEquals("b", metaObj.getValue("list[1]"));
metaObj.setValue("list[2]", "x");
assertEquals("x", list.get(2));
try {
metaObj.setValue("list[3]", "y");
fail();
} catch (IndexOutOfBoundsException e) {
// pass
}
assertTrue(metaObj.hasGetter("list[1]"));
assertTrue(metaObj.hasSetter("list[1]"));
// this one looks wrong
// assertFalse(metaObj.hasSetter("list[3]"));
}
@Test
void accessIndexedMap() {
Map<String, Object> map = new HashMap<>();
Map<String, String> submap = new HashMap<>();
submap.put("a", "100");
submap.put("b", "200");
submap.put("c", "300");
map.put("submap", submap);
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
new DefaultReflectorFactory());
assertEquals("200", metaObj.getValue("submap[b]"));
metaObj.setValue("submap[c]", "999");
assertEquals("999", submap.get("c"));
metaObj.setValue("submap[d]", "400");
assertEquals(4, submap.size());
assertEquals("400", submap.get("d"));
assertTrue(metaObj.hasGetter("submap[b]"));
assertTrue(metaObj.hasGetter("submap[anykey]"));
assertTrue(metaObj.hasSetter("submap[d]"));
assertTrue(metaObj.hasSetter("submap[anykey]"));
}
@ParameterizedTest
@CsvSource({ "abc[def]", "abc.def", "abc.def.ghi", "abc[d.ef].ghi" })
void customMapWrapper(String key) {
Map<String, Object> map = new HashMap<>();
MetaObject metaObj = MetaObject.forObject(map, new DefaultObjectFactory(), new FlatMapWrapperFactory(),
new DefaultReflectorFactory());
metaObj.setValue(key, "1");
assertEquals("1", map.get(key));
assertEquals("1", metaObj.getValue(key));
}
static class FlatMapWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
return object instanceof Map;
}
@SuppressWarnings("unchecked")
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new FlatMapWrapper(metaObject, (Map<String, Object>) object, metaObject.getObjectFactory());
}
}
static class FlatMapWrapper extends MapWrapper {
public FlatMapWrapper(MetaObject metaObject, Map<String, Object> map, ObjectFactory objectFactory) {
super(metaObject, map);
}
@Override
public Object get(PropertyTokenizer prop) {
String key;
if (prop.getChildren() == null) {
key = prop.getIndexedName();
} else {
key = prop.getIndexedName() + "." + prop.getChildren();
}
return map.get(key);
}
@Override
public void set(PropertyTokenizer prop, Object value) {
String key;
if (prop.getChildren() == null) {
key = prop.getIndexedName();
} else {
key = prop.getIndexedName() + "." + prop.getChildren();
}
map.put(key, value);
}
}
}