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