001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023 002package io.cui.tools.property; 003 004import static io.cui.tools.string.MoreStrings.requireNotEmptyTrimmed; 005import static java.util.Objects.requireNonNull; 006import java.lang.reflect.InvocationTargetException; 007import java.lang.reflect.Method; 008import java.util.Objects; 009import java.util.Optional; 010import io.cui.tools.base.Preconditions; 011import io.cui.tools.logging.CuiLogger; 012import io.cui.tools.reflect.MoreReflection; 013import io.cui.tools.string.MoreStrings; 014 015/** 016 * Helper class providing convenient methods for reading from / writing to java beans. 017 * <h2>Caution:</h2> 018 * <p> 019 * Use reflection only if there is no other way. Even if some of the problems are 020 * minimized by using this type. It should be used either in test-code, what is we actually do, and 021 * not production code. An other reason could be framework code. as for that you should exactly know 022 * what you do. 023 * </p> 024 * 025 * @author Oliver Wolff 026 */ 027public final class PropertyUtil { 028 private static final CuiLogger log = new CuiLogger(PropertyUtil.class); 029 static final String UNABLE_TO_WRITE_PROPERTY = "Unable to write property \'%s\' to beanType \'%s\': no suitable write method found. Needed property-type \'%s\'"; 030 static final String UNABLE_TO_WRITE_PROPERTY_RUNTIME = "Unable to write property \'%s\' to beanType \'%s\'. Needed property-type \'%s\'"; 031 static final String UNABLE_TO_READ_PROPERTY = "Unable to read property \'%s\' from beanType \'%s\'."; 032 033 // owolff: False Positive, Optional#isPresent is checked 034 /** 035 * @param bean instance to be read from, must not be null 036 * @param propertyName to be read, must not be null nor empty nor blank 037 * @return the object read from the property 038 * @throws IllegalArgumentException in case the property does not exist (determined by a read 039 * method) 040 * @throws IllegalStateException in case some Exception occurred while reading 041 */ 042 @SuppressWarnings("squid:S3655") 043 public static Object readProperty(Object bean, String propertyName) { 044 log.debug("Reading property \'%s\' from %s", propertyName, bean); 045 requireNonNull(bean); 046 requireNotEmptyTrimmed(propertyName); 047 var reader = MoreReflection.retrieveAccessMethod(bean.getClass(), propertyName); 048 Preconditions.checkArgument(reader.isPresent(), UNABLE_TO_READ_PROPERTY, propertyName, bean.getClass()); 049 try { 050 return reader.get().invoke(bean); 051 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 052 throw new IllegalStateException(MoreStrings.lenientFormat(UNABLE_TO_READ_PROPERTY, propertyName, bean.getClass()), e); 053 } 054 } 055 056 /** 057 * @param bean instance to be read from, must not be null 058 * @param propertyName to be read, must not be null nor empty nor blank 059 * @param propertyValue to be set 060 * @return In case the property set method is void the given bean will be returned. Otherwise, 061 * the return value of the method invocation, assuming the setMethods is a builder type. 062 * @throws IllegalArgumentException in case the property does not exist (determined by a write 063 * method) 064 * @throws IllegalStateException in case some Exception occurred while writing 065 */ 066 public static Object writeProperty(Object bean, String propertyName, Object propertyValue) { 067 log.debug("Writing \'%s\' to property \'%s\' on \'%s\'", propertyValue, propertyName, bean); 068 requireNonNull(bean); 069 requireNotEmptyTrimmed(propertyName); 070 var writeMethod = determineWriteMethod(bean, propertyName, propertyValue); 071 try { 072 var result = writeMethod.invoke(bean, propertyValue); 073 return Objects.requireNonNullElse(result, bean); 074 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 075 var target = propertyValue != null ? propertyValue.getClass().getName() : "Undefined"; 076 throw new IllegalStateException(MoreStrings.lenientFormat(UNABLE_TO_WRITE_PROPERTY_RUNTIME, propertyName, bean.getClass(), target), e); 077 } 078 } 079 080 /** 081 * Tries to determine the type of given property 082 * 083 * @param beanType to be checked, must not be null 084 * @param propertyName to be checked, must not be null 085 * @return an {@link Optional} on the actual type of the property. First it checks an 086 * access-methods, then it tries to directly access the field, and if that fails it uses 087 * the first read method found, {@link Optional#empty()} otherwise. 088 */ 089 public static Optional<Class<?>> resolvePropertyType(Class<?> beanType, String propertyName) { 090 var retrieveAccessMethod = MoreReflection.retrieveAccessMethod(beanType, propertyName); 091 if (retrieveAccessMethod.isPresent()) { 092 log.trace("Found read-method on class \'%s\' for property-name \'%s\'", beanType, propertyName); 093 return Optional.of(retrieveAccessMethod.get().getReturnType()); 094 } 095 var field = MoreReflection.accessField(beanType, propertyName); 096 if (field.isPresent()) { 097 log.trace("Found field on class \'%s\' with name \'%s\'", beanType, propertyName); 098 return Optional.of(field.get().getType()); 099 } 100 log.debug("Neither read-method nor field found on class \'%s\' for property-name \'%s\', checking write methods, returning first type found", beanType, propertyName); 101 var candidates = MoreReflection.retrieveWriteMethodCandidates(beanType, propertyName); 102 if (!candidates.isEmpty()) { 103 return Optional.of(candidates.iterator().next().getParameterTypes()[0]); 104 } 105 log.debug("Unable to detect property-type on class \'%s\' for property-name \'%s\'", beanType, propertyName); 106 return Optional.empty(); 107 } 108 109 static Method determineWriteMethod(Object bean, String propertyName, Object propertyValue) { 110 var candidates = MoreReflection.retrieveWriteMethodCandidates(bean.getClass(), propertyName); 111 var target = propertyValue != null ? propertyValue.getClass().getName() : "Undefined"; 112 Preconditions.checkArgument(!candidates.isEmpty(), UNABLE_TO_WRITE_PROPERTY, propertyName, bean.getClass(), target); 113 if (null == propertyValue) { 114 log.trace("No / Null propertyValue given, so any method should suffice to write property \'%s\' to %s", propertyName, bean); 115 return candidates.iterator().next(); 116 } 117 for (Method candidate : candidates) { 118 if (MoreReflection.checkWhetherParameterIsAssignable(candidate.getParameterTypes()[0], propertyValue.getClass())) { 119 log.trace("Found method %s to write property \'%s\' to %s", candidate, propertyName, bean); 120 return candidate; 121 } 122 } 123 throw new IllegalArgumentException(MoreStrings.lenientFormat(UNABLE_TO_WRITE_PROPERTY, propertyName, bean.getClass(), target)); 124 } 125 126 @java.lang.SuppressWarnings("all") 127 @lombok.Generated 128 private PropertyUtil() { 129 throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); 130 } 131}