001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
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 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
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 */
016package de.cuioss.test.valueobjects.objects.impl;
017
018import static de.cuioss.test.valueobjects.objects.impl.ExceptionHelper.extractCauseMessageFromThrowable;
019import static de.cuioss.tools.string.MoreStrings.emptyToNull;
020import static java.util.Objects.requireNonNull;
021
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024
025import de.cuioss.test.valueobjects.contract.BuilderContractImpl;
026import de.cuioss.test.valueobjects.objects.BuilderInstantiator;
027import de.cuioss.test.valueobjects.objects.ObjectInstantiator;
028import lombok.Getter;
029import lombok.ToString;
030
031/**
032 * Used for creating instances of a builder. This variant relies on the
033 * builder-class having a parameter-free constructor.
034 *
035 * @author Oliver Wolff
036 * @param <T> identifying the type of the {@link Object} created by the builder
037 */
038@ToString
039public class BuilderConstructorBasedInstantiator<T> implements BuilderInstantiator<T> {
040
041    private final Method builderMethod;
042
043    @Getter
044    private final Class<T> targetClass;
045
046    @Getter
047    private final Class<?> builderClass;
048
049    private final ObjectInstantiator<?> builderInstantiator;
050
051    /**
052     * Constructor. Shortcut for calling
053     * {@link #BuilderConstructorBasedInstantiator(Class, String)} with
054     * {@link BuilderContractImpl#DEFAULT_BUILD_METHOD_NAME}
055     *
056     * @param builderType identifying the actual type of the builder. It is assumed
057     *                    that it provides is parameter-free public constructor
058     */
059    public BuilderConstructorBasedInstantiator(final Class<?> builderType) {
060        this(builderType, BuilderContractImpl.DEFAULT_BUILD_METHOD_NAME);
061    }
062
063    /**
064     * Constructor.
065     *
066     * @param builderType     identifying the actual type of the builder. It is
067     *                        assumed that it provides is parameter-free public
068     *                        constructor
069     * @param buildMethodName the actual name or the builder-method, must not be
070     *                        null nor empty
071     */
072    @SuppressWarnings("unchecked")
073    public BuilderConstructorBasedInstantiator(final Class<?> builderType, final String buildMethodName) {
074
075        requireNonNull(builderType, "builderType must not be null");
076        requireNonNull(emptyToNull(buildMethodName), "builderMethodName must not be null");
077
078        builderClass = builderType;
079        builderInstantiator = new DefaultInstantiator<>(builderType);
080
081        try {
082            builderMethod = builderInstantiator.getTargetClass().getDeclaredMethod(buildMethodName);
083            targetClass = (Class<T>) builderMethod.getReturnType();
084        } catch (NoSuchMethodException | SecurityException e) {
085            throw new AssertionError("Unable to access method " + buildMethodName + " on type " + builderType.getName()
086                    + ", due to " + extractCauseMessageFromThrowable(e), e);
087        }
088
089    }
090
091    @Override
092    public Object newBuilderInstance() {
093        return builderInstantiator.newInstance();
094    }
095
096    @SuppressWarnings("unchecked")
097    @Override
098    public T build(final Object builder) {
099        try {
100            return (T) builderMethod.invoke(builder);
101        } catch (IllegalAccessException | InvocationTargetException | RuntimeException e) {
102            throw new AssertionError("Unable to access method " + builderMethod.getName() + " on type "
103                    + getBuilderClass().getName() + ", due to " + extractCauseMessageFromThrowable(e), e);
104        }
105    }
106}