All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oneandone.iocunit.analyzer.Phase3Fixer Maven / Gradle / Ivy

package com.oneandone.iocunit.analyzer;

import static java.lang.Integer.min;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.enterprise.inject.Specializes;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author aschoerk
 */
public class Phase3Fixer extends PhasesBase {
    static Logger logger = LoggerFactory.getLogger(Phase3Fixer.class);

    public Phase3Fixer(Configuration configuration) {
        super(configuration);
    }

    public void work() {
        configuration.setPhase(Configuration.Phase.FIXING);
        logger.trace("Phase3Fixer starting");
        HashSet> newCandidates = new HashSet<>();
        HashMultiMap> ambiguus = new HashMultiMap<>();
        HashMap injectsDone = new HashMap<>();
        for (QualifiedType inject : configuration.getInjects()) {
            Set matching = configuration.getAvailableProducerMap().findMatchingProducersRegardingAlternatives(inject);
            if (matching.size() == 0)
                continue;
            matching = matching
                    .stream()
                    .filter(q -> !configuration.isExcluded(q.getDeclaringClass()))
                    .collect(Collectors.toSet());
            if (matching.size() == 1) {
                QualifiedType producer = matching.iterator().next();
                Class declaringClass = producer.getDeclaringClass();
                if (configuration.isToBeStarted(declaringClass)) {
                    logger.error("Declaring Class {} already to be started", declaringClass.getName());
                } else if (newCandidates.contains(declaringClass)) {
                        logger.trace("Declaring Class {} already in new candidates", declaringClass.getName());
                } else {
                    logger.trace("Adding declaring Class {} to new candidates", declaringClass.getName());
                    configuration.candidate(declaringClass);
                    newCandidates.add(declaringClass);
                }
                injectsDone.put(inject, producer);
            } else {
                if (inject.isInstance()) {
                    // add everything fitting if Instance-Inject
                    for (QualifiedType q: matching) {
                        addToCandidates(newCandidates, q.getDeclaringClass());
                        // TODO: add to classes2injects
                    }
                } else {  // Search for specializing and make them higher prior
                    List> specializingCandidates  = new ArrayList<>();
                    QualifiedType firstQ = null;
                    for (QualifiedType q: matching) {
                        if (q.getDeclaringClass().getAnnotation(Specializes.class) != null) {
                            specializingCandidates.add(q.getDeclaringClass());
                            firstQ = q;
                        }
                    }
                    if (specializingCandidates.size() == 1) {
                        addToCandidates(newCandidates, specializingCandidates.get(0));
                        injectsDone.put(inject, firstQ);
                    } else if (specializingCandidates.size() > 1) {
                        matching = matching
                                .stream()
                                .filter(q -> q.getDeclaringClass().getAnnotation(Specializes.class) != null)
                                .collect(Collectors.toSet());
                    }

                }
            }
        }

        injectsDone.entrySet().forEach(entry -> configuration.injectHandled(entry.getKey(), entry.getValue()));

        for (QualifiedType inject : configuration.getInjects()) {
            if (inject.isInstance())
                continue;
            Set matching = configuration.getAvailableProducerMap().findMatchingProducersRegardingAlternatives(inject);
            if (matching.size() == 0)
                continue;
            matching = matching
                    .stream()
                    .filter(q -> !configuration.isExcluded(q.getDeclaringClass()))
                    .collect(Collectors.toSet());
            Map> testClassBacked = matching
                    .stream()
                    .collect(Collectors.groupingBy(
                            match -> configuration.isTestClass(match.getDeclaringClass())));
            final List testClassBackedProducers = testClassBacked.get(true);
            if(testClassBackedProducers != null && testClassBackedProducers.size() > 0) {
                if(testClassBackedProducers.size() > 1) {
                    logger.error("More than one available Testclass available to produce: {}", inject);
                }
                final Class declaringClass = testClassBackedProducers.iterator().next().getDeclaringClass();
                logger.trace("Selected test class: {}", declaringClass.getName());
                configuration.candidate(declaringClass);
            }
            else {
                final List sutClassBackedProducers = testClassBacked.get(false);
                if(sutClassBackedProducers != null) {
                    if (sutClassBackedProducers.size() > 1) {
                        logger.warn("More than one available Sutclass available to produce: {}", inject);
                        if (ConfigStatics.isParameterizedType(inject.getDeclaringClass())) {
                            for (QualifiedType q : sutClassBackedProducers) {
                                logger.warn("-- : {}", q);
                                addToCandidates(newCandidates, q.getDeclaringClass());
                            }
                            logger.warn("Added all of them to candidates since declaring class is generic.");
                        } else {
                            Optional oneAlreadyThere = sutClassBackedProducers
                                    .stream()
                                    .filter(q -> configuration.isToBeStarted(q.getDeclaringClass()) ||
                                                 configuration.isCandidate(q.getDeclaringClass()))
                                    .findAny();
                            if(oneAlreadyThere.isPresent()) {
                                logger.warn("Chose one because of backing class {} already there", oneAlreadyThere.get());
                            }
                            else {
                                sutClassBackedProducers.forEach(q -> {
                                    ambiguus.put(inject, q.getDeclaringClass());
                                });
                            }
                        }
                    } else {
                        final Class declaringClass = sutClassBackedProducers.iterator().next().getDeclaringClass();
                        logger.trace("Selected sut class: {}", declaringClass.getName());
                        addToCandidates(newCandidates, declaringClass);
                    }
                }
            }
        }
        if (ambiguus.size() > 0) {
            /**
             * If two different classes are started which back producers to the same inject it comes to ambiguity problems
             * So search for that case.
             * Search for the best solution: the backing class which is usable for the most injects.
             */
            List>> classesArrayList = new ArrayList();

            for (QualifiedType inject : ambiguus.keySet()) {
                ArrayList> al = new ArrayList<>();
                al.addAll(ambiguus.get(inject));
                classesArrayList.add(al);
            }

            Set> solution = optimizeUsage(classesArrayList);
            if (solution.isEmpty())
                logger.error("Did not find solution");
            else {
                if (logger.isTraceEnabled()) {
                    for (Class c: solution) {
                        logger.trace("Chose: {}", c.getName());
                    }
                }
            }
            for (Class c: solution) {
                logger.trace("Optimize usage selected test? {} class: {}", configuration.isTestClass(c), c);
                configuration.candidate(c);
            }

        }
        logger.trace("Phase3Fixer ready");

    }

    private void addToCandidates(final HashSet> newCandidates, final Class declaringClass) {
        if (newCandidates.contains(declaringClass)) {
            logger.trace("Declaring Class {} already in new candidates", declaringClass.getName());
        } else {
            newCandidates.add(declaringClass);
            configuration.candidate(declaringClass);
        }
    }

    /**
     * for each entry of input choose one element from the candidates
     * @param input testerExtensionsConfigsFinder list of non empty sets of unique candidates. These sets might share elements.
     * @param  the type of the elements
     * @return the combination of single elements of the sets so that: if two sets share  elements, the solution
     * for these two sets must contain exactly one of these shared elements.
     */
    public  Set optimizeUsage(List> input) {
        int entrynum = input.size();
        int pos[] = new int[entrynum];
        ArrayList solution = new ArrayList();

        ArrayList> classesArrayList = new ArrayList();
        ArrayList> orgClassesArrayList = new ArrayList();
        int index = 0;
        for (List t : input) {
            ArrayList al = new ArrayList<>();
            al.addAll(t);
            classesArrayList.add(al);
            orgClassesArrayList.add((ArrayList) al.clone());
            pos[index] = 0;
            solution.add(null);
        }
        boolean doBackTrack = false;
        int currentIndex = min(0,classesArrayList.size() - 1);

        while (currentIndex >= 0 && currentIndex < entrynum) {
            doBackTrack = false;
            ArrayList classes = (ArrayList) classesArrayList.get(currentIndex).clone();
            T chosen = classes.get(pos[currentIndex]);
            solution.set(currentIndex, chosen);
            for (int j = currentIndex+1; j < entrynum; j++) {
                List entry = classesArrayList.get(j);
                if (entry.contains(chosen)) {
                    classes.addAll(entry);
                    entry.clear();
                    entry.add(chosen);
                }
            }
            for (int j = currentIndex+1; j < entrynum; j++) {
                List entry = classesArrayList.get(j);
                if (!entry.contains(chosen)) {
                    for (T c : classes) {
                        entry.remove(c);
                    }
                    if (entry.isEmpty()) {
                        doBackTrack = true;
                    }
                }
            }


            if (doBackTrack) {
                for (int j = currentIndex+1; j < entrynum; j++) {
                    classesArrayList.set(j, (ArrayList) (orgClassesArrayList.get(j).clone()));
                }
                if (pos[currentIndex] >= classesArrayList.get(currentIndex).size()-1) {
                    pos[currentIndex] = 0;  // reset
                    classesArrayList.set(currentIndex, (ArrayList) (orgClassesArrayList.get(currentIndex).clone()));
                    solution.set(currentIndex, null); // fix solution
                    currentIndex --;
                    if (currentIndex < 0)
                        return Collections.EMPTY_SET;
                } else {
                    pos[currentIndex]++;
                }
            } else {
                currentIndex ++;
            }
        }
        return solution.stream().collect(Collectors.toSet());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy