com.yahoo.config.model.graph.ModelNode Maven / Gradle / Ivy
// 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 t) {
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 t) {
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 - 2025 Weber Informatics LLC | Privacy Policy