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