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