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

com.yahoo.config.model.graph.ModelNode Maven / Gradle / Ivy

There is a newer version: 8.409.18
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.model.graph;

import com.yahoo.component.ComponentId;
import com.yahoo.config.model.ConfigModel;
import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.model.ConfigModelInstanceFactory;
import com.yahoo.config.model.builder.xml.ConfigModelBuilder;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents a node in the dependency graph, and contains information about a builders dependencies.
 * Constructor signatures of model classes must have ConfigModelContext as the first argument and
 * ConfigModel subclasses or Collection of ConfigModels as subsequent arguments.
 * Only Collection, not Collection subtypes can be used.
 *
 * @author Ulf Lilleengen
 */
public class ModelNode implements ConfigModelInstanceFactory {

    final ComponentId id;
    public final ConfigModelBuilder builder;
    final Class clazz;
    final Constructor constructor;
    final List instances = new ArrayList<>();
    private final Map dependencies = new HashMap<>();

    public ModelNode(ConfigModelBuilder builder) {
        this.id = builder.getId();
        this.builder = builder;
        this.clazz = builder.getModelClass();
        this.constructor = findConstructor(clazz);
    }

    private Constructor findConstructor(Class clazz) {
        for (Constructor ctor : clazz.getDeclaredConstructors()) {
            if (ctor.getAnnotation(com.google.inject.Inject.class) != null
                    || ctor.getAnnotation(com.yahoo.component.annotation.Inject.class) != null) {
                return (Constructor) ctor;
            }
        }
        return (Constructor) clazz.getDeclaredConstructors()[0];
    }

    boolean hasDependencies() {
        return !dependencies.isEmpty();
    }

    boolean dependsOn(ModelNode node) {
        return dependencies.containsKey(node.id);
    }

    /**
     * This adds dependencies base on constructor arguments in the model classes themselves.
     * These then have to be created by this mini-di framework and then handed to the builders
     * that will fill them.
     *
     * TODO: This should be changed to model dependencies between model builders instead, such
     * that they can create their model objects, and eventually make them immutable.
     */
    int addDependenciesFrom(List modelNodes) {
        int numDependencies = 0;
        for (Type param : constructor.getGenericParameterTypes()) {
            for (ModelNode node : modelNodes) {
                if (param.equals(node.clazz) || isCollectionOf(param, node.clazz)) {
                    addDependency(node);
                    numDependencies++;
                }
            }
        }
        return numDependencies;
    }

    private boolean isCollectionOf(Type type, Class nodeClazz) {
        if (type instanceof ParameterizedType) {
            ParameterizedType t = (ParameterizedType) type;
            // Note: IntelliJ says the following cannot be equal but that is wrong
            return (t.getRawType().equals(java.util.Collection.class) && t.getActualTypeArguments().length == 1 && t.getActualTypeArguments()[0].equals(nodeClazz));
        }
        return false;
    }

    private boolean isCollection(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType t = (ParameterizedType) type;
            // Note: IntelliJ says the following cannot be equal but that is wrong
            return (t.getRawType().equals(java.util.Collection.class) && t.getActualTypeArguments().length == 1);
        }
        return false;
    }

    private void addDependency(ModelNode node) {
        dependencies.put(node.id, node);
    }

    Collection listDependencyIds() {
        return dependencies.keySet();
    }

    @Override
    public MODEL createModel(ConfigModelContext context) {
        try {
            Type [] params = constructor.getGenericParameterTypes();
            if (params.length < 1 || ! params[0].equals(ConfigModelContext.class)) {
                throw new IllegalArgumentException("Constructor for " + clazz.getName() + " must have as its first argument a " + ConfigModelContext.class.getName());
            }
            Object arguments[] = new Object[params.length];
            arguments[0] = context;
            for (int i = 1; i < params.length; i++)
                arguments[i] = findArgument(params[i]);
            MODEL instance = constructor.newInstance(arguments);
            instances.add(instance);
            return instance;
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("Error constructing model '" + clazz.getName() + "'", e);
        }
    }

    private Object findArgument(Type param) {
        for (ModelNode dependency : dependencies.values()) {
            if (param.equals(dependency.clazz))
                return dependency.instances.get(0);
            if (isCollectionOf(param, dependency.clazz))
                return Collections.unmodifiableCollection(dependency.instances);
        }
        // For collections, we don't require that dependency has been added, we just give an empty collection
        if (isCollection(param))
            return List.of();
        throw new IllegalArgumentException("Unable to find constructor argument " + param + " for " + clazz.getName());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy