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.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.ThrowableUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import org.apache.juneau.commons.function.*; 024 025/** 026 * A typed property descriptor that allows you to specify arbitrary consumers and producers 027 * corresponding to setters and getters. 028 * 029 * <p> 030 * This class provides a flexible, builder-based approach to defining properties on objects, 031 * supporting both method-based and field-based access patterns. 032 * 033 * <h5 class='section'>Features:</h5> 034 * <ul class='spaced-list'> 035 * <li>Type-safe - generic types for object and value types 036 * <li>Builder-based - fluent API for construction 037 * <li>Flexible - supports arbitrary consumers and producers that can throw exceptions 038 * <li>Convenience methods - easy integration with {@link FieldInfo} and {@link MethodInfo} 039 * <li>Exception handling - uses {@link ThrowingFunction} and {@link ThrowingConsumer2} for exception support 040 * </ul> 041 * 042 * <h5 class='section'>Usage:</h5> 043 * <p class='bjava'> 044 * <jc>// Create property with method getter and setter</jc> 045 * Property<MyClass, String> <jv>nameProperty</jv> = Property 046 * .<MyClass, String><jsm>create</jsm>() 047 * .getter(<jv>obj</jv> -> <jv>obj</jv>.getName()) 048 * .setter((<jv>obj</jv>, <jv>val</jv>) -> <jv>obj</jv>.setName(<jv>val</jv>)) 049 * .build(); 050 * 051 * <jc>// Create property from FieldInfo</jc> 052 * FieldInfo <jv>field</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getField(<js>"name"</js>); 053 * Property<MyClass, String> <jv>fieldProperty</jv> = Property 054 * .<MyClass, String><jsm>create</jsm>() 055 * .field(<jv>field</jv>) 056 * .build(); 057 * 058 * <jc>// Create property from MethodInfo getter and setter</jc> 059 * MethodInfo <jv>getter</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"getName"</js>); 060 * MethodInfo <jv>setter</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getMethod(<js>"setName"</js>, String.<jk>class</jk>); 061 * Property<MyClass, String> <jv>methodProperty</jv> = Property 062 * .<MyClass, String><jsm>create</jsm>() 063 * .getter(<jv>getter</jv>) 064 * .setter(<jv>setter</jv>) 065 * .build(); 066 * 067 * <jc>// Use property</jc> 068 * MyClass <jv>obj</jv> = <jk>new</jk> MyClass(); 069 * <jv>nameProperty</jv>.set(<jv>obj</jv>, <js>"John"</js>); 070 * String <jv>value</jv> = <jv>nameProperty</jv>.get(<jv>obj</jv>); 071 * </p> 072 * 073 * <h5 class='section'>See Also:</h5><ul> 074 * <li class='jc'>{@link FieldInfo} - Field introspection 075 * <li class='jc'>{@link MethodInfo} - Method introspection 076 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauCommonsReflection">Reflection Package</a> 077 * </ul> 078 * 079 * @param <T> The object type. 080 * @param <V> The value type. 081 */ 082public class Property<T, V> { 083 084 /** 085 * Creates a new builder for constructing a property. 086 * 087 * <h5 class='section'>Example:</h5> 088 * <p class='bjava'> 089 * Property<MyClass, String> <jv>prop</jv> = Property 090 * .<MyClass, String><jsm>create</jsm>() 091 * .getter(<jv>obj</jv> -> <jv>obj</jv>.getName()) 092 * .setter((<jv>obj</jv>, <jv>val</jv>) -> <jv>obj</jv>.setName(<jv>val</jv>)) 093 * .build(); 094 * </p> 095 * 096 * @param <T> The object type. 097 * @param <V> The value type. 098 * @return A new builder instance. 099 */ 100 public static <T, V> Builder<T, V> create() { 101 return new Builder<>(); 102 } 103 104 private final ThrowingFunction<T, V> producer; 105 private final ThrowingConsumer2<T, V> consumer; 106 107 /** 108 * Constructor. 109 * 110 * @param producer The producer function (getter). Can be <jk>null</jk>. 111 * @param consumer The consumer function (setter). Can be <jk>null</jk>. 112 */ 113 public Property(ThrowingFunction<T,V> producer, ThrowingConsumer2<T, V> consumer) { 114 this.producer = producer; 115 this.consumer = consumer; 116 } 117 118 /** 119 * Gets the value from the specified object using the producer (getter). 120 * 121 * @param object The object from which to get the value. Must not be <jk>null</jk>. 122 * @return The value. 123 * @throws ExecutableException 124 */ 125 public V get(T object) throws ExecutableException { 126 assertArgNotNull("object", object); 127 if (producer == null) 128 throw exex("No getter defined for this property"); 129 return safe(() -> producer.applyThrows(object)); 130 } 131 132 /** 133 * Sets the value on the specified object using the consumer (setter). 134 * 135 * @param object The object on which to set the value. Must not be <jk>null</jk>. 136 * @param value The value to set. Can be <jk>null</jk>. 137 * @throws ExecutableException 138 */ 139 public void set(T object, V value) throws ExecutableException { 140 assertArgNotNull("object", object); 141 if (consumer == null) 142 throw exex("No setter defined for this property"); 143 safe(() -> consumer.acceptThrows(object, value)); 144 } 145 146 /** 147 * Returns <jk>true</jk> if this property can be read. 148 * 149 * @return <jk>true</jk> if this property can be read. 150 */ 151 public boolean canRead() { 152 return producer != null; 153 } 154 155 /** 156 * Returns <jk>true</jk> if this property can be written. 157 * 158 * @return <jk>true</jk> if this property can be written. 159 */ 160 public boolean canWrite() { 161 return consumer != null; 162 } 163 164 /** 165 * Builder for constructing {@link Property} instances. 166 * 167 * @param <T> The object type. 168 * @param <V> The value type. 169 */ 170 public static class Builder<T, V> { 171 private ThrowingFunction<T, V> producer; 172 private ThrowingConsumer2<T, V> consumer; 173 174 /** 175 * Sets the producer (getter) using a function. 176 * 177 * @param producer The producer function. Can be <jk>null</jk>. 178 * @return This object. 179 */ 180 public Builder<T, V> getter(ThrowingFunction<T, V> producer) { 181 this.producer = producer; 182 return this; 183 } 184 185 /** 186 * Sets the consumer (setter) using a throwing consumer. 187 * 188 * @param consumer The consumer function. Can be <jk>null</jk>. 189 * @return This object. 190 */ 191 public Builder<T, V> setter(ThrowingConsumer2<T, V> consumer) { 192 this.consumer = consumer; 193 return this; 194 } 195 196 /** 197 * Sets both getter and setter from a {@link FieldInfo}. 198 * 199 * <p> 200 * This is a convenience method that creates both producer and consumer from a field. 201 * 202 * @param field The field info. Must not be <jk>null</jk>. 203 * @return This object. 204 */ 205 @SuppressWarnings("unchecked") 206 public Builder<T, V> field(FieldInfo field) { 207 assertArgNotNull("field", field); 208 field.accessible(); 209 boolean isStatic = field.isStatic(); 210 this.producer = obj -> (V)field.get(isStatic ? null : obj); 211 this.consumer = (obj, val) -> field.set(isStatic ? null : obj, val); 212 return this; 213 } 214 215 /** 216 * Sets the producer (getter) from a {@link MethodInfo}. 217 * 218 * <p> 219 * The method should take no parameters and return the property value. 220 * 221 * @param method The method info. Must not be <jk>null</jk>. 222 * @return This object. 223 */ 224 @SuppressWarnings("unchecked") 225 public Builder<T, V> getter(MethodInfo method) { 226 assertArgNotNull("method", method); 227 method.accessible(); 228 boolean isStatic = method.isStatic(); 229 this.producer = obj -> (V)method.invoke(isStatic ? null : obj); 230 return this; 231 } 232 233 /** 234 * Sets the consumer (setter) from a {@link MethodInfo}. 235 * 236 * <p> 237 * The method should take one parameter (the value to set) and return void. 238 * 239 * @param method The method info. Must not be <jk>null</jk>. 240 * @return This object. 241 */ 242 public Builder<T, V> setter(MethodInfo method) { 243 assertArgNotNull("method", method); 244 method.accessible(); 245 boolean isStatic = method.isStatic(); 246 this.consumer = (obj, val) -> method.invoke(isStatic ? null : obj, val); 247 return this; 248 } 249 250 /** 251 * Builds the property instance. 252 * 253 * @return A new property instance. 254 */ 255 public Property<T, V> build() { 256 return new Property<>(producer, consumer); 257 } 258 } 259} 260