001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.pyranid; 018 019import com.pyranid.JsonParameter.BindingPreference; 020import org.jspecify.annotations.NonNull; 021import org.jspecify.annotations.Nullable; 022 023import javax.annotation.concurrent.ThreadSafe; 024import java.lang.reflect.Array; 025import java.lang.reflect.Type; 026import java.math.BigDecimal; 027import java.util.Collection; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.Set; 032 033import static java.util.Objects.requireNonNull; 034 035/** 036 * Fluent interface for acquiring instances of specialized parameter types. 037 * 038 * @author <a href="https://www.revetkn.com">Mark Allen</a> 039 * @since 3.0.0 040 */ 041@ThreadSafe 042public final class Parameters { 043 private Parameters() { 044 // Prevents instantiation 045 } 046 047 /** 048 * Acquires a secure parameter with the default {@code <redacted>} diagnostics mask. 049 * <p> 050 * This is display-only: Pyranid binds the wrapped value exactly as if it had not been secured, but renders the mask 051 * instead of the value in its own diagnostics. 052 * 053 * @param value the value to bind 054 * @return a secure parameter for the given value 055 * @since 4.4.0 056 */ 057 @NonNull 058 public static SecureParameter secure(@Nullable Object value) { 059 return secure(value, SecureParameterSupport.DEFAULT_MASK); 060 } 061 062 /** 063 * Acquires a secure parameter with a custom diagnostics mask. 064 * <p> 065 * This is display-only: Pyranid binds the wrapped value exactly as if it had not been secured, but renders the mask 066 * instead of the value in its own diagnostics. 067 * 068 * @param value the value to bind 069 * @param mask the value to render in diagnostics 070 * @return a secure parameter for the given value 071 * @since 4.4.0 072 */ 073 @NonNull 074 public static SecureParameter secure(@Nullable Object value, 075 @NonNull String mask) { 076 requireNonNull(mask); 077 return new DefaultSecureParameter(value, mask); 078 } 079 080 /** 081 * Acquires a SQL ARRAY parameter for a {@link List} given an appropriate <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 082 * <p> 083 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 084 * <p> 085 * SQL ARRAY binding requires JDBC driver support for {@link java.sql.Connection#createArrayOf(String, Object[])}. 086 * Unsupported dialects, including MySQL, MariaDB, SQLite, SQL Server, and Oracle, fail with a clear {@link DatabaseException}. 087 * <p> 088 * For non-SQL array binding where you need to preserve the Java element type for custom binders, 089 * use {@link #arrayOf(Class, Object)} instead. 090 * 091 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 092 * @param list the list whose elements will be used to populate the SQL ARRAY 093 * @param <E> the element type of the Java list ({@code List<E>}); each element must be bindable to {@code baseTypeName} by the active {@link PreparedStatementBinder}. 094 * @return a SQL ARRAY parameter for the given list 095 */ 096 @NonNull 097 public static <E> SqlArrayParameter<E> sqlArrayOf(@NonNull String baseTypeName, 098 @Nullable List<E> list) { 099 requireNonNull(baseTypeName); 100 return new DefaultSqlArrayParameter(baseTypeName, list == null ? null : list.toArray()); 101 } 102 103 /** 104 * Acquires a SQL ARRAY parameter for a native Java array given an appropriate <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a>. 105 * <p> 106 * You may determine available {@code baseTypeName} values for your database by examining metadata exposed via {@link Database#readDatabaseMetaData(DatabaseMetaDataReader)}. 107 * <p> 108 * SQL ARRAY binding requires JDBC driver support for {@link java.sql.Connection#createArrayOf(String, Object[])}. 109 * Unsupported dialects, including MySQL, MariaDB, SQLite, SQL Server, and Oracle, fail with a clear {@link DatabaseException}. 110 * <p> 111 * For non-SQL array binding where you need to preserve the Java element type for custom binders, 112 * use {@link #arrayOf(Class, Object)} instead. 113 * 114 * @param baseTypeName the SQL ARRAY element type, e.g. {@code "text"}, {@code "uuid"}, {@code "float4"}, {@code "float8"} ... 115 * @param array the native Java array whose elements will be used to populate the SQL ARRAY 116 * @param <E> the element type of the Java array ({@code T[]}); each element must be bindable to {@code baseTypeName} by the active {@link PreparedStatementBinder}. 117 * @return a SQL ARRAY parameter for the given Java array 118 */ 119 @NonNull 120 public static <E> SqlArrayParameter<E> sqlArrayOf(@NonNull String baseTypeName, 121 E @Nullable [] array) { 122 requireNonNull(baseTypeName); 123 return new DefaultSqlArrayParameter(baseTypeName, array); 124 } 125 126 /** 127 * Default package-private implementation of {@link SqlArrayParameter}. 128 * 129 * @author <a href="https://www.revetkn.com">Mark Allen</a> 130 * @since 3.0.0 131 */ 132 @ThreadSafe 133 static class DefaultSqlArrayParameter implements SqlArrayParameter { 134 @NonNull 135 private final String baseTypeName; // e.g. "text", "uuid", "integer", ... 136 @Nullable 137 private final Object @Nullable [] elements; 138 139 DefaultSqlArrayParameter(@NonNull String baseTypeName, 140 Object @Nullable [] elements) { 141 requireNonNull(baseTypeName); 142 143 this.baseTypeName = baseTypeName; 144 this.elements = elements == null ? null : elements.clone(); // Always perform a defensive copy 145 } 146 147 /** 148 * Gets the element type of this SQL ARRAY, which corresponds to the value of <a href="https://docs.oracle.com/en/java/javase/26/docs/api/java.sql/java/sql/Array.html#getBaseTypeName()" target="_blank">{@code java.sql.Array#getBaseTypeName()}</a> 149 * and is database-specific. 150 * 151 * @return the element type of this SQL ARRAY 152 */ 153 @NonNull 154 @Override 155 public String getBaseTypeName() { 156 return this.baseTypeName; 157 } 158 159 /** 160 * Gets the elements of this SQL ARRAY. 161 * 162 * @return the elements of this SQL ARRAY 163 */ 164 @NonNull 165 @Override 166 public Optional<Object[]> getElements() { 167 // Defensive copy 168 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 169 } 170 } 171 172 /** 173 * Acquires a parameter for SQL {@code IN} list expansion using a {@link Collection}. 174 * Elements must be non-empty and must not contain {@code null} or empty {@link Optional} values. 175 * <p> 176 * SQL {@code IN} does not match {@code NULL}; use an explicit {@code IS NULL} predicate when null matching 177 * is required. 178 * 179 * @param elements the elements to expand into SQL {@code IN} list placeholders 180 * @param <E> the element type 181 * @return an IN-list parameter for the given elements 182 */ 183 @NonNull 184 public static <E> InListParameter inList(@NonNull Collection<@NonNull E> elements) { 185 requireNonNull(elements); 186 return new DefaultInListParameter(elements.toArray()); 187 } 188 189 /** 190 * Acquires a parameter for SQL {@code IN} list expansion using a Java array. 191 * Elements must be non-empty and must not contain {@code null} or empty {@link Optional} values. 192 * <p> 193 * SQL {@code IN} does not match {@code NULL}; use an explicit {@code IS NULL} predicate when null matching 194 * is required. 195 * 196 * @param elements the elements to expand into SQL {@code IN} list placeholders 197 * @param <E> the element type 198 * @return an IN-list parameter for the given elements 199 */ 200 @NonNull 201 public static <E> InListParameter inList(@NonNull E @NonNull [] elements) { 202 requireNonNull(elements); 203 return new DefaultInListParameter(elements); 204 } 205 206 /** 207 * Acquires a parameter for SQL {@code IN} list expansion using a {@code byte[]} array. 208 * Elements must be non-empty. 209 * 210 * @param elements the elements to expand into SQL {@code IN} list placeholders 211 * @return an IN-list parameter for the given elements 212 */ 213 @NonNull 214 public static InListParameter inList(byte @NonNull [] elements) { 215 requireNonNull(elements); 216 Object[] boxed = new Object[elements.length]; 217 for (int i = 0; i < elements.length; i++) 218 boxed[i] = elements[i]; 219 return new DefaultInListParameter(boxed); 220 } 221 222 /** 223 * Acquires a parameter for SQL {@code IN} list expansion using a {@code short[]} array. 224 * Elements must be non-empty. 225 * 226 * @param elements the elements to expand into SQL {@code IN} list placeholders 227 * @return an IN-list parameter for the given elements 228 */ 229 @NonNull 230 public static InListParameter inList(short @NonNull [] elements) { 231 requireNonNull(elements); 232 Object[] boxed = new Object[elements.length]; 233 for (int i = 0; i < elements.length; i++) 234 boxed[i] = elements[i]; 235 return new DefaultInListParameter(boxed); 236 } 237 238 /** 239 * Acquires a parameter for SQL {@code IN} list expansion using an {@code int[]} array. 240 * Elements must be non-empty. 241 * 242 * @param elements the elements to expand into SQL {@code IN} list placeholders 243 * @return an IN-list parameter for the given elements 244 */ 245 @NonNull 246 public static InListParameter inList(int @NonNull [] elements) { 247 requireNonNull(elements); 248 Object[] boxed = new Object[elements.length]; 249 for (int i = 0; i < elements.length; i++) 250 boxed[i] = elements[i]; 251 return new DefaultInListParameter(boxed); 252 } 253 254 /** 255 * Acquires a parameter for SQL {@code IN} list expansion using a {@code long[]} array. 256 * Elements must be non-empty. 257 * 258 * @param elements the elements to expand into SQL {@code IN} list placeholders 259 * @return an IN-list parameter for the given elements 260 */ 261 @NonNull 262 public static InListParameter inList(long @NonNull [] elements) { 263 requireNonNull(elements); 264 Object[] boxed = new Object[elements.length]; 265 for (int i = 0; i < elements.length; i++) 266 boxed[i] = elements[i]; 267 return new DefaultInListParameter(boxed); 268 } 269 270 /** 271 * Acquires a parameter for SQL {@code IN} list expansion using a {@code float[]} array. 272 * Elements must be non-empty. 273 * 274 * @param elements the elements to expand into SQL {@code IN} list placeholders 275 * @return an IN-list parameter for the given elements 276 */ 277 @NonNull 278 public static InListParameter inList(float @NonNull [] elements) { 279 requireNonNull(elements); 280 Object[] boxed = new Object[elements.length]; 281 for (int i = 0; i < elements.length; i++) 282 boxed[i] = elements[i]; 283 return new DefaultInListParameter(boxed); 284 } 285 286 /** 287 * Acquires a parameter for SQL {@code IN} list expansion using a {@code double[]} array. 288 * Elements must be non-empty. 289 * 290 * @param elements the elements to expand into SQL {@code IN} list placeholders 291 * @return an IN-list parameter for the given elements 292 */ 293 @NonNull 294 public static InListParameter inList(double @NonNull [] elements) { 295 requireNonNull(elements); 296 Object[] boxed = new Object[elements.length]; 297 for (int i = 0; i < elements.length; i++) 298 boxed[i] = elements[i]; 299 return new DefaultInListParameter(boxed); 300 } 301 302 /** 303 * Acquires a parameter for SQL {@code IN} list expansion using a {@code boolean[]} array. 304 * Elements must be non-empty. 305 * 306 * @param elements the elements to expand into SQL {@code IN} list placeholders 307 * @return an IN-list parameter for the given elements 308 */ 309 @NonNull 310 public static InListParameter inList(boolean @NonNull [] elements) { 311 requireNonNull(elements); 312 Object[] boxed = new Object[elements.length]; 313 for (int i = 0; i < elements.length; i++) 314 boxed[i] = elements[i]; 315 return new DefaultInListParameter(boxed); 316 } 317 318 /** 319 * Acquires a parameter for SQL {@code IN} list expansion using a {@code char[]} array. 320 * Elements must be non-empty. 321 * 322 * @param elements the elements to expand into SQL {@code IN} list placeholders 323 * @return an IN-list parameter for the given elements 324 */ 325 @NonNull 326 public static InListParameter inList(char @NonNull [] elements) { 327 requireNonNull(elements); 328 Object[] boxed = new Object[elements.length]; 329 for (int i = 0; i < elements.length; i++) 330 boxed[i] = elements[i]; 331 return new DefaultInListParameter(boxed); 332 } 333 334 /** 335 * Default package-private implementation of {@link InListParameter}. 336 * 337 * @author <a href="https://www.revetkn.com">Mark Allen</a> 338 * @since 4.0.0 339 */ 340 @ThreadSafe 341 static class DefaultInListParameter implements InListParameter { 342 private final @NonNull Object @NonNull [] elements; 343 344 DefaultInListParameter(@NonNull Object @NonNull [] elements) { 345 requireNonNull(elements); 346 this.elements = elements.clone(); // Always perform a defensive copy 347 } 348 349 @Override 350 public @NonNull Object @NonNull [] getElements() { 351 // Defensive copy 352 return this.elements.clone(); 353 } 354 } 355 356 /** 357 * Acquires a parameter of array type, preserving element type information so it is accessible at runtime. 358 * <p> 359 * This is useful when you want to bind an array via a {@link CustomParameterBinder} and need the 360 * component type to be preserved for {@link TargetType} matching. 361 * <p> 362 * For SQL {@code ARRAY} binding, use {@link #sqlArrayOf(String, Object[])} instead. 363 * If you need a formal SQL {@code ARRAY} parameter for JDBC array binding, do not use this method. 364 * <p> 365 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 366 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 367 * if you want typed nulls to bind successfully. 368 * 369 * @param elementType the element type of the array 370 * @param array the array value to wrap; may be {@code null} 371 * @param <E> the element type of the array 372 * @return a {@link TypedParameter} representing a {@code E[]} suitable for use with custom binders 373 */ 374 @NonNull 375 public static <E> TypedParameter arrayOf(@NonNull Class<E> elementType, 376 E @Nullable [] array) { 377 requireNonNull(elementType); 378 return arrayOf((Class<?>) elementType, array); 379 } 380 381 /** 382 * Acquires a parameter of array type, preserving element type information so it is accessible at runtime. 383 * <p> 384 * This overload supports primitive arrays by passing the primitive component class 385 * (for example, {@code int.class}) and the corresponding primitive array. 386 * <p> 387 * For SQL {@code ARRAY} binding, use {@link #sqlArrayOf(String, Object[])} instead. 388 * If you need a formal SQL {@code ARRAY} parameter for JDBC array binding, do not use this method. 389 * <p> 390 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 391 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 392 * if you want typed nulls to bind successfully. 393 * 394 * @param elementType the element type of the array (may be primitive) 395 * @param array the array value to wrap; may be {@code null} 396 * @return a {@link TypedParameter} representing an array suitable for use with custom binders 397 */ 398 @NonNull 399 public static TypedParameter arrayOf(@NonNull Class<?> elementType, 400 @Nullable Object array) { 401 requireNonNull(elementType); 402 403 if (array != null) { 404 Class<?> arrayClass = array.getClass(); 405 406 if (!arrayClass.isArray()) 407 throw new IllegalArgumentException("Array parameter is not an array"); 408 409 Class<?> componentType = arrayClass.getComponentType(); 410 411 if (elementType.isPrimitive()) { 412 if (!componentType.equals(elementType)) 413 throw new IllegalArgumentException("Array parameter component type does not match elementType"); 414 } else if (!elementType.isAssignableFrom(componentType)) { 415 throw new IllegalArgumentException("Array parameter component type is not assignable to elementType"); 416 } 417 } 418 419 Class<?> arrayType = Array.newInstance(elementType, 0).getClass(); 420 return new DefaultTypedParameter(arrayType, array); 421 } 422 423 /** 424 * Acquires a vector parameter for an array of {@code double}. 425 * 426 * @param elements the elements of the vector parameter 427 * @return the vector parameter 428 */ 429 @NonNull 430 public static VectorParameter vectorOfDoubles(double @Nullable [] elements) { 431 return new DefaultVectorParameter(elements); 432 } 433 434 /** 435 * Acquires a vector parameter for a {@link List} of {@link Double}. 436 * 437 * @param elements the elements of the vector parameter 438 * @return the vector parameter 439 */ 440 @NonNull 441 public static VectorParameter vectorOfDoubles(@Nullable List<@NonNull Double> elements) { 442 if (elements == null) 443 return new DefaultVectorParameter(null); 444 445 double[] doubles = new double[elements.size()]; 446 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 447 return new DefaultVectorParameter(doubles); 448 } 449 450 /** 451 * Acquires a vector parameter for an array of {@code float}. 452 * 453 * @param elements the elements of the vector parameter 454 * @return the vector parameter 455 */ 456 @NonNull 457 public static VectorParameter vectorOfFloats(float @Nullable [] elements) { 458 if (elements == null) 459 return new DefaultVectorParameter(null); 460 461 double[] doubles = new double[elements.length]; 462 for (int i = 0; i < elements.length; i++) doubles[i] = elements[i]; 463 return new DefaultVectorParameter(doubles); 464 } 465 466 /** 467 * Acquires a vector parameter for a {@link List} of {@link Float}. 468 * 469 * @param elements the elements of the vector parameter 470 * @return the vector parameter 471 */ 472 @NonNull 473 public static VectorParameter vectorOfFloats(@Nullable List<@NonNull Float> elements) { 474 if (elements == null) 475 return new DefaultVectorParameter(null); 476 477 double[] doubles = new double[elements.size()]; 478 for (int i = 0; i < doubles.length; i++) doubles[i] = requireNonNull(elements.get(i)); 479 return new DefaultVectorParameter(doubles); 480 } 481 482 /** 483 * Acquires a vector parameter for a {@link List} of {@link BigDecimal}. 484 * 485 * @param elements the elements of the vector parameter 486 * @return the vector parameter 487 */ 488 @NonNull 489 public static VectorParameter vectorOfBigDecimals(@Nullable List<@NonNull BigDecimal> elements) { 490 if (elements == null) 491 return new DefaultVectorParameter(null); 492 493 double[] d = new double[elements.size()]; 494 for (int i = 0; i < d.length; i++) d[i] = requireNonNull(elements.get(i)).doubleValue(); 495 return new DefaultVectorParameter(d); 496 } 497 498 /** 499 * Default package-private implementation of {@link VectorParameter}. 500 * 501 * @author <a href="https://www.revetkn.com">Mark Allen</a> 502 * @since 3.0.0 503 */ 504 @ThreadSafe 505 static class DefaultVectorParameter implements VectorParameter { 506 @Nullable 507 private final double[] elements; 508 509 private DefaultVectorParameter(double @Nullable [] elements) { 510 if (elements == null) { 511 this.elements = null; 512 return; 513 } 514 515 if (elements.length == 0) 516 throw new IllegalArgumentException("Vector parameters must have at least 1 element"); 517 518 for (double d : elements) 519 if (!Double.isFinite(d)) 520 throw new IllegalArgumentException("Vector parameter elements must be finite (no NaN/Infinity)"); 521 522 // Always defensive copy 523 this.elements = elements.clone(); 524 } 525 526 /** 527 * Gets the elements of this vector. 528 * 529 * @return the elements of this vector 530 */ 531 @NonNull 532 @Override 533 public Optional<double[]> getElements() { 534 // Defensive copy 535 return this.elements == null ? Optional.empty() : Optional.of(this.elements.clone()); 536 } 537 } 538 539 /** 540 * Acquires a JSON parameter for "stringified" JSON, using {@link BindingPreference#BINARY}. 541 * 542 * @param json the stringified JSON for this parameter 543 * @return the JSON parameter 544 */ 545 @NonNull 546 public static JsonParameter json(@Nullable String json) { 547 return new DefaultJsonParameter(json, BindingPreference.BINARY); 548 } 549 550 /** 551 * Acquires a JSON parameter for "stringified" JSON. 552 * 553 * @param json the stringified JSON for this parameter 554 * @param bindingPreference how the JSON parameter should be bound to a {@link java.sql.PreparedStatement} 555 * @return the JSON parameter 556 */ 557 @NonNull 558 public static JsonParameter json(@Nullable String json, 559 @NonNull BindingPreference bindingPreference) { 560 requireNonNull(bindingPreference); 561 562 return new DefaultJsonParameter(json, bindingPreference); 563 } 564 565 /** 566 * Default package-private implementation of {@link JsonParameter}. 567 * 568 * @author <a href="https://www.revetkn.com">Mark Allen</a> 569 * @since 3.0.0 570 */ 571 @ThreadSafe 572 static class DefaultJsonParameter implements JsonParameter { 573 @Nullable 574 private final String json; 575 @NonNull 576 private final BindingPreference bindingPreference; 577 578 private DefaultJsonParameter(@Nullable String json, 579 @NonNull BindingPreference bindingPreference) { 580 requireNonNull(bindingPreference); 581 582 this.json = json; 583 this.bindingPreference = bindingPreference; 584 } 585 586 @NonNull 587 @Override 588 public Optional<String> getJson() { 589 return Optional.ofNullable(this.json); 590 } 591 592 @NonNull 593 @Override 594 public BindingPreference getBindingPreference() { 595 return this.bindingPreference; 596 } 597 } 598 599 /** 600 * Acquires a parameter of type {@link List}, preserving type information so it is accessible at runtime. 601 * <p> 602 * This is useful when you want to bind a parameterized collection such as {@code List<UUID>} or 603 * {@code List<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 604 * <p> 605 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 606 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 607 * <p> 608 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 609 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 610 * if you want typed nulls to bind successfully. 611 * 612 * @param elementType the {@link Class} representing the type of elements contained in the list; 613 * used to preserve generic type information 614 * @param list the list value to wrap; may be {@code null} 615 * @param <E> the element type of the list 616 * @return a {@link TypedParameter} representing a {@code List<E>} suitable for use with custom binders 617 */ 618 @NonNull 619 public static <E> TypedParameter listOf(@NonNull Class<E> elementType, 620 @Nullable List<E> list) { 621 requireNonNull(elementType); 622 623 Type listOfE = new DefaultParameterizedType(List.class, new Type[]{elementType}, null); 624 return new DefaultTypedParameter(listOfE, list); 625 } 626 627 /** 628 * Acquires a parameter of type {@link Set}, preserving type information so it is accessible at runtime. 629 * <p> 630 * This is useful when you want to bind a parameterized collection such as {@code Set<UUID>} or 631 * {@code Set<String>} and need the generic type argument (e.g. {@code UUID.class}) to be preserved. 632 * <p> 633 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 634 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 635 * <p> 636 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 637 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 638 * if you want typed nulls to bind successfully. 639 * 640 * @param elementType the {@link Class} representing the type of elements contained in the set; 641 * used to preserve generic type information 642 * @param set the set value to wrap; may be {@code null} 643 * @param <E> the element type of the set 644 * @return a {@link TypedParameter} representing a {@code Set<E>} suitable for use with custom binders 645 */ 646 @NonNull 647 public static <E> TypedParameter setOf(@NonNull Class<E> elementType, 648 @Nullable Set<E> set) { 649 requireNonNull(elementType); 650 651 Type setOfE = new DefaultParameterizedType(Set.class, new Type[]{elementType}, null); 652 return new DefaultTypedParameter(setOfE, set); 653 } 654 655 /** 656 * Acquires a parameter of type {@link Map}, preserving key and value type information 657 * so they are accessible at runtime. 658 * <p> 659 * This is useful when you want to bind a parameterized collection such as {@code Map<UUID, Integer>} 660 * and need the generic type arguments (e.g. {@code UUID.class} and {@code Integer.class}) to be preserved. 661 * <p> 662 * The resulting {@link TypedParameter} carries both the runtime value and its generic type so that 663 * {@link CustomParameterBinder#appliesTo(TargetType)} can match against the element type. 664 * <p> 665 * <strong>Note:</strong> this kind of parameter requires a corresponding {@link CustomParameterBinder} 666 * to be registered, even when the wrapped value is {@code null}; implement {@link CustomParameterBinder#bindNull} 667 * if you want typed nulls to bind successfully. 668 * 669 * @param keyType the type of the map keys 670 * @param valueType the type of the map values 671 * @param map the map value; may be {@code null} 672 * @param <K> the key type 673 * @param <V> the value type 674 * @return a {@link TypedParameter} representing {@code Map<K,V>} 675 */ 676 @NonNull 677 public static <K, V> TypedParameter mapOf(@NonNull Class<K> keyType, 678 @NonNull Class<V> valueType, 679 @Nullable Map<K, V> map) { 680 requireNonNull(keyType); 681 requireNonNull(valueType); 682 683 Type mapOfKV = new DefaultParameterizedType(Map.class, new Type[]{keyType, valueType}, null); 684 return new DefaultTypedParameter(mapOfKV, map); 685 } 686}