diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java
index 01b08e1..b929cee 100644
--- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java
+++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java
@@ -19,204 +19,98 @@
import lombok.extern.slf4j.Slf4j;
-import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Optional;
/**
- * {@code MapUtil} is a utility class that provides methods for converting
- * objects to maps and maps to objects.
+ * {@code MapUtil} is a utility class that provides methods for converting objects to maps and maps
+ * to objects.
*
- * It also provides methods for getting and setting field values using
- * reflection.
+ * Note: Since version 1.4.2, this util class removed reflection API and transferred to a safer API.
+ * Please see documentation for more information.
*
* @author Zihlu Wang
- * @version 1.1.0
+ * @version 1.4.2
+ * @see com.onixbyte.devkit.utils.unsafe.ReflectMapUtil
* @since 1.0.0
*/
@Slf4j
public final class MapUtil {
/**
- * Converts an object to a map by mapping the field names to their
- * corresponding values.
+ * Converts an object to a map by mapping the field names to their corresponding values.
*
- * @param obj the object to be converted to a map
+ * @param entity the object to be converted to a map
* @return a map representing the fields and their values of the object
- * @throws IllegalAccessException if an error occurs while accessing the
- * fields of the object
*/
- public static Map objectToMap(Object obj) throws IllegalAccessException {
- if (obj == null) {
- return null;
- }
-
- var map = new HashMap();
-
- var declaredFields = obj.getClass().getDeclaredFields();
- for (var field : declaredFields) {
- field.setAccessible(true);
- Object result = field.get(obj);
- if (result != null) {
- map.put(field.getName(), result);
- }
- }
-
- return map;
+ public static Map objectToMap(T entity,
+ Map> adapters) {
+ var resultMap = new HashMap();
+ adapters.forEach((fieldName, adapter) -> resultMap.put(fieldName, adapter.fetch(entity)));
+ return resultMap;
}
/**
- * Converts a map to an object of the specified type by setting the field
- * values using the map entries.
+ * Converts a map to an object of the specified type by setting the field values using the
+ * map entries.
*
- * @param map the map representing the fields and their values
- * @param requiredType the class of the object to be created
- * @param the type of the object to be created
- * @return an object of the specified type with the field values set from
- * the map
- * @throws NoSuchMethodException if the constructor of the required
- * type is not found
- * @throws InvocationTargetException if an error occurs while invoking the
- * constructor
- * @throws InstantiationException if the required type is abstract or an
- * interface
- * @throws IllegalAccessException if an error occurs while accessing the
- * fields of the object
+ * @param objectMap the map representing the fields and their values
+ * @param entity an empty entity of the target class
+ * @param adapters the adapters to execute the setter for the entity
+ * @param the type of the object to be created
+ * @return an object of the specified type with the field values set from the map
*/
- public static T mapToObject(Map map, Class requiredType) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
- var bean = requiredType.getConstructor().newInstance();
- if (map != null) {
- for (var entry : map.entrySet()) {
- try {
- var entryValue = entry.getValue().toString();
- // get the field by field name
- var field = requiredType.getDeclaredField(entry.getKey());
- var fieldType = field.getGenericType();
-
- // convert field value by class
- if (fieldType instanceof Class> fieldClass) {
- if (fieldClass == Short.class || fieldClass == short.class) {
- entry.setValue(Short.parseShort(entryValue));
- } else if (fieldClass == Integer.class || fieldClass == int.class) {
- entry.setValue(Integer.parseInt(entryValue));
- } else if (fieldClass == Long.class || fieldClass == long.class) {
- entry.setValue(Long.parseLong(entryValue));
- } else if (fieldClass == Float.class || fieldClass == float.class) {
- entry.setValue(Float.parseFloat(entryValue));
- } else if (fieldClass == Double.class || fieldClass == double.class) {
- entry.setValue(Double.parseDouble(entryValue));
- } else if (fieldClass == Character.class || fieldClass == char.class) {
- entry.setValue(entryValue.charAt(0));
- } else if (fieldClass == Byte.class || fieldClass == byte.class) {
- entry.setValue(Byte.parseByte(entryValue));
- } else if (fieldClass == Boolean.class || fieldClass == boolean.class) {
- entry.setValue(Boolean.parseBoolean(entryValue));
- } else if (fieldClass == String.class) {
- entry.setValue(entryValue);
- } else {
- log.error("Unable to determine the type of property {}.", field.getName());
- continue;
- }
- }
-
- setFieldValue(bean, entry.getKey(), entry.getValue());
- } catch (Exception e) {
- log.error("Map to Object failed.");
- }
- }
- }
- return bean;
+ public static T mapToObject(Map objectMap,
+ T entity,
+ Map> adapters) {
+ adapters.forEach((fieldName, adapter) -> Optional.ofNullable(objectMap)
+ .map((data) -> data.get(fieldName))
+ .ifPresent((fieldValue) -> adapter.setValue(entity, fieldValue)));
+ return entity;
}
/**
* Retrieves the value of a field from an object using reflection.
*
- * @param obj the object from which to retrieve the field value
- * @param fieldName the name of the field
- * @param fieldType the class representing the type of the field value
- * @param the type of the field value
- * @return the value of the field in the object, or null if the field does
- * not exist or cannot be accessed
- * @throws IllegalAccessException if an error occurs while accessing the
- * field
- * @throws InvocationTargetException if an error occurs while invoking the
- * field getter method
- * @throws NoSuchMethodException if the specified getter is not present
+ * @param the type of the field value
+ * @param entity the object from which to retrieve the field value
+ * @param adapter the adapter to execute the getter
+ * @return the value of the field in the object, or null if the field does not exist or cannot
+ * be accessed
*/
- public static T getFieldValue(Object obj, String fieldName, Class fieldType) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- var methodName = getMethodName("get", fieldName);
- var objectClass = obj.getClass();
- var method = objectClass.getDeclaredMethod(methodName);
-
- method.setAccessible(true);
- return cast(method.invoke(obj), fieldType);
+ public static T getFieldValue(E entity, ObjectMapAdapter adapter) {
+ return adapter.fetch(entity);
}
/**
* Sets the value of a field in an object using reflection.
*
- * @param obj the object in which to set the field value
- * @param fieldName the name of the field
+ * @param entity the object in which to set the field value
+ * @param adapter the adapter to execute the setter
* @param fieldValue the value to be set
- * @throws InvocationTargetException if an error occurs while invoking the
- * field setter method
- * @throws IllegalAccessException if an error occurs while accessing the
- * field
- * @throws NoSuchMethodException if the specific setter is not present
*/
- public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
- var objectClass = obj.getClass();
- var methodName = getMethodName("set", fieldName);
- var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass());
- method.setAccessible(true);
- method.invoke(obj, fieldValue);
+ public static void setFieldValue(E entity,
+ ObjectMapAdapter adapter,
+ Object fieldValue) {
+ adapter.setValue(entity, fieldValue);
}
/**
- * Casts the specified value to the required type.
+ * Casts the specified value to the required type with Optional.
*
* @param value the value to be cast
* @param requiredType the type to which the value should be cast
* @param the type to which the value should be cast
- * @return the cast value, or null if the value cannot be cast to the
- * required type
+ * @return the cast value, or {@code null} if the value is not an instance of the requiredType
*/
public static T cast(Object value, Class requiredType) {
- if (requiredType.isInstance(value)) {
- return requiredType.cast(value);
- }
- return null;
- }
-
- /**
- * Constructs a method name based on the given prefix and field name.
- *
- * @param prefix the prefix to be added to the field name
- * @param fieldName the name of the field
- * @return the constructed method name
- */
- private static String getMethodName(String prefix, String fieldName) {
- return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
+ return Optional.ofNullable(requiredType)
+ .filter((clazz) -> clazz.isInstance(value))
+ .map((clazz) -> clazz.cast(value))
+ .orElse(null);
}
- /**
- * Returns the default string representation of the specified object.
- *
- * @param obj the object for which to return the default string
- * representation
- * @return the default string representation of the object
- */
- private static String defaultObject(Object obj) {
- if (obj == null) {
- return "";
- } else {
- return String.valueOf(obj);
- }
- }
-
- /**
- * Private constructor will protect this class from being instantiated.
- */
private MapUtil() {
}
}
diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ObjectMapAdapter.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ObjectMapAdapter.java
new file mode 100644
index 0000000..863aa60
--- /dev/null
+++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ObjectMapAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024-2024 OnixByte.
+ *
+ * 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
+ *
+ * http://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 com.onixbyte.devkit.utils;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * Adapts an Object to a Map, making conversion between Map and Object much more safe.
+ *
+ * @param entity type
+ * @param field type
+ * @author zihluwang
+ * @version 1.4.2
+ * @since 1.4.2
+ */
+public class ObjectMapAdapter {
+
+ private final Function getter;
+
+ private final BiConsumer setter;
+
+ /**
+ * Create an adapter.
+ *
+ * @param getter the getter of the field
+ * @param setter the setter of the field
+ */
+ public ObjectMapAdapter(Function getter, BiConsumer setter) {
+ this.getter = getter;
+ this.setter = setter;
+ }
+
+ /**
+ * Get data from the entity.
+ *
+ * @param entity the source of the data
+ * @return the data
+ */
+ public T fetch(E entity) {
+ return getter.apply(entity);
+ }
+
+ /**
+ * Set value to the entity.
+ *
+ * @param entity the target of the data
+ * @param value the value
+ */
+ public void setValue(E entity, Object value) {
+ setter.accept(entity, value);
+ }
+
+}
diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java
new file mode 100644
index 0000000..c69ec69
--- /dev/null
+++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/unsafe/ReflectMapUtil.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2024 OnixByte.
+ *
+ * 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
+ *
+ * http://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 com.onixbyte.devkit.utils.unsafe;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@code MapUtil} is a utility class that provides methods for converting
+ * objects to maps and maps to objects.
+ *
+ * It also provides methods for getting and setting field values using
+ * reflection.
+ *
+ * @author zihluwang
+ * @version 1.4.2
+ * @since 1.4.2
+ */
+@Slf4j
+public final class ReflectMapUtil {
+
+ /**
+ * Converts an object to a map by mapping the field names to their
+ * corresponding values.
+ *
+ * @param obj the object to be converted to a map
+ * @return a map representing the fields and their values of the object
+ * @throws IllegalAccessException if an error occurs while accessing the
+ * fields of the object
+ */
+ public static Map objectToMap(Object obj) throws IllegalAccessException {
+ if (obj == null) {
+ return null;
+ }
+
+ var map = new HashMap();
+
+ var declaredFields = obj.getClass().getDeclaredFields();
+ for (var field : declaredFields) {
+ field.setAccessible(true);
+ Object result = field.get(obj);
+ if (result != null) {
+ map.put(field.getName(), result);
+ }
+ }
+
+ return map;
+ }
+
+ /**
+ * Converts a map to an object of the specified type by setting the field
+ * values using the map entries.
+ *
+ * @param map the map representing the fields and their values
+ * @param requiredType the class of the object to be created
+ * @param the type of the object to be created
+ * @return an object of the specified type with the field values set from
+ * the map
+ * @throws NoSuchMethodException if the constructor of the required
+ * type is not found
+ * @throws InvocationTargetException if an error occurs while invoking the
+ * constructor
+ * @throws InstantiationException if the required type is abstract or an
+ * interface
+ * @throws IllegalAccessException if an error occurs while accessing the
+ * fields of the object
+ */
+ public static T mapToObject(Map map, Class requiredType) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+ var bean = requiredType.getConstructor().newInstance();
+ if (map != null) {
+ for (var entry : map.entrySet()) {
+ try {
+ var entryValue = entry.getValue().toString();
+ // get the field by field name
+ var field = requiredType.getDeclaredField(entry.getKey());
+ var fieldType = field.getGenericType();
+
+ // convert field value by class
+ if (fieldType instanceof Class> fieldClass) {
+ if (fieldClass == Short.class || fieldClass == short.class) {
+ entry.setValue(Short.parseShort(entryValue));
+ } else if (fieldClass == Integer.class || fieldClass == int.class) {
+ entry.setValue(Integer.parseInt(entryValue));
+ } else if (fieldClass == Long.class || fieldClass == long.class) {
+ entry.setValue(Long.parseLong(entryValue));
+ } else if (fieldClass == Float.class || fieldClass == float.class) {
+ entry.setValue(Float.parseFloat(entryValue));
+ } else if (fieldClass == Double.class || fieldClass == double.class) {
+ entry.setValue(Double.parseDouble(entryValue));
+ } else if (fieldClass == Character.class || fieldClass == char.class) {
+ entry.setValue(entryValue.charAt(0));
+ } else if (fieldClass == Byte.class || fieldClass == byte.class) {
+ entry.setValue(Byte.parseByte(entryValue));
+ } else if (fieldClass == Boolean.class || fieldClass == boolean.class) {
+ entry.setValue(Boolean.parseBoolean(entryValue));
+ } else if (fieldClass == String.class) {
+ entry.setValue(entryValue);
+ } else {
+ log.error("Unable to determine the type of property {}.", field.getName());
+ continue;
+ }
+ }
+
+ setFieldValue(bean, entry.getKey(), entry.getValue());
+ } catch (Exception e) {
+ log.error("Map to Object failed.");
+ }
+ }
+ }
+ return bean;
+ }
+
+ /**
+ * Retrieves the value of a field from an object using reflection.
+ *
+ * @param obj the object from which to retrieve the field value
+ * @param fieldName the name of the field
+ * @param fieldType the class representing the type of the field value
+ * @param the type of the field value
+ * @return the value of the field in the object, or null if the field does
+ * not exist or cannot be accessed
+ * @throws IllegalAccessException if an error occurs while accessing the
+ * field
+ * @throws InvocationTargetException if an error occurs while invoking the
+ * field getter method
+ * @throws NoSuchMethodException if the specified getter is not present
+ */
+ public static T getFieldValue(Object obj, String fieldName, Class fieldType) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+ var methodName = getMethodName("get", fieldName);
+ var objectClass = obj.getClass();
+ var method = objectClass.getDeclaredMethod(methodName);
+
+ method.setAccessible(true);
+ return cast(method.invoke(obj), fieldType);
+ }
+
+ /**
+ * Sets the value of a field in an object using reflection.
+ *
+ * @param obj the object in which to set the field value
+ * @param fieldName the name of the field
+ * @param fieldValue the value to be set
+ * @throws InvocationTargetException if an error occurs while invoking the
+ * field setter method
+ * @throws IllegalAccessException if an error occurs while accessing the
+ * field
+ * @throws NoSuchMethodException if the specific setter is not present
+ */
+ public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
+ var objectClass = obj.getClass();
+ var methodName = getMethodName("set", fieldName);
+ var method = objectClass.getDeclaredMethod(methodName, fieldValue.getClass());
+ method.setAccessible(true);
+ method.invoke(obj, fieldValue);
+ }
+
+ /**
+ * Casts the specified value to the required type.
+ *
+ * @param value the value to be cast
+ * @param requiredType the type to which the value should be cast
+ * @param the type to which the value should be cast
+ * @return the cast value, or null if the value cannot be cast to the
+ * required type
+ */
+ public static T cast(Object value, Class requiredType) {
+ if (requiredType.isInstance(value)) {
+ return requiredType.cast(value);
+ }
+ return null;
+ }
+
+ /**
+ * Constructs a method name based on the given prefix and field name.
+ *
+ * @param prefix the prefix to be added to the field name
+ * @param fieldName the name of the field
+ * @return the constructed method name
+ */
+ private static String getMethodName(String prefix, String fieldName) {
+ return prefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
+ }
+
+ /**
+ * Returns the default string representation of the specified object.
+ *
+ * @param obj the object for which to return the default string
+ * representation
+ * @return the default string representation of the object
+ */
+ private static String defaultObject(Object obj) {
+ if (obj == null) {
+ return "";
+ } else {
+ return String.valueOf(obj);
+ }
+ }
+
+ /**
+ * Private constructor will protect this class from being instantiated.
+ */
+ private ReflectMapUtil() {
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index b1df34e..1e3dd3e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -27,4 +27,5 @@ include(
"simple-jwt-jjwt",
"simple-jwt-spring-boot-starter",
"property-guard-spring-boot-starter"
-)
\ No newline at end of file
+)
+include("map-util-unsafe")