Skip to content

Commit

Permalink
Allow custom Map wrapper to handle keys with dots or breackets
Browse files Browse the repository at this point in the history
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
  • Loading branch information
harawata committed Jan 24, 2024
1 parent e34bb50 commit c2f18b7
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 41 deletions.
26 changes: 3 additions & 23 deletions src/main/java/org/apache/ibatis/reflection/MetaObject.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2024 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.
Expand Down Expand Up @@ -112,31 +112,11 @@ public boolean hasGetter(String name) {

public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (!prop.hasNext()) {
return objectWrapper.get(prop);
}
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
}
return metaValue.getValue(prop.getChildren());
return objectWrapper.get(prop);
}

public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null
return;
}
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
objectWrapper.set(new PropertyTokenizer(name), value);
}

public MetaObject metaObjectForProperty(String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2024 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.
Expand All @@ -20,6 +20,7 @@

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectionException;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;

/**
Expand Down Expand Up @@ -104,4 +105,23 @@ protected void setCollectionValue(PropertyTokenizer prop, Object collection, Obj
}
}

protected Object getChildValue(PropertyTokenizer prop) {
MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
}
return metaValue.getValue(prop.getChildren());
}

protected void setChildValue(PropertyTokenizer prop, Object value) {
MetaObject metaValue = metaObject.metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null
return;
}
metaValue = instantiatePropertyValue(null, new PropertyTokenizer(prop.getName()), metaObject.getObjectFactory());
}
metaValue.setValue(prop.getChildren(), value);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2024 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.
Expand Down Expand Up @@ -42,18 +42,21 @@ public BeanWrapper(MetaObject metaObject, Object object) {

@Override
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
return getCollectionValue(prop, collection);
if (prop.hasNext()) {
return getChildValue(prop);
} else if (prop.getIndex() != null) {
return getCollectionValue(prop, resolveCollection(prop, object));
} else {
return getBeanProperty(prop, object);
}
return getBeanProperty(prop, object);
}

@Override
public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
setCollectionValue(prop, collection, value);
if (prop.hasNext()) {
setChildValue(prop, value);
} else if (prop.getIndex() != null) {
setCollectionValue(prop, resolveCollection(prop, object), value);
} else {
setBeanProperty(prop, object, value);
}
Expand Down
21 changes: 12 additions & 9 deletions src/main/java/org/apache/ibatis/reflection/wrapper/MapWrapper.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2024 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.
Expand Down Expand Up @@ -29,7 +29,7 @@
*/
public class MapWrapper extends BaseWrapper {

private final Map<String, Object> map;
protected final Map<String, Object> map;

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

@Override
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, map);
return getCollectionValue(prop, collection);
if (prop.hasNext()) {
return getChildValue(prop);
} else if (prop.getIndex() != null) {
return getCollectionValue(prop, resolveCollection(prop, map));
} else {
return map.get(prop.getName());
}
return map.get(prop.getName());
}

@Override
public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, map);
setCollectionValue(prop, collection, value);
if (prop.hasNext()) {
setChildValue(prop, value);
} else if (prop.getIndex() != null) {
setCollectionValue(prop, resolveCollection(prop, map), value);
} else {
map.put(prop.getName(), value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
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 {

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

@ParameterizedTest
@CsvSource({ "abc[def]", "abc.def", "abc.def.ghi", "abc[d.ef].ghi" })
void testCustomMapWrapper(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);
}
}

}

0 comments on commit c2f18b7

Please sign in to comment.