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>) -> { 071 * <jc>// Lambda-based extractor for simple cases</jc> 072 * <jk>if</jk> (<jp>obj</jp> <jk>instanceof</jk> MyConfig <jv>config</jv> && <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}