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 org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import java.lang.reflect.GenericArrayType;
023import java.lang.reflect.ParameterizedType;
024import java.lang.reflect.Type;
025import java.sql.ResultSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Optional;
029import java.util.Set;
030
031import static java.util.Objects.requireNonNull;
032
033/**
034 * A developer-friendly view over a reflective {@link java.lang.reflect.Type} used by the {@link ResultSet}-mapping pipeline for standard {@link ResultSetMapper} instances.
035 *
036 * @author <a href="https://www.revetkn.com">Mark Allen</a>
037 * @see java.lang.reflect.Type
038 * @since 3.0.0
039 */
040public interface TargetType {
041        /**
042         * The original reflective type ({@code Class}, {@code ParameterizedType}, etc.)
043         */
044        @NonNull
045        Type getType();
046
047        /**
048         * Raw class, with erasure ({@code List.class} for {@code List<UUID>}, etc.)
049         */
050        @NonNull
051        Class<?> getRawClass();
052
053        /**
054         * @return {@code true} if {@link #getRawClass()} matches the provided {@code rawClass} (no subtype/parameter checks), {@code false} otherwise.
055         */
056        @NonNull
057        default Boolean matchesClass(@NonNull Class<?> rawClass) {
058                requireNonNull(rawClass);
059                return getRawClass().equals(rawClass);
060        }
061
062        /**
063         * Does this instance match the given raw class and its parameterized type arguments?
064         * <p>
065         * For example, invoke {@code matchesParameterizedType(List.class, UUID.class)} to determine "is this type a {@code List<UUID>}?"
066         */
067        @NonNull
068        default Boolean matchesParameterizedType(@NonNull Class<?> rawClass,
069                                                                                                                                                                         Class<?> @Nullable ... typeArguments) {
070                requireNonNull(rawClass);
071
072                if (typeArguments == null || typeArguments.length == 0)
073                        return matchesClass(rawClass);
074
075                if (!(getType() instanceof ParameterizedType parameterizedType) || !rawClass.equals(parameterizedType.getRawType()))
076                        return false;
077
078                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
079
080                if (actualTypeArguments.length != typeArguments.length)
081                        return false;
082
083                for (int i = 0; i < actualTypeArguments.length; i++)
084                        if (!(actualTypeArguments[i] instanceof Class<?> actualClass) || !actualClass.equals(typeArguments[i]))
085                                return false;
086
087                return true;
088        }
089
090        @NonNull
091        default Boolean isList() {
092                return matchesClass(List.class);
093        }
094
095        @NonNull
096        default Optional<TargetType> getListElementType() {
097                return matchesClass(List.class) ? getFirstTargetTypeArgument() : Optional.empty();
098        }
099
100        @NonNull
101        default Boolean isSet() {
102                return matchesClass(Set.class);
103        }
104
105        @NonNull
106        default Optional<TargetType> getSetElementType() {
107                return matchesClass(Set.class) ? getFirstTargetTypeArgument() : Optional.empty();
108        }
109
110        @NonNull
111        default Boolean isMap() {
112                return matchesClass(Map.class);
113        }
114
115        @NonNull
116        default Optional<TargetType> getMapKeyType() {
117                return matchesClass(Map.class) ? getTargetTypeArgumentAtIndex(0) : Optional.empty();
118        }
119
120        @NonNull
121        default Optional<TargetType> getMapValueType() {
122                return matchesClass(Map.class) ? getTargetTypeArgumentAtIndex(1) : Optional.empty();
123        }
124
125        @NonNull
126        default Boolean isArray() {
127                return getRawClass().isArray() || getType() instanceof GenericArrayType;
128        }
129
130        @NonNull
131        default Optional<TargetType> getArrayComponentType() {
132                if (getRawClass().isArray())
133                        return Optional.of(TargetType.of(getRawClass().getComponentType()));
134
135                if (getType() instanceof GenericArrayType genericArrayType)
136                        return Optional.of(TargetType.of(genericArrayType.getGenericComponentType()));
137
138                return Optional.empty();
139        }
140
141        /**
142         * All type arguments wrapped (empty for raw/non-parameterized).
143         */
144        @NonNull
145        List<TargetType> getTypeArguments();
146
147        @NonNull
148        static TargetType of(@NonNull Type type) {
149                requireNonNull(type);
150                return new DefaultTargetType(type);
151        }
152
153        @NonNull
154        private Optional<TargetType> getFirstTargetTypeArgument() {
155                return getTargetTypeArgumentAtIndex(0);
156        }
157
158        @NonNull
159        private Optional<TargetType> getTargetTypeArgumentAtIndex(@NonNull Integer index) {
160                requireNonNull(index);
161
162                List<TargetType> targetTypeArguments = getTypeArguments();
163                return index < targetTypeArguments.size() ? Optional.of(targetTypeArguments.get(index)) : Optional.empty();
164        }
165}