001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.reflect; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.ThrowableUtils.*; 021 022import org.apache.juneau.commons.function.*; 023 024/** 025 * A typed property descriptor that allows you to specify arbitrary consumers and producers 026 * corresponding to setters and getters. 027 * 028 * <p> 029 * This class provides a flexible, builder-based approach to defining properties on objects, 030 * supporting both method-based and field-based access patterns. 031 * 032 * <h5 class='section'>Features:</h5> 033 * <ul class='spaced-list'> 034 * <li>Type-safe - generic types for object and value types 035 * <li>Builder-based - fluent API for construction 036 * <li>Flexible - supports arbitrary consumers and producers that can throw exceptions 037 * <li>Convenience methods - easy integration with {@link FieldInfo} and {@link MethodInfo} 038 * <li>Exception handling - uses {@link ThrowingFunction} and {@link ThrowingConsumer2} for exception support 039 * </ul> 040 * 041 * <h5 class='section'>Usage:</h5> 042 * <p class='bjava'> 043 * <jc>// Create property with method getter and setter</jc> 044 * Property<MyClass, String> <jv>nameProperty</jv> = Property 045 * .<MyClass, String><jsm>create</jsm>() 046 * .getter(<jv>obj</jv> -> <jv>obj</jv>.getName()) 047 * .setter((<jv>obj</jv>, <jv>val</jv>) -> <jv>obj</jv>.setName(<jv>val</jv>)) 048 * .build(); 049 * 050 * <jc>// Create property from FieldInfo</jc> 051 * FieldInfo <jv>field</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"name"</js>); 052 * Property<MyClass, String> <jv>fieldProperty</jv> = Property 053 * .<MyClass, String><jsm>create</jsm>() 054 * .field(<jv>field</jv>) 055 * .build(); 056 * 057 * <jc>// Create property from MethodInfo getter and setter</jc> 058 * MethodInfo <jv>getter</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getName"</js>); 059 * MethodInfo <jv>setter</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"setName"</js>, String.<jk>class</jk>); 060 * Property<MyClass, String> <jv>methodProperty</jv> = Property 061 * .<MyClass, String><jsm>create</jsm>() 062 * .getter(<jv>getter</jv>) 063 * .setter(<jv>setter</jv>) 064 * .build(); 065 * 066 * <jc>// Use property</jc> 067 * MyClass <jv>obj</jv> = <jk>new</jk> MyClass(); 068 * <jv>nameProperty</jv>.set(<jv>obj</jv>, <js>"John"</js>); 069 * String <jv>value</jv> = <jv>nameProperty</jv>.get(<jv>obj</jv>); 070 * </p> 071 * 072 * <h5 class='section'>See Also:</h5><ul> 073 * <li class='jc'>{@link FieldInfo} - Field introspection 074 * <li class='jc'>{@link MethodInfo} - Method introspection 075 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 076 * </ul> 077 * 078 * @param <T> The object type. 079 * @param <V> The value type. 080 */ 081@SuppressWarnings({ 082 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 083 "unchecked" // Type erasure requires cast to Builder<T,V> 084}) 085public class Property<T,V> { 086 087 /** 088 * Creates a new builder for constructing a property. 089 * 090 * <h5 class='section'>Example:</h5> 091 * <p class='bjava'> 092 * Property<MyClass, String> <jv>prop</jv> = Property 093 * .<MyClass, String><jsm>create</jsm>() 094 * .getter(<jv>obj</jv> -> <jv>obj</jv>.getName()) 095 * .setter((<jv>obj</jv>, <jv>val</jv>) -> <jv>obj</jv>.setName(<jv>val</jv>)) 096 * .build(); 097 * </p> 098 * 099 * @param <T> The object type. 100 * @param <V> The value type. 101 * @return A new builder instance. 102 */ 103 public static <T,V> Builder<T,V> create() { 104 return new Builder<>(); 105 } 106 107 private final ThrowingFunction<T,V> producer; 108 private final ThrowingConsumer2<T,V> consumer; 109 110 /** 111 * Constructor. 112 * 113 * @param producer The producer function (getter). Can be <jk>null</jk>, in which case {@link #canRead()} returns <jk>false</jk> and {@link #get(Object)} throws an {@link ExecutableException}. 114 * @param consumer The consumer function (setter). Can be <jk>null</jk>, in which case {@link #canWrite()} returns <jk>false</jk> and {@link #set(Object,Object)} throws an {@link ExecutableException}. 115 */ 116 public Property(ThrowingFunction<T,V> producer, ThrowingConsumer2<T,V> consumer) { 117 this.producer = producer; 118 this.consumer = consumer; 119 } 120 121 /** 122 * Gets the value from the specified object using the producer (getter). 123 * 124 * @param object The object from which to get the value. Must not be <jk>null</jk>. 125 * @return The value. 126 * @throws ExecutableException 127 */ 128 public V get(T object) throws ExecutableException { 129 reqnn("object", object); 130 if (producer == null) 131 throw exex("No getter defined for this property"); 132 return safe(() -> producer.applyThrows(object)); 133 } 134 135 /** 136 * Sets the value on the specified object using the consumer (setter). 137 * 138 * @param object The object on which to set the value. Must not be <jk>null</jk>. 139 * @param value The value to set. Can be <jk>null</jk>. 140 * @throws ExecutableException 141 */ 142 public void set(T object, V value) throws ExecutableException { 143 reqnn("object", object); 144 if (consumer == null) 145 throw exex("No setter defined for this property"); 146 safe(() -> consumer.acceptThrows(object, value)); 147 } 148 149 /** 150 * Returns <jk>true</jk> if this property can be read. 151 * 152 * @return <jk>true</jk> if this property can be read. 153 */ 154 public boolean canRead() { 155 return producer != null; 156 } 157 158 /** 159 * Returns <jk>true</jk> if this property can be written. 160 * 161 * @return <jk>true</jk> if this property can be written. 162 */ 163 public boolean canWrite() { 164 return consumer != null; 165 } 166 167 /** 168 * Builder for constructing {@link Property} instances. 169 * 170 * @param <T> The object type. 171 * @param <V> The value type. 172 */ 173 public static class Builder<T,V> { 174 private ThrowingFunction<T,V> producer; 175 private ThrowingConsumer2<T,V> consumer; 176 177 /** 178 * Sets the producer (getter) using a function. 179 * 180 * @param producer The producer function. Can be <jk>null</jk> to leave the property unreadable. 181 * @return This object. 182 */ 183 public Builder<T,V> getter(ThrowingFunction<T,V> producer) { 184 this.producer = producer; 185 return this; 186 } 187 188 /** 189 * Sets the consumer (setter) using a throwing consumer. 190 * 191 * @param consumer The consumer function. Can be <jk>null</jk> to leave the property unwritable. 192 * @return This object. 193 */ 194 public Builder<T,V> setter(ThrowingConsumer2<T,V> consumer) { 195 this.consumer = consumer; 196 return this; 197 } 198 199 /** 200 * Sets both getter and setter from a {@link FieldInfo}. 201 * 202 * <p> 203 * This is a convenience method that creates both producer and consumer from a field. 204 * 205 * @param field The field info. Must not be <jk>null</jk>. 206 * @return This object. 207 */ 208 public Builder<T,V> field(FieldInfo field) { 209 reqnn("field", field); 210 field.accessible(); 211 boolean isStatic = field.isStatic(); 212 this.producer = obj -> (V)field.get(isStatic ? null : obj); 213 this.consumer = (obj, val) -> field.set(isStatic ? null : obj, val); 214 return this; 215 } 216 217 /** 218 * Sets the producer (getter) from a {@link MethodInfo}. 219 * 220 * <p> 221 * The method should take no parameters and return the property value. 222 * 223 * @param method The method info. Must not be <jk>null</jk>. 224 * @return This object. 225 */ 226 public Builder<T,V> getter(MethodInfo method) { 227 reqnn("method", method); 228 method.accessible(); 229 boolean isStatic = method.isStatic(); 230 this.producer = obj -> (V)method.invoke(isStatic ? null : obj); 231 return this; 232 } 233 234 /** 235 * Sets the consumer (setter) from a {@link MethodInfo}. 236 * 237 * <p> 238 * The method should take one parameter (the value to set) and return void. 239 * 240 * @param method The method info. Must not be <jk>null</jk>. 241 * @return This object. 242 */ 243 public Builder<T,V> setter(MethodInfo method) { 244 reqnn("method", method); 245 method.accessible(); 246 boolean isStatic = method.isStatic(); 247 this.consumer = (obj, val) -> method.invoke(isStatic ? null : obj, val); 248 return this; 249 } 250 251 /** 252 * Builds the property instance. 253 * 254 * @return A new property instance. 255 */ 256 public Property<T,V> build() { 257 return new Property<>(producer, consumer); 258 } 259 } 260} 261