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.junit.bct;
018
019/**
020 * Interface for custom property extraction strategies in the Bean-Centric Testing framework.
021 *
022 * <p>Property extractors define how the converter accesses object properties during nested
023 * field navigation (e.g., {@code "user.address.city"}). The framework uses a chain-of-responsibility
024 * pattern, trying each registered extractor until one can handle the property access.</p>
025 *
026 * <h5 class='section'>Extraction Strategy:</h5>
027 * <p>The two-phase approach ensures efficient and flexible property access:</p>
028 * <ol>
029 *    <li><b>{@link #canExtract(BeanConverter, Object, String)}:</b> Quick check if this extractor can handle the property</li>
030 *    <li><b>{@link #extract(BeanConverter, Object, String)}:</b> Perform the actual property extraction</li>
031 * </ol>
032 *
033 * <h5 class='section'>Common Use Cases:</h5>
034 * <ul>
035 *    <li><b>JavaBean properties:</b> Standard getter methods and public fields</li>
036 *    <li><b>Map-style access:</b> Key-based property retrieval from Map objects</li>
037 *    <li><b>Collection indices:</b> Numeric access for arrays and lists</li>
038 *    <li><b>Custom data structures:</b> Domain-specific property access patterns</li>
039 *    <li><b>Dynamic properties:</b> Computed or cached property values</li>
040 * </ul>
041 *
042 * <h5 class='section'>Implementation Example:</h5>
043 * <p class='bjava'>
044 *    <jc>// Custom extractor for database entities</jc>
045 *    <jk>public class</jk> DatabaseEntityExtractor <jk>implements</jk> PropertyExtractor {
046 *
047 *       <ja>@Override</ja>
048 *       <jk>public boolean</jk> canExtract(BeanConverter <jv>converter</jv>, Object <jv>obj</jv>, String <jv>property</jv>) {
049 *          <jk>return</jk> <jv>obj</jv> <jk>instanceof</jk> DatabaseEntity;
050 *       }
051 *
052 *       <ja>@Override</ja>
053 *       <jk>public</jk> Object extract(BeanConverter <jv>converter</jv>, Object <jv>obj</jv>, String <jv>property</jv>) {
054 *          DatabaseEntity <jv>entity</jv> = (DatabaseEntity) <jv>obj</jv>;
055 *          <jk>switch</jk> (<jv>property</jv>) {
056 *             <jk>case</jk> <js>"id"</js>: <jk>return</jk> <jv>entity</jv>.getPrimaryKey();
057 *             <jk>case</jk> <js>"lastModified"</js>: <jk>return</jk> <jv>entity</jv>.getTimestamp();
058 *             <jk>case</jk> <js>"metadata"</js>: <jk>return</jk> <jv>entity</jv>.getMetadata().asMap();
059 *             <jk>default</jk>: <jk>return</jk> <jv>entity</jv>.getAttribute(<jv>property</jv>);
060 *          }
061 *       }
062 *    }
063 * </p>
064 *
065 * <h5 class='section'>Registration and Usage:</h5>
066 * <p class='bjava'>
067 *    <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
068 *       .defaultSettings() <jc>// Adds standard extractors</jc>
069 *       .addPropertyExtractor(<jk>new</jk> DatabaseEntityExtractor())
070 *       .addPropertyExtractor((<jp>conv</jp>, <jp>obj</jp>, <jp>prop</jp>) -&gt; {
071 *          <jc>// Lambda-based extractor for simple cases</jc>
072 *          <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> MyConfig <jv>config</jv> &amp;&amp; <js>"timeout"</js>.equals(<jp>prop</jp>)) {
073 *             <jk>return</jk> <jv>config</jv>.getTimeoutMillis();
074 *          }
075 *          <jk>return</jk> <jk>null</jk>; <jc>// Let next extractor try</jc>
076 *       })
077 *       .build();
078 * </p>
079 *
080 * <h5 class='section'>Best Practices:</h5>
081 * <ul>
082 *    <li><b>Fast canExtract() checks:</b> Use efficient type checking and avoid expensive operations</li>
083 *    <li><b>Handle edge cases:</b> Gracefully handle null objects and invalid property names</li>
084 *    <li><b>Consider caching:</b> Cache reflection results for better performance</li>
085 * </ul>
086 *
087 * @see PropertyExtractors
088 * @see BasicBeanConverter.Builder#addPropertyExtractor(PropertyExtractor)
089 * @see BeanConverter#getProperty(Object, String)
090 */
091public interface PropertyExtractor {
092
093   /**
094    * Determines if this extractor can handle property access for the given object and property name.
095    *
096    * <p>This method should perform a quick check to determine compatibility without doing
097    * expensive operations. It's called frequently during property navigation.</p>
098    *
099    * @param converter The bean converter instance (for recursive operations if needed)
100    * @param o The object to extract the property from
101    * @param key The property name to extract
102    * @return {@code true} if this extractor can handle the property access, {@code false} otherwise
103    */
104   boolean canExtract(BeanConverter converter, Object o, String key);
105
106   /**
107    * Extracts the specified property value from the given object.
108    *
109    * <p>This method is only called after {@link #canExtract(BeanConverter, Object, String)}
110    * returns {@code true}. It should perform the actual property extraction and return
111    * the property value.</p>
112    *
113    * @param converter The bean converter instance (for recursive operations if needed)
114    * @param o The object to extract the property from
115    * @param key The property name to extract
116    * @return The property value
117    * @throws PropertyNotFoundException if the property cannot be found on the object
118    */
119   Object extract(BeanConverter converter, Object o, String key);
120}