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