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 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("rawtypes") 070public class Swappers { 071 072 /** 073 * Returns a swapper for {@link Future} objects that extracts completed results or status information. 074 * 075 * <p>This swapper handles {@link Future} objects in a non-blocking manner, providing meaningful 076 * information about the future's state without waiting for completion:</p> 077 * 078 * <h5 class='section'>Behavior:</h5> 079 * <ul> 080 * <li><b>Completed successfully:</b> Returns the actual result value</li> 081 * <li><b>Completed with exception:</b> Returns <js>"<error: {message}>"</js> format</li> 082 * <li><b>Cancelled:</b> Returns <js>"<cancelled>"</js></li> 083 * <li><b>Still pending:</b> Returns <js>"<pending>"</js></li> 084 * </ul> 085 * 086 * <h5 class='section'>Usage Examples:</h5> 087 * <p class='bjava'> 088 * <jc>// Test completed future</jc> 089 * <jk>var</jk> <jv>future</jv> = CompletableFuture.<jsm>completedFuture</jsm>(<js>"Hello"</js>); 090 * <jsm>assertBean</jsm>(<jv>future</jv>, <js>"<self>"</js>, <js>"Hello"</js>); 091 * 092 * <jc>// Test pending future</jc> 093 * <jk>var</jk> <jv>pending</jv> = <jk>new</jk> CompletableFuture<String>(); 094 * <jsm>assertBean</jsm>(<jv>pending</jv>, <js>"<self>"</js>, <js>"<pending>"</js>); 095 * 096 * <jc>// Test cancelled future</jc> 097 * <jk>var</jk> <jv>cancelled</jv> = <jk>new</jk> CompletableFuture<String>(); 098 * <jv>cancelled</jv>.cancel(<jk>true</jk>); 099 * <jsm>assertBean</jsm>(<jv>cancelled</jv>, <js>"<self>"</js>, <js>"<cancelled>"</js>); 100 * 101 * <jc>// Test failed future</jc> 102 * <jk>var</jk> <jv>failed</jv> = <jk>new</jk> CompletableFuture<String>(); 103 * <jv>failed</jv>.completeExceptionally(<jk>new</jk> RuntimeException(<js>"Test error"</js>)); 104 * <jsm>assertBean</jsm>(<jv>failed</jv>, <js>"<self>"</js>, <js>"<error: Test error>"</js>); 105 * </p> 106 * 107 * <h5 class='section'>Non-blocking Guarantee:</h5> 108 * <p>This swapper never calls {@link Future#get()} without first checking {@link Future#isDone()}, 109 * ensuring that test execution is never blocked by incomplete futures. This makes it safe to use 110 * in unit tests without risking hangs or timeouts.</p> 111 * 112 * <h5 class='section'>Error Handling:</h5> 113 * <p>When a future completes exceptionally, the swapper extracts the exception message and 114 * formats it as <js>"<error: {message}>"</js>. This provides useful debugging information 115 * while maintaining a consistent string format for assertions.</p> 116 * 117 * @return A {@link Swapper} for {@link Future} objects 118 * @see Future 119 * @see CompletableFuture 120 */ 121 public static Swapper<Future> futureSwapper() { 122 return (bc, future) -> { 123 if (future.isDone() && ! future.isCancelled()) { 124 try { 125 return future.get(); 126 } catch (Exception e) { // NOSONAR 127 return "<error: " + e.getMessage() + ">"; 128 } 129 } 130 return future.isCancelled() ? "<cancelled>" : "<pending>"; 131 }; 132 } 133 134 /** 135 * Returns a swapper for {@link Optional} objects that unwraps them to their contained values. 136 * 137 * <p>This swapper extracts the value from Optional instances, returning the contained object 138 * if present, or <jk>null</jk> if the Optional is empty. This allows Optional-wrapped values 139 * to be processed naturally by the converter without special handling.</p> 140 * 141 * <h5 class='section'>Behavior:</h5> 142 * <ul> 143 * <li><b>Present value:</b> Returns the contained object for further processing</li> 144 * <li><b>Empty Optional:</b> Returns <jk>null</jk>, which will be rendered as the configured null value</li> 145 * </ul> 146 * 147 * <h5 class='section'>Usage Examples:</h5> 148 * <p class='bjava'> 149 * <jc>// Test Optional with value</jc> 150 * <jk>var</jk> <jv>optional</jv> = Optional.<jsm>of</jsm>(<js>"Hello"</js>); 151 * <jsm>assertBean</jsm>(<jv>optional</jv>, <js>"<self>"</js>, <js>"Hello"</js>); 152 * 153 * <jc>// Test empty Optional</jc> 154 * <jk>var</jk> <jv>empty</jv> = Optional.<jsm>empty</jsm>(); 155 * <jsm>assertBean</jsm>(<jv>empty</jv>, <js>"<self>"</js>, <js>"<null>"</js>); 156 * 157 * <jc>// Test nested Optional in object</jc> 158 * <jk>var</jk> <jv>user</jv> = <jk>new</jk> User().setName(Optional.<jsm>of</jsm>(<js>"John"</js>)); 159 * <jsm>assertBean</jsm>(<jv>user</jv>, <js>"name"</js>, <js>"John"</js>); 160 * </p> 161 * 162 * <h5 class='section'>Integration:</h5> 163 * <p>This swapper is particularly useful when working with modern Java APIs that return 164 * Optional values. It allows test assertions to focus on the actual data rather than 165 * Optional wrapper mechanics.</p> 166 * 167 * @return A {@link Swapper} for {@link Optional} objects 168 * @see Optional 169 */ 170 @SuppressWarnings("unchecked") 171 public static Swapper<Optional> optionalSwapper() { 172 return (bc, optional) -> optional.orElse(null); 173 } 174 175 /** 176 * Returns a swapper for {@link Supplier} objects that evaluates them to get their supplied values. 177 * 178 * <p>This swapper calls the {@link Supplier#get()} method to obtain the value that the supplier 179 * provides. This enables testing of lazy-evaluated or dynamically-computed values as if they 180 * were regular objects.</p> 181 * 182 * <h5 class='section'>Behavior:</h5> 183 * <ul> 184 * <li><b>Successful evaluation:</b> Returns the result of calling {@link Supplier#get()}</li> 185 * <li><b>Exception during evaluation:</b> Allows the exception to propagate (no special handling)</li> 186 * </ul> 187 * 188 * <h5 class='section'>Usage Examples:</h5> 189 * <p class='bjava'> 190 * <jc>// Test supplier with simple value</jc> 191 * <jk>var</jk> <jv>supplier</jv> = () -> <js>"Hello World"</js>; 192 * <jsm>assertBean</jsm>(<jv>supplier</jv>, <js>"<self>"</js>, <js>"Hello World"</js>); 193 * 194 * <jc>// Test supplier in object property</jc> 195 * <jk>var</jk> <jv>config</jv> = <jk>new</jk> Configuration().setDynamicValue(() -> calculateValue()); 196 * <jsm>assertBean</jsm>(<jv>config</jv>, <js>"dynamicValue"</js>, <js>"computed-result"</js>); 197 * 198 * <jc>// Test supplier returning null</jc> 199 * <jk>var</jk> <jv>nullSupplier</jv> = () -> <jk>null</jk>; 200 * <jsm>assertBean</jsm>(<jv>nullSupplier</jv>, <js>"<self>"</js>, <js>"<null>"</js>); 201 * </p> 202 * 203 * <h5 class='section'>Important Notes:</h5> 204 * <ul> 205 * <li><b>Side Effects:</b> The supplier will be evaluated during conversion, which may cause side effects</li> 206 * <li><b>Performance:</b> Expensive computations in suppliers will impact test performance</li> 207 * <li><b>Exception Handling:</b> Exceptions from suppliers are not caught by this swapper</li> 208 * </ul> 209 * 210 * @return A {@link Swapper} for {@link Supplier} objects 211 * @see Supplier 212 */ 213 public static Swapper<Supplier> supplierSwapper() { 214 return (bc, supplier) -> supplier.get(); 215 } 216 217 /** 218 * Constructor. 219 */ 220 private Swappers() {} 221}