aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java
Publish
Diffstat (limited to 'config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java')
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java135
1 files changed, 135 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java b/config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java
new file mode 100644
index 00000000000..0cd045b09c8
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/config/model/graph/ModelNode.java
@@ -0,0 +1,135 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.model.graph;
+
+import com.google.inject.Inject;
+import com.yahoo.component.ComponentId;
+import com.yahoo.config.model.ConfigModel;
+import com.yahoo.config.model.ConfigModelContext;
+import com.yahoo.config.model.builder.xml.ConfigModelBuilder;
+import com.yahoo.config.model.ConfigModelInstanceFactory;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * 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 lulf
+ * @since 5.1
+ */
+public class ModelNode<MODEL extends ConfigModel> implements ConfigModelInstanceFactory<MODEL> {
+
+ final ComponentId id;
+ public final ConfigModelBuilder<MODEL> builder;
+ final Class<MODEL> clazz;
+ final Constructor<MODEL> constructor;
+ final List<MODEL> instances = new ArrayList<>();
+ private final Map<ComponentId, ModelNode> dependencies = new HashMap<>();
+
+ public ModelNode(ConfigModelBuilder<MODEL> builder) {
+ this.id = builder.getId();
+ this.builder = builder;
+ this.clazz = builder.getModelClass();
+ this.constructor = findConstructor(clazz);
+ }
+
+ private Constructor<MODEL> findConstructor(Class<MODEL> clazz) {
+ for (Constructor<?> ctor : clazz.getDeclaredConstructors()) {
+ if (ctor.getAnnotation(Inject.class) != null) {
+ return (Constructor<MODEL>) ctor;
+ }
+ }
+ return (Constructor<MODEL>) 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<ModelNode> 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<ComponentId> 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 Collections.emptyList();
+ throw new IllegalArgumentException("Unable to find constructor argument " + param + " for " + clazz.getName());
+ }
+
+}