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