001/* 002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de) 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 */ 016package de.cuioss.test.generator.junit.parameterized; 017 018import de.cuioss.test.generator.TypedGenerator; 019import org.junit.jupiter.api.extension.ExtensionContext; 020import org.junit.jupiter.params.provider.Arguments; 021import org.junit.jupiter.params.support.AnnotationConsumer; 022import org.junit.platform.commons.JUnitException; 023import org.junit.platform.commons.util.ReflectionUtils; 024 025import java.lang.reflect.Method; 026import java.util.Arrays; 027import java.util.List; 028import java.util.stream.Stream; 029 030import static java.util.Objects.requireNonNull; 031 032/** 033 * Implementation of {@link org.junit.jupiter.params.provider.ArgumentsProvider} that provides arguments from a 034 * {@link TypedGenerator} created by a factory class for parameterized tests 035 * annotated with {@link TypeGeneratorFactorySource}. 036 * 037 * <p> 038 * This provider invokes the specified factory method to obtain a {@link TypedGenerator} 039 * instance and generates the requested number of values to be used as test arguments. 040 * </p> 041 * 042 * <p> 043 * Seed management is integrated with the existing 044 * {@link de.cuioss.test.generator.junit.GeneratorControllerExtension} to ensure 045 * consistent and reproducible test data generation. 046 * </p> 047 * 048 * @author Oliver Wolff 049 * @since 2.0 050 * @see TypeGeneratorFactorySource 051 * @see TypedGenerator 052 */ 053public class TypeGeneratorFactoryArgumentsProvider extends AbstractTypedGeneratorArgumentsProvider 054 implements AnnotationConsumer<TypeGeneratorFactorySource> { 055 056 private Class<?> factoryClass; 057 private String factoryMethod; 058 private String[] methodParameters; 059 private int count; 060 061 @Override 062 public void accept(TypeGeneratorFactorySource annotation) { 063 factoryClass = annotation.factoryClass(); 064 factoryMethod = annotation.factoryMethod(); 065 methodParameters = annotation.methodParameters(); 066 count = Math.max(1, annotation.count()); 067 } 068 069 @Override 070 protected Stream<? extends Arguments> provideArgumentsForGenerators(ExtensionContext context) { 071 // Create generator instance using factory 072 var generator = createGeneratorFromFactory(); 073 074 // Generate values 075 return generateArguments(generator).stream(); 076 } 077 078 @Override 079 protected long getSeed() { 080 return -1L; 081 } 082 083 @Override 084 protected int getCount() { 085 return count; 086 } 087 088 /** 089 * Creates a TypedGenerator instance by invoking the specified factory method. 090 * 091 * @return a TypedGenerator instance 092 * @throws JUnitException if the factory method cannot be found or invoked 093 */ 094 private TypedGenerator<?> createGeneratorFromFactory() { 095 requireNonNull(factoryClass, "Factory class must not be null"); 096 requireNonNull(factoryMethod, "Factory method must not be null"); 097 098 try { 099 // Find the factory method 100 Method method = findFactoryMethod(); 101 102 // Invoke the factory method with the provided parameters 103 return (TypedGenerator<?>) method.invoke(null, (Object[]) methodParameters); 104 } catch (Exception e) { 105 throw new JUnitException( 106 "Failed to create TypedGenerator using factory method '" + factoryMethod + 107 "' in class '" + factoryClass.getName() + "'", e); 108 } 109 } 110 111 /** 112 * Finds the factory method in the factory class. 113 * 114 * @return the factory method 115 * @throws JUnitException if the method cannot be found 116 */ 117 private Method findFactoryMethod() { 118 // Look for a method with the exact parameter count 119 List<Method> candidateMethods = Arrays.stream(factoryClass.getMethods()) 120 .filter(m -> m.getName().equals(factoryMethod)) 121 .filter(ReflectionUtils::isStatic) 122 .filter(m -> m.getParameterCount() == methodParameters.length) 123 .filter(m -> TypedGenerator.class.isAssignableFrom(m.getReturnType())) 124 .toList(); 125 126 if (candidateMethods.isEmpty()) { 127 throw new JUnitException( 128 "Could not find static factory method '" + factoryMethod + 129 "' in class '" + factoryClass.getName() + 130 "' with " + methodParameters.length + " parameters that returns TypedGenerator"); 131 } 132 133 if (candidateMethods.size() > 1) { 134 // If multiple methods match, try to find one that accepts String parameters 135 var stringParamMethods = candidateMethods.stream() 136 .filter(m -> Arrays.stream(m.getParameterTypes()) 137 .allMatch(p -> p == String.class)) 138 .toList(); 139 140 if (stringParamMethods.size() == 1) { 141 return stringParamMethods.getFirst(); 142 } 143 } 144 145 // If we have exactly one candidate or couldn't narrow down further, use the first one 146 return candidateMethods.getFirst(); 147 } 148}