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.base.Preconditions.checkArgument; 020import static de.cuioss.tools.string.MoreStrings.isEmpty; 021import static java.util.Objects.requireNonNull; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import java.lang.reflect.InvocationTargetException; 026import java.lang.reflect.Method; 027import java.util.ArrayList; 028import java.util.List; 029 030import de.cuioss.test.valueobjects.objects.ParameterizedInstantiator; 031import de.cuioss.test.valueobjects.objects.RuntimeProperties; 032import de.cuioss.tools.logging.CuiLogger; 033 034/** 035 * This {@link ParameterizedInstantiator} uses a factory method derived by the 036 * given {@link RuntimeProperties#getAllProperties()} in order to instantiate 037 * {@link Object}s 038 * 039 * @author Oliver Wolff 040 * @param <T> identifying the type of objects to be created 041 */ 042public class FactoryBasedInstantiator<T> extends AbstractOrderedArgsInstantiator<T> { 043 044 private static final CuiLogger log = new CuiLogger(FactoryBasedInstantiator.class); 045 046 private final Method factoryMethod; 047 048 /** 049 * Constructor. 050 * 051 * @param type the type of the object to be created 052 * @param runtimeProperties must not be null. defines the attributes in the 053 * exact order to be used for the constructor: 054 * {@link RuntimeProperties#getAllProperties()} 055 * @param enclosingType the type providing the factory method, usually the 056 * target type to be created, must not be null 057 * @param factoryMethodName the name of the factory method, must not be null, 058 * nor empty. 059 */ 060 public FactoryBasedInstantiator(final Class<T> type, final RuntimeProperties runtimeProperties, 061 final Class<?> enclosingType, final String factoryMethodName) { 062 063 super(runtimeProperties); 064 requireNonNull(type, "type must not be null"); 065 requireNonNull(enclosingType, "enclosingType must not be null"); 066 requireNonNull(factoryMethodName); 067 checkArgument(!isEmpty(factoryMethodName), "factoryMethodName msut not be null nor empty"); 068 069 final List<Class<?>> parameter = new ArrayList<>(); 070 runtimeProperties.getAllProperties().forEach(meta -> parameter.add(meta.resolveActualClass())); 071 try { 072 if (parameter.isEmpty()) { 073 factoryMethod = enclosingType.getDeclaredMethod(factoryMethodName); 074 } else { 075 factoryMethod = enclosingType.getDeclaredMethod(factoryMethodName, toClassArray(parameter)); 076 } 077 assertNotNull( 078 079 factoryMethod, 080 "Unable to find a factory method with signature " + parameter + " and name " + factoryMethodName); 081 assertTrue(type.isAssignableFrom(factoryMethod.getReturnType()), 082 "Invalid type found on factory method: " + factoryMethod.getReturnType()); 083 } catch (NoSuchMethodException | SecurityException e) { 084 final var message = new StringBuilder("Unable to find a constructor with signature ").append(parameter) 085 .toString(); 086 log.error(message, e); 087 throw new AssertionError(message); 088 } 089 } 090 091 @SuppressWarnings("unchecked") 092 @Override 093 protected T doInstantiate(final Object... args) { 094 try { 095 return (T) factoryMethod.invoke(null, args); 096 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 097 final var message = new StringBuilder("Unable to invoke constructor ").append(", due to ") 098 .append(extractCauseMessageFromThrowable(e)).toString(); 099 throw new AssertionError(message, e); 100 } 101 } 102 103 @Override 104 public String toString() { 105 final var builder = new StringBuilder(getClass().getName()); 106 builder.append("\nFactory Method: ").append(factoryMethod); 107 builder.append("\nProperty Configuration: ").append(getRuntimeProperties().toString()); 108 return builder.toString(); 109 } 110}