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.util;
017
018import static java.util.Objects.requireNonNull;
019
020import java.util.Arrays;
021import java.util.List;
022import java.util.Set;
023
024import de.cuioss.test.generator.TypedGenerator;
025import de.cuioss.test.valueobjects.api.contracts.VerifyConstructor;
026import de.cuioss.test.valueobjects.api.generator.PropertyGenerator;
027import de.cuioss.test.valueobjects.api.generator.PropertyGeneratorHint;
028import de.cuioss.test.valueobjects.api.generator.PropertyGeneratorHints;
029import de.cuioss.test.valueobjects.api.generator.PropertyGenerators;
030import de.cuioss.test.valueobjects.generator.TypedGeneratorRegistry;
031import de.cuioss.test.valueobjects.generator.dynamic.GeneratorResolver;
032import de.cuioss.test.valueobjects.objects.impl.DefaultInstantiator;
033import de.cuioss.tools.collect.CollectionBuilder;
034import de.cuioss.tools.reflect.MoreReflection;
035import lombok.experimental.UtilityClass;
036
037/**
038 * Provides utility methods for dealing with the creation of
039 * {@link TypedGenerator} usually in conjunction with annotations
040 *
041 * @author Oliver Wolff
042 */
043@UtilityClass
044public final class GeneratorAnnotationHelper {
045
046    /**  */
047    public static final String UNABLE_TO_INSTANTIATE_GENERATOR = "Unable to instantiate generator, You must provide a no-arg public constructor: ";
048
049    /**
050     * Convenience method for handling all Generator specific aspects like initially
051     * calling {@link TypedGeneratorRegistry#registerBasicTypes()} and calling of
052     * the individual registering methods like
053     * {@link #handleUnitClassImplementation(Object)},
054     * {@link #handlePropertyGenerator(Class)} and
055     * {@link #handleGeneratorHints(Class)} in case there are additionalGenerator
056     * given they will be registered as well
057     *
058     * @param testClass           must not null
059     * @param additionalGenerator
060     */
061    public static void handleGeneratorsForTestClass(final Object testClass,
062            final List<TypedGenerator<?>> additionalGenerator) {
063        requireNonNull(testClass);
064        // Handle Generator
065        TypedGeneratorRegistry.registerBasicTypes();
066        handleUnitClassImplementation(testClass);
067        handlePropertyGenerator(testClass.getClass());
068        handleGeneratorHints(testClass.getClass());
069        if (null != additionalGenerator) {
070            for (final TypedGenerator<?> additional : additionalGenerator) {
071                TypedGeneratorRegistry.registerGenerator(additional);
072            }
073        }
074    }
075
076    /**
077     * Checks the given type for the annotation {@link PropertyGeneratorHint} and
078     * {@link PropertyGeneratorHints} and registers all found to the
079     * {@link TypedGeneratorRegistry}
080     *
081     * @param annotated the class that may or may not provide the annotations, must
082     *                  not be null
083     */
084    public static void handleGeneratorHints(final Class<?> annotated) {
085        for (final PropertyGeneratorHint hint : extractConfiguredGeneratorHints(annotated)) {
086
087            final TypedGenerator<?> resolved = GeneratorResolver.resolveGenerator(hint.implementationType());
088            TypedGeneratorRegistry.registerTypedGenerator(hint.declaredType(),
089                    new WildcardDecoratorGenerator(hint.declaredType(), resolved));
090        }
091    }
092
093    /**
094     * Checks the given type for the annotation {@link PropertyGenerator} and
095     * {@link PropertyGenerators} and registers all found to the
096     * {@link TypedGeneratorRegistry}
097     *
098     * @param annotated the class that may or may not provide the annotations, must
099     *                  not be null
100     */
101    public static void handlePropertyGenerator(final Class<?> annotated) {
102        for (final PropertyGenerator config : extractConfiguredPropertyGenerator(annotated)) {
103            for (final Class<?> typedClass : config.value()) {
104                TypedGeneratorRegistry
105                        .registerGenerator((TypedGenerator<?>) new DefaultInstantiator<>(typedClass).newInstance());
106            }
107        }
108    }
109
110    /**
111     * Checks whether the actual implementation of the test implements
112     * {@link TypedGenerator}. If so it will be registered to the
113     * {@link TypedGeneratorRegistry}
114     *
115     * @param testClass the actual test-object
116     */
117    public static void handleUnitClassImplementation(final Object testClass) {
118        if (testClass instanceof TypedGenerator<?> generator) {
119            TypedGeneratorRegistry.registerGenerator(generator);
120        }
121    }
122
123    /**
124     * Checks the given type for the annotation {@link PropertyGeneratorHint} and
125     * {@link PropertyGeneratorHints} and puts all found in the returned list
126     *
127     * @param annotated the class that may or may not provide the annotations, must
128     *                  not be null
129     * @return a {@link Set} of {@link VerifyConstructor} extracted from the
130     *         annotations of the given type. May be empty but never null
131     */
132    public static Set<PropertyGeneratorHint> extractConfiguredGeneratorHints(final Class<?> annotated) {
133        requireNonNull(annotated);
134        final var builder = new CollectionBuilder<PropertyGeneratorHint>();
135
136        MoreReflection.extractAllAnnotations(annotated, PropertyGeneratorHints.class)
137                .forEach(contract -> builder.add(Arrays.asList(contract.value())));
138        MoreReflection.extractAllAnnotations(annotated, PropertyGeneratorHint.class).forEach(builder::add);
139
140        return builder.toImmutableSet();
141    }
142
143    /**
144     * Checks the given type for the annotation {@link PropertyGenerator} and
145     * {@link PropertyGenerators} and puts all found in the returned list
146     *
147     * @param annotated the class that may or may not provide the annotations, must
148     *                  not be null
149     * @return a {@link Set} of {@link PropertyGenerator} extract from the
150     *         annotations of the given type. May be empty but never null
151     */
152    public static Set<PropertyGenerator> extractConfiguredPropertyGenerator(final Class<?> annotated) {
153        requireNonNull(annotated);
154        final var builder = new CollectionBuilder<PropertyGenerator>();
155
156        MoreReflection.extractAllAnnotations(annotated, PropertyGenerators.class)
157                .forEach(contract -> builder.add(Arrays.asList(contract.value())));
158        MoreReflection.extractAllAnnotations(annotated, PropertyGenerator.class).forEach(builder::add);
159
160        return builder.toImmutableSet();
161    }
162
163}