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.bct; 018 019import java.util.*; 020import java.util.concurrent.*; 021import java.util.function.*; 022 023/** 024 * Collection of standard swapper implementations for the Bean-Centric Testing framework. 025 * 026 * <p>This class provides built-in object transformation strategies that handle common wrapper 027 * types and asynchronous objects. These swappers are automatically registered when using 028 * {@link BasicBeanConverter.Builder#defaultSettings()}.</p> 029 * 030 * <h5 class='section'>Purpose:</h5> 031 * <p>Swappers pre-process objects before stringification or listification, enabling the converter 032 * to handle wrapper types, lazy evaluation objects, and asynchronous results consistently. They 033 * transform objects into more primitive forms that can be easily converted to strings or lists.</p> 034 * 035 * <h5 class='section'>Built-in Swappers:</h5> 036 * <ul> 037 * <li><b>{@link #optionalSwapper()}</b> - Unwraps {@link Optional} values to their contained objects or <jk>null</jk></li> 038 * <li><b>{@link #supplierSwapper()}</b> - Evaluates {@link Supplier} functions to get their supplied values</li> 039 * <li><b>{@link #futureSwapper()}</b> - Extracts completed {@link Future} results or returns status messages</li> 040 * </ul> 041 * 042 * <h5 class='section'>Usage Example:</h5> 043 * <p class='bjava'> 044 * <jc>// Register swappers using builder</jc> 045 * <jk>var</jk> <jv>converter</jv> = BasicBeanConverter.<jsm>builder</jsm>() 046 * .defaultSettings() 047 * .addSwapper(Optional.<jk>class</jk>, Swappers.<jsm>optionalSwapper</jsm>()) 048 * .addSwapper(Supplier.<jk>class</jk>, Swappers.<jsm>supplierSwapper</jsm>()) 049 * .addSwapper(Future.<jk>class</jk>, Swappers.<jsm>futureSwapper</jsm>()) 050 * .build(); 051 * </p> 052 * 053 * <h5 class='section'>Custom Swapper Development:</h5> 054 * <p>When creating custom swappers, follow these patterns:</p> 055 * <ul> 056 * <li><b>Null Safety:</b> Handle <jk>null</jk> inputs gracefully</li> 057 * <li><b>Exception Handling:</b> Convert exceptions to meaningful error messages</li> 058 * <li><b>Status Indication:</b> Provide clear status for incomplete or error states</li> 059 * <li><b>Non-blocking:</b> Avoid blocking operations that might hang tests</li> 060 * <li><b>Recursion Prevention:</b> Ensure swappers don't create circular transformations that could lead to {@link StackOverflowError}. 061 * The framework does not check for recursion, so developers must avoid registering swappers that transform objects 062 * back to types that would trigger the same or other swappers in an endless cycle.</li> 063 * </ul> 064 * 065 * @see Swapper 066 * @see BasicBeanConverter.Builder#addSwapper(Class, Swapper) 067 * @see BasicBeanConverter.Builder#defaultSettings() 068 */ 069@SuppressWarnings({ 070 "rawtypes" // Raw types necessary for generic type handling 071}) 072public class Swappers { 073 074 /** 075 * Returns a swapper for {@link Future} objects that extracts completed results or status information. 076 * 077 * <p>This swapper handles {@link Future} objects in a non-blocking manner, providing meaningful 078 * information about the future's state without waiting for completion:</p> 079 * 080 * <h5 class='section'>Behavior:</h5> 081 * <ul> 082 * <li><b>Completed successfully:</b> Returns the actual result value</li> 083 * <li><b>Completed with exception:</b> Returns <js>"<error: {message}>"</js> format</li> 084 * <li><b>Cancelled:</b> Returns <js>"<cancelled>"</js></li> 085 * <li><b>Still pending:</b> Returns <js>"<pending>"</js></li> 086 * </ul> 087 * 088 * <h5 class='section'>Usage Examples:</h5> 089 * <p class='bjava'> 090 * <jc>// Test completed future</jc> 091 * <jk>var</jk> <jv>future</jv> = CompletableFuture.<jsm>completedFuture</jsm>(<js>"Hello"</js>); 092 * <jsm>assertBean</jsm>(<jv>future</jv>, <js>"<self>"</js>, <js>"Hello"</js>); 093 * 094 * <jc>// Test pending future</jc> 095 * <jk>var</jk> <jv>pending</jv> = <jk>new</jk> CompletableFuture<String>(); 096 * <jsm>assertBean</jsm>(<jv>pending</jv>, <js>"<self>"</js>, <js>"<pending>"</js>); 097 * 098 * <jc>// Test cancelled future</jc> 099 * <jk>var</jk> <jv>cancelled</jv> = <jk>new</jk> CompletableFuture<String>(); 100 * <jv>cancelled</jv>.cancel(<jk>true</jk>); 101 * <jsm>assertBean</jsm>(<jv>cancelled</jv>, <js>"<self>"</js>, <js>"<cancelled>"</js>); 102 * 103 * <jc>// Test failed future</jc> 104 * <jk>var</jk> <jv>failed</jv> = <jk>new</jk> CompletableFuture<String>(); 105 * <jv>failed</jv>.completeExceptionally(<jk>new</jk> RuntimeException(<js>"Test error"</js>)); 106 * <jsm>assertBean</jsm>(<jv>failed</jv>, <js>"<self>"</js>, <js>"<error: Test error>"</js>); 107 * </p> 108 * 109 * <h5 class='section'>Non-blocking Guarantee:</h5> 110 * <p>This swapper never calls {@link Future#get()} without first checking {@link Future#isDone()}, 111 * ensuring that test execution is never blocked by incomplete futures. This makes it safe to use 112 * in unit tests without risking hangs or timeouts.</p> 113 * 114 * <h5 class='section'>Error Handling:</h5> 115 * <p>When a future completes exceptionally, the swapper extracts the exception message and 116 * formats it as <js>"<error: {message}>"</js>. This provides useful debugging information 117 * while maintaining a consistent string format for assertions.</p> 118 * 119 * @return A {@link Swapper} for {@link Future} objects 120 * @see Future 121 * @see CompletableFuture 122 */ 123 public static Swapper<Future> futureSwapper() { 124 return (bc, future) -> { 125 if (future.isDone() && ! future.isCancelled()) { 126 try { 127 return future.get(); 128 } catch (@SuppressWarnings("unused") InterruptedException e) { 129 Thread.currentThread().interrupt(); // HTT - requires thread interruption during Future.get() 130 return "<interrupted>"; // HTT 131 } catch (Exception e) { 132 return "<error: " + e.getMessage() + ">"; 133 } 134 } 135 return future.isCancelled() ? "<cancelled>" : "<pending>"; 136 }; 137 } 138 139 /** 140 * Returns a swapper for {@link Optional} objects that unwraps them to their contained values. 141 * 142 * <p>This swapper extracts the value from Optional instances, returning the contained object 143 * if present, or <jk>null</jk> if the Optional is empty. This allows Optional-wrapped values 144 * to be processed naturally by the converter without special handling.</p> 145 * 146 * <h5 class='section'>Behavior:</h5> 147 * <ul> 148 * <li><b>Present value:</b> Returns the contained object for further processing</li> 149 * <li><b>Empty Optional:</b> Returns <jk>null</jk>, which will be rendered as the configured null value</li> 150 * </ul> 151 * 152 * <h5 class='section'>Usage Examples:</h5> 153 * <p class='bjava'> 154 * <jc>// Test Optional with value</jc> 155 * <jk>var</jk> <jv>optional</jv> = Optional.<jsm>of</jsm>(<js>"Hello"</js>); 156 * <jsm>assertBean</jsm>(<jv>optional</jv>, <js>"<self>"</js>, <js>"Hello"</js>); 157 * 158 * <jc>// Test empty Optional</jc> 159 * <jk>var</jk> <jv>empty</jv> = Optional.<jsm>empty</jsm>(); 160 * <jsm>assertBean</jsm>(<jv>empty</jv>, <js>"<self>"</js>, <js>"<null>"</js>); 161 * 162 * <jc>// Test nested Optional in object</jc> 163 * <jk>var</jk> <jv>user</jv> = <jk>new</jk> User().setName(Optional.<jsm>of</jsm>(<js>"John"</js>)); 164 * <jsm>assertBean</jsm>(<jv>user</jv>, <js>"name"</js>, <js>"John"</js>); 165 * </p> 166 * 167 * <h5 class='section'>Integration:</h5> 168 * <p>This swapper is particularly useful when working with modern Java APIs that return 169 * Optional values. It allows test assertions to focus on the actual data rather than 170 * Optional wrapper mechanics.</p> 171 * 172 * @return A {@link Swapper} for {@link Optional} objects 173 * @see Optional 174 */ 175 @SuppressWarnings({ 176 "unchecked" // Type erasure requires unchecked cast 177 }) 178 public static Swapper<Optional> optionalSwapper() { 179 return (bc, optional) -> optional.orElse(null); 180 } 181 182 /** 183 * Returns a swapper for {@link Supplier} objects that evaluates them to get their supplied values. 184 * 185 * <p>This swapper calls the {@link Supplier#get()} method to obtain the value that the supplier 186 * provides. This enables testing of lazy-evaluated or dynamically-computed values as if they 187 * were regular objects.</p> 188 * 189 * <h5 class='section'>Behavior:</h5> 190 * <ul> 191 * <li><b>Successful evaluation:</b> Returns the result of calling {@link Supplier#get()}</li> 192 * <li><b>Exception during evaluation:</b> Allows the exception to propagate (no special handling)</li> 193 * </ul> 194 * 195 * <h5 class='section'>Usage Examples:</h5> 196 * <p class='bjava'> 197 * <jc>// Test supplier with simple value</jc> 198 * <jk>var</jk> <jv>supplier</jv> = () -> <js>"Hello World"</js>; 199 * <jsm>assertBean</jsm>(<jv>supplier</jv>, <js>"<self>"</js>, <js>"Hello World"</js>); 200 * 201 * <jc>// Test supplier in object property</jc> 202 * <jk>var</jk> <jv>config</jv> = <jk>new</jk> Configuration().setDynamicValue(() -> calculateValue()); 203 * <jsm>assertBean</jsm>(<jv>config</jv>, <js>"dynamicValue"</js>, <js>"computed-result"</js>); 204 * 205 * <jc>// Test supplier returning null</jc> 206 * <jk>var</jk> <jv>nullSupplier</jv> = () -> <jk>null</jk>; 207 * <jsm>assertBean</jsm>(<jv>nullSupplier</jv>, <js>"<self>"</js>, <js>"<null>"</js>); 208 * </p> 209 * 210 * <h5 class='section'>Important Notes:</h5> 211 * <ul> 212 * <li><b>Side Effects:</b> The supplier will be evaluated during conversion, which may cause side effects</li> 213 * <li><b>Performance:</b> Expensive computations in suppliers will impact test performance</li> 214 * <li><b>Exception Handling:</b> Exceptions from suppliers are not caught by this swapper</li> 215 * </ul> 216 * 217 * @return A {@link Swapper} for {@link Supplier} objects 218 * @see Supplier 219 */ 220 public static Swapper<Supplier> supplierSwapper() { 221 return (bc, supplier) -> supplier.get(); 222 } 223 224 /** 225 * Constructor. 226 */ 227 private Swappers() {} 228}