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.lang.reflect.Constructor; 024import java.lang.reflect.InvocationTargetException; 025import java.util.Arrays; 026 027import static java.lang.String.format; 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Contract for a factory that creates instances given a type. 032 * <p> 033 * Useful for resultset mapping, where each row in the resultset might require a new instance. 034 * <p> 035 * Implementors are suggested to employ application-specific strategies, such as having a DI container handle instance 036 * creation. 037 * <p> 038 * Implementations should be threadsafe. 039 * 040 * @author <a href="https://www.revetkn.com">Mark Allen</a> 041 * @since 1.0.0 042 */ 043@ThreadSafe 044public interface InstanceProvider { 045 /** 046 * Provides an instance of the given {@code instanceType}. 047 * <p> 048 * Whether the instance is new every time or shared/reused is implementation-dependent. 049 * 050 * @param <T> instance type token 051 * @param instanceType the type of instance to create 052 * @return an instance of the given {@code instanceType} 053 */ 054 @NonNull 055 default <T> T provide(@NonNull StatementContext<T> statementContext, 056 @NonNull Class<T> instanceType) { 057 requireNonNull(statementContext); 058 requireNonNull(instanceType); 059 060 try { 061 return instanceType.getDeclaredConstructor().newInstance(); 062 } catch (Exception e) { 063 throw new DatabaseException(format( 064 "Unable to create an instance of %s. Please verify that %s has a public no-argument constructor", 065 instanceType, instanceType.getSimpleName()), e); 066 } 067 } 068 069 /** 070 * Provides an instance of the given {@code recordType}. 071 * <p> 072 * Whether the instance is new every time or shared/reused is implementation-dependent. 073 * 074 * @param <T> instance type token 075 * @param recordType the type of instance to create (must be a record) 076 * @param initargs values used to construct the record instance 077 * @return an instance of the given {@code recordType} 078 * @since 2.0.0 079 */ 080 @NonNull 081 default <T extends Record> T provideRecord(@NonNull StatementContext<T> statementContext, 082 @NonNull Class<T> recordType, 083 Object @Nullable ... initargs) { 084 requireNonNull(statementContext); 085 requireNonNull(recordType); 086 087 try { 088 // Find the canonical constructor for the record. 089 // Hat tip to https://stackoverflow.com/a/67127067 090 Class<?>[] componentTypes = Arrays.stream(recordType.getRecordComponents()) 091 .map(rc -> rc.getType()) 092 .toArray(Class<?>[]::new); 093 094 Constructor<T> constructor = recordType.getDeclaredConstructor(componentTypes); 095 return constructor.newInstance(initargs); 096 } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | 097 IllegalArgumentException | InvocationTargetException e) { 098 throw new DatabaseException(String.format("Unable to instantiate Record type %s with args %s", recordType, 099 initargs == null ? "[none]" : Arrays.asList(initargs)), e); 100 } 101 } 102}