<commit_before>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.di.componentgraph.core;

import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.BindingAnnotation;
import com.google.inject.ConfigurationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.yahoo.collections.Pair;
import com.yahoo.component.ComponentId;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.config.ConfigInstance;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.container.di.componentgraph.cycle.CycleFinder;
import com.yahoo.container.di.componentgraph.cycle.Graph;
import com.yahoo.vespa.config.ConfigKey;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static com.yahoo.container.di.componentgraph.core.Exceptions.removeStackTrace;

/**
 * @author <NAME>
 * @author gjoranv
 * @author ollivir
 *
 * Not thread safe.
 */
public class ComponentGraph {

    private static final Logger log = Logger.getLogger(ComponentGraph.class.getName());

    private final long generation;
    private final Map<ComponentId, Node> nodesById = new HashMap<>();

    public ComponentGraph(long generation) {
        this.generation = generation;
    }

    public ComponentGraph() {
        this(0L);
    }

    public long generation() {
        return generation;
    }

    public int size() {
        return nodesById.size();
    }

    public Collection<Node> nodes() {
        return nodesById.values();
    }

    public void add(Node component) {
        if (nodesById.containsKey(component.componentId())) {
            throw new IllegalStateException("Multiple components with the same id " + component.componentId());
        }
        nodesById.put(component.componentId(), component);
    }

    private Optional<Node> lookupGlobalComponent(Key<?> key) {
        if (!(key.getTypeLiteral().getType() instanceof Class)) {
            throw new RuntimeException("Type not supported " + key.getTypeLiteral());
        }
        Class<?> clazz = key.getTypeLiteral().getRawType();

        Collection<ComponentNode> components = matchingComponentNodes(nodes(), key);
        if (components.isEmpty()) {
            return Optional.empty();
        } else if (components.size() == 1) {
            return Optional.ofNullable(Iterables.get(components, 0));
        } else {

            List<Node> nonProviderComponents = components.stream().filter(c -> !Provider.class.isAssignableFrom(c.instanceType()))
                    .collect(Collectors.toList());
            if (nonProviderComponents.isEmpty()) {
                throw new IllegalStateException("Multiple global component providers for class '" + clazz.getName() + "' found :" + components);
            } else if (nonProviderComponents.size() == 1) {
                return Optional.of(nonProviderComponents.get(0));
            } else {
                throw new IllegalStateException("Multiple global components with class '" + clazz.getName() + "' found : " + nonProviderComponents);
            }
        }
    }

    public <T> T getInstance(Class<T> clazz) {
        return getInstance(Key.get(clazz));
    }

    @SuppressWarnings("unchecked")
    public <T> T getInstance(Key<T> key) {
        // TODO: Combine exception handling with lookupGlobalComponent.
        Object ob = lookupGlobalComponent(key).map(Node::component)
                .orElseThrow(() -> new IllegalStateException(String.format("No global component with key '%s'  ", key)));
        return (T) ob;
    }

    private Collection<ComponentNode> componentNodes() {
        return nodesOfType(nodes(), ComponentNode.class);
    }

    private Collection<ComponentRegistryNode> componentRegistryNodes() {
        return nodesOfType(nodes(), ComponentRegistryNode.class);
    }

    private Collection<ComponentNode> osgiComponentsOfClass(Class<?> clazz) {
        return componentNodes().stream().filter(node -> clazz.isAssignableFrom(node.componentType())).collect(Collectors.toList());
    }

    public List<Node> complete(Injector fallbackInjector) {
        componentNodes().forEach(node -> completeNode(node, fallbackInjector));
        componentRegistryNodes().forEach(this::completeComponentRegistryNode);
        return topologicalSort(nodes());
    }

    public List<Node> complete() {
        return complete(Guice.createInjector());
    }

    public Set<ConfigKey<? extends ConfigInstance>> configKeys() {
        return nodes().stream().flatMap(node -> node.configKeys().stream()).collect(Collectors.toSet());
    }

    public void setAvailableConfigs(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configs) {
        componentNodes().forEach(node -> node.setAvailableConfigs(Keys.invariantCopy(configs)));
    }

    public void reuseNodes(ComponentGraph old) {
        //  copy instances if node equal
        Set<ComponentId> commonComponentIds = Sets.intersection(nodesById.keySet(), old.nodesById.keySet());
        for (ComponentId id : commonComponentIds) {
            if (nodesById.get(id).equals(old.nodesById.get(id))) {
                nodesById.get(id).instance = old.nodesById.get(id).instance;
            }
        }

        // reset instances with modified dependencies
        for (Node node : topologicalSort(nodes())) {
            for (Node usedComponent : node.usedComponents()) {
                if (usedComponent.instance.isEmpty()) {
                    node.instance = Optional.empty();
                }
            }
        }
    }

    /** All constructed components and providers of this, in reverse creation order, i.e., suited for ordered deconstruction. */
    public List<Object> allConstructedComponentsAndProviders() {
        List<Node> orderedNodes = topologicalSort(nodes());
        Collections.reverse(orderedNodes);
        return orderedNodes.stream().map(node -> node.constructedInstance().get()).collect(Collectors.toList());
    }

    private void completeComponentRegistryNode(ComponentRegistryNode registry) {
        registry.injectAll(osgiComponentsOfClass(registry.componentClass()));
    }

    private void completeNode(ComponentNode node, Injector fallbackInjector) {
        try {
            Object[] arguments = node.getAnnotatedConstructorParams().stream().map(param -> handleParameter(node, fallbackInjector, param))
                    .toArray();

            node.setArguments(arguments);
        } catch (Exception e) {
            throw removeStackTrace(new RuntimeException("When resolving dependencies of " + node.idAndType(), e));
        }
    }

    private Object handleParameter(Node node, Injector fallbackInjector, Pair<Type, List<Annotation>> annotatedParameterType) {
        Type parameterType = annotatedParameterType.getFirst();
        List<Annotation> annotations = annotatedParameterType.getSecond();<commit_msg>Filter out components not constructed<commit_after>// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.di.componentgraph.core;

import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.BindingAnnotation;
import com.google.inject.ConfigurationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.yahoo.collections.Pair;
import com.yahoo.component.ComponentId;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.config.ConfigInstance;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.container.di.componentgraph.cycle.CycleFinder;
import com.yahoo.container.di.componentgraph.cycle.Graph;
import com.yahoo.vespa.config.ConfigKey;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static com.yahoo.container.di.componentgraph.core.Exceptions.removeStackTrace;

/**
 * @author <NAME>
 * @author gjoranv
 * @author ollivir
 *
 * Not thread safe.
 */
public class ComponentGraph {

    private static final Logger log = Logger.getLogger(ComponentGraph.class.getName());

    private final long generation;
    private final Map<ComponentId, Node> nodesById = new HashMap<>();

    public ComponentGraph(long generation) {
        this.generation = generation;
    }

    public ComponentGraph() {
        this(0L);
    }

    public long generation() {
        return generation;
    }

    public int size() {
        return nodesById.size();
    }

    public Collection<Node> nodes() {
        return nodesById.values();
    }

    public void add(Node component) {
        if (nodesById.containsKey(component.componentId())) {
            throw new IllegalStateException("Multiple components with the same id " + component.componentId());
        }
        nodesById.put(component.componentId(), component);
    }

    private Optional<Node> lookupGlobalComponent(Key<?> key) {
        if (!(key.getTypeLiteral().getType() instanceof Class)) {
            throw new RuntimeException("Type not supported " + key.getTypeLiteral());
        }
        Class<?> clazz = key.getTypeLiteral().getRawType();

        Collection<ComponentNode> components = matchingComponentNodes(nodes(), key);
        if (components.isEmpty()) {
            return Optional.empty();
        } else if (components.size() == 1) {
            return Optional.ofNullable(Iterables.get(components, 0));
        } else {

            List<Node> nonProviderComponents = components.stream().filter(c -> !Provider.class.isAssignableFrom(c.instanceType()))
                    .collect(Collectors.toList());
            if (nonProviderComponents.isEmpty()) {
                throw new IllegalStateException("Multiple global component providers for class '" + clazz.getName() + "' found :" + components);
            } else if (nonProviderComponents.size() == 1) {
                return Optional.of(nonProviderComponents.get(0));
            } else {
                throw new IllegalStateException("Multiple global components with class '" + clazz.getName() + "' found : " + nonProviderComponents);
            }
        }
    }

    public <T> T getInstance(Class<T> clazz) {
        return getInstance(Key.get(clazz));
    }

    @SuppressWarnings("unchecked")
    public <T> T getInstance(Key<T> key) {
        // TODO: Combine exception handling with lookupGlobalComponent.
        Object ob = lookupGlobalComponent(key).map(Node::component)
                .orElseThrow(() -> new IllegalStateException(String.format("No global component with key '%s'  ", key)));
        return (T) ob;
    }

    private Collection<ComponentNode> componentNodes() {
        return nodesOfType(nodes(), ComponentNode.class);
    }

    private Collection<ComponentRegistryNode> componentRegistryNodes() {
        return nodesOfType(nodes(), ComponentRegistryNode.class);
    }

    private Collection<ComponentNode> osgiComponentsOfClass(Class<?> clazz) {
        return componentNodes().stream().filter(node -> clazz.isAssignableFrom(node.componentType())).collect(Collectors.toList());
    }

    public List<Node> complete(Injector fallbackInjector) {
        componentNodes().forEach(node -> completeNode(node, fallbackInjector));
        componentRegistryNodes().forEach(this::completeComponentRegistryNode);
        return topologicalSort(nodes());
    }

    public List<Node> complete() {
        return complete(Guice.createInjector());
    }

    public Set<ConfigKey<? extends ConfigInstance>> configKeys() {
        return nodes().stream().flatMap(node -> node.configKeys().stream()).collect(Collectors.toSet());
    }

    public void setAvailableConfigs(Map<ConfigKey<? extends ConfigInstance>, ConfigInstance> configs) {
        componentNodes().forEach(node -> node.setAvailableConfigs(Keys.invariantCopy(configs)));
    }

    public void reuseNodes(ComponentGraph old) {
        //  copy instances if node equal
        Set<ComponentId> commonComponentIds = Sets.intersection(nodesById.keySet(), old.nodesById.keySet());
        for (ComponentId id : commonComponentIds) {
            if (nodesById.get(id).equals(old.nodesById.get(id))) {
                nodesById.get(id).instance = old.nodesById.get(id).instance;
            }
        }

        // reset instances with modified dependencies
        for (Node node : topologicalSort(nodes())) {
            for (Node usedComponent : node.usedComponents()) {
                if (usedComponent.instance.isEmpty()) {
                    node.instance = Optional.empty();
                }
            }
        }
    }

    /** All constructed components and providers of this, in reverse creation order, i.e., suited for ordered deconstruction. */
    public List<Object> allConstructedComponentsAndProviders() {
        List<Node> orderedNodes = topologicalSort(nodes());
        Collections.reverse(orderedNodes);
        return orderedNodes.stream()
                .filter(node -> node.constructedInstance().isPresent())
                .map(node -> node.constructedInstance().orElseThrow())
                .collect(Collectors.toList());
    }

    private void completeComponentRegistryNode(ComponentRegistryNode registry) {
        registry.injectAll(osgiComponentsOfClass(registry.componentClass()));
    }

    private void completeNode(ComponentNode node, Injector fallbackInjector) {
        try {
            Object[] arguments = node.getAnnotatedConstructorParams().stream().map(param -> handleParameter(node, fallbackInjector, param))
                    .toArray();

            node.setArguments(arguments);
        } catch (Exception e) {
            throw removeStackTrace(new RuntimeException("When resolving dependencies of " + node.idAndType(), e));
        }
    }

    private Object handleParameter(Node node, Injector fallbackInjector, Pair<Type, List<Annotation>> annotatedParameterType) {
        Type parameterType = annotatedParameterType.getFirst();
        List<Annotation> annotations = annotatedParameterType.getSecond();
{'fraction_non_alphanumeric': 0.11442358540454786, 'fraction_numerical': 0.0009254362771020624, 'mean_word_length': 3.288265306122449, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 78, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '264030', 'n_tokens_mistral': 3951, 'n_tokens_neox': 3842, 'n_words': 849}