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}