boolivar/jdoc-test

View on GitHub
jdoc-spock-commons/src/main/java/org/bool/jdoc/spock/ClassIntrospector.java

Summary

Maintainability
A
0 mins
Test Coverage
package org.bool.jdoc.spock;

import org.bool.jdoc.spock.exception.SpockEngineException;

import lombok.SneakyThrows;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

public class ClassIntrospector {

    /**
     * Biggest constructor containing all non-final parameter types.
     * 
     * <pre><code lang="spock">
     * def "mockable constructor"() {
     *   when:
     *     def ctor = $target.findMockConstructor(ArrayList).get()
     *   then:
     *     ctor.parameterTypes == [Collection]
     * }
     * 
     * def "default constructor"() {
     *   when:
     *     def ctor = $target.findMockConstructor(String).get()
     *   then:
     *     ctor.parameterTypes == []
     * }
     * 
     * def "no mockable constructor found"() {
     *   when:
     *     def ctor = $target.findMockConstructor(java.time.LocalTime)
     *   then:
     *     !ctor.isPresent()
     * }
     * </code></pre>
     */
    @SneakyThrows
    public <T> Optional<Constructor<T>> findMockConstructor(Class<T> targetClass) {
        TreeMap<Integer, List<Constructor<?>>> ctors = Stream.of(targetClass.getDeclaredConstructors())
            .filter(this::canBeMocked)
            .collect(groupingBy(Constructor::getParameterCount, TreeMap::new, toList()));
        for (List<Constructor<?>> mockable : ctors.descendingMap().values()) {
            if (mockable.size() == 1) {
                return Optional.of(targetClass.getDeclaredConstructor(mockable.get(0).getParameterTypes()));
            }
            if (mockable.size() > 1) {
                throw new SpockEngineException(targetClass + " mockable constructors ambiguity: " + mockable);
            }
        }
        return Optional.empty();
    }

    private boolean canBeMocked(Constructor<?> ctor) {
        return !Modifier.isPrivate(ctor.getModifiers()) && Stream.of(ctor.getParameterTypes())
            .map(Class::getModifiers)
            .noneMatch(Modifier::isFinal);
    }
}