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>"&lt;error: {message}&gt;"</js> format</li>
082    *    <li><b>Cancelled:</b> Returns <js>"&lt;cancelled&gt;"</js></li>
083    *    <li><b>Still pending:</b> Returns <js>"&lt;pending&gt;"</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>"&lt;self&gt;"</js>, <js>"Hello"</js>);
091    *
092    *    <jc>// Test pending future</jc>
093    *    <jk>var</jk> <jv>pending</jv> = <jk>new</jk> CompletableFuture&lt;String&gt;();
094    *    <jsm>assertBean</jsm>(<jv>pending</jv>, <js>"&lt;self&gt;"</js>, <js>"&lt;pending&gt;"</js>);
095    *
096    *    <jc>// Test cancelled future</jc>
097    *    <jk>var</jk> <jv>cancelled</jv> = <jk>new</jk> CompletableFuture&lt;String&gt;();
098    *    <jv>cancelled</jv>.cancel(<jk>true</jk>);
099    *    <jsm>assertBean</jsm>(<jv>cancelled</jv>, <js>"&lt;self&gt;"</js>, <js>"&lt;cancelled&gt;"</js>);
100    *
101    *    <jc>// Test failed future</jc>
102    *    <jk>var</jk> <jv>failed</jv> = <jk>new</jk> CompletableFuture&lt;String&gt;();
103    *    <jv>failed</jv>.completeExceptionally(<jk>new</jk> RuntimeException(<js>"Test error"</js>));
104    *    <jsm>assertBean</jsm>(<jv>failed</jv>, <js>"&lt;self&gt;"</js>, <js>"&lt;error: Test error&gt;"</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>"&lt;error: {message}&gt;"</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>"&lt;self&gt;"</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>"&lt;self&gt;"</js>, <js>"&lt;null&gt;"</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> = () -&gt; <js>"Hello World"</js>;
192    *    <jsm>assertBean</jsm>(<jv>supplier</jv>, <js>"&lt;self&gt;"</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(() -&gt; 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> = () -&gt; <jk>null</jk>;
200    *    <jsm>assertBean</jsm>(<jv>nullSupplier</jv>, <js>"&lt;self&gt;"</js>, <js>"&lt;null&gt;"</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}