Skip to content

Commit a59fbe4

Browse files
committed
perf: optimised MapUtil with safer implementations
1 parent 0031750 commit a59fbe4

File tree

4 files changed

+340
-154
lines changed

4 files changed

+340
-154
lines changed

devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java

Lines changed: 47 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -19,204 +19,98 @@
1919

2020
import lombok.extern.slf4j.Slf4j;
2121

22-
import java.lang.reflect.InvocationTargetException;
2322
import java.util.HashMap;
2423
import java.util.Map;
24+
import java.util.Optional;
2525

2626
/**
27-
* {@code MapUtil} is a utility class that provides methods for converting
28-
* objects to maps and maps to objects.
27+
* {@code MapUtil} is a utility class that provides methods for converting objects to maps and maps
28+
* to objects.
2929
* <p>
30-
* It also provides methods for getting and setting field values using
31-
* reflection.
30+
* Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API.
31+
* Please see <a href="">documentation</a> for more information.
3232
*
3333
* @author Zihlu Wang
34-
* @version 1.1.0
34+
* @version 1.4.2
35+
* @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil
3536
* @since 1.0.0
3637
*/
3738
@Slf4j
3839
public final class MapUtil {
3940

4041
/**
41-
* Converts an object to a map by mapping the field names to their
42-
* corresponding values.
42+
* Converts an object to a map by mapping the field names to their corresponding values.
4343
*
44-
* @param obj the object to be converted to a map
44+
* @param entity the object to be converted to a map
4545
* @return a map representing the fields and their values of the object
46-
* @throws IllegalAccessException if an error occurs while accessing the
47-
* fields of the object
4846
*/
49-
public static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
50-
if (obj == null) {
51-
return null;
52-
}
53-
54-
var map = new HashMap<String, Object>();
55-
56-
var declaredFields = obj.getClass().getDeclaredFields();
57-
for (var field : declaredFields) {
58-
field.setAccessible(true);
59-
Object result = field.get(obj);
60-
if (result != null) {
61-
map.put(field.getName(), result);
62-
}
63-
}
64-
65-
return map;
47+
public static <T> Map<String, Object> objectToMap(T entity,
48+
Map<String, ObjectMapAdapter<T, ?>> adapters) {
49+
var resultMap = new HashMap<String, Object>();
50+
adapters.forEach((fieldName, adapter) -> resultMap.put(fieldName, adapter.fetch(entity)));
51+
return resultMap;
6652
}
6753

6854
/**
69-
* Converts a map to an object of the specified type by setting the field
70-
* values using the map entries.
55+
* Converts a map to an object of the specified type by setting the field values using the
56+
* map entries.
7157
*
72-
* @param map the map representing the fields and their values
73-
* @param requiredType the class of the object to be created
74-
* @param <T> the type of the object to be created
75-
* @return an object of the specified type with the field values set from
76-
* the map
77-
* @throws NoSuchMethodException if the constructor of the required
78-
* type is not found
79-
* @throws InvocationTargetException if an error occurs while invoking the
80-
* constructor
81-
* @throws InstantiationException if the required type is abstract or an
82-
* interface
83-
* @throws IllegalAccessException if an error occurs while accessing the
84-
* fields of the object
58+
* @param objectMap the map representing the fields and their values
59+
* @param entity an empty entity of the target class
60+
* @param adapters the adapters to execute the setter for the entity
61+
* @param <T> the type of the object to be created
62+
* @return an object of the specified type with the field values set from the map
8563
*/
86-
public static <T> T mapToObject(Map<String, Object> map, Class<T> requiredType) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
87-
var bean = requiredType.getConstructor().newInstance();
88-
if (map != null) {
89-
for (var entry : map.entrySet()) {
90-
try {
91-
var entryValue = entry.getValue().toString();
92-
// get the field by field name
93-
var field = requiredType.getDeclaredField(entry.getKey());
94-
var fieldType = field.getGenericType();
95-
96-
// convert field value by class
97-
if (fieldType instanceof Class<?> fieldClass) {
98-
if (fieldClass == Short.class || fieldClass == short.class) {
99-
entry.setValue(Short.parseShort(entryValue));
100-
} else if (fieldClass == Integer.class || fieldClass == int.class) {
101-
entry.setValue(Integer.parseInt(entryValue));
102-
} else if (fieldClass == Long.class || fieldClass == long.class) {
103-
entry.setValue(Long.parseLong(entryValue));
104-
} else if (fieldClass == Float.class || fieldClass == float.class) {
105-
entry.setValue(Float.parseFloat(entryValue));
106-
} else if (fieldClass == Double.class || fieldClass == double.class) {
107-
entry.setValue(Double.parseDouble(entryValue));
108-
} else if (fieldClass == Character.class || fieldClass == char.class) {
109-
entry.setValue(entryValue.charAt(0));
110-
} else if (fieldClass == Byte.class || fieldClass == byte.class) {
111-
entry.setValue(Byte.parseByte(entryValue));
112-
} else if (fieldClass == Boolean.class || fieldClass == boolean.class) {
113-
entry.setValue(Boolean.parseBoolean(entryValue));
114-
} else if (fieldClass == String.class) {
115-
entry.setValue(entryValue);
116-
} else {
117-
log.error("Unable to determine the type of property {}.", field.getName());
118-
continue;
119-
}
120-
}
121-
122-
setFieldValue(bean, entry.getKey(), entry.getValue());
123-
} catch (Exception e) {
124-
log.error("Map to Object failed.");
125-
}
126-
}
127-
}
128-
return bean;
64+
public static <T> T mapToObject(Map<String, Object> objectMap,
65+
T entity,
66+
Map<String, ObjectMapAdapter<T, ?>> adapters) {
67+
adapters.forEach((fieldName, adapter) -> Optional.ofNullable(objectMap)
68+
.map((data) -> data.get(fieldName))
69+
.ifPresent((fieldValue) -> adapter.setValue(entity, fieldValue)));
70+
return entity;
12971
}
13072

13173
/**
13274
* Retrieves the value of a field from an object using reflection.
13375
*
134-
* @param obj the object from which to retrieve the field value
135-
* @param fieldName the name of the field
136-
* @param fieldType the class representing the type of the field value
137-
* @param <T> the type of the field value
138-
* @return the value of the field in the object, or null if the field does
139-
* not exist or cannot be accessed
140-
* @throws IllegalAccessException if an error occurs while accessing the
141-
* field
142-
* @throws InvocationTargetException if an error occurs while invoking the
143-
* field getter method
144-
* @throws NoSuchMethodException if the specified getter is not present
76+
* @param <T> the type of the field value
77+
* @param entity the object from which to retrieve the field value
78+
* @param adapter the adapter to execute the getter
79+
* @return the value of the field in the object, or null if the field does not exist or cannot
80+
* be accessed
14581
*/
146-
public static <T> T getFieldValue(Object obj, String fieldName, Class<T> fieldType) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
147-
var methodName = getMethodName("get", fieldName);
148-
var objectClass = obj.getClass();
149-
var method = objectClass.getDeclaredMethod(methodName);
150-
151-
method.setAccessible(true);
152-
return cast(method.invoke(obj), fieldType);
82+
public static <E, T> T getFieldValue(E entity, ObjectMapAdapter<E, T> adapter) {
83+
return adapter.fetch(entity);
15384
}
15485

15586
/**
15687
* Sets the value of a field in an object using reflection.
15788
*
158-
* @param obj the object in which to set the field value
159-
* @param fieldName the name of the field
89+
* @param entity the object in which to set the field value
90+
* @param adapter the adapter to execute the setter
16091
* @param fieldValue the value to be set
161-
* @throws InvocationTargetException if an error occurs while invoking the
162-
* field setter method
163-
* @throws IllegalAccessException if an error occurs while accessing the
164-
* field
165-
* @throws NoSuchMethodException if the specific setter is not present
16692
*/
167-
public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
168-
var objectClass = obj.getClass();
169-
var methodName = getMethodName("set", fieldName);
170-
var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass());
171-
method.setAccessible(true);
172-
method.invoke(obj, fieldValue);
93+
public static <E, T> void setFieldValue(E entity,
94+
ObjectMapAdapter<E, T> adapter,
95+
Object fieldValue) {
96+
adapter.setValue(entity, fieldValue);
17397
}
17498

17599
/**
176-
* Casts the specified value to the required type.
100+
* Casts the specified value to the required type with Optional.
177101
*
178102
* @param value the value to be cast
179103
* @param requiredType the type to which the value should be cast
180104
* @param <T> the type to which the value should be cast
181-
* @return the cast value, or null if the value cannot be cast to the
182-
* required type
105+
* @return the cast value, or {@code null} if the value is not an instance of the requiredType
183106
*/
184107
public static <T> T cast(Object value, Class<T> requiredType) {
185-
if (requiredType.isInstance(value)) {
186-
return requiredType.cast(value);
187-
}
188-
return null;
189-
}
190-
191-
/**
192-
* Constructs a method name based on the given prefix and field name.
193-
*
194-
* @param prefix the prefix to be added to the field name
195-
* @param fieldName the name of the field
196-
* @return the constructed method name
197-
*/
198-
private static String getMethodName(String prefix, String fieldName) {
199-
return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
108+
return Optional.ofNullable(requiredType)
109+
.filter((clazz) -> clazz.isInstance(value))
110+
.map((clazz) -> clazz.cast(value))
111+
.orElse(null);
200112
}
201113

202-
/**
203-
* Returns the default string representation of the specified object.
204-
*
205-
* @param obj the object for which to return the default string
206-
* representation
207-
* @return the default string representation of the object
208-
*/
209-
private static String defaultObject(Object obj) {
210-
if (obj == null) {
211-
return "";
212-
} else {
213-
return String.valueOf(obj);
214-
}
215-
}
216-
217-
/**
218-
* Private constructor will protect this class from being instantiated.
219-
*/
220114
private MapUtil() {
221115
}
222116
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (C) 2024-2024 OnixByte.
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+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package com.onixbyte.devkit.utils;
19+
20+
import java.util.function.BiConsumer;
21+
import java.util.function.Function;
22+
23+
/**
24+
* Adapts an Object to a Map, making conversion between Map and Object much more safe.
25+
*
26+
* @param <E> entity type
27+
* @param <T> field type
28+
* @author zihluwang
29+
* @version 1.4.2
30+
* @since 1.4.2
31+
*/
32+
public class ObjectMapAdapter<E, T> {
33+
34+
private final Function<E, T> getter;
35+
36+
private final BiConsumer<E, Object> setter;
37+
38+
/**
39+
* Create an adapter.
40+
*
41+
* @param getter the getter of the field
42+
* @param setter the setter of the field
43+
*/
44+
public ObjectMapAdapter(Function<E, T> getter, BiConsumer<E, Object> setter) {
45+
this.getter = getter;
46+
this.setter = setter;
47+
}
48+
49+
/**
50+
* Get data from the entity.
51+
*
52+
* @param entity the source of the data
53+
* @return the data
54+
*/
55+
public T fetch(E entity) {
56+
return getter.apply(entity);
57+
}
58+
59+
/**
60+
* Set value to the entity.
61+
*
62+
* @param entity the target of the data
63+
* @param value the value
64+
*/
65+
public void setValue(E entity, Object value) {
66+
setter.accept(entity, value);
67+
}
68+
69+
}

0 commit comments

Comments
 (0)