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 javax.annotation.concurrent.ThreadSafe; 023import java.sql.ResultSet; 024import java.sql.SQLException; 025import java.util.Optional; 026 027/** 028 * Enables per-column {@link ResultSet} mapping customization via {@link ResultSetMapper}. 029 * 030 * @author <a href="https://www.revetkn.com">Mark Allen</a> 031 * @since 3.0.0 032 */ 033public interface CustomColumnMapper { 034 /** 035 * Perform custom mapping of a {@link ResultSet} column: given a {@code resultSetValue}, optionally return an instance of {@code targetType} instead. 036 * <p> 037 * This method is only invoked when {@code resultSetValue} is non-null. 038 * 039 * @param statementContext current SQL context 040 * @param resultSet the {@link ResultSet} from which data was read 041 * @param resultSetValue the already-read value from the {@link ResultSet}, to be optionally converted to an instance of {@code targetType} 042 * @param targetType the type to which the {@code resultSetValue} should be converted 043 * @param columnIndex 1-based column index 044 * @param columnLabel normalized column label, if available 045 * @param instanceProvider instance-creation factory, may be used to instantiate values 046 * @return the result of the custom column mapping operation - either {@link MappingResult#of(Object)} to indicate a successfully-mapped value or {@link MappingResult#fallback()} if Pyranid should fall back to the registered {@link ResultSetMapper} mapping behavior. 047 * @throws SQLException if an error occurs during mapping 048 */ 049 @NonNull 050 MappingResult map(@NonNull StatementContext<?> statementContext, 051 @NonNull ResultSet resultSet, 052 @NonNull Object resultSetValue, 053 @NonNull TargetType targetType, 054 @NonNull Integer columnIndex, 055 @Nullable String columnLabel, 056 @NonNull InstanceProvider instanceProvider) throws SQLException; 057 058 /** 059 * Specifies which types this mapper should handle. 060 * <p> 061 * For example, if this mapper should apply when marshaling to {@code MyCustomType}, this method could return {@code targetType.matchesClass(MyCustomType.class)}. 062 * <p> 063 * For parameterized types like {@code List<UUID>}, this method could return {@code targetType.matchesParameterizedType(List.class, UUID.class)}. 064 * 065 * @param targetType the target type to evaluate - should this mapper handle it or not? 066 * @return {@code true} if this mapper should handle the type, {@code false} otherwise. 067 */ 068 @NonNull 069 Boolean appliesTo(@NonNull TargetType targetType); 070 071 /** 072 * Result of a custom column mapping attempt. 073 * <p> 074 * Use {@link #of(Object)} to indicate a successfully mapped value or {@link #fallback()} to indicate "didn't map; fall back to the registered {@link ResultSetMapper} behavior".</p> 075 */ 076 @ThreadSafe 077 sealed abstract class MappingResult permits MappingResult.CustomMapping, MappingResult.Fallback { 078 private MappingResult() {} 079 080 /** 081 * Indicates a successfully-mapped custom value. 082 * 083 * @param value the custom value, may be {@code null} 084 * @return a result which indicates a successfully-mapped custom value 085 */ 086 @NonNull 087 public static MappingResult of(@Nullable Object value) { 088 return new CustomMapping(value); 089 } 090 091 /** 092 * Indicates that this mapper did not map a custom value and prefers to fall back to the behavior of the registered {@link ResultSetMapper}. 093 * 094 * @return a result which indicates that this mapper did not map a custom value 095 */ 096 @NonNull 097 public static MappingResult fallback() { 098 return Fallback.INSTANCE; 099 } 100 101 @ThreadSafe 102 static final class CustomMapping extends MappingResult { 103 @Nullable 104 private final Object value; 105 106 private CustomMapping(@Nullable Object value) { 107 this.value = value; 108 } 109 110 @NonNull 111 public Optional<Object> getValue() { 112 return Optional.ofNullable(value); 113 } 114 } 115 116 @ThreadSafe 117 static final class Fallback extends MappingResult { 118 static final Fallback INSTANCE = new Fallback(); 119 120 private Fallback() {} 121 } 122 } 123}