All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.dubbo.common.utils.PojoUtils Maven / Gradle / Ivy

There is a newer version: 3.3.0-beta.3
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.dubbo.common.utils;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;

import static org.apache.dubbo.common.utils.ClassUtils.isAssignableFrom;

/**
 * PojoUtils. Travel object deeply, and convert complex type to simple type.
 * 

* Simple type below will be remained: *

    *
  • Primitive Type, also include String, Number(Integer, Long), Date *
  • Array of Primitive Type *
  • Collection, eg: List, Map, Set etc. *
*

* Other type will be covert to a map which contains the attributes and value pair of object. */ public class PojoUtils { private static final Logger logger = LoggerFactory.getLogger(PojoUtils.class); private static final ConcurrentMap NAME_METHODS_CACHE = new ConcurrentHashMap(); private static final ConcurrentMap, ConcurrentMap> CLASS_FIELD_CACHE = new ConcurrentHashMap, ConcurrentMap>(); private static final boolean GENERIC_WITH_CLZ = Boolean.parseBoolean(ConfigUtils.getProperty(CommonConstants.GENERIC_WITH_CLZ_KEY, "true")); private static final List> CLASS_CAN_BE_STRING = Arrays.asList(Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, Character.class); public static Object[] generalize(Object[] objs) { Object[] dests = new Object[objs.length]; for (int i = 0; i < objs.length; i++) { dests[i] = generalize(objs[i]); } return dests; } public static Object[] realize(Object[] objs, Class[] types) { if (objs.length != types.length) { throw new IllegalArgumentException("args.length != types.length"); } Object[] dests = new Object[objs.length]; for (int i = 0; i < objs.length; i++) { dests[i] = realize(objs[i], types[i]); } return dests; } public static Object[] realize(Object[] objs, Class[] types, Type[] gtypes) { if (objs.length != types.length || objs.length != gtypes.length) { throw new IllegalArgumentException("args.length != types.length"); } Object[] dests = new Object[objs.length]; for (int i = 0; i < objs.length; i++) { dests[i] = realize(objs[i], types[i], gtypes[i]); } return dests; } public static Object generalize(Object pojo) { return generalize(pojo, new IdentityHashMap()); } @SuppressWarnings("unchecked") private static Object generalize(Object pojo, Map history) { if (pojo == null) { return null; } if (pojo instanceof Enum) { return ((Enum) pojo).name(); } if (pojo.getClass().isArray() && Enum.class.isAssignableFrom(pojo.getClass().getComponentType())) { int len = Array.getLength(pojo); String[] values = new String[len]; for (int i = 0; i < len; i++) { values[i] = ((Enum) Array.get(pojo, i)).name(); } return values; } if (ReflectUtils.isPrimitives(pojo.getClass())) { return pojo; } if (pojo instanceof LocalDate || pojo instanceof LocalDateTime || pojo instanceof LocalTime) { return pojo.toString(); } if (pojo instanceof Class) { return ((Class) pojo).getName(); } Object o = history.get(pojo); if (o != null) { return o; } history.put(pojo, pojo); if (pojo.getClass().isArray()) { int len = Array.getLength(pojo); Object[] dest = new Object[len]; history.put(pojo, dest); for (int i = 0; i < len; i++) { Object obj = Array.get(pojo, i); dest[i] = generalize(obj, history); } return dest; } if (pojo instanceof Collection) { Collection src = (Collection) pojo; int len = src.size(); Collection dest = (pojo instanceof List) ? new ArrayList(len) : new HashSet(len); history.put(pojo, dest); for (Object obj : src) { dest.add(generalize(obj, history)); } return dest; } if (pojo instanceof Map) { Map src = (Map) pojo; Map dest = createMap(src); history.put(pojo, dest); for (Map.Entry obj : src.entrySet()) { dest.put(generalize(obj.getKey(), history), generalize(obj.getValue(), history)); } return dest; } Map map = new HashMap(); history.put(pojo, map); if (GENERIC_WITH_CLZ) { map.put("class", pojo.getClass().getName()); } for (Method method : pojo.getClass().getMethods()) { if (ReflectUtils.isBeanPropertyReadMethod(method)) { ReflectUtils.makeAccessible(method); try { map.put(ReflectUtils.getPropertyNameFromBeanReadMethod(method), generalize(method.invoke(pojo), history)); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } // public field for (Field field : pojo.getClass().getFields()) { if (ReflectUtils.isPublicInstanceField(field)) { try { Object fieldValue = field.get(pojo); if (history.containsKey(pojo)) { Object pojoGeneralizedValue = history.get(pojo); if (pojoGeneralizedValue instanceof Map && ((Map) pojoGeneralizedValue).containsKey(field.getName())) { continue; } } if (fieldValue != null) { map.put(field.getName(), generalize(fieldValue, history)); } } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } return map; } public static Object realize(Object pojo, Class type) { return realize0(pojo, type, null, new IdentityHashMap()); } public static Object realize(Object pojo, Class type, Type genericType) { return realize0(pojo, type, genericType, new IdentityHashMap()); } private static class PojoInvocationHandler implements InvocationHandler { private Map map; public PojoInvocationHandler(Map map) { this.map = map; } @Override @SuppressWarnings("unchecked") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { return method.invoke(map, args); } String methodName = method.getName(); Object value = null; if (methodName.length() > 3 && methodName.startsWith("get")) { value = map.get(methodName.substring(3, 4).toLowerCase() + methodName.substring(4)); } else if (methodName.length() > 2 && methodName.startsWith("is")) { value = map.get(methodName.substring(2, 3).toLowerCase() + methodName.substring(3)); } else { value = map.get(methodName.substring(0, 1).toLowerCase() + methodName.substring(1)); } if (value instanceof Map && !Map.class.isAssignableFrom(method.getReturnType())) { value = realize0((Map) value, method.getReturnType(), null, new IdentityHashMap()); } return value; } } @SuppressWarnings("unchecked") private static Collection createCollection(Class type, int len) { if (type.isAssignableFrom(ArrayList.class)) { return new ArrayList(len); } if (type.isAssignableFrom(HashSet.class)) { return new HashSet(Math.max((int) (len/.75f) + 1, 16)); } if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { try { return (Collection) type.newInstance(); } catch (Exception e) { // ignore } } return new ArrayList(len); } private static Map createMap(Map src) { Class cl = src.getClass(); int size = src.size(); Map result = null; if (HashMap.class == cl) { result = new HashMap(Math.max((int) (size/.75f) + 1, 16)); } else if (Hashtable.class == cl) { result = new Hashtable(Math.max((int) (size/.75f) + 1, 16)); } else if (IdentityHashMap.class == cl) { result = new IdentityHashMap((int)(1 + size * 1.1)); } else if (LinkedHashMap.class == cl) { result = new LinkedHashMap(); } else if (Properties.class == cl) { result = new Properties(); } else if (TreeMap.class == cl) { result = new TreeMap(); } else if (WeakHashMap.class == cl) { return new WeakHashMap(Math.max((int) (size/.75f) + 1, 16)); } else if (ConcurrentHashMap.class == cl) { result = new ConcurrentHashMap(Math.max((int) (size/.75f) + 1, 16)); } else if (ConcurrentSkipListMap.class == cl) { result = new ConcurrentSkipListMap(); } else { try { result = cl.newInstance(); } catch (Exception e) { /* ignore */ } if (result == null) { try { Constructor constructor = cl.getConstructor(Map.class); result = (Map) constructor.newInstance(Collections.EMPTY_MAP); } catch (Exception e) { /* ignore */ } } } if (result == null) { result = new HashMap(Math.max((int) (size/.75f) + 1, 16)); } return result; } @SuppressWarnings({"unchecked", "rawtypes"}) private static Object realize0(Object pojo, Class type, Type genericType, final Map history) { if (pojo == null) { return null; } if (type != null && type.isEnum() && pojo.getClass() == String.class) { return Enum.valueOf((Class) type, (String) pojo); } if (ReflectUtils.isPrimitives(pojo.getClass()) && !(type != null && type.isArray() && type.getComponentType().isEnum() && pojo.getClass() == String[].class)) { return CompatibleTypeUtils.compatibleTypeConvert(pojo, type); } Object o = history.get(pojo); if (o != null) { return o; } history.put(pojo, pojo); if (pojo.getClass().isArray()) { if (Collection.class.isAssignableFrom(type)) { Class ctype = pojo.getClass().getComponentType(); int len = Array.getLength(pojo); Collection dest = createCollection(type, len); history.put(pojo, dest); for (int i = 0; i < len; i++) { Object obj = Array.get(pojo, i); Object value = realize0(obj, ctype, null, history); dest.add(value); } return dest; } else { Class ctype = (type != null && type.isArray() ? type.getComponentType() : pojo.getClass().getComponentType()); int len = Array.getLength(pojo); Object dest = Array.newInstance(ctype, len); history.put(pojo, dest); for (int i = 0; i < len; i++) { Object obj = Array.get(pojo, i); Object value = realize0(obj, ctype, null, history); Array.set(dest, i, value); } return dest; } } if (pojo instanceof Collection) { if (type.isArray()) { Class ctype = type.getComponentType(); Collection src = (Collection) pojo; int len = src.size(); Object dest = Array.newInstance(ctype, len); history.put(pojo, dest); int i = 0; for (Object obj : src) { Object value = realize0(obj, ctype, null, history); Array.set(dest, i, value); i++; } return dest; } else { Collection src = (Collection) pojo; int len = src.size(); Collection dest = createCollection(type, len); history.put(pojo, dest); for (Object obj : src) { Type keyType = getGenericClassByIndex(genericType, 0); Class keyClazz = obj == null ? null : obj.getClass(); if (keyType instanceof Class) { keyClazz = (Class) keyType; } Object value = realize0(obj, keyClazz, keyType, history); dest.add(value); } return dest; } } if (pojo instanceof Map && type != null) { Map map = (Map) pojo; Object className = ((Map) pojo).get("class"); if (className instanceof String) { SerializeClassChecker.getInstance().validateClass((String) className); try { type = ClassUtils.forName((String) className); if (GENERIC_WITH_CLZ) { map.remove("class"); } } catch (ClassNotFoundException e) { // ignore } SerializeClassChecker.getInstance().validateClass(type); } // special logic for enum if (type.isEnum()) { Object name = ((Map) pojo).get("name"); if (name != null) { if (!(name instanceof String)) { throw new IllegalArgumentException("`name` filed should be string!"); } else { return Enum.valueOf((Class) type, (String) name); } } } if (Map.class.isAssignableFrom(type) || type == Object.class) { final Map result; // fix issue#5939 Type mapKeyType = getKeyTypeForMap(map.getClass()); Type typeKeyType = getGenericClassByIndex(genericType, 0); boolean typeMismatch = mapKeyType instanceof Class && typeKeyType instanceof Class && !typeKeyType.getTypeName().equals(mapKeyType.getTypeName()); if (typeMismatch) { result = createMap(new HashMap(0)); } else { result = createMap(map); } history.put(pojo, result); for (Map.Entry entry : map.entrySet()) { Type keyType = getGenericClassByIndex(genericType, 0); Type valueType = getGenericClassByIndex(genericType, 1); Class keyClazz; if (keyType instanceof Class) { keyClazz = (Class) keyType; } else if (keyType instanceof ParameterizedType) { keyClazz = (Class) ((ParameterizedType) keyType).getRawType(); } else { keyClazz = entry.getKey() == null ? null : entry.getKey().getClass(); } Class valueClazz; if (valueType instanceof Class) { valueClazz = (Class) valueType; } else if (valueType instanceof ParameterizedType) { valueClazz = (Class) ((ParameterizedType) valueType).getRawType(); } else { valueClazz = entry.getValue() == null ? null : entry.getValue().getClass(); } Object key = keyClazz == null ? entry.getKey() : realize0(entry.getKey(), keyClazz, keyType, history); Object value = valueClazz == null ? entry.getValue() : realize0(entry.getValue(), valueClazz, valueType, history); result.put(key, value); } return result; } else if (type.isInterface()) { Object dest = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{type}, new PojoInvocationHandler(map)); history.put(pojo, dest); return dest; } else { Object dest = newInstance(type); history.put(pojo, dest); for (Map.Entry entry : map.entrySet()) { Object key = entry.getKey(); if (key instanceof String) { String name = (String) key; Object value = entry.getValue(); if (value != null) { Method method = getSetterMethod(dest.getClass(), name, value.getClass()); if (method != null) { Type ptype = method.getGenericParameterTypes()[0]; value = realize0(value, method.getParameterTypes()[0], ptype, history); try { method.invoke(dest, value); } catch (Exception e) { String exceptionDescription = "Failed to set pojo " + dest.getClass().getSimpleName() + " property " + name + " value " + value.getClass() + ", cause: " + e.getMessage(); logger.error(exceptionDescription, e); throw new RuntimeException(exceptionDescription, e); } } else { Field field = getField(dest.getClass(), name); if (field != null) { value = realize0(value, field.getType(), field.getGenericType(), history); try { field.set(dest, value); } catch (IllegalAccessException e) { String exceptionDescription = "Failed to set field " + name + " of pojo " + dest.getClass().getName() + " : " + e.getMessage(); throw new RuntimeException(exceptionDescription, e); } } } } } } if (dest instanceof Throwable) { Object message = map.get("message"); if (message instanceof String) { try { Field field = Throwable.class.getDeclaredField("detailMessage"); ReflectUtils.makeAccessible(field); field.set(dest, message); } catch (Exception e) { } } } return dest; } } return pojo; } /** * Get key type for {@link Map} directly implemented by {@code clazz}. * If {@code clazz} does not implement {@link Map} directly, return {@code null}. * * @param clazz {@link Class} * @return Return String.class for {@link com.alibaba.fastjson.JSONObject} */ private static Type getKeyTypeForMap(Class clazz) { Type[] interfaces = clazz.getGenericInterfaces(); if (!ArrayUtils.isEmpty(interfaces)) { for (Type type : interfaces) { if (type instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) type; if ("java.util.Map".equals(t.getRawType().getTypeName())) { return t.getActualTypeArguments()[0]; } } } } return null; } /** * Get parameterized type * * @param genericType generic type * @param index index of the target parameterized type * @return Return Person.class for List, return Person.class for Map when index=0 */ private static Type getGenericClassByIndex(Type genericType, int index) { Type clazz = null; // find parameterized type if (genericType instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) genericType; Type[] types = t.getActualTypeArguments(); clazz = types[index]; } return clazz; } private static Object newInstance(Class cls) { try { return cls.newInstance(); } catch (Throwable t) { try { Constructor[] constructors = cls.getDeclaredConstructors(); /** * From Javadoc java.lang.Class#getDeclaredConstructors * This method returns an array of Constructor objects reflecting all the constructors * declared by the class represented by this Class object. * This method returns an array of length 0, * if this Class object represents an interface, a primitive type, an array class, or void. */ if (constructors.length == 0) { throw new RuntimeException("Illegal constructor: " + cls.getName()); } Constructor constructor = constructors[0]; if (constructor.getParameterTypes().length > 0) { for (Constructor c : constructors) { if (c.getParameterTypes().length < constructor.getParameterTypes().length) { constructor = c; if (constructor.getParameterTypes().length == 0) { break; } } } } ReflectUtils.makeAccessible(constructor); Object[] parameters = Arrays.stream(constructor.getParameterTypes()).map(PojoUtils::getDefaultValue).toArray(); return constructor.newInstance(parameters); } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e.getMessage(), e); } } } /** * return init value * * @param parameterType * @return */ private static Object getDefaultValue(Class parameterType) { if ("char".equals(parameterType.getName())) { return Character.MIN_VALUE; } if ("boolean".equals(parameterType.getName())) { return false; } if ("byte".equals(parameterType.getName())) { return (byte) 0; } if ("short".equals(parameterType.getName())) { return (short) 0; } return parameterType.isPrimitive() ? 0 : null; } private static Method getSetterMethod(Class cls, String property, Class valueCls) { String name = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); Method method = NAME_METHODS_CACHE.get(cls.getName() + "." + name + "(" + valueCls.getName() + ")"); if (method == null) { try { method = cls.getMethod(name, valueCls); } catch (NoSuchMethodException e) { for (Method m : cls.getMethods()) { if (ReflectUtils.isBeanPropertyWriteMethod(m) && m.getName().equals(name)) { method = m; break; } } } if (method != null) { ReflectUtils.makeAccessible(method); NAME_METHODS_CACHE.put(cls.getName() + "." + name + "(" + valueCls.getName() + ")", method); } } return method; } private static Field getField(Class cls, String fieldName) { Field result = null; if (CLASS_FIELD_CACHE.containsKey(cls) && CLASS_FIELD_CACHE.get(cls).containsKey(fieldName)) { return CLASS_FIELD_CACHE.get(cls).get(fieldName); } try { result = cls.getDeclaredField(fieldName); ReflectUtils.makeAccessible(result); } catch (NoSuchFieldException e) { for (Field field : cls.getFields()) { if (fieldName.equals(field.getName()) && ReflectUtils.isPublicInstanceField(field)) { result = field; break; } } } if (result != null) { ConcurrentMap fields = CLASS_FIELD_CACHE.computeIfAbsent(cls, k -> new ConcurrentHashMap<>()); fields.putIfAbsent(fieldName, result); } return result; } public static boolean isPojo(Class cls) { return !ReflectUtils.isPrimitives(cls) && !Collection.class.isAssignableFrom(cls) && !Map.class.isAssignableFrom(cls); } /** * Update the property if absent * * @param getterMethod the getter method * @param setterMethod the setter method * @param newValue the new value * @param the value type * @since 2.7.8 */ public static void updatePropertyIfAbsent(Supplier getterMethod, Consumer setterMethod, T newValue) { if (newValue != null && getterMethod.get() == null) { setterMethod.accept(newValue); } } /** * convert map to a specific class instance * * @param map map wait for convert * @param cls the specified class * @param the type of {@code cls} * @return class instance declare in param {@code cls} * @throws ReflectiveOperationException if the instance creation is failed * @since 2.7.10 */ public static T mapToPojo(Map map, Class cls) throws ReflectiveOperationException { T instance = cls.getDeclaredConstructor().newInstance(); Map beanPropertyFields = ReflectUtils.getBeanPropertyFields(cls); for (Map.Entry entry : beanPropertyFields.entrySet()) { String name = entry.getKey(); Field field = entry.getValue(); Object mapObject = map.get(name); if (mapObject == null) { continue; } Type type = field.getGenericType(); Object fieldObject = getFieldObject(mapObject, type); field.set(instance, fieldObject); } return instance; } private static Object getFieldObject(Object mapObject, Type fieldType) throws ReflectiveOperationException { if (fieldType instanceof Class) { return convertClassType(mapObject, (Class) fieldType); } else if (fieldType instanceof ParameterizedType) { return convertParameterizedType(mapObject, (ParameterizedType) fieldType); } else if (fieldType instanceof GenericArrayType || fieldType instanceof TypeVariable || fieldType instanceof WildcardType) { // ignore these type currently return null; } else { throw new IllegalArgumentException("Unrecognized Type: " + fieldType.toString()); } } @SuppressWarnings("unchecked") private static Object convertClassType(Object mapObject, Class type) throws ReflectiveOperationException { if (type.isPrimitive() || isAssignableFrom(type, mapObject.getClass())) { return mapObject; } else if (Objects.equals(type, String.class) && CLASS_CAN_BE_STRING.contains(mapObject.getClass())) { // auto convert specified type to string return mapObject.toString(); } else if (mapObject instanceof Map) { return mapToPojo((Map) mapObject, type); } else { // type didn't match and mapObject is not another Map struct. // we just ignore this situation. return null; } } @SuppressWarnings("unchecked") private static Object convertParameterizedType(Object mapObject, ParameterizedType type) throws ReflectiveOperationException { Type rawType = type.getRawType(); if (!isAssignableFrom((Class) rawType, mapObject.getClass())) { return null; } Type[] actualTypeArguments = type.getActualTypeArguments(); if (isAssignableFrom(Map.class, (Class) rawType)) { Map map = (Map) mapObject.getClass().getDeclaredConstructor().newInstance(); for (Map.Entry entry : ((Map) mapObject).entrySet()) { Object key = getFieldObject(entry.getKey(), actualTypeArguments[0]); Object value = getFieldObject(entry.getValue(), actualTypeArguments[1]); map.put(key, value); } return map; } else if (isAssignableFrom(Collection.class, (Class) rawType)) { Collection collection = (Collection) mapObject.getClass().getDeclaredConstructor().newInstance(); for (Object m : (Iterable) mapObject) { Object ele = getFieldObject(m, actualTypeArguments[0]); collection.add(ele); } return collection; } else { // ignore other type currently return null; } } }