001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023 002package io.cui.tools.property; 003 004import static io.cui.tools.collect.CollectionLiterals.mutableList; 005import static io.cui.tools.string.MoreStrings.requireNotEmptyTrimmed; 006import static java.util.Objects.requireNonNull; 007import java.beans.Beans; 008import java.beans.IntrospectionException; 009import java.beans.Introspector; 010import java.beans.PropertyDescriptor; 011import java.lang.reflect.InvocationTargetException; 012import java.lang.reflect.Method; 013import java.util.Objects; 014import java.util.Optional; 015import io.cui.tools.base.Preconditions; 016import io.cui.tools.logging.CuiLogger; 017import io.cui.tools.reflect.MoreReflection; 018import io.cui.tools.string.MoreStrings; 019import lombok.NonNull; 020 021/** 022 * <h2>Overview</h2> 023 * An instance of {@link PropertyHolder} provides runtime information for a specific BeanProperty. 024 * Under the hood it uses {@link Beans} tooling provided by the JDK and the utilities 025 * {@link PropertyUtil} an and {@link MoreReflection}. Compared to the standard tooling it is 026 * more flexible regarding fluent api style of bean / DTOs. 027 * <h3>Usage</h3> 028 * <p> 029 * The usual entry-point is {@link #from(Class, String)}. In case you want to create your own 030 * instance you can use the contained builder directly using {@link #builder()} 031 * </p> 032 * <p> 033 * Now you can access the metadata for that property, see {@link #getMemberInfo()}, 034 * {@link #getName()}, {@link #getType()}, {@link #getReadMethod()} 035 * </p> 036 * <p> 037 * Reading and writing of properties should be done by {@link #readFrom(Object)} 038 * and {@link #writeTo(Object, Object)}. Directly using 039 * {@link #getReadMethod()} and {@link #getWriteMethod()} is more error-prone and less versatile. 040 * </p> 041 * 042 * <h2>Caution:</h2> 043 * <p> 044 * Use reflection only if there is no other way. Even if some of the problems are 045 * minimized by using this type. It should be used either in test-code, what we actually do, and 046 * not production code. An other reason could be framework code. as for that you should exactly know 047 * what you do. 048 * </p> 049 * 050 * @author Oliver Wolff 051 */ 052public final class PropertyHolder { 053 private static final String UNABLE_TO_LOAD_PROPERTY_DESCRIPTOR = "Unable to load property-descriptor for attribute \'%s\' on type \'%s\'"; 054 private static final CuiLogger log = new CuiLogger(PropertyHolder.class); 055 /** 056 * The name of the property. 057 */ 058 @NonNull 059 private final String name; 060 /** 061 * The actual type of the property. 062 */ 063 @NonNull 064 private final Class<?> type; 065 /** 066 * Provides additional runtime information for the property, see {@link PropertyMemberInfo} 067 */ 068 @NonNull 069 private final PropertyMemberInfo memberInfo; 070 /** 071 * Provides additional Runtime-information, see {@link PropertyReadWrite} 072 */ 073 @NonNull 074 private final PropertyReadWrite readWrite; 075 /** 076 * Derived by {@link PropertyDescriptor}, may be null 077 */ 078 private final Method readMethod; 079 /** 080 * Derived by {@link PropertyDescriptor}, may be null 081 */ 082 private final Method writeMethod; 083 084 /** 085 * Reads the property on the given bean identified by the concrete {@link PropertyHolder} and 086 * the given bean. First it tries to access the readMethod derived by the 087 * {@link PropertyDescriptor}. If this can not be achieved, e.g. for types that do not match 088 * exactly Java-Bean-Specification it tries to read the property by using 089 * {@link PropertyUtil#readProperty(Object, String)} 090 * 091 * @param bean instance to be read from, must not be null 092 * @return the object read from the property 093 * @throws IllegalStateException in case the property can not be read, see 094 * {@link PropertyReadWrite#isReadable()} 095 * @throws IllegalStateException in case some Exception occurred while reading 096 */ 097 public Object readFrom(Object bean) { 098 log.debug("Reading property \'%s\' from %s", name, bean); 099 requireNonNull(bean); 100 Preconditions.checkState(readWrite.isReadable(), "Property \'%s\' on bean \'%s\' can not be read", name, bean); 101 if (null != readMethod) { 102 try { 103 return readMethod.invoke(bean); 104 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 105 throw new IllegalStateException(MoreStrings.lenientFormat(PropertyUtil.UNABLE_TO_READ_PROPERTY, name, bean.getClass()), e); 106 } 107 } 108 return PropertyUtil.readProperty(bean, name); 109 } 110 111 /** 112 * @param bean instance to be read from, must not be null 113 * @param propertyValue to be set 114 * @return In case the property set method is void the given bean will be returned. Otherwise, 115 * the return value of the method invocation, assuming the setMethods is a builder / 116 * fluent-api type. 117 * @throws IllegalStateException in case the property can not be read, see 118 * {@link PropertyReadWrite#isWriteable()} 119 * @throws IllegalStateException in case some Exception occurred while writing 120 */ 121 public Object writeTo(Object bean, Object propertyValue) { 122 log.debug("Writing %s to property \'%s\' on %s", propertyValue, name, bean); 123 requireNonNull(bean); 124 Preconditions.checkState(readWrite.isWriteable(), "Property \'%s\' on bean \'%s\' can not be written", name, bean); 125 if (null != writeMethod) { 126 try { 127 var result = writeMethod.invoke(bean, propertyValue); 128 return Objects.requireNonNullElse(result, bean); 129 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 130 throw new IllegalStateException(MoreStrings.lenientFormat(PropertyUtil.UNABLE_TO_WRITE_PROPERTY_RUNTIME, name, bean.getClass()), e); 131 } 132 } 133 return PropertyUtil.writeProperty(bean, name, propertyValue); 134 } 135 136 /** 137 * Factory Method for creating a concrete {@link PropertyHolder} 138 * 139 * @param beanType must not be null 140 * @param attributeName must not be null nor empty 141 * @return the concrete {@link PropertyHolder} for the given parameter if applicable 142 * @throws IllegalArgumentException for cases where {@link Introspector} is not capable of 143 * resolving a {@link PropertyDescriptor}. This is usually the case if it is not a 144 * valid bean. 145 */ 146 public static Optional<PropertyHolder> from(Class<?> beanType, String attributeName) { 147 requireNonNull(beanType); 148 requireNotEmptyTrimmed(attributeName); 149 try { 150 var info = Introspector.getBeanInfo(beanType); 151 var descriptor = mutableList(info.getPropertyDescriptors()).stream().filter(desc -> attributeName.equalsIgnoreCase(desc.getName())).findFirst(); 152 if (!descriptor.isPresent()) { 153 log.debug(UNABLE_TO_LOAD_PROPERTY_DESCRIPTOR, attributeName, beanType); 154 return buildByReflection(beanType, attributeName); 155 } 156 return doBuild(descriptor.get(), beanType, attributeName); 157 } catch (IntrospectionException e) { 158 throw new IllegalArgumentException(MoreStrings.lenientFormat(UNABLE_TO_LOAD_PROPERTY_DESCRIPTOR, attributeName, beanType), e); 159 } 160 } 161 162 private static Optional<PropertyHolder> doBuild(PropertyDescriptor propertyDescriptor, Class<?> type, String attributeName) { 163 var builder = builder(); 164 builder.name(attributeName); 165 builder.readWrite(PropertyReadWrite.fromPropertyDescriptor(propertyDescriptor, type, attributeName)); 166 builder.readMethod(propertyDescriptor.getReadMethod()); 167 builder.writeMethod(propertyDescriptor.getWriteMethod()); 168 builder.memberInfo(PropertyMemberInfo.resolveForBean(type, attributeName)); 169 builder.type(propertyDescriptor.getPropertyType()); 170 return Optional.of(builder.build()); 171 } 172 173 static Optional<PropertyHolder> buildByReflection(Class<?> beanType, String attributeName) { 174 log.trace("Trying reflection for determining attribute \'%s\' on type \'%s\'", attributeName, beanType); 175 var field = MoreReflection.accessField(beanType, attributeName); 176 if (!field.isPresent()) { 177 return Optional.empty(); 178 } 179 var builder = builder(); 180 builder.name(attributeName); 181 builder.readWrite(PropertyReadWrite.resolveForBean(beanType, attributeName)); 182 builder.memberInfo(PropertyMemberInfo.resolveForBean(beanType, attributeName)); 183 builder.type(PropertyUtil.resolvePropertyType(beanType, attributeName).orElse(Object.class)); 184 return Optional.of(builder.build()); 185 } 186 187 @java.lang.SuppressWarnings("all") 188 @lombok.Generated 189 PropertyHolder(@NonNull final String name, @NonNull final Class<?> type, @NonNull final PropertyMemberInfo memberInfo, @NonNull final PropertyReadWrite readWrite, final Method readMethod, final Method writeMethod) { 190 if (name == null) { 191 throw new java.lang.NullPointerException("name is marked non-null but is null"); 192 } 193 if (type == null) { 194 throw new java.lang.NullPointerException("type is marked non-null but is null"); 195 } 196 if (memberInfo == null) { 197 throw new java.lang.NullPointerException("memberInfo is marked non-null but is null"); 198 } 199 if (readWrite == null) { 200 throw new java.lang.NullPointerException("readWrite is marked non-null but is null"); 201 } 202 this.name = name; 203 this.type = type; 204 this.memberInfo = memberInfo; 205 this.readWrite = readWrite; 206 this.readMethod = readMethod; 207 this.writeMethod = writeMethod; 208 } 209 210 211 @java.lang.SuppressWarnings("all") 212 @lombok.Generated 213 public static class PropertyHolderBuilder { 214 @java.lang.SuppressWarnings("all") 215 @lombok.Generated 216 private String name; 217 @java.lang.SuppressWarnings("all") 218 @lombok.Generated 219 private Class<?> type; 220 @java.lang.SuppressWarnings("all") 221 @lombok.Generated 222 private PropertyMemberInfo memberInfo; 223 @java.lang.SuppressWarnings("all") 224 @lombok.Generated 225 private PropertyReadWrite readWrite; 226 @java.lang.SuppressWarnings("all") 227 @lombok.Generated 228 private Method readMethod; 229 @java.lang.SuppressWarnings("all") 230 @lombok.Generated 231 private Method writeMethod; 232 233 @java.lang.SuppressWarnings("all") 234 @lombok.Generated 235 PropertyHolderBuilder() { 236 } 237 238 /** 239 * The name of the property. 240 * @return {@code this}. 241 */ 242 @java.lang.SuppressWarnings("all") 243 @lombok.Generated 244 public PropertyHolder.PropertyHolderBuilder name(@NonNull final String name) { 245 if (name == null) { 246 throw new java.lang.NullPointerException("name is marked non-null but is null"); 247 } 248 this.name = name; 249 return this; 250 } 251 252 /** 253 * The actual type of the property. 254 * @return {@code this}. 255 */ 256 @java.lang.SuppressWarnings("all") 257 @lombok.Generated 258 public PropertyHolder.PropertyHolderBuilder type(@NonNull final Class<?> type) { 259 if (type == null) { 260 throw new java.lang.NullPointerException("type is marked non-null but is null"); 261 } 262 this.type = type; 263 return this; 264 } 265 266 /** 267 * Provides additional runtime information for the property, see {@link PropertyMemberInfo} 268 * @return {@code this}. 269 */ 270 @java.lang.SuppressWarnings("all") 271 @lombok.Generated 272 public PropertyHolder.PropertyHolderBuilder memberInfo(@NonNull final PropertyMemberInfo memberInfo) { 273 if (memberInfo == null) { 274 throw new java.lang.NullPointerException("memberInfo is marked non-null but is null"); 275 } 276 this.memberInfo = memberInfo; 277 return this; 278 } 279 280 /** 281 * Provides additional Runtime-information, see {@link PropertyReadWrite} 282 * @return {@code this}. 283 */ 284 @java.lang.SuppressWarnings("all") 285 @lombok.Generated 286 public PropertyHolder.PropertyHolderBuilder readWrite(@NonNull final PropertyReadWrite readWrite) { 287 if (readWrite == null) { 288 throw new java.lang.NullPointerException("readWrite is marked non-null but is null"); 289 } 290 this.readWrite = readWrite; 291 return this; 292 } 293 294 /** 295 * Derived by {@link PropertyDescriptor}, may be null 296 * @return {@code this}. 297 */ 298 @java.lang.SuppressWarnings("all") 299 @lombok.Generated 300 public PropertyHolder.PropertyHolderBuilder readMethod(final Method readMethod) { 301 this.readMethod = readMethod; 302 return this; 303 } 304 305 /** 306 * Derived by {@link PropertyDescriptor}, may be null 307 * @return {@code this}. 308 */ 309 @java.lang.SuppressWarnings("all") 310 @lombok.Generated 311 public PropertyHolder.PropertyHolderBuilder writeMethod(final Method writeMethod) { 312 this.writeMethod = writeMethod; 313 return this; 314 } 315 316 @java.lang.SuppressWarnings("all") 317 @lombok.Generated 318 public PropertyHolder build() { 319 return new PropertyHolder(this.name, this.type, this.memberInfo, this.readWrite, this.readMethod, this.writeMethod); 320 } 321 322 @java.lang.Override 323 @java.lang.SuppressWarnings("all") 324 @lombok.Generated 325 public java.lang.String toString() { 326 return "PropertyHolder.PropertyHolderBuilder(name=" + this.name + ", type=" + this.type + ", memberInfo=" + this.memberInfo + ", readWrite=" + this.readWrite + ", readMethod=" + this.readMethod + ", writeMethod=" + this.writeMethod + ")"; 327 } 328 } 329 330 @java.lang.SuppressWarnings("all") 331 @lombok.Generated 332 public static PropertyHolder.PropertyHolderBuilder builder() { 333 return new PropertyHolder.PropertyHolderBuilder(); 334 } 335 336 /** 337 * The name of the property. 338 */ 339 @NonNull 340 @java.lang.SuppressWarnings("all") 341 @lombok.Generated 342 public String getName() { 343 return this.name; 344 } 345 346 /** 347 * The actual type of the property. 348 */ 349 @NonNull 350 @java.lang.SuppressWarnings("all") 351 @lombok.Generated 352 public Class<?> getType() { 353 return this.type; 354 } 355 356 /** 357 * Provides additional runtime information for the property, see {@link PropertyMemberInfo} 358 */ 359 @NonNull 360 @java.lang.SuppressWarnings("all") 361 @lombok.Generated 362 public PropertyMemberInfo getMemberInfo() { 363 return this.memberInfo; 364 } 365 366 /** 367 * Provides additional Runtime-information, see {@link PropertyReadWrite} 368 */ 369 @NonNull 370 @java.lang.SuppressWarnings("all") 371 @lombok.Generated 372 public PropertyReadWrite getReadWrite() { 373 return this.readWrite; 374 } 375 376 /** 377 * Derived by {@link PropertyDescriptor}, may be null 378 */ 379 @java.lang.SuppressWarnings("all") 380 @lombok.Generated 381 public Method getReadMethod() { 382 return this.readMethod; 383 } 384 385 /** 386 * Derived by {@link PropertyDescriptor}, may be null 387 */ 388 @java.lang.SuppressWarnings("all") 389 @lombok.Generated 390 public Method getWriteMethod() { 391 return this.writeMethod; 392 } 393 394 @java.lang.Override 395 @java.lang.SuppressWarnings("all") 396 @lombok.Generated 397 public boolean equals(final java.lang.Object o) { 398 if (o == this) return true; 399 if (!(o instanceof PropertyHolder)) return false; 400 final PropertyHolder other = (PropertyHolder) o; 401 final java.lang.Object this$name = this.getName(); 402 final java.lang.Object other$name = other.getName(); 403 if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; 404 final java.lang.Object this$type = this.getType(); 405 final java.lang.Object other$type = other.getType(); 406 if (this$type == null ? other$type != null : !this$type.equals(other$type)) return false; 407 final java.lang.Object this$memberInfo = this.getMemberInfo(); 408 final java.lang.Object other$memberInfo = other.getMemberInfo(); 409 if (this$memberInfo == null ? other$memberInfo != null : !this$memberInfo.equals(other$memberInfo)) return false; 410 final java.lang.Object this$readWrite = this.getReadWrite(); 411 final java.lang.Object other$readWrite = other.getReadWrite(); 412 if (this$readWrite == null ? other$readWrite != null : !this$readWrite.equals(other$readWrite)) return false; 413 final java.lang.Object this$readMethod = this.getReadMethod(); 414 final java.lang.Object other$readMethod = other.getReadMethod(); 415 if (this$readMethod == null ? other$readMethod != null : !this$readMethod.equals(other$readMethod)) return false; 416 final java.lang.Object this$writeMethod = this.getWriteMethod(); 417 final java.lang.Object other$writeMethod = other.getWriteMethod(); 418 if (this$writeMethod == null ? other$writeMethod != null : !this$writeMethod.equals(other$writeMethod)) return false; 419 return true; 420 } 421 422 @java.lang.Override 423 @java.lang.SuppressWarnings("all") 424 @lombok.Generated 425 public int hashCode() { 426 final int PRIME = 59; 427 int result = 1; 428 final java.lang.Object $name = this.getName(); 429 result = result * PRIME + ($name == null ? 43 : $name.hashCode()); 430 final java.lang.Object $type = this.getType(); 431 result = result * PRIME + ($type == null ? 43 : $type.hashCode()); 432 final java.lang.Object $memberInfo = this.getMemberInfo(); 433 result = result * PRIME + ($memberInfo == null ? 43 : $memberInfo.hashCode()); 434 final java.lang.Object $readWrite = this.getReadWrite(); 435 result = result * PRIME + ($readWrite == null ? 43 : $readWrite.hashCode()); 436 final java.lang.Object $readMethod = this.getReadMethod(); 437 result = result * PRIME + ($readMethod == null ? 43 : $readMethod.hashCode()); 438 final java.lang.Object $writeMethod = this.getWriteMethod(); 439 result = result * PRIME + ($writeMethod == null ? 43 : $writeMethod.hashCode()); 440 return result; 441 } 442 443 @java.lang.Override 444 @java.lang.SuppressWarnings("all") 445 @lombok.Generated 446 public java.lang.String toString() { 447 return "PropertyHolder(name=" + this.getName() + ", type=" + this.getType() + ", memberInfo=" + this.getMemberInfo() + ", readWrite=" + this.getReadWrite() + ", readMethod=" + this.getReadMethod() + ", writeMethod=" + this.getWriteMethod() + ")"; 448 } 449}