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