Skip to content

Commit

Permalink
ApplicationGraphDraw api enhancements (#151)
Browse files Browse the repository at this point in the history
- graph nodes now contain Type
- graph nodes are static fields now
- graph draw copy, replaceNode, findNodeByType and subgraph methods
- generated graph draw now initialized in static block
  • Loading branch information
Squiry authored Jun 1, 2023
1 parent 8c2553c commit b37e94d
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 342 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import reactor.core.publisher.Mono;

import javax.annotation.Nullable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;

public class ApplicationGraphDraw {
private final List<Node<?>> graphNodes = new ArrayList<>();
Expand All @@ -18,18 +21,18 @@ public Class<?> getRoot() {
return root;
}

public <T> Node<T> addNode0(Class<?>[] tags, Graph.Factory<T> factory, Node<?>... dependencies) {
return this.addNode0(tags, factory, List.of(), dependencies);
public <T> Node<T> addNode0(Type type, Class<?>[] tags, Graph.Factory<T> factory, Node<?>... dependencies) {
return this.addNode0(type, tags, factory, List.of(), dependencies);
}

public <T> Node<T> addNode0(Class<?>[] tags, Graph.Factory<T> factory, List<Node<? extends GraphInterceptor<T>>> interceptors, Node<?>... dependencies) {
public <T> Node<T> addNode0(Type type, Class<?>[] tags, Graph.Factory<T> factory, List<Node<? extends GraphInterceptor<T>>> interceptors, Node<?>... dependencies) {
for (var dependency : dependencies) {
if (dependency.index >= 0 && dependency.graphDraw != this) {
throw new IllegalArgumentException("Dependency is from another graph");
}
}

var node = new Node<>(this, this.graphNodes.size(), factory, List.of(dependencies), List.copyOf(interceptors), tags);
var node = new Node<>(this, this.graphNodes.size(), factory, type, List.of(dependencies), List.copyOf(interceptors), tags);
this.graphNodes.add(node);
for (var dependency : dependencies) {
if (dependency.isValueOf()) {
Expand Down Expand Up @@ -60,4 +63,97 @@ public int size() {
return this.graphNodes.size();
}

@Nullable
public Node<?> findNodeByType(Type type) {
for (var graphNode : this.graphNodes) {
if (graphNode.type().equals(type) && graphNode.tags().length == 0) {
return graphNode;
}
}
return null;
}

public <T> void replaceNode(Node<T> node, Graph.Factory<T> factory) {
this.graphNodes.set(node.index, new Node<T>(
this, node.index, factory, node.type(), List.of(), List.of(), node.tags()
));
for (var graphNode : graphNodes) {
graphNode.deleteDependentNode(node);
}
}

public ApplicationGraphDraw copy() {
var draw = new ApplicationGraphDraw(this.root);
for (var node : this.graphNodes) {
class T {
static <T> void addNode(ApplicationGraphDraw draw, Node<T> node) {
var dependencies = new Node<?>[node.getDependencyNodes().size()];
for (int i = 0; i < dependencies.length; i++) {
var dependency = node.getDependencyNodes().get(i);
dependencies[i] = draw.graphNodes.get(dependency.index);
}
draw.addNode0(node.type(), node.tags(), node.factory, node.getInterceptors(), dependencies);
}
}
T.addNode(draw, node);
}
return draw;
}

public ApplicationGraphDraw subgraph(Node<?>... rootNodes) {
var seen = new TreeMap<Integer, Integer>();
var subgraph = new ApplicationGraphDraw(this.root);
var visitor = new Object() {
public <T> Node<T> accept(Node<T> node) {
if (!seen.containsKey(node.index)) {
var dependencies = new ArrayList<Node<?>>();
var interceptors = new ArrayList<Node<? extends GraphInterceptor<T>>>();
for (var dependencyNode : node.getDependencyNodes()) {
dependencies.add(this.accept(dependencyNode));
}
for (var interceptor : node.getInterceptors()) {
interceptors.add(this.accept(interceptor));
}
Graph.Factory<T> factory = graph -> node.factory.get(new Graph() {
@Override
public ApplicationGraphDraw draw() {
return subgraph;
}

@Override
public <Q> Q get(Node<Q> node1) {
@SuppressWarnings("unchecked")
var realNode = (Node<Q>) subgraph.graphNodes.get(seen.get(node1.index));
return graph.get(realNode);
}

@Override
public <Q> ValueOf<Q> valueOf(Node<? extends Q> node1) {
@SuppressWarnings("unchecked")
var realNode = (Node<Q>) subgraph.graphNodes.get(seen.get(node1.index));
return graph.valueOf(realNode);
}

@Override
public <Q> PromiseOf<Q> promiseOf(Node<Q> node1) {
@SuppressWarnings("unchecked")
var realNode = (Node<Q>) subgraph.graphNodes.get(seen.get(node1.index));
return graph.promiseOf(realNode);
}
});
var newNode = subgraph.addNode0(node.type(), node.tags(), factory, interceptors, dependencies.toArray(new Node<?>[0]));
seen.put(node.index, newNode.index);
return newNode;
}
var index = seen.get(node.index);
@SuppressWarnings("unchecked")
var newNode = (Node<T>) subgraph.graphNodes.get(index);
return newNode;
}
};
for (var rootNode : rootNodes) {
visitor.accept(this.graphNodes.get(rootNode.index));
}
return subgraph;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
import java.util.stream.Stream;

final class GraphImpl implements RefreshableGraph, Lifecycle {
public static int EMPTY_OPTIONAL_INDEX = -1;
public static int EMPTY_NULLABLE_INDEX = -2;

private volatile AtomicReferenceArray<Object> objects;
private final ApplicationGraphDraw draw;
private final Logger log;
Expand All @@ -32,18 +29,13 @@ final class GraphImpl implements RefreshableGraph, Lifecycle {
this.objects = new AtomicReferenceArray<>(this.draw.size());
}

@Override
public ApplicationGraphDraw draw() {
return this.draw;
}

@Override
public <T> T get(Node<T> node) {
if (node.index == EMPTY_OPTIONAL_INDEX) {
return (T) Optional.empty();
}
if (node.index == EMPTY_NULLABLE_INDEX) {
return null;
}
if (node.graphDraw != this.draw) {
throw new IllegalArgumentException("Node is from another graph");
}
Expand All @@ -57,7 +49,7 @@ public <T> T get(Node<T> node) {

@Override
public <T> ValueOf<T> valueOf(final Node<? extends T> node) {
if (node.index >= 0 && node.graphDraw != this.draw) {
if (node.graphDraw != this.draw) {
throw new IllegalArgumentException("Node is from another graph");
}
return new ValueOf<>() {
Expand Down Expand Up @@ -244,10 +236,14 @@ private <T> Mono<Void> release(AtomicReferenceArray<Object> objects, Mono<?>[] r
.switchIfEmpty(Mono.just(o))
.doOnEach(s -> {
switch (s.getType()) {
case CANCEL -> this.log.trace("Intercepting release node {} of class {} with node {} of class {} cancelled", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
case ON_SUBSCRIBE -> this.log.trace("Intercepting release node {} of class {} with node {} of class {}", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
case ON_ERROR -> this.log.trace("Intercepting release node {} of class {} with node {} of class {} error", node.index, o.getClass(), interceptorNode.index, interceptor.getClass(), s.getThrowable());
case ON_COMPLETE -> this.log.trace("Intercepting release node {} of class {} with node {} of class {} complete", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
case CANCEL ->
this.log.trace("Intercepting release node {} of class {} with node {} of class {} cancelled", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
case ON_SUBSCRIBE ->
this.log.trace("Intercepting release node {} of class {} with node {} of class {}", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
case ON_ERROR ->
this.log.trace("Intercepting release node {} of class {} with node {} of class {} error", node.index, o.getClass(), interceptorNode.index, interceptor.getClass(), s.getThrowable());
case ON_COMPLETE ->
this.log.trace("Intercepting release node {} of class {} with node {} of class {} complete", node.index, o.getClass(), interceptorNode.index, interceptor.getClass());
default -> {}
}
})
Expand Down Expand Up @@ -352,10 +348,14 @@ private <T> void createNode(Node<T> node, AtomicIntegerArray dependencies) {
.switchIfEmpty(Mono.just(o))
.doOnEach(s -> {
switch (s.getType()) {
case CANCEL -> this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} cancelled", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
case ON_SUBSCRIBE -> this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {}", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
case ON_ERROR -> this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} error", node.index, o.getClass(), interceptor.index, interceptorObject.getClass(), s.getThrowable());
case ON_COMPLETE -> this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} complete", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
case CANCEL ->
this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} cancelled", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
case ON_SUBSCRIBE ->
this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {}", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
case ON_ERROR ->
this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} error", node.index, o.getClass(), interceptor.index, interceptorObject.getClass(), s.getThrowable());
case ON_COMPLETE ->
this.rootGraph.log.trace("Intercepting init node {} of class {} with node {} of class {} complete", node.index, o.getClass(), interceptor.index, interceptorObject.getClass());
default -> {}
}
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
package ru.tinkoff.kora.application.graph;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static ru.tinkoff.kora.application.graph.GraphImpl.EMPTY_NULLABLE_INDEX;
import static ru.tinkoff.kora.application.graph.GraphImpl.EMPTY_OPTIONAL_INDEX;

public final class Node<T> {
final ApplicationGraphDraw graphDraw;
final int index;
final Graph.Factory<T> factory;
// leaks for the test purposes
private final Type type;
private final Class<?>[] tags;
private final List<Node<?>> dependencyNodes;
private final List<Node<? extends GraphInterceptor<T>>> interceptors;
private final List<Node<?>> intercepts;
private final List<Node<?>> dependentNodes;
private final boolean isValueOf;

Node(ApplicationGraphDraw graphDraw, int index, Graph.Factory<T> factory, List<Node<?>> dependencyNodes, List<Node<? extends GraphInterceptor<T>>> interceptors, Class<?>[] tags) {
Node(ApplicationGraphDraw graphDraw, int index, Graph.Factory<T> factory, Type type, List<Node<?>> dependencyNodes, List<Node<? extends GraphInterceptor<T>>> interceptors, Class<?>[] tags) {
this.graphDraw = graphDraw;
this.index = index;
this.factory = factory;
this.type = type;
this.dependencyNodes = List.copyOf(dependencyNodes);
this.dependentNodes = new ArrayList<>();
this.interceptors = List.copyOf(interceptors);
Expand All @@ -32,10 +31,11 @@ public final class Node<T> {
this.tags = tags;
}

private Node(ApplicationGraphDraw graphDraw, int index, Graph.Factory<T> factory, List<Node<?>> dependencyNodes, List<Node<? extends GraphInterceptor<T>>> interceptors, List<Node<?>> dependentNodes, List<Node<?>> intercepts, boolean isValueOf, Class<?>[] tags) {
private Node(ApplicationGraphDraw graphDraw, int index, Graph.Factory<T> factory, Type type, List<Node<?>> dependencyNodes, List<Node<? extends GraphInterceptor<T>>> interceptors, List<Node<?>> dependentNodes, List<Node<?>> intercepts, boolean isValueOf, Class<?>[] tags) {
this.graphDraw = graphDraw;
this.index = index;
this.factory = factory;
this.type = type;
this.dependencyNodes = List.copyOf(dependencyNodes);
this.interceptors = List.copyOf(interceptors);
this.dependentNodes = dependentNodes;
Expand All @@ -45,13 +45,17 @@ private Node(ApplicationGraphDraw graphDraw, int index, Graph.Factory<T> factory
}

public Node<T> valueOf() {
return new Node<>(this.graphDraw, this.index, this.factory, this.dependencyNodes, this.interceptors, this.dependentNodes, this.intercepts, true, this.tags);
return new Node<>(this.graphDraw, this.index, this.factory, this.type, this.dependencyNodes, this.interceptors, this.dependentNodes, this.intercepts, true, this.tags);
}

void addDependentNode(Node<?> node) {
this.dependentNodes.add(node);
}

public void deleteDependentNode(Node<?> node) {
this.dependentNodes.remove(node);
}

void intercepts(Node<?> node) {
this.intercepts.add(node);
}
Expand All @@ -76,16 +80,11 @@ boolean isValueOf() {
return this.isValueOf;
}

public Class<?>[] getTags() {
return tags;
}

public static <T> Node<Optional<T>> emptyOptional() {
return new Node<>(null, EMPTY_OPTIONAL_INDEX, g -> Optional.empty(), List.of(), List.of(), new Class<?>[0]);
public Type type() {
return this.type;
}

public static <T> Node<T> emptyNullable() {
return new Node<>(null, EMPTY_NULLABLE_INDEX, g -> null, List.of(), List.of(), new Class<?>[0]);
public Class<?>[] tags() {
return tags;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ch.qos.logback.classic.Logger;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
Expand Down Expand Up @@ -352,6 +353,35 @@ void graphRefreshCallsRefreshListeners() {
assertThat(object2.refreshTime).isGreaterThan(beforeRefresh);
}

@Test
void subgraphTest() {
var graph = ReferenceGraph.graph();
var subgraphDraw = graph.draw.subgraph(graph.object4Node);
assertThat(subgraphDraw.getNodes()).hasSize(5);
var subgraph = subgraphDraw.init().block();
}

@Test
void replaceNodeTest() {
var graph = ReferenceGraph.graph();
var draw = graph.draw.copy();
var mock = Mockito.mock(TestObject.class);
Mockito.when(mock.init()).thenReturn(Mono.empty());
Mockito.when(mock.release()).thenReturn(Mono.empty());

draw.replaceNode(graph.object2Node, g -> mock);
var newGraph = draw.init().block();

@SuppressWarnings("unchecked")
var object5Node = (Node<TestObject>) draw.getNodes().stream()
.filter(n -> n.factory == graph.object5Factory)
.findFirst()
.get();

Mockito.verify(mock).init();
var o5 = newGraph.get(object5Node);
assertThat(o5.dependencies.get(0)).isSameAs(mock);
}

/**
* <pre>
Expand All @@ -369,21 +399,20 @@ void graphRefreshCallsRefreshListeners() {
private static class ReferenceGraph {
private final ApplicationGraphDraw draw = new ApplicationGraphDraw(ReferenceGraph.class);
private final TestObjectFactory rootFactory = factory();
private final Node<TestObject> rootNode = draw.addNode0(TestObject.class, TAGS, rootFactory);
private final TestObjectFactory object1Factory = factory(rootNode);
private final Node<TestObject> object1Node = draw.addNode0(TestObject.class, TAGS, object1Factory, rootNode);
private final TestObjectFactory interceptor1Factory = factory();
private final TestObjectFactory object1Factory = factory();
private final TestObjectFactory object2Factory = factory();
private final TestObjectFactory object3Factory = factory();
private final TestObjectFactory object5Factory = factory();
private final Node<TestObject> rootNode = draw.addNode0(TAGS, rootFactory);
private final Node<TestObject> interceptor1 = draw.addNode0(TAGS, interceptor1Factory);
private final Node<TestObject> object1Node = draw.addNode0(TAGS, object1Factory, rootNode);
private final Node<TestObject> object2Node = draw.addNode0(TAGS, object2Factory, List.of(interceptor1), rootNode);
private final Node<TestObject> object3Node = draw.addNode0(TAGS, object3Factory, object1Node);
private final Node<TestObject> interceptor1 = draw.addNode0(TestObject.class, TAGS, interceptor1Factory);
private final TestObjectFactory object2Factory = factory(rootNode);
private final Node<TestObject> object2Node = draw.addNode0(TestObject.class, TAGS, object2Factory, List.of(interceptor1), rootNode);
private final TestObjectFactory object3Factory = factory(object1Node);
private final Node<TestObject> object3Node = draw.addNode0(TestObject.class, TAGS, object3Factory, object1Node);
private final TestObjectFactory object4Factory = factory(object1Node, object2Node.valueOf());
private final Node<TestObject> object4Node = draw.addNode0(TestObject.class, TAGS, object4Factory, object1Node, object2Node.valueOf());
private final TestObjectFactory object5Factory = factory(object2Node);
private final Node<TestObject> object5Node = draw.addNode0(TestObject.class, TAGS, object5Factory, object2Node);


private final Node<TestObject> object4Node = draw.addNode0(TAGS, object4Factory, object1Node, object2Node.valueOf());
private final Node<TestObject> object5Node = draw.addNode0(TAGS, object5Factory, object2Node);
private final RefreshableGraph graph = this.draw.init().block();

public static ReferenceGraph graph() {
Expand Down
Loading

0 comments on commit b37e94d

Please sign in to comment.