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 java.util.Objects.requireNonNull; 020 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023 024import de.cuioss.test.valueobjects.contract.BuilderContractImpl; 025import de.cuioss.test.valueobjects.objects.BuilderInstantiator; 026import de.cuioss.tools.logging.CuiLogger; 027import lombok.Getter; 028import lombok.ToString; 029 030/** 031 * Used for creating instances of a builder. This variant relies on a factory 032 * method on the target type usually with the name "builder". See 033 * {@link BuilderFactoryBasedInstantiator#BuilderFactoryBasedInstantiator(Class, String, String)} 034 * for details 035 * 036 * @author Oliver Wolff 037 * @param <T> identifying the type of the {@link Object} created by the builder 038 */ 039@ToString 040public class BuilderFactoryBasedInstantiator<T> implements BuilderInstantiator<T> { 041 042 private static final String UNABLE_TO_ACCESS_METHOD = "Unable to access method %s on type %s, due to %s"; 043 044 private static final CuiLogger log = new CuiLogger(BuilderFactoryBasedInstantiator.class); 045 046 private final Method builderFactoryMethod; 047 private final Method builderMethod; 048 049 @Getter 050 private final Class<T> targetClass; 051 052 @Getter 053 private final Class<?> builderClass; 054 055 /** 056 * Constructor. shortcut for calling 057 * {@link #BuilderFactoryBasedInstantiator(Class, String, String)} with 058 * {@link BuilderContractImpl#DEFAULT_BUILDER_FACTORY_METHOD_NAME} and 059 * {@value BuilderContractImpl#DEFAULT_BUILD_METHOD_NAME} 060 * 061 * @param enclosingType identifying the type where the method is located in, 062 * must not be null. 063 */ 064 public BuilderFactoryBasedInstantiator(final Class<?> enclosingType) { 065 this(enclosingType, BuilderContractImpl.DEFAULT_BUILDER_FACTORY_METHOD_NAME, 066 BuilderContractImpl.DEFAULT_BUILD_METHOD_NAME); 067 } 068 069 /** 070 * Constructor. 071 * 072 * @param enclosingType identifying the type where the method is 073 * located in, must not be null. 074 * @param builderFactoryMethodName The name of the factory method on the 075 * enclosing type. It is assumed that it is 076 * parameter-free static method 077 * @param builderMethodName the actual name or the builder-method 078 */ 079 @SuppressWarnings("unchecked") 080 public BuilderFactoryBasedInstantiator(final Class<?> enclosingType, final String builderFactoryMethodName, 081 final String builderMethodName) { 082 083 requireNonNull(enclosingType, "enclosingType must not be null"); 084 requireNonNull(builderMethodName, "builderMethodName must not be null"); 085 requireNonNull(builderFactoryMethodName, "builderFactoryMethodName must not be null"); 086 087 try { 088 builderFactoryMethod = enclosingType.getDeclaredMethod(builderFactoryMethodName); 089 builderClass = builderFactoryMethod.getReturnType(); 090 } catch (NoSuchMethodException | SecurityException e) { 091 final var message = UNABLE_TO_ACCESS_METHOD.formatted(builderFactoryMethodName, enclosingType.getName(), 092 extractCauseMessageFromThrowable(e)); 093 log.error(message, e); 094 throw new AssertionError(message, e); 095 } 096 097 try { 098 builderMethod = builderClass.getDeclaredMethod(builderMethodName); 099 targetClass = (Class<T>) builderMethod.getReturnType(); 100 } catch (NoSuchMethodException | SecurityException e) { 101 final var message = UNABLE_TO_ACCESS_METHOD.formatted(builderMethodName, builderClass, 102 extractCauseMessageFromThrowable(e)); 103 log.error(message, e); 104 throw new AssertionError(message, e); 105 } 106 107 } 108 109 @Override 110 public Object newBuilderInstance() { 111 try { 112 return builderFactoryMethod.invoke(null); 113 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 114 final var message = UNABLE_TO_ACCESS_METHOD.formatted(builderFactoryMethod.getName(), targetClass, 115 extractCauseMessageFromThrowable(e)); 116 log.error(message, e); 117 throw new AssertionError(message, e); 118 } 119 } 120 121 @SuppressWarnings("unchecked") 122 @Override 123 public T build(final Object builder) { 124 try { 125 return (T) builderMethod.invoke(builder); 126 } catch (IllegalAccessException | InvocationTargetException | RuntimeException e) { 127 final var message = UNABLE_TO_ACCESS_METHOD.formatted(builderMethod.getName(), builderClass.getName(), 128 extractCauseMessageFromThrowable(e)); 129 log.debug(message, e); 130 throw new AssertionError(message, e); 131 } 132 } 133}