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}