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&lt;MyClass, String&gt; <jv>nameProperty</jv> = Property
046 *       .&lt;MyClass, String&gt;<jsm>create</jsm>()
047 *       .getter(<jv>obj</jv> -&gt; <jv>obj</jv>.getName())
048 *       .setter((<jv>obj</jv>, <jv>val</jv>) -&gt; <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&lt;MyClass, String&gt; <jv>fieldProperty</jv> = Property
054 *       .&lt;MyClass, String&gt;<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&lt;MyClass, String&gt; <jv>methodProperty</jv> = Property
062 *       .&lt;MyClass, String&gt;<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&lt;MyClass, String&gt; <jv>prop</jv> = Property
090    *       .&lt;MyClass, String&gt;<jsm>create</jsm>()
091    *       .getter(<jv>obj</jv> -&gt; <jv>obj</jv>.getName())
092    *       .setter((<jv>obj</jv>, <jv>val</jv>) -&gt; <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