001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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
072            // computeRawAggregationType cannot return these values
073            case AS_BASIC_PROPERTY_COLLECTION:
074            case AS_COMPLEX_PROPERTY_COLLECTION:
075                addError("Unexpected AggregationType " + type);
076            }
077        }
078
079        Method setter = findSetterMethod(name);
080        if (setter != null) {
081            return computeRawAggregationType(setter);
082        } else {
083            // we have failed
084            return AggregationType.NOT_FOUND;
085        }
086    }
087
088
089//    String capitalizeFirstLetter(String name) {
090//        return StringUtil.capitalizeFirstLetter(name);
091//    }
092
093    public Method findAdderMethod(String name) {
094        String propertyName = BeanUtil.toLowerCamelCase(name);
095        return beanDescription.getAdder(propertyName);
096    }
097
098    public Method findSetterMethod(String name) {
099        String propertyName = BeanUtil.toLowerCamelCase(name);
100        return beanDescription.getSetter(propertyName);
101    }
102
103    private AggregationType computeRawAggregationType(Method method) {
104        Class<?> parameterClass = getParameterClassForMethod(method);
105        if (parameterClass == null) {
106            return AggregationType.NOT_FOUND;
107        }
108        if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) {
109            return AggregationType.AS_BASIC_PROPERTY;
110        } else {
111            return AggregationType.AS_COMPLEX_PROPERTY;
112        }
113    }
114
115    private Class<?> getParameterClassForMethod(Method method) {
116        if (method == null) {
117            return null;
118        }
119        Class<?>[] classArray = method.getParameterTypes();
120        if (classArray.length != 1) {
121            return null;
122        } else {
123            return classArray[0];
124        }
125    }
126
127    public Class<?> getClassNameViaImplicitRules(String name, AggregationType aggregationType,
128            DefaultNestedComponentRegistry registry) {
129
130        Class<?> registryResult = registry.findDefaultComponentType(objClass, name);
131        if (registryResult != null) {
132            return registryResult;
133        }
134        // find the relevant method for the given property name and aggregationType
135        Method relevantMethod = getRelevantMethod(name, aggregationType);
136        if (relevantMethod == null) {
137            return null;
138        }
139        Class<?> byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod);
140        if (byAnnotation != null) {
141            return byAnnotation;
142        }
143        return getByConcreteType(name, relevantMethod);
144    }
145
146    <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass, Method relevantMethod) {
147
148        if (relevantMethod != null) {
149            return relevantMethod.getAnnotation(annonationClass);
150        } else {
151            return null;
152        }
153    }
154
155    Class<?> getDefaultClassNameByAnnonation(String name, Method relevantMethod) {
156        DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class, relevantMethod);
157        if (defaultClassAnnon != null) {
158            return defaultClassAnnon.value();
159        }
160        return null;
161    }
162    Method getRelevantMethod(String name, AggregationType aggregationType) {
163        Method relevantMethod;
164        if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) {
165            relevantMethod = findAdderMethod(name);
166        } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) {
167            relevantMethod = findSetterMethod(name);
168        } else {
169            throw new IllegalStateException(aggregationType + " not allowed here");
170        }
171        return relevantMethod;
172    }
173
174    Class<?> getByConcreteType(String name, Method relevantMethod) {
175
176        Class<?> paramType = getParameterClassForMethod(relevantMethod);
177        if (paramType == null) {
178            return null;
179        }
180
181        boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType);
182        if (isUnequivocallyInstantiable) {
183            return paramType;
184        } else {
185            return null;
186        }
187    }
188
189    /**
190     * Can the given clazz instantiable with certainty?
191     *
192     * @param clazz The class to test for instantiability
193     * @return true if clazz can be instantiated, and false otherwise.
194     */
195    private boolean isUnequivocallyInstantiable(Class<?> clazz) {
196        if (clazz.isInterface()) {
197            return false;
198        }
199        // checking for constructors would be more elegant, but in
200        // classes without any declared constructors, Class.getConstructor()
201        // returns null.
202        Object o;
203        try {
204            o = clazz.getDeclaredConstructor().newInstance();
205            if (o != null) {
206                return true;
207            } else {
208                return false;
209            }
210        } catch (InstantiationException | IllegalAccessException | InvocationTargetException
211                | NoSuchMethodException e) {
212            return false;
213        }
214    }
215}