1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.core.joran.util;
16  
17  import ch.qos.logback.core.joran.spi.DefaultClass;
18  import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
19  import ch.qos.logback.core.joran.util.beans.BeanDescription;
20  import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
21  import ch.qos.logback.core.joran.util.beans.BeanUtil;
22  import ch.qos.logback.core.spi.ContextAwareBase;
23  import ch.qos.logback.core.util.AggregationType;
24  import ch.qos.logback.core.util.StringUtil;
25  
26  import java.lang.annotation.Annotation;
27  import java.lang.reflect.InvocationTargetException;
28  import java.lang.reflect.Method;
29  
30  /**
31   *
32   * Various utility methods for computing the {@link AggregationType} of a given property or
33   * the class name of a property given implicit rules.
34   *
35   * <p>This class was extracted from {@link PropertySetter}. </p>
36   *
37   * @since 1.5.1
38   */
39  public class AggregationAssessor extends ContextAwareBase  {
40  
41      protected final Class<?> objClass;
42      protected final BeanDescription beanDescription;
43  
44      public AggregationAssessor(BeanDescriptionCache beanDescriptionCache, Class objClass) {
45          this.objClass = objClass;
46          this.beanDescription = beanDescriptionCache.getBeanDescription(objClass);
47      }
48  
49      /**
50       *  Given a property name, this method computes/assesses {@link AggregationType}
51       *  for the property for the class passed to the constructor.
52       *
53       * @param name
54       * @return the computed {@link AggregationType}
55       */
56      public AggregationType computeAggregationType(String name) {
57          String cName = StringUtil.capitalizeFirstLetter(name);
58  
59          Method addMethod = findAdderMethod(cName);
60  
61          if (addMethod != null) {
62              AggregationType type = computeRawAggregationType(addMethod);
63              switch (type) {
64              case NOT_FOUND:
65                  return AggregationType.NOT_FOUND;
66              case AS_BASIC_PROPERTY:
67                  return AggregationType.AS_BASIC_PROPERTY_COLLECTION;
68  
69              case AS_COMPLEX_PROPERTY:
70                  return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION;
71  
72              // computeRawAggregationType cannot return these values
73              case AS_BASIC_PROPERTY_COLLECTION:
74              case AS_COMPLEX_PROPERTY_COLLECTION:
75                  addError("Unexpected AggregationType " + type);
76              }
77          }
78  
79          Method setter = findSetterMethod(name);
80          if (setter != null) {
81              return computeRawAggregationType(setter);
82          } else {
83              // we have failed
84              return AggregationType.NOT_FOUND;
85          }
86      }
87  
88  
89  //    String capitalizeFirstLetter(String name) {
90  //        return StringUtil.capitalizeFirstLetter(name);
91  //    }
92  
93      public Method findAdderMethod(String name) {
94          String propertyName = BeanUtil.toLowerCamelCase(name);
95          return beanDescription.getAdder(propertyName);
96      }
97  
98      public Method findSetterMethod(String name) {
99          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 }