aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/component/chain/dependencies/ordering/ChainBuilder.java
blob: fef23f3cc2737e7e30cb6c131084b7847c517ffd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.component.chain.dependencies.ordering;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.Chain;
import com.yahoo.component.chain.ChainedComponent;
import com.yahoo.component.chain.Phase;


/**
 * Given a set of phases and a set of components,
 * a ordered list of components satisfying the dependencies is given if possible.
 * <p>
 * The phase list implicitly defines the ordering:
 *     {@literal if i  < j : p_i before p_j  where i,j are valid indexes of the phrase list p.}
 * <p>
 * If multiple components provide the same name, ALL the components providing
 * the same name must be placed earlier/later than an entity depending on
 * that name.
 * <p>
 * A warning will be logged if multiple components of different types provides the
 * same name. A component can not provide the same name as a phase.
 *
 * @author Tony Vaagenes
 */
public class ChainBuilder<T extends ChainedComponent> {

    private final ComponentId id;
    private int numComponents = 0;
    private int priority = 1;

    private Map<String, NameProvider> nameProviders =
            new LinkedHashMap<>();

    private Node allPhase;

    public ChainBuilder(ComponentId id) {
        this.id = id;
        allPhase = addPhase(new Phase("*", set("*"), Set.of()));
    }

    private Set<String> set(String... s) {
        return new HashSet<>(Arrays.asList(s));
    }

    public PhaseNameProvider addPhase(Phase phase) {
        NameProvider nameProvider = nameProviders.get(phase.getName());
        if (nameProvider instanceof ComponentNameProvider) {
            throw new ConflictingNodeTypeException("Cannot add phase '" + phase.getName() + "' as it is already provided by " + nameProvider);
        }
        PhaseNameProvider phaseNameProvider;
        if(nameProvider == null) {
            phaseNameProvider = new PhaseNameProvider(phase.getName(), priority++);
        } else {
            phaseNameProvider = (PhaseNameProvider) nameProvider;
        }
        nameProviders.put(phase.getName(), phaseNameProvider);
        for(String before : phase.before()) {
            phaseNameProvider.before(getPhaseNameProvider(before));
        }
        for(String after : phase.after()) {
            getPhaseNameProvider(after).before(phaseNameProvider);
        }

        return phaseNameProvider;
    }

    public void addComponent(ChainedComponent component) {
        ComponentNode<ChainedComponent> componentNode = new ComponentNode<>(component, priority++);

        ensureProvidesNotEmpty(component);
        for (String name : component.getDependencies().provides()) {
            NameProvider nameProvider = getNameProvider(name);

            nameProvider.addNode(componentNode);
        }

        for (String before : component.getDependencies().before()) {
            componentNode.before(getNameProvider(before));
        }

        for (String after : component.getDependencies().after()) {
            getNameProvider(after).before(componentNode);
        }

        ++numComponents;
    }

    //destroys this dependency handler in the process
    @SuppressWarnings("unchecked")
    public Chain<T> orderNodes() {
        List<T> chain = new ArrayList<>();
        OrderedReadyNodes readyNodes = getReadyNodes();

        while (!readyNodes.isEmpty() || popAllPhase(readyNodes) ) {
            Node candidate = readyNodes.pop();

            candidate.removed(readyNodes);

            if ( candidate instanceof ComponentNode)
                chain.add(((ComponentNode<T>)candidate).getComponent());
        }

        if (  chain.size() != numComponents)
            throw new CycleDependenciesException(nameProviders);

        //prevent accidental reuse
        nameProviders = null;

        return new Chain<>(id, chain);
    }

    private void ensureProvidesNotEmpty(ChainedComponent component) {
        if (component.getDependencies().provides().isEmpty()) {
            throw new RuntimeException("The component " + component.getId() + " did not provide anything.");
        }
    }

    private Node getPhaseNameProvider(String name) {
        NameProvider nameProvider = nameProviders.get(name);
        if (nameProvider != null)
            return nameProvider;
        else {
            nameProvider = new PhaseNameProvider(name, priority++);
            nameProviders.put(name, nameProvider);
            return nameProvider;
        }
    }

    private boolean popAllPhase(OrderedReadyNodes readyNodes) {
        if (allPhase == null) {
            return false;
        } else {
            Node phase = allPhase;
            allPhase = null;
            phase.removed(readyNodes);
            return !readyNodes.isEmpty();
        }
    }

    private NameProvider getNameProvider(String name) {
        NameProvider nameProvider = nameProviders.get(name);
        if (nameProvider != null)
            return nameProvider;
        else {
            nameProvider = new ComponentNameProvider(name);
            nameProviders.put(name, nameProvider);
            return nameProvider;
        }
    }

    private OrderedReadyNodes getReadyNodes() {
        OrderedReadyNodes readyNodes = new OrderedReadyNodes();
        for (Node node : nameProviders.values() ) {
            if (node.ready())
                readyNodes.add(node);
        }
        return readyNodes;
    }

}