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.generator.dynamic;
017
018import static java.util.Objects.requireNonNull;
019
020import java.util.Collection;
021import java.util.Map;
022import java.util.Optional;
023
024import de.cuioss.test.generator.Generators;
025import de.cuioss.test.generator.TypedGenerator;
026import de.cuioss.test.valueobjects.generator.TypedGeneratorRegistry;
027import de.cuioss.test.valueobjects.generator.dynamic.impl.ArraysGenerator;
028import de.cuioss.test.valueobjects.generator.dynamic.impl.CollectionTypeGenerator;
029import de.cuioss.test.valueobjects.generator.dynamic.impl.ConstructorBasedGenerator;
030import de.cuioss.test.valueobjects.generator.dynamic.impl.DynamicProxyGenerator;
031import de.cuioss.test.valueobjects.generator.dynamic.impl.EmptyMapGenerator;
032import de.cuioss.test.valueobjects.generator.dynamic.impl.InterfaceProxyGenerator;
033import de.cuioss.test.valueobjects.property.util.CollectionType;
034import de.cuioss.tools.logging.CuiLogger;
035import lombok.AccessLevel;
036import lombok.NoArgsConstructor;
037
038/**
039 * Provides strategies for creating objects dynamically
040 *
041 * @author Oliver Wolff
042 */
043@NoArgsConstructor(access = AccessLevel.PRIVATE)
044public final class GeneratorResolver {
045
046    private static final String FOUND_GENERATOR_FOR_TYPE = "Found generator {} for type {}";
047
048    private static final String TYPE_MUST_NOT_BE_NULL = "type must not be null";
049
050    private static final CuiLogger log = new CuiLogger(GeneratorResolver.class);
051
052    /**
053     * Central method for finding / accessing a concrete {@link TypedGenerator} for
054     * the given type. It works through all existing find methods. as last resort is
055     * uses {@link InterfaceProxyGenerator} or {@link DynamicProxyGenerator} that
056     * will always return a valid one <em>Caution:</em> The resolving system relies
057     * on {@link TypedGeneratorRegistry} being configured properly, saying
058     * {@link TypedGeneratorRegistry#registerBasicTypes()} has been called prior to
059     * this method
060     *
061     * @param type must not be null
062     * @return a concrete {@link TypedGenerator} for the given type
063     */
064    public static <T> TypedGenerator<T> resolveGenerator(final Class<T> type) {
065        requireNonNull(type, TYPE_MUST_NOT_BE_NULL);
066        log.debug("resolving generator for {}", type.getName());
067
068        Optional<TypedGenerator<T>> found = TypedGeneratorRegistry.getGenerator(type);
069        if (found.isPresent()) {
070            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
071            return found.get();
072        }
073        found = Generators.enumValuesIfAvailable(type);
074        if (found.isPresent()) {
075            TypedGeneratorRegistry.registerGenerator(found.get());
076            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
077            return found.get();
078        }
079        found = ArraysGenerator.getGeneratorForType(type);
080        if (found.isPresent()) {
081            TypedGeneratorRegistry.registerGenerator(found.get());
082            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
083            return found.get();
084        }
085        found = resolveCollectionGenerator(type);
086        if (found.isPresent()) {
087            TypedGeneratorRegistry.registerGenerator(found.get());
088            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
089            return found.get();
090        }
091        found = ConstructorBasedGenerator.getGeneratorForType(type);
092        if (found.isPresent()) {
093            TypedGeneratorRegistry.registerGenerator(found.get());
094            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
095            return found.get();
096        }
097        return resolveProxyGenerator(type);
098    }
099
100    private static <T> TypedGenerator<T> resolveProxyGenerator(final Class<T> type) {
101        log.debug("resolveProxyGenerator for type {}", type.getName());
102        Optional<TypedGenerator<T>> found = InterfaceProxyGenerator.getGeneratorForType(type);
103        if (found.isPresent()) {
104            TypedGeneratorRegistry.registerGenerator(found.get());
105            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
106            return found.get();
107        }
108        found = DynamicProxyGenerator.getGeneratorForType(type);
109        if (found.isPresent()) {
110            TypedGeneratorRegistry.registerGenerator(found.get());
111            log.trace(FOUND_GENERATOR_FOR_TYPE, found.get().getClass().getName(), type.getName());
112            return found.get();
113        }
114        throw new IllegalArgumentException("Unable to determine generator for type=" + type);
115    }
116
117    /**
118     * Provides a {@link TypedGenerator} for generating empty {@link Iterable} /
119     * {@link Collection} or {@link Map}s for given interfaces
120     *
121     * @param type to be checked
122     * @return an {@link TypedGenerator} if applicable or or <code>not
123     *         {@link Optional#isPresent()}</code>
124     */
125    @SuppressWarnings("unchecked") // Checked beforehand
126    public static <T> Optional<TypedGenerator<T>> resolveCollectionGenerator(final Class<T> type) {
127        if (null == type || !type.isInterface()) {
128            return Optional.empty();
129        }
130        final var optional = CollectionType.findResponsibleCollectionType(type);
131        if (optional.isPresent()) {
132            return Optional.of(new CollectionTypeGenerator<>(type, optional.get()));
133        }
134        if (Map.class.isAssignableFrom(type)) {
135            return Optional.of((TypedGenerator<T>) new EmptyMapGenerator());
136        }
137        return Optional.empty();
138    }
139}