|
19 | 19 |
|
20 | 20 | import lombok.extern.slf4j.Slf4j; |
21 | 21 |
|
22 | | -import java.lang.reflect.InvocationTargetException; |
23 | 22 | import java.util.HashMap; |
24 | 23 | import java.util.Map; |
| 24 | +import java.util.Optional; |
25 | 25 |
|
26 | 26 | /** |
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. |
29 | 29 | * <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. |
32 | 32 | * |
33 | 33 | * @author Zihlu Wang |
34 | | - * @version 1.1.0 |
| 34 | + * @version 1.4.2 |
| 35 | + * @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil |
35 | 36 | * @since 1.0.0 |
36 | 37 | */ |
37 | 38 | @Slf4j |
38 | 39 | public final class MapUtil { |
39 | 40 |
|
40 | 41 | /** |
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. |
43 | 43 | * |
44 | | - * @param obj the object to be converted to a map |
| 44 | + * @param entity the object to be converted to a map |
45 | 45 | * @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 |
48 | 46 | */ |
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; |
66 | 52 | } |
67 | 53 |
|
68 | 54 | /** |
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. |
71 | 57 | * |
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 |
85 | 63 | */ |
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; |
129 | 71 | } |
130 | 72 |
|
131 | 73 | /** |
132 | 74 | * Retrieves the value of a field from an object using reflection. |
133 | 75 | * |
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 |
145 | 81 | */ |
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); |
153 | 84 | } |
154 | 85 |
|
155 | 86 | /** |
156 | 87 | * Sets the value of a field in an object using reflection. |
157 | 88 | * |
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 |
160 | 91 | * @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 |
166 | 92 | */ |
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); |
173 | 97 | } |
174 | 98 |
|
175 | 99 | /** |
176 | | - * Casts the specified value to the required type. |
| 100 | + * Casts the specified value to the required type with Optional. |
177 | 101 | * |
178 | 102 | * @param value the value to be cast |
179 | 103 | * @param requiredType the type to which the value should be cast |
180 | 104 | * @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 |
183 | 106 | */ |
184 | 107 | 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); |
200 | 112 | } |
201 | 113 |
|
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 | | - */ |
220 | 114 | private MapUtil() { |
221 | 115 | } |
222 | 116 | } |
0 commit comments