package com.supaham.commons.utils; import com.google.common.base.Preconditions; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import pluginbase.config.annotation.SerializeWith; import pluginbase.config.serializers.Serializer; import pluginbase.config.serializers.SerializerSet; /** * Represents a utility class for interacting with bean classes. * * @since 0.5.1 */ public class BeanUtils { private static final Map<Class, PropertyCacheList> CACHE_MAP = new HashMap<>(); /** * Returns a Map of field names and their serialized values for a given {@link Object}. This object is cached after * the first generation. * * @param object object to get properties from * * @return Map of string (field names) and objects (field values) * * @see #getPropertiesList(Object, SerializerSet) */ public static Map<String, Object> getPropertiesList(@Nonnull Object object) { return getPropertiesList(object, null); } /** * Returns a Map of field names and their serialized values for a given {@link Object}. This object is cached after * the first generation. A {@link SerializerSet} is optional but adds more meaning to the output of the serialized * object if the SerializerSet supports the type to be serialized. If no SerializerSet is present, the field value * itself will be pass on into the returned Map. * * @param object object to get properties from * @param serializerSet serializer set to control serialization, nullable * * @return Map of string (field names) and objects (field values) */ public static Map<String, Object> getPropertiesList(@Nonnull Object object, @Nullable SerializerSet serializerSet) { Preconditions.checkNotNull(object, "object cannot be null."); try { final Class<?> clazz = object.getClass(); PropertyCacheList cache = CACHE_MAP.get(clazz); if (cache == null) { CACHE_MAP.put(clazz, cache = new PropertyCacheList(clazz)); } Map<String, Object> result = new LinkedHashMap<>(); for (PropertyCache propertyCache : cache) { result.put(propertyCache.descriptor.getName(), propertyCache.invoke(object, serializerSet)); } return result; } catch (IntrospectionException e) { e.printStackTrace(); return null; } } /* * ArrayList collection of PropertyCache objects. Each class has one of these lists. */ private static final class PropertyCacheList extends ArrayList<PropertyCache> { public PropertyCacheList(Class clazz) throws IntrospectionException { for (PropertyDescriptor descriptor : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) { if (!descriptor.getName().equals("class") // class prints current class (useless to us). && descriptor.getReadMethod() != null) { Field declaredField; try { declaredField = getField(clazz, descriptor.getName()); // Don't include transient fields if (Modifier.isTransient(declaredField.getModifiers())) { continue; } } catch (NoSuchFieldException ignored) { continue; } add(new PropertyCache(declaredField, descriptor)); } } } private Field getField(Class clazz, String name) throws NoSuchFieldException { try { return clazz.getDeclaredField(name); } catch (NoSuchFieldException e) { if (!clazz.getSuperclass().equals(Object.class)) { return getField(clazz.getSuperclass(), name); } throw e; } } } /* * Serves as a cache for both Field and a descriptor. This class makes use of the optional SerializerSet allowing * for custom serialization. */ private static final class PropertyCache { private Field field; private PropertyDescriptor descriptor; public PropertyCache(Field field, PropertyDescriptor descriptor) { this.field = field; this.descriptor = descriptor; } public Object invoke(Object object) { return invoke(object, null); } public Object invoke(Object object, SerializerSet serializerSet) { Class<?> returnType = descriptor.getReadMethod().getReturnType(); Object invoke; try { invoke = descriptor.getReadMethod().invoke(object); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); return null; } if (serializerSet == null) { return invoke; } else { Serializer serializer; SerializeWith annotation = field.getDeclaredAnnotation(SerializeWith.class); if (annotation != null) { serializer = serializerSet.getSerializerInstance(annotation.value()); } else { serializer = serializerSet.getClassSerializer(returnType); } return serializer.serialize(invoke, serializerSet); } } } }