001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2024, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014
015package ch.qos.logback.core.joran.util;
016
017import ch.qos.logback.core.joran.spi.DefaultClass;
018import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
019import ch.qos.logback.core.joran.util.beans.BeanDescription;
020import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
021import ch.qos.logback.core.joran.util.beans.BeanUtil;
022import ch.qos.logback.core.spi.ContextAwareBase;
023import ch.qos.logback.core.util.AggregationType;
024import ch.qos.logback.core.util.StringUtil;
025
026import java.lang.annotation.Annotation;
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.Method;
029
030/**
031 *
032 * Various utility methods for computing the {@link AggregationType} of a given property or
033 * the class name of a property given implicit rules.
034 *
035 * <p>This class was extracted from {@link PropertySetter}. </p>
036 *
037 * @since 1.5.1
038 */
039public class AggregationAssessor extends ContextAwareBase  {
040
041    protected final Class<?> objClass;
042    protected final BeanDescription beanDescription;
043
044    public AggregationAssessor(BeanDescriptionCache beanDescriptionCache, Class objClass) {
045        this.objClass = objClass;
046        this.beanDescription = beanDescriptionCache.getBeanDescription(objClass);
047    }
048
049    /**
050     *  Given a property name, this method computes/assesses {@link AggregationType}
051     *  for the property for the class passed to the constructor.
052     *
053     * @param name
054     * @return the computed {@link AggregationType}
055     */
056    public AggregationType computeAggregationType(String name) {
057        String cName = StringUtil.capitalizeFirstLetter(name);
058
059        Method addMethod = findAdderMethod(cName);
060
061        if (addMethod != null) {
062            AggregationType type = computeRawAggregationType(addMethod);
063            switch (type) {
064            case NOT_FOUND:
065                return AggregationType.NOT_FOUND;
066            case AS_BASIC_PROPERTY:
067                return AggregationType.AS_BASIC_PROPERTY_COLLECTION;
068
069            case AS_COMPLEX_PROPERTY:
070                return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION;
071            case AS_BASIC_PROPERTY_COLLECTION:
072            case AS_COMPLEX_PROPERTY_COLLECTION:
073                addError("Unexpected AggregationType " + type);
074            }
075        }
076
077        Method setter = findSetterMethod(name);
078        if (setter != null) {
079            return computeRawAggregationType(setter);
080        } else {
081            // we have failed
082            return AggregationType.NOT_FOUND;
083        }
084    }
085
086
087//    String capitalizeFirstLetter(String name) {
088//        return StringUtil.capitalizeFirstLetter(name);
089//    }
090
091    public Method findAdderMethod(String name) {
092        String propertyName = BeanUtil.toLowerCamelCase(name);
093        return beanDescription.getAdder(propertyName);
094    }
095
096    public Method findSetterMethod(String name) {
097        String propertyName = BeanUtil.toLowerCamelCase(name);
098        return beanDescription.getSetter(propertyName);
099    }
100
101    private AggregationType computeRawAggregationType(Method method) {
102        Class<?> parameterClass = getParameterClassForMethod(method);
103        if (parameterClass == null) {
104            return AggregationType.NOT_FOUND;
105        }
106        if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) {
107            return AggregationType.AS_BASIC_PROPERTY;
108        } else {
109            return AggregationType.AS_COMPLEX_PROPERTY;
110        }
111    }
112
113    private Class<?> getParameterClassForMethod(Method method) {
114        if (method == null) {
115            return null;
116        }
117        Class<?>[] classArray = method.getParameterTypes();
118        if (classArray.length != 1) {
119            return null;
120        } else {
121            return classArray[0];
122        }
123    }
124
125    public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType,
126            DefaultNestedComponentRegistry registry) {
127
128        Class<?> registryResult = registry.findDefaultComponentType(objClass, name);
129        if (registryResult != null) {
130            return registryResult;
131        }
132        // find the relevant method for the given property name and aggregationType
133        Method relevantMethod = getRelevantMethod(name, aggregationType);
134        if (relevantMethod == null) {
135            return null;
136        }
137        Class<?> byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod);
138        if (byAnnotation != null) {
139            return byAnnotation;
140        }
141        return getByConcreteType(name, relevantMethod);
142    }
143
144    <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass, Method relevantMethod) {
145
146        if (relevantMethod != null) {
147            return relevantMethod.getAnnotation(annonationClass);
148        } else {
149            return null;
150        }
151    }
152
153    Class<?> getDefaultClassNameByAnnonation(String name, Method relevantMethod) {
154        DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class, relevantMethod);
155        if (defaultClassAnnon != null) {
156            return defaultClassAnnon.value();
157        }
158        return null;
159    }
160    Method getRelevantMethod(String name, AggregationType aggregationType) {
161        Method relevantMethod;
162        if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) {
163            relevantMethod = findAdderMethod(name);
164        } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) {
165            relevantMethod = findSetterMethod(name);
166        } else {
167            throw new IllegalStateException(aggregationType + " not allowed here");
168        }
169        return relevantMethod;
170    }
171
172    Class<?> getByConcreteType(String name, Method relevantMethod) {
173
174        Class<?> paramType = getParameterClassForMethod(relevantMethod);
175        if (paramType == null) {
176            return null;
177        }
178
179        boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType);
180        if (isUnequivocallyInstantiable) {
181            return paramType;
182        } else {
183            return null;
184        }
185    }
186
187    /**
188     * Can the given clazz instantiable with certainty?
189     *
190     * @param clazz The class to test for instantiability
191     * @return true if clazz can be instantiated, and false otherwise.
192     */
193    private boolean isUnequivocallyInstantiable(Class<?> clazz) {
194        if (clazz.isInterface()) {
195            return false;
196        }
197        // checking for constructors would be more elegant, but in
198        // classes without any declared constructors, Class.getConstructor()
199        // returns null.
200        Object o;
201        try {
202            o = clazz.getDeclaredConstructor().newInstance();
203            if (o != null) {
204                return true;
205            } else {
206                return false;
207            }
208        } catch (InstantiationException | IllegalAccessException | InvocationTargetException
209                | NoSuchMethodException e) {
210            return false;
211        }
212    }
213}