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}