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

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