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 static org.apache.juneau.commons.utils.AssertionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import org.apache.juneau.commons.function.*;
023import org.apache.juneau.commons.settings.*;
024
025/**
026 * Configuration utility for Bean-Centric Testing (BCT) framework.
027 *
028 * <p>
029 * This class provides thread-local configuration settings for BCT assertions, allowing test-specific
030 * customization of behavior such as map sorting, collection sorting, and bean converter selection.
031 *
032 * <p>
033 * Configuration is managed using thread-local storage, ensuring that parallel test execution doesn't
034 * interfere with each other's settings. Settings are typically configured via the {@link org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig}
035 * annotation, but can also be set programmatically.
036 *
037 * <h5 class='section'>Configuration Properties:</h5>
038 * <ul>
039 *    <li><b>{@link #BCT_SORT_MAPS}:</b> Enable sorting of maps in assertions</li>
040 *    <li><b>{@link #BCT_SORT_COLLECTIONS}:</b> Enable sorting of collections in assertions</li>
041 *    <li><b>Bean Converter:</b> Custom bean converter instance for property access and conversion</li>
042 * </ul>
043 *
044 * <h5 class='section'>Usage via Annotation:</h5>
045 * <p class='bjava'>
046 *    <ja>@BctConfig</ja>(sortMaps=TriState.<jsf>TRUE</jsf>, beanConverter=MyConverter.<jk>class</jk>)
047 *    <jk>class</jk> MyTest {
048 *       <ja>@Test</ja>
049 *       <ja>@BctConfig</ja>(sortCollections=TriState.<jsf>TRUE</jsf>)
050 *       <jk>void</jk> testSomething() {
051 *          <jc>// sortMaps=true (from class), sortCollections=true (from method),</jc>
052 *          <jc>// beanConverter=MyConverter (from class)</jc>
053 *       }
054 *    }
055 * </p>
056 *
057 * <h5 class='section'>Usage via Programmatic API:</h5>
058 * <p class='bjava'>
059 *    <ja>@BeforeEach</ja>
060 *    <jk>void</jk> setUp() {
061 *       BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>);
062 *       BctConfiguration.<jsm>set</jsm>(<jk>new</jk> MyCustomConverter());
063 *    }
064 *
065 *    <ja>@AfterEach</ja>
066 *    <jk>void</jk> tearDown() {
067 *       BctConfiguration.<jsm>clear</jsm>();
068 *    }
069 * </p>
070 *
071 * <h5 class='section'>Thread Safety:</h5>
072 * <p>
073 * All methods in this class are thread-safe. Configuration is stored in thread-local storage,
074 * ensuring that each test thread has its own isolated configuration that doesn't interfere
075 * with other concurrently running tests.
076 * </p>
077 *
078 * @see org.apache.juneau.junit.bct.annotations.BctConfig
079 * @see org.apache.juneau.junit.bct.BctAssertions
080 */
081public class BctConfiguration {
082
083   // Thread-local memoized supplier for default converter (defaults to BasicBeanConverter.DEFAULT)
084   private static final ThreadLocal<ResettableSupplier<BeanConverter>> CONVERTER_SUPPLIER = ThreadLocal.withInitial(() -> memr(() -> BasicBeanConverter.DEFAULT));
085
086   /**
087    * Configuration property name for enabling map sorting in BCT assertions.
088    *
089    * <p>
090    * When set to <jk>true</jk>, maps will be sorted by key before comparison in assertions.
091    * This ensures consistent ordering regardless of the map's internal ordering.
092    *
093    * <h5 class='section'>Example:</h5>
094    * <p class='bjava'>
095    *    BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>);
096    * </p>
097    */
098   public static final String BCT_SORT_MAPS = "Bct.sortMaps";
099
100   /**
101    * Configuration property name for enabling collection sorting in BCT assertions.
102    *
103    * <p>
104    * When set to <jk>true</jk>, collections will be sorted before comparison in assertions.
105    * This ensures consistent ordering regardless of the collection's internal ordering.
106    *
107    * <h5 class='section'>Example:</h5>
108    * <p class='bjava'>
109    *    BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_COLLECTIONS</jsf>, <jk>true</jk>);
110    * </p>
111    */
112   public static final String BCT_SORT_COLLECTIONS = "Bct.sortCollections";
113
114   // Thread-local override for method-level converter customization
115   private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE = new ThreadLocal<>();
116
117   private static final Settings SETTINGS = Settings.create().build();
118
119   /**
120    * Sets a thread-local configuration value.
121    *
122    * <p>
123    * The value is stored in thread-local storage and will only affect the current thread.
124    * This is the recommended way to set configuration for individual tests.
125    *
126    * <h5 class='section'>Example:</h5>
127    * <p class='bjava'>
128    *    BctConfiguration.<jsm>set</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>);
129    * </p>
130    *
131    * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}).
132    * @param value The value to set.
133    * @see #get(String)
134    * @see #clear()
135    */
136   public static void set(String name, Object value) {
137      SETTINGS.setLocal(name, s(value));
138   }
139
140   /**
141    * Sets a global configuration value.
142    *
143    * <p>
144    * Global values apply to all threads and persist until explicitly cleared.
145    * Use with caution in multi-threaded test environments.
146    *
147    * <h5 class='section'>Example:</h5>
148    * <p class='bjava'>
149    *    BctConfiguration.<jsm>setGlobal</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>true</jk>);
150    * </p>
151    *
152    * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}).
153    * @param value The value to set.
154    * @see #get(String)
155    * @see #clearGlobal()
156    */
157   public static void setGlobal(String name, Object value) {
158      SETTINGS.setGlobal(name, s(value));
159   }
160
161   /**
162    * Gets a configuration setting by name.
163    *
164    * <p>
165    * Returns the setting object which can be used to check if a value is set and retrieve it.
166    * Thread-local values take precedence over global values.
167    *
168    * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}).
169    * @return The setting object, or <jk>null</jk> if not set.
170    * @see #get(String, Object)
171    * @see #set(String, Object)
172    */
173   public static StringSetting get(String name) {
174      return SETTINGS.get(name);
175   }
176
177   /**
178    * Gets a configuration value by name with a default value.
179    *
180    * <p>
181    * Returns the configured value if set, otherwise returns the provided default value.
182    * Thread-local values take precedence over global values.
183    *
184    * <h5 class='section'>Example:</h5>
185    * <p class='bjava'>
186    *    <jk>boolean</jk> <jv>sortMaps</jv> = BctConfiguration.<jsm>get</jsm>(BctConfiguration.<jsf>BCT_SORT_MAPS</jsf>, <jk>false</jk>);
187    * </p>
188    *
189    * @param <T> The type of the value.
190    * @param name The configuration property name (e.g., {@link #BCT_SORT_MAPS}).
191    * @param def The default value to return if not set.
192    * @return The configured value, or the default if not set.
193    * @see #get(String)
194    * @see #set(String, Object)
195    */
196   public static <T> T get(String name, T def) {
197      return SETTINGS.get(name, def);
198   }
199
200   /**
201    * Clears all thread-local configuration settings.
202    *
203    * <p>
204    * This method removes all thread-local settings including:
205    * <ul>
206    *    <li>Configuration properties (e.g., {@link #BCT_SORT_MAPS}, {@link #BCT_SORT_COLLECTIONS})</li>
207    *    <li>Bean converter override</li>
208    * </ul>
209    *
210    * <p>
211    * This is typically called in test teardown methods (e.g., {@code @AfterEach}) to ensure
212    * a clean state for subsequent tests. The {@link org.apache.juneau.junit.bct.annotations.BctConfigExtension BctConfigExtension}
213    * automatically calls this method after each test.
214    *
215    * <h5 class='section'>Example:</h5>
216    * <p class='bjava'>
217    *    <ja>@AfterEach</ja>
218    *    <jk>void</jk> tearDown() {
219    *       BctConfiguration.<jsm>clear</jsm>();
220    *    }
221    * </p>
222    *
223    * @see #clearGlobal()
224    * @see #set(String, Object)
225    */
226   public static void clear() {
227      SETTINGS.clearLocal();
228      CONVERTER_OVERRIDE.remove();
229   }
230
231   /**
232    * Clears all global configuration settings.
233    *
234    * <p>
235    * This method removes all global settings. Use with caution as this affects all threads.
236    *
237    * @see #clear()
238    * @see #setGlobal(String, Object)
239    */
240   public static void clearGlobal() {
241      SETTINGS.clearGlobal();
242   }
243
244   /**
245    * Sets a custom bean converter for the current thread.
246    *
247    * <p>
248    * This method allows you to override the default converter ({@link BasicBeanConverter#DEFAULT}) for all
249    * assertions in the current test method. The converter will be used by all assertion methods in the current thread.
250    *
251    * <p>
252    * This is particularly useful when you need custom property access, stringification, or conversion logic
253    * for specific tests. The converter can be configured via the {@link org.apache.juneau.junit.bct.annotations.BctConfig @BctConfig}
254    * annotation or set programmatically.
255    *
256    * <h5 class='section'>Usage Example:</h5>
257    * <p class='bjava'>
258    *    <ja>@BeforeEach</ja>
259    *    <jk>void</jk> setUp() {
260    *       <jk>var</jk> <jv>customConverter</jv> = BasicBeanConverter.<jsm>builder</jsm>()
261    *          .defaultSettings()
262    *          .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> <jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
263    *          .build();
264    *       BctConfiguration.<jsm>set</jsm>(<jv>customConverter</jv>);
265    *    }
266    *
267    *    <ja>@Test</ja>
268    *    <jk>void</jk> testWithCustomConverter() {
269    *       <jc>// All assertions use the custom converter</jc>
270    *       <jsm>assertBean</jsm>(<jv>event</jv>, <js>"date"</js>, <js>"2023-12-01"</js>);
271    *    }
272    *
273    *    <ja>@AfterEach</ja>
274    *    <jk>void</jk> tearDown() {
275    *       BctConfiguration.<jsm>clear</jsm>(); <jc>// Also clears converter override</jc>
276    *    }
277    * </p>
278    *
279    * <h5 class='section'>Thread Safety:</h5>
280    * <p>
281    * This method is thread-safe and uses thread-local storage. Each test method running in parallel
282    * will have its own converter instance, preventing cross-thread interference.
283    * </p>
284    *
285    * @param converter The bean converter to use for the current thread. Must not be <jk>null</jk>.
286    * @throws IllegalArgumentException If converter is <jk>null</jk>.
287    * @see #clear()
288    * @see org.apache.juneau.junit.bct.annotations.BctConfig#beanConverter()
289    * @see BeanConverter
290    * @see BasicBeanConverter
291    */
292   public static void set(BeanConverter converter) {
293      assertArgNotNull("converter", converter);
294      CONVERTER_OVERRIDE.set(converter);
295   }
296
297
298   /**
299    * Gets the bean converter for the current thread.
300    *
301    * <p>Returns the thread-local converter override if set, otherwise returns the memoized default converter.
302    * This method is used internally by all assertion methods to get the current thread-local converter.</p>
303    *
304    * @return The bean converter to use for the current thread.
305    */
306   static BeanConverter getConverter() {
307      return opt(BctConfiguration.CONVERTER_OVERRIDE.get()).orElseGet(CONVERTER_SUPPLIER.get());
308   }
309}