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}