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.commons.settings; 018 019import static org.apache.juneau.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.ThrowableUtils.*; 022import static org.apache.juneau.commons.utils.Utils.*; 023 024import java.nio.charset.*; 025import java.util.*; 026import java.util.concurrent.*; 027import java.util.function.*; 028 029import org.apache.juneau.commons.function.*; 030import org.apache.juneau.commons.reflect.*; 031 032/** 033 * Encapsulates Java system properties with support for global and per-thread overrides for unit testing. 034 * 035 * <p> 036 * This class provides a thread-safe way to access system properties that can be overridden at both the global level 037 * and per-thread level, making it useful for unit tests that need to temporarily change system property 038 * values without affecting other tests or threads. 039 * 040 * <p> 041 * Settings instances are created using the {@link Builder} pattern. Use {@link #create()} to create a new builder, 042 * or {@link #get()} to get the singleton instance (which is created using default builder settings). 043 * 044 * <h5 class='section'>Lookup Order:</h5> 045 * <p> 046 * When retrieving a property value, the lookup order is: 047 * <ol> 048 * <li>Per-thread store (if set via {@link #setLocal(String, String)}) 049 * <li>Global store (if set via {@link #setGlobal(String, String)}) 050 * <li>Sources in reverse order (last source added via {@link Builder#addSource(SettingSource)} is checked first) 051 * <li>System property source (default, always second-to-last) 052 * <li>System environment variable source (default, always last) 053 * </ol> 054 * 055 * <h5 class='section'>Sources vs Stores:</h5> 056 * <ul class='spaced-list'> 057 * <li><b>Sources</b> ({@link SettingSource}) - Provide read-only access to property values. Examples: {@link FunctionalSource} 058 * <li><b>Stores</b> ({@link SettingStore}) - Provide read/write access to property values. Examples: {@link MapStore}, {@link FunctionalStore} 059 * <li>Stores can be used as sources (they extend {@link SettingSource}), so you can add stores via {@link Builder#addSource(SettingSource)} 060 * </ul> 061 * 062 * <h5 class='section'>Features:</h5> 063 * <ul class='spaced-list'> 064 * <li>System property access - read Java system properties with type conversion via {@link StringSetting} 065 * <li>Global overrides - override system properties globally for all threads (stored in a {@link SettingStore}) 066 * <li>Per-thread overrides - override system properties for specific threads (stored in a per-thread {@link SettingStore}) 067 * <li>Custom sources - add arbitrary property sources (e.g., Spring properties, environment variables, config files) via the {@link Builder} 068 * <li>Disable override support - system property to prevent new global overrides from being set 069 * <li>Type-safe accessors - type conversion methods on {@link StringSetting} for common types: Integer, Long, Boolean, Double, Float, File, Path, URI, Charset 070 * <li>Resettable suppliers - settings are returned as {@link StringSetting} instances that can be reset to force recomputation 071 * </ul> 072 * 073 * <h5 class='section'>Usage Examples:</h5> 074 * <p class='bjava'> 075 * <jc>// Get a system property as a StringSetting (using singleton instance)</jc> 076 * StringSetting <jv>setting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.property"</js>); 077 * String <jv>value</jv> = <jv>setting</jv>.get(); <jc>// Get the string value</jc> 078 * 079 * <jc>// Get with type conversion using StringSetting methods</jc> 080 * Setting<Integer> <jv>intSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.int.property"</js>).asInteger(); 081 * Setting<Long> <jv>longSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.long.property"</js>).asLong(); 082 * Setting<Boolean> <jv>boolSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.bool.property"</js>).asBoolean(); 083 * Setting<Double> <jv>doubleSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.double.property"</js>).asDouble(); 084 * Setting<Float> <jv>floatSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.float.property"</js>).asFloat(); 085 * Setting<File> <jv>fileSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.file.property"</js>).asFile(); 086 * Setting<Path> <jv>pathSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.path.property"</js>).asPath(); 087 * Setting<URI> <jv>uriSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.uri.property"</js>).asURI(); 088 * Setting<Charset> <jv>charsetSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.charset.property"</js>).asCharset(); 089 * 090 * <jc>// Use custom type conversion</jc> 091 * Setting<MyCustomType> <jv>customSetting</jv> = Settings.<jsf>get</jsf>().get(<js>"my.custom.property"</js>).asType(MyCustomType.<jk>class</jk>); 092 * 093 * <jc>// Reset a setting to force recomputation</jc> 094 * <jv>setting</jv>.reset(); 095 * 096 * <jc>// Override for current thread (useful in unit tests)</jc> 097 * Settings.<jsf>get</jsf>().setLocal(<js>"my.property"</js>, <js>"test-value"</js>); 098 * <jc>// ... test code that uses the override ...</jc> 099 * Settings.<jsf>get</jsf>().unsetLocal(<js>"my.property"</js>); <jc>// Remove specific override</jc> 100 * <jc>// OR</jc> 101 * Settings.<jsf>get</jsf>().clearLocal(); <jc>// Clear all thread-local overrides</jc> 102 * 103 * <jc>// Set global override (applies to all threads)</jc> 104 * Settings.<jsf>get</jsf>().setGlobal(<js>"my.property"</js>, <js>"global-value"</js>); 105 * <jc>// ... code that uses the global override ...</jc> 106 * Settings.<jsf>get</jsf>().unsetGlobal(<js>"my.property"</js>); <jc>// Remove specific override</jc> 107 * <jc>// OR</jc> 108 * Settings.<jsf>get</jsf>().clearGlobal(); <jc>// Clear all global overrides</jc> 109 * 110 * <jc>// Create a custom Settings instance with custom sources (e.g., Spring properties)</jc> 111 * MapStore <jv>springSource</jv> = <jk>new</jk> MapStore(); 112 * <jv>springSource</jv>.set(<js>"spring.datasource.url"</js>, <js>"jdbc:postgresql://localhost/db"</js>); 113 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 114 * .addSource(<jv>springSource</jv>) 115 * .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty)) 116 * .build(); 117 * </p> 118 * 119 * <h5 class='section'>System Properties:</h5> 120 * <ul class='spaced-list'> 121 * <li><c>juneau.settings.disableGlobal</c> (system property) or <c>JUNEAU_SETTINGS_DISABLEGLOBAL</c> (system env) - If set to <c>true</c>, prevents new global overrides 122 * from being set via {@link #setGlobal(String, String)}. Existing global overrides will still be 123 * returned by {@link #get(String)} until explicitly removed. 124 * <p class='bnote'> 125 * Note: This property is read once at class initialization time when creating the singleton instance 126 * and cannot be changed at runtime. Changing the system property after the class has been loaded will 127 * have no effect on the singleton instance. However, you can create custom Settings instances using 128 * {@link #create()} that ignore this property. 129 * </p> 130 * </ul> 131 */ 132public class Settings { 133 134 /** 135 * System property source that delegates to {@link System#getProperty(String)}. 136 */ 137 public static final SettingSource SYSTEM_PROPERTY_SOURCE = FunctionalSource.of(System::getProperty); 138 139 private static final Set<String> FROM_STRING_METHOD_NAMES = new LinkedHashSet<>(Arrays.asList("fromString", "parse", "forName", "valueOf")); 140 141 /** 142 * System environment variable source that delegates to {@link System#getenv(String)}. 143 */ 144 public static final SettingSource SYSTEM_ENV_SOURCE = FunctionalSource.of(System::getenv); 145 146 private static final String DISABLE_GLOBAL_PROP = "juneau.settings.disableGlobal"; 147 private static final String MSG_globalDisabled = "Global settings not enabled"; 148 private static final String MSG_localDisabled = "Local settings not enabled"; 149 150 /** 151 * Returns properties for this Settings object itself. 152 * Note that these are initialized at startup and not changeable through System.setProperty(). 153 */ 154 private static final Optional<String> initProperty(String property) { 155 var v = SYSTEM_PROPERTY_SOURCE.get(property); 156 if (v != null) 157 return v; // Not testable 158 v = SYSTEM_ENV_SOURCE.get(property.replace('.', '_').toUpperCase()); 159 if (v != null) 160 return v; // Not testable 161 return opte(); 162 } 163 164 /** 165 * Creates a new builder for constructing a Settings instance. 166 * 167 * <p> 168 * This method provides a convenient way to create custom Settings instances with specific 169 * configuration. The builder allows you to configure stores, sources, and other settings 170 * before building the final Settings instance. 171 * 172 * <h5 class='section'>Example:</h5> 173 * <p class='bjava'> 174 * <jc>// Create a custom Settings instance</jc> 175 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 176 * .globalStore(() -> <jk>new</jk> MapStore()) 177 * .localStore(() -> <jk>new</jk> MapStore()) 178 * .addSource(FunctionalSource.<jsf>of</jsf>(System::getProperty)) 179 * .build(); 180 * </p> 181 * 182 * @return A new Builder instance. 183 */ 184 public static Builder create() { 185 return new Builder(); 186 } 187 188 /** 189 * Builder for creating Settings instances. 190 */ 191 public static class Builder { 192 private Supplier<SettingStore> globalStoreSupplier = () -> new MapStore(); 193 private Supplier<SettingStore> localStoreSupplier = () -> new MapStore(); 194 private final List<SettingSource> sources = new ArrayList<>(); 195 private final Map<Class<?>,Function<String,?>> customTypeFunctions = new IdentityHashMap<>(); 196 197 /** 198 * Sets the supplier for the global store. 199 * 200 * @param supplier The supplier for the global store. Must not be <c>null</c>. Can supply null to disable global store. 201 * @return This builder for method chaining. 202 */ 203 public Builder globalStore(OptionalSupplier<SettingStore> supplier) { 204 this.globalStoreSupplier = assertArgNotNull("supplier", supplier); 205 return this; 206 } 207 208 /** 209 * Sets the supplier for the local (per-thread) store. 210 * 211 * @param supplier The supplier for the local store. Must not be <c>null</c>. 212 * @return This builder for method chaining. 213 */ 214 public Builder localStore(OptionalSupplier<SettingStore> supplier) { 215 this.localStoreSupplier = assertArgNotNull("supplier", supplier); 216 return this; 217 } 218 219 /** 220 * Sets the sources list, replacing any existing sources. 221 * 222 * @param sources The sources to set. Must not be <c>null</c> or contain <c>null</c> elements. 223 * @return This builder for method chaining. 224 */ 225 @SafeVarargs 226 public final Builder setSources(SettingSource...sources) { 227 assertArgNoNulls("sources", sources); 228 this.sources.clear(); 229 for (var source : sources) { 230 this.sources.add(source); 231 } 232 return this; 233 } 234 235 /** 236 * Adds a source to the sources list. 237 * 238 * @param source The source to add. Must not be <c>null</c>. 239 * @return This builder for method chaining. 240 */ 241 public Builder addSource(SettingSource source) { 242 assertArgNotNull("source", source); 243 this.sources.add(source); 244 return this; 245 } 246 247 /** 248 * Adds a functional source to the sources list. 249 * 250 * @param source The functional source to add. Must not be <c>null</c>. 251 * @return This builder for method chaining. 252 */ 253 public Builder addSource(FunctionalSource source) { 254 return addSource((SettingSource)source); 255 } 256 257 /** 258 * Registers a custom type conversion function for the specified type. 259 * 260 * <p> 261 * This allows you to add support for converting string values to custom types when using 262 * {@link Settings#get(String, Object)}. The function will be used to convert string values 263 * to the specified type. 264 * 265 * <h5 class='section'>Example:</h5> 266 * <p class='bjava'> 267 * <jc>// Register a custom converter for a custom type</jc> 268 * Settings <jv>custom</jv> = Settings.<jsf>create</jsf>() 269 * .addTypeFunction(Integer.<jk>class</jk>, Integer::valueOf) 270 * .addTypeFunction(MyCustomType.<jk>class</jk>, MyCustomType::fromString) 271 * .build(); 272 * 273 * <jc>// Now you can use get() with these types</jc> 274 * Integer <jv>intValue</jv> = <jv>custom</jv>.get(<js>"my.int.property"</js>, 0); 275 * MyCustomType <jv>customValue</jv> = <jv>custom</jv>.get(<js>"my.custom.property"</js>, MyCustomType.DEFAULT); 276 * </p> 277 * 278 * @param <T> The type to register a converter for. 279 * @param type The class type to register a converter for. Must not be <c>null</c>. 280 * @param function The function that converts a string to the specified type. Must not be <c>null</c>. 281 * @return This builder for method chaining. 282 */ 283 public <T> Builder addTypeFunction(Class<T> type, Function<String,T> function) { 284 assertArgNotNull("type", type); 285 assertArgNotNull("function", function); 286 customTypeFunctions.put(type, function); 287 return this; 288 } 289 290 /** 291 * Builds a Settings instance from this builder. 292 * 293 * @return A new Settings instance. 294 */ 295 public Settings build() { 296 return new Settings(this); 297 } 298 } 299 300 private static final Settings INSTANCE = new Builder() 301 .globalStore(initProperty(DISABLE_GLOBAL_PROP).map(Boolean::valueOf).orElse(false) ? () -> null : () -> new MapStore()) 302 .setSources(SYSTEM_ENV_SOURCE, SYSTEM_PROPERTY_SOURCE) 303 .build(); 304 305 /** 306 * Returns the singleton instance of Settings. 307 * 308 * @return The singleton Settings instance. 309 */ 310 public static Settings get() { 311 return INSTANCE; 312 } 313 314 private final ResettableSupplier<SettingStore> globalStore; 315 private final ThreadLocal<SettingStore> localStore; 316 private final List<SettingSource> sources; 317 private final Map<Class<?>,Function<String,?>> toTypeFunctions; 318 319 /** 320 * Constructor. 321 */ 322 private Settings(Builder builder) { 323 this.globalStore = memr(builder.globalStoreSupplier); 324 this.localStore = ThreadLocal.withInitial(builder.localStoreSupplier); 325 this.sources = new CopyOnWriteArrayList<>(builder.sources); 326 this.toTypeFunctions = new ConcurrentHashMap<>(builder.customTypeFunctions); 327 } 328 329 /** 330 * Returns a {@link StringSetting} for the specified system property. 331 * 332 * <p> 333 * The returned {@link StringSetting} is a resettable supplier that caches the lookup result. 334 * Use the {@link StringSetting#asInteger()}, {@link StringSetting#asBoolean()}, etc. methods 335 * to convert to different types. 336 * 337 * <p> 338 * The lookup order is: 339 * <ol> 340 * <li>Per-thread override (if set via {@link #setLocal(String, String)}) 341 * <li>Global override (if set via {@link #setGlobal(String, String)}) 342 * <li>Sources in reverse order (last source added via {@link Builder#addSource(SettingSource)} is checked first) 343 * <li>System property source (default, always second-to-last) 344 * <li>System environment variable source (default, always last) 345 * </ol> 346 * 347 * @param name The property name. Must not be <jk>null</jk>. 348 * @return A {@link StringSetting} that provides the property value, or <jk>null</jk> if not found. 349 */ 350 public StringSetting get(String name) { 351 assertArgNotNull("name", name); 352 return new StringSetting(this, () -> { 353 // 1. Check thread-local override 354 var v = localStore.get().get(name); 355 if (v != null) 356 return v.orElse(null); // v is Optional.empty() if key exists with null value, or Optional.of(value) if present 357 358 // 2. Check global override 359 v = globalStore.get().get(name); 360 if (v != null) 361 return v.orElse(null); // v is Optional.empty() if key exists with null value, or Optional.of(value) if present 362 363 // 3. Check sources in reverse order (last added first) 364 for (int i = sources.size() - 1; i >= 0; i--) { 365 var source = sources.get(i); 366 var result = source.get(name); 367 if (result != null) 368 return result.orElse(null); 369 } 370 371 return null; 372 }); 373 } 374 375 /** 376 * Looks up a system property, returning a default value if not found. 377 * 378 * <p> 379 * This method searches for a value using the same lookup order as {@link #get(String)}. 380 * If a value is found, it is converted to the type of the default value using {@link #toType(String, Class)}. 381 * Supported types include any type that has a static method with signature <c>public static <T> T anyName(String arg)</c> 382 * or a public constructor with signature <c>public T(String arg)</c>, such as {@link Boolean}, {@link Integer}, {@link java.nio.charset.Charset}, {@link java.io.File}, etc. 383 * 384 * <h5 class='section'>Example:</h5> 385 * <p class='bjava'> 386 * <jc>// System property: -Dmy.property=true</jc> 387 * Boolean <jv>flag</jv> = get(<js>"my.property"</js>, <jk>false</jk>); <jc>// true</jc> 388 * 389 * <jc>// Environment variable: MY_PROPERTY=UTF-8</jc> 390 * Charset <jv>charset</jv> = get(<js>"my.property"</js>, Charset.defaultCharset()); <jc>// UTF-8</jc> 391 * 392 * <jc>// Not found, returns default</jc> 393 * String <jv>value</jv> = get(<js>"nonexistent"</js>, <js>"default"</js>); <jc>// "default"</jc> 394 * </p> 395 * 396 * @param <T> The type to convert the value to. 397 * @param name The property name. 398 * @param def The default value to return if not found. 399 * @return The found value (converted to type T), or the default value if not found. 400 * @see #get(String) 401 * @see #toType(String, Class) 402 */ 403 @SuppressWarnings("unchecked") 404 public <T> T get(String name, T def) { 405 assertArgNotNull("def", def); 406 return get(name).asType((Class<T>)def.getClass()).orElse(def); 407 } 408 409 /** 410 * Sets a global override for the specified property. 411 * 412 * <p> 413 * This override will apply to all threads and takes precedence over system properties. 414 * However, per-thread overrides (set via {@link #setLocal(String, String)}) will still take precedence 415 * over global overrides. 416 * 417 * <p> 418 * If the <c>juneau.settings.disableGlobal</c> system property is set to <c>true</c>, 419 * this method will throw an exception. This allows system administrators 420 * to prevent applications from overriding system properties globally. 421 * 422 * <p> 423 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the system 424 * property to return empty. Use {@link #unsetGlobal(String)} to completely remove the override. 425 * 426 * @param name The property name. 427 * @param value The override value, or <c>null</c> to set an empty override. 428 * @return This object for method chaining. 429 * @see #unsetGlobal(String) 430 * @see #clearGlobal() 431 */ 432 public Settings setGlobal(String name, String value) { 433 assertArgNotNull("name", name); 434 globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).set(name, value); 435 return this; 436 } 437 438 /** 439 * Removes a global override for the specified property. 440 * 441 * <p> 442 * After calling this method, the property will fall back to the system property value 443 * (or per-thread override if one exists). 444 * 445 * @param name The property name. 446 * @see #setGlobal(String, String) 447 * @see #clearGlobal() 448 */ 449 public void unsetGlobal(String name) { 450 assertArgNotNull("name", name); 451 globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).unset(name); 452 } 453 454 /** 455 * Sets a per-thread override for the specified property. 456 * 457 * <p> 458 * This override will only apply to the current thread and takes precedence over global overrides 459 * and system properties. This is particularly useful in unit tests where you need to temporarily 460 * change a system property value without affecting other threads or tests. 461 * 462 * <p> 463 * Setting a value to <c>null</c> will store an empty optional, effectively overriding the property 464 * to return empty for this thread. Use {@link #unsetLocal(String)} to completely remove the override. 465 * 466 * @param name The property name. 467 * @param value The override value, or <c>null</c> to set an empty override. 468 * @return This object for method chaining. 469 * @see #unsetLocal(String) 470 * @see #clearLocal() 471 */ 472 public Settings setLocal(String name, String value) { 473 assertArgNotNull("name", name); 474 assertState(nn(localStore.get()), MSG_localDisabled); 475 localStore.get().set(name, value); 476 return this; 477 } 478 479 /** 480 * Removes a per-thread override for the specified property. 481 * 482 * <p> 483 * After calling this method, the property will fall back to the global override (if set) 484 * or the system property value for the current thread. 485 * 486 * @param name The property name. 487 * @see #setLocal(String, String) 488 * @see #clearLocal() 489 */ 490 public void unsetLocal(String name) { 491 assertArgNotNull("name", name); 492 assertState(nn(localStore.get()), MSG_localDisabled); 493 localStore.get().unset(name); 494 } 495 496 /** 497 * Clears all per-thread overrides for the current thread. 498 * 499 * <p> 500 * After calling this method, all properties will fall back to global overrides (if set) 501 * or system property values for the current thread. 502 * 503 * <p> 504 * This is typically called in a <c>@AfterEach</c> or <c>@After</c> test method to clean up 505 * thread-local overrides after a test completes. 506 * 507 * @return This object for method chaining. 508 * @see #setLocal(String, String) 509 * @see #unsetLocal(String) 510 */ 511 public Settings clearLocal() { 512 assertState(nn(localStore.get()), MSG_localDisabled); 513 localStore.get().clear(); 514 return this; 515 } 516 517 /** 518 * Clears all global overrides. 519 * 520 * <p> 521 * After calling this method, all properties will fall back to resolver values 522 * (or per-thread overrides if they exist). 523 * 524 * @return This object for method chaining. 525 * @see #setGlobal(String, String) 526 * @see #unsetGlobal(String) 527 */ 528 public Settings clearGlobal() { 529 globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).clear(); 530 return this; 531 } 532 533 /** 534 * Converts a string to the specified type using reflection to find conversion methods or constructors. 535 * 536 * <p> 537 * This method attempts to convert a string to the specified type using the following lookup order: 538 * <ol> 539 * <li>Custom type functions registered via {@link Builder#addTypeFunction(Class, Function)}</li> 540 * <li>Special handling for {@link String} (returns the string as-is)</li> 541 * <li>Special handling for {@link Enum} types (uses {@link Enum#valueOf(Class, String)})</li> 542 * <li>Reflection lookup for static methods with signature <c>public static <T> T anyName(String arg)</c> 543 * on the target class</li> 544 * <li>Reflection lookup for static methods on the superclass (for abstract classes like {@link Charset} 545 * where concrete implementations need to use the abstract class's static method)</li> 546 * <li>Reflection lookup for public constructors with signature <c>public T(String arg)</c></li> 547 * </ol> 548 * 549 * <p> 550 * When a conversion method or constructor is found via reflection, it is cached in the 551 * <c>toTypeFunctions</c> map for future use. 552 * 553 * <h5 class='section'>Examples:</h5> 554 * <ul class='spaced-list'> 555 * <li><c>Boolean</c> - Uses <c>Boolean.valueOf(String)</c> static method 556 * <li><c>Integer</c> - Uses <c>Integer.valueOf(String)</c> static method 557 * <li><c>Charset</c> - Uses <c>Charset.forName(String)</c> static method (even for concrete implementations) 558 * <li><c>File</c> - Uses <c>File(String)</c> constructor 559 * </ul> 560 * 561 * @param <T> The target type. 562 * @param s The string to convert. Must not be <jk>null</jk>. 563 * @param c The target class. Must not be <jk>null</jk>. 564 * @return The converted value. 565 * @throws RuntimeException If the type is not supported for conversion (no static method or constructor found). 566 */ 567 @SuppressWarnings({ "unchecked", "rawtypes" }) 568 protected <T> T toType(String s, Class<T> c) { 569 assertArgNotNull("s", s); 570 assertArgNotNull("c", c); 571 var f = (Function<String,T>)toTypeFunctions.get(c); 572 if (f == null) { 573 if (c == String.class) 574 return (T)s; 575 if (c.isEnum()) 576 return (T)Enum.valueOf((Class<? extends Enum>)c, s); 577 ClassInfoTyped<T> ci = info(c); 578 f = ci.getDeclaredMethod(x -> x.isStatic() && x.hasParameterTypes(String.class) && x.hasReturnType(c) && FROM_STRING_METHOD_NAMES.contains(x.getName())).map(x -> (Function<String,T>)s2 -> x.invoke(null, s2)).orElse(null); 579 if (f == null) 580 f = ci.getPublicConstructor(x -> x.hasParameterTypes(String.class)).map(x -> (Function<String,T>)s2 -> x.newInstance(s2)).orElse(null); 581 if (f != null) 582 toTypeFunctions.putIfAbsent(c, f); 583 } 584 if (f == null) 585 throw rex("Invalid env type: {0}", c); 586 return f.apply(s); 587 } 588} 589