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