
com.yahoo.component.chain.model.ChainSpecification Maven / Gradle / Ivy
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.component.chain.model;
import com.google.common.collect.ImmutableSet;
import com.yahoo.component.ComponentId;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.chain.Phase;
import java.util.*;
/**
* Specifies how the components should be selected to create a chain. Immutable.
*
* @author Tony Vaagenes
*/
public class ChainSpecification {
public static class Inheritance {
public final Set chainSpecifications;
public final Set excludedComponents;
Inheritance flattened() {
return new Inheritance(Collections.emptySet(), excludedComponents);
}
public Inheritance(Set inheritedChains, Set excludedComponents) {
this.chainSpecifications = immutableCopy(inheritedChains);
this.excludedComponents = immutableCopy(excludedComponents);
}
public Inheritance addInherits(Collection inheritedChains) {
Set newChainSpecifications =
new LinkedHashSet<>(chainSpecifications);
newChainSpecifications.addAll(inheritedChains);
return new Inheritance(newChainSpecifications, excludedComponents);
}
}
public final ComponentId componentId;
public final Inheritance inheritance;
final Map phases;
public final Set componentReferences;
public ChainSpecification(ComponentId componentId, Inheritance inheritance,
Collection phases,
Set componentReferences) {
assertNotNull(componentId, inheritance, phases, componentReferences);
if (componentsByName(componentReferences).size() != componentReferences.size())
throw new RuntimeException("Two components with the same name are specified in '" + componentId +
"', but name must be unique inside a given chain.");
this.componentId = componentId;
this.inheritance = inheritance;
this.phases = copyPhasesImmutable(phases);
this.componentReferences = ImmutableSet.copyOf(
filterByComponentSpecification(componentReferences, inheritance.excludedComponents));
}
public ChainSpecification addComponents(Collection componentSpecifications) {
Set newComponentReferences = new LinkedHashSet<>(componentReferences);
newComponentReferences.addAll(componentSpecifications);
return new ChainSpecification(componentId, inheritance, phases(), newComponentReferences);
}
public ChainSpecification addInherits(Collection inheritedChains) {
return new ChainSpecification(componentId, inheritance.addInherits(inheritedChains), phases(), componentReferences);
}
public ChainSpecification setComponentId(ComponentId newComponentId) {
return new ChainSpecification(newComponentId, inheritance, phases(), componentReferences);
}
public ChainSpecification flatten(Resolver allChainSpecifications) {
Deque path = new ArrayDeque<>();
return flatten(allChainSpecifications, path);
}
/**
* @param allChainSpecifications resolves ChainSpecifications from ComponentSpecifications
* as given in the inheritance fields.
* @param path tracks which chains are used in each recursive invocation of flatten, used for detecting cycles.
* @return ChainSpecification directly containing all the component references and phases of the inherited chains.
*/
private ChainSpecification flatten(Resolver allChainSpecifications,
Deque path) {
path.push(componentId);
//if this turns out to be a bottleneck(which I seriously doubt), please add memoization
Map resultingComponents = componentsByName(componentReferences);
Map resultingPhases = new LinkedHashMap<>(phases);
for (ComponentSpecification inheritedChainSpecification : inheritance.chainSpecifications) {
ChainSpecification inheritedChain =
resolveChain(path, allChainSpecifications, inheritedChainSpecification).
flatten(allChainSpecifications, path);
mergeInto(resultingComponents,
filterByComponentSpecification(
filterByName(inheritedChain.componentReferences, names(componentReferences)),
inheritance.excludedComponents));
mergeInto(resultingPhases, inheritedChain.phases);
}
path.pop();
return new ChainSpecification(componentId, inheritance.flattened(), resultingPhases.values(),
new LinkedHashSet<>(resultingComponents.values()));
}
public Collection phases() {
return phases.values();
}
private static Set immutableCopy(Set set) {
if (set == null) return ImmutableSet.of();
return ImmutableSet.copyOf(set);
}
private static Map copyPhasesImmutable(Collection phases) {
Map result = new LinkedHashMap<>();
for (Phase phase : phases) {
Phase oldValue = result.put(phase.getName(), phase);
if (oldValue != null)
throw new RuntimeException("Two phases with the same name " + phase.getName() + " present in the same scope.");
}
return Collections.unmodifiableMap(result);
}
private static void assertNotNull(Object... objects) {
for (Object o : objects) {
assert(o != null);
}
}
static Map componentsByName(Set componentSpecifications) {
Map componentsByName = new LinkedHashMap<>();
for (ComponentSpecification component : componentSpecifications)
componentsByName.put(component.getName(), component);
return componentsByName;
}
private static void mergeInto(Map resultingComponents,
Set components) {
for (ComponentSpecification component : components) {
String name = component.getName();
if (resultingComponents.containsKey(name)) {
resultingComponents.put(name, component.intersect(resultingComponents.get(name)));
} else {
resultingComponents.put(name, component);
}
}
}
private static void mergeInto(Map resultingPhases, Map phases) {
for (Phase phase : phases.values()) {
String name = phase.getName();
if (resultingPhases.containsKey(name)) {
phase = phase.union(resultingPhases.get(name));
}
resultingPhases.put(name, phase);
}
}
private static Set names(Set components) {
Set names = new LinkedHashSet<>();
for (ComponentSpecification component : components) {
names.add(component.getName());
}
return names;
}
private static Set filterByComponentSpecification(Set components, Set excludes) {
Set result = new LinkedHashSet<>();
for (ComponentSpecification component : components) {
if (!matches(component, excludes))
result.add(component);
}
return result;
}
private static Set filterByName(Set components, Set names) {
Set result = new LinkedHashSet<>();
for (ComponentSpecification component : components) {
if (!names.contains(component.getName()))
result.add(component);
}
return result;
}
private static boolean matches(ComponentSpecification component, Set excludes) {
ComponentId id = component.toId().withoutNamespace();
for (ComponentSpecification exclude : excludes) {
if (exclude.matches(id)) {
return true;
}
}
return false;
}
private ChainSpecification resolveChain(Deque path,
Resolver allChainSpecifications,
ComponentSpecification chainSpecification) {
ChainSpecification chain = allChainSpecifications.resolve(chainSpecification);
if (chain == null) {
throw new RuntimeException("Missing chain '" + chainSpecification + "'.");
} else if (path.contains(chain.componentId)) {
throw new RuntimeException("The chain " + chain.componentId + " inherits(possibly indirectly) from itself.");
} else {
return chain;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy