package io.micronaut.core.beans;
import static io.micronaut.core.naming.NameUtils.decapitalize;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Internal
@Deprecated
class SimpleBeanInfo implements BeanInfo {
private static final Set<String> EXCLUDED_PROPERTIES = CollectionUtils.setOf("class", "metaClass");
private static final String PREFIX_IS = "is";
private static final String PREFIX_GET = "get";
private static final String PREFIX_SET = "set";
private static final String STR_GETTERS = "getters";
private static final String STR_SETTERS = "setters";
private static final String STR_NORMAL = "normal";
private static final String STR_VALID = "valid";
private static final String STR_PROPERTY_TYPE = "PropertyType";
private static final String STR_INVALID = "invalid";
private final Class<?> beanClass;
private final Map<String, PropertyDescriptor> properties;
SimpleBeanInfo(Class<?> beanClass) {
this.beanClass = beanClass;
List<PropertyDescriptor> propertyList = introspectProperties(introspectMethods(beanClass));
if (CollectionUtils.isEmpty(propertyList)) {
this.properties = Collections.emptyMap();
} else {
HashMap<String, PropertyDescriptor> propertyMap = new HashMap<>(propertyList.size());
for (PropertyDescriptor propertyDescriptor : propertyList) {
propertyMap.put(propertyDescriptor.getName(), propertyDescriptor);
}
this.properties = Collections.unmodifiableMap(propertyMap);
}
}
@Override
public Class<?> getBeanClass() {
return beanClass;
}
@Override
public Map<String, PropertyDescriptor> getPropertyDescriptors() {
return properties;
}
@SuppressWarnings("unchecked")
private List<PropertyDescriptor> introspectProperties(Method[] methodDescriptors) {
if (methodDescriptors == null) {
return null;
}
HashMap<String, HashMap> propertyTable = new HashMap<>(methodDescriptors.length);
for (Method methodDescriptor : methodDescriptors) {
introspectGet(methodDescriptor, propertyTable);
introspectSet(methodDescriptor, propertyTable);
}
fixGetSet(propertyTable);
ArrayList<PropertyDescriptor> propertyList = new ArrayList<>();
for (Map.Entry<String, HashMap> entry : propertyTable.entrySet()) {
String propertyName = entry.getKey();
HashMap table = entry.getValue();
if (table == null) {
continue;
}
String normalTag = (String) table.get(STR_NORMAL);
if ((normalTag == null)) {
continue;
}
Method get = (Method) table.get(STR_NORMAL + PREFIX_GET);
Method set = (Method) table.get(STR_NORMAL + PREFIX_SET);
PropertyDescriptor propertyDesc = new PropertyDescriptor(propertyName, get, set);
propertyList.add(propertyDesc);
}
return Collections.unmodifiableList(propertyList);
}
@SuppressWarnings("unchecked")
private static void introspectGet(Method theMethod,
HashMap<String, HashMap> propertyTable) {
String methodName = theMethod.getName();
int prefixLength = 0;
String propertyName;
Class propertyType;
Class[] paramTypes;
HashMap table;
ArrayList<Method> getters;
if (methodName == null) {
return;
}
if (methodName.startsWith(PREFIX_GET)) {
prefixLength = PREFIX_GET.length();
}
if (methodName.startsWith(PREFIX_IS)) {
prefixLength = PREFIX_IS.length();
}
if (prefixLength == 0) {
return;
}
propertyName = decapitalize(methodName.substring(prefixLength));
if (isInvalidProperty(propertyName)) {
return;
}
propertyType = theMethod.getReturnType();
if (propertyType == null || propertyType == void.class) {
return;
}
if (prefixLength == 2 && propertyType != boolean.class) {
return;
}
paramTypes = theMethod.getParameterTypes();
if (paramTypes.length > 1 || (paramTypes.length == 1 && paramTypes[0] != int.class)) {
return;
}
table = propertyTable.computeIfAbsent(propertyName, k -> new HashMap());
getters = (ArrayList<Method>) table.get(STR_GETTERS);
if (getters == null) {
getters = new ArrayList<>();
table.put(STR_GETTERS, getters);
}
getters.add(theMethod);
}
@SuppressWarnings("unchecked")
private static void introspectSet(Method theMethod,
HashMap<String, HashMap> propertyTable) {
String methodName = theMethod.getName();
if (methodName == null) {
return;
}
String propertyName;
Class returnType;
Class[] paramTypes;
returnType = theMethod.getReturnType();
if (returnType != void.class) {
return;
}
if (!methodName.startsWith(PREFIX_SET)) {
return;
}
propertyName = decapitalize(methodName.substring(PREFIX_SET.length()));
if (isInvalidProperty(propertyName)) {
return;
}
paramTypes = theMethod.getParameterTypes();
if (paramTypes.length == 0 || paramTypes.length > 1) {
return;
}
HashMap table = propertyTable.computeIfAbsent(propertyName, k -> new HashMap());
ArrayList<Method> setters = (ArrayList<Method>) table.computeIfAbsent(STR_SETTERS, k -> new ArrayList<Method>());
setters.add(theMethod);
}
private static Method[] introspectMethods(Class<?> beanClass) {
Method[] basicMethods = beanClass.getMethods();
if (ArrayUtils.isEmpty(basicMethods)) {
return null;
}
ArrayList<Method> methodList = new ArrayList<>(basicMethods.length);
for (Method basicMethod : basicMethods) {
if (basicMethod.getDeclaringClass() == Object.class) {
break;
}
int modifiers = basicMethod.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !basicMethod.isBridge() && !basicMethod.isSynthetic() && basicMethod.getName().indexOf('$') == -1) {
methodList.add(basicMethod);
}
}
int methodCount = methodList.size();
Method[] theMethods = null;
if (methodCount > 0) {
theMethods = new Method[methodCount];
theMethods = methodList.toArray(theMethods);
}
return theMethods;
}
@SuppressWarnings("unchecked")
private void fixGetSet(HashMap<String, HashMap> propertyTable) {
if (propertyTable == null) {
return;
}
for (Map.Entry<String, HashMap> entry : propertyTable.entrySet()) {
HashMap<String, Object> table = entry.getValue();
ArrayList<Method> getters = (ArrayList<Method>) table.get(STR_GETTERS);
ArrayList<Method> setters = (ArrayList<Method>) table.get(STR_SETTERS);
Method normalGetter = null;
Method normalSetter = null;
Class<?> normalPropType = null;
if (getters == null) {
getters = new ArrayList<>();
}
if (setters == null) {
setters = new ArrayList<>();
}
Class<?>[] paramTypes;
String methodName;
for (Method getter : getters) {
paramTypes = getter.getParameterTypes();
methodName = getter.getName();
if (paramTypes == null || paramTypes.length == 0) {
if (normalGetter == null || methodName.startsWith(PREFIX_IS)) {
normalGetter = getter;
}
}
}
if (normalGetter != null) {
Class<?> propertyType = normalGetter.getReturnType();
for (Method setter : setters) {
if (setter.getParameterTypes().length == 1
&& propertyType.equals(setter.getParameterTypes()[0])) {
normalSetter = setter;
break;
}
}
} else {
for (Method setter : setters) {
if (setter.getParameterTypes().length == 1) {
normalSetter = setter;
}
}
}
if (normalGetter != null) {
normalPropType = normalGetter.getReturnType();
} else if (normalSetter != null) {
normalPropType = normalSetter.getParameterTypes()[0];
}
if (normalGetter != null && normalSetter != null) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE, normalPropType);
continue;
}
if ((normalGetter != null || normalSetter != null)) {
table.put(STR_NORMAL, STR_VALID);
table.put(STR_NORMAL + PREFIX_GET, normalGetter);
table.put(STR_NORMAL + PREFIX_SET, normalSetter);
table.put(STR_NORMAL + STR_PROPERTY_TYPE, normalPropType);
continue;
}
table.put(STR_NORMAL, STR_INVALID);
}
}
private static boolean isInvalidProperty(String propertyName) {
return (propertyName == null) || (propertyName.length() == 0) || EXCLUDED_PROPERTIES.contains(propertyName);
}
}