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&lt;MyClass, String&gt; <jv>nameProperty</jv> = Property
045 *       .&lt;MyClass, String&gt;<jsm>create</jsm>()
046 *       .getter(<jv>obj</jv> -&gt; <jv>obj</jv>.getName())
047 *       .setter((<jv>obj</jv>, <jv>val</jv>) -&gt; <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&lt;MyClass, String&gt; <jv>fieldProperty</jv> = Property
053 *       .&lt;MyClass, String&gt;<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&lt;MyClass, String&gt; <jv>methodProperty</jv> = Property
061 *       .&lt;MyClass, String&gt;<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&lt;MyClass, String&gt; <jv>prop</jv> = Property
093    *       .&lt;MyClass, String&gt;<jsm>create</jsm>()
094    *       .getter(<jv>obj</jv> -&gt; <jv>obj</jv>.getName())
095    *       .setter((<jv>obj</jv>, <jv>val</jv>) -&gt; <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