summaryrefslogtreecommitdiffstats
path: root/yolean
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 /yolean
Publish
Diffstat (limited to 'yolean')
-rw-r--r--yolean/.gitignore2
-rw-r--r--yolean/OWNERS1
-rwxr-xr-xyolean/README.sh13
-rw-r--r--yolean/pom.xml62
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/Exceptions.java47
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/After.java23
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/Before.java23
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/Chain.java98
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/ChainBuilder.java247
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java24
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java191
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java106
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java183
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/Provides.java23
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/Vertex.java9
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/chain/package-info.java6
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java94
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java122
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/concurrent/package-info.java7
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java20
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java25
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java13
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/function/package-info.java10
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/package-info.java7
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/trace/TraceNode.java247
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java45
-rw-r--r--yolean/src/main/java/com/yahoo/yolean/trace/package-info.java7
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java23
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/chain/ChainBuilderTest.java394
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/chain/ChainTest.java55
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/chain/ContainsSameElements.java74
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/chain/DirectedGraphTest.java54
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/chain/EnumeratedIdentitySetTest.java249
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java106
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java146
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java231
-rw-r--r--yolean/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java21
37 files changed, 3008 insertions, 0 deletions
diff --git a/yolean/.gitignore b/yolean/.gitignore
new file mode 100644
index 00000000000..12251442258
--- /dev/null
+++ b/yolean/.gitignore
@@ -0,0 +1,2 @@
+/target
+/pom.xml.build
diff --git a/yolean/OWNERS b/yolean/OWNERS
new file mode 100644
index 00000000000..90fdb511ae3
--- /dev/null
+++ b/yolean/OWNERS
@@ -0,0 +1 @@
+bakksjo
diff --git a/yolean/README.sh b/yolean/README.sh
new file mode 100755
index 00000000000..af0cc99f589
--- /dev/null
+++ b/yolean/README.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+if [ -z ${VERSION} ]; then
+ echo "Environment VERSION not set." >&2
+ exit 1;
+fi
+cat <<EOF
+
+Yolean is a collection of Java utility classes that may be useful
+across various products. INclusion here has a higher threshold than
+vespajlib.
+
+EOF
diff --git a/yolean/pom.xml b/yolean/pom.xml
new file mode 100644
index 00000000000..c832f5e7f46
--- /dev/null
+++ b/yolean/pom.xml
@@ -0,0 +1,62 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>6-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>yolean</artifactId>
+ <version>6-SNAPSHOT</version>
+ <packaging>container-plugin</packaging>
+ <name>${project.artifactId}</name>
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/yolean/src/main/java/com/yahoo/yolean/Exceptions.java b/yolean/src/main/java/com/yahoo/yolean/Exceptions.java
new file mode 100644
index 00000000000..51e8332809b
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/Exceptions.java
@@ -0,0 +1,47 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean;
+
+/**
+ * Helper methods for handling exceptions
+ *
+ * @author bratseth
+ */
+public class Exceptions {
+
+ /**
+ * <p>Returns a user friendly error message string which includes information from all nested exceptions.</p>
+ *
+ * <p>The form of this string is
+ * <code>e.getMessage(): e.getCause().getMessage(): e.getCause().getCause().getMessage()...</code>
+ * In addition, some heuristics are used to clean up common cases where exception nesting causes bad messages.
+ */
+ public static String toMessageString(Throwable t) {
+ StringBuilder b = new StringBuilder();
+ String lastMessage = null;
+ String message;
+ for (; t != null; t = t.getCause()) {
+ message = getMessage(t);
+ if (message == null) continue;
+ if (message.equals(lastMessage)) continue;
+ if (b.length() > 0) {
+ b.append(": ");
+ }
+ b.append(message);
+ lastMessage = message;
+ }
+ return b.toString();
+ }
+
+ /** Returns a useful message from *this* exception, or null if there is nothing useful to return */
+ private static String getMessage(Throwable t) {
+ String message = t.getMessage();
+ if (t.getCause() == null) {
+ if (message == null) return t.getClass().getSimpleName();
+ } else {
+ if (message == null) return null;
+ if (message.equals(t.getCause().getClass().getName() + ": " + t.getCause().getMessage())) return null;
+ }
+ return message;
+ }
+
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/After.java b/yolean/src/main/java/com/yahoo/yolean/chain/After.java
new file mode 100644
index 00000000000..7664f30cc6b
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/After.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>The component that is annotated with this must be placed later than the components or phases providing names
+ * contained in the given list.</p>
+ *
+ * @author tonytv
+ * @since 5.1.18
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface After {
+
+ public abstract String[] value() default { };
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Before.java b/yolean/src/main/java/com/yahoo/yolean/chain/Before.java
new file mode 100644
index 00000000000..cd8c90be085
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/Before.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>The component that is annotated with this must be placed earlier than the components or phases providing names
+ * contained in the given list.</p>
+ *
+ * @author tonytv
+ * @since 5.1.18
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface Before {
+
+ public abstract String[] value() default { };
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java b/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java
new file mode 100644
index 00000000000..86ff0e07d00
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java
@@ -0,0 +1,98 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An immutable and ordered list of components
+ *
+ * @author tonytv
+ */
+public final class Chain<T> implements Iterable<T> {
+
+ private final String id;
+ private final Collection<T> components;
+
+ @SafeVarargs
+ public Chain(String id, T... components) {
+ this(id, Arrays.asList(components));
+ }
+
+ public Chain(String id, List<? extends T> components) {
+ requireNonNull(id, "id must be non-null.");
+ requireNonNull(components, "components must be non-null");
+
+ this.components = ImmutableList.copyOf(components);
+ this.id = id;
+ }
+
+ public String id() {
+ return id;
+ }
+
+ public boolean isEmpty() {
+ return components.isEmpty();
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return components.iterator();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("chain '").append(id).append("'{");
+ boolean first = true;
+ for (T component : components) {
+ if (!first) {
+ b.append("->");
+ } else {
+ first = false;
+ }
+ b.append(" ").append(component.getClass().getSimpleName()).append(" ");
+ }
+ b.append("}");
+ return b.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof Chain && equals((Chain<?>)other);
+ }
+
+ public boolean equals(Chain<?> other) {
+ return id.equals(other.id) && componentsIdentical(components, other.components);
+ }
+
+ private boolean componentsIdentical(Collection<T> components1, Collection<?> components2) {
+ if (components1.size() != components2.size()) {
+ return false;
+ }
+ Iterator<T> iterator1 = components1.iterator();
+ Iterator<?> iterator2 = components2.iterator();
+ while (iterator1.hasNext()) {
+ T c1 = iterator1.next();
+ Object c2 = iterator2.next();
+
+ if (c1 != c2) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/ChainBuilder.java b/yolean/src/main/java/com/yahoo/yolean/chain/ChainBuilder.java
new file mode 100644
index 00000000000..ad44f2416d4
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/ChainBuilder.java
@@ -0,0 +1,247 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author tonytv
+ * @author gjoranv
+ */
+public final class ChainBuilder<T> {
+
+ private final String chainId;
+ private final List<T> components = new ArrayList<>();
+ private final IdentityHashMap<T, Dependencies<T>> dependencies = new IdentityHashMap<>();
+
+ public ChainBuilder(String chainId) {
+ this.chainId = chainId;
+ }
+
+ @SafeVarargs
+ public final ChainBuilder<T> add(T component, Dependencies<? extends T>... dependenciesList) {
+ if (dependencies.containsKey(component)) {
+ throw new IllegalArgumentException("The same component cannot be added twice: " + component);
+ }
+ components.add(component);
+
+ List<Dependencies<? extends T>> allDependencies =
+ Dependencies.allOf(dependenciesList, Dependencies.getAnnotatedDependencies(component));
+ dependencies.put(component, Dependencies.union(allDependencies));
+
+ return this;
+ }
+
+ public Chain<T> build() {
+ DirectedGraph graph = buildGraph();
+ List<Vertex> sortedVertices = graph.topologicalSort();
+ return new Chain<>(chainId, components(sortedVertices));
+ }
+
+ private DirectedGraph buildGraph() {
+ DirectedGraph graph = new DirectedGraph();
+ List<ComponentVertex<T>> vertices = createVertices();
+
+ addVertices(graph, vertices);
+ addEdges(graph, vertices, dependencies);
+
+ return graph;
+ }
+
+ private List<ComponentVertex<T>> createVertices() {
+ List<ComponentVertex<T>> vertices = new ArrayList<>();
+ for (T component : components) {
+ vertices.add(new ComponentVertex<>(component));
+ }
+ return vertices;
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<T> components(List<Vertex> sortedVertices) {
+ List<T> result = new ArrayList<>();
+ for (Vertex vertex : sortedVertices) {
+ if (vertex instanceof ComponentVertex) {
+ result.add((T)((ComponentVertex)vertex).component);
+ }
+ }
+ return result;
+ }
+
+ // TODO: create subclasses Beginning/EdingVertex instead? We could then create the correct class in createVertices,
+ // TODO: and call the same method in DirGraph for all types.
+ private void addVertices(DirectedGraph graph, List<ComponentVertex<T>> vertices) {
+ for (ComponentVertex<T> v : vertices) {
+ if (isBeginningVertex(v)) {
+ graph.addBeginningVertex(v);
+ } else if (isEndingVertex(v)) {
+ graph.addEndingVertex(v);
+ } else {
+ graph.addVertex(v);
+ }
+ }
+ }
+
+ private boolean isBeginningVertex(ComponentVertex<T> v) {
+ return dependencies.get(v.component).before.providedNames.contains("*");
+ }
+
+ private boolean isEndingVertex(ComponentVertex<T> v) {
+ return dependencies.get(v.component).after.providedNames.contains("*");
+ }
+
+ private static <T> void addEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ addBeforeInstanceEdges(graph, vertices, dependencies);
+ addAfterInstanceEdges(graph, vertices, dependencies);
+ addBeforeClassEdges(graph, vertices, dependencies);
+ addAfterClassEdges(graph, vertices, dependencies);
+ addBeforeProvidedEdges(graph, vertices, dependencies);
+ addAfterProvidedEdges(graph, vertices, dependencies);
+ }
+
+ // NOTE: When reading 'beforeVertex' below, think that 'vertex' should be _before_ beforeVertex.
+
+ private static <T> void addBeforeClassEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ for (ComponentVertex<T> vertex : vertices) {
+ for (Class<? extends T> beforeClass : dependencies.get(vertex.component).before.classes) {
+ for (Vertex beforeVertex : componentsWithClass(vertices, beforeClass)) {
+ graph.addEdge(vertex, beforeVertex);
+ }
+ }
+ }
+ }
+
+ private static <T> void addAfterClassEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ for (ComponentVertex<T> vertex : vertices) {
+ for (Class<? extends T> afterClass : dependencies.get(vertex.component).after.classes) {
+ for (Vertex afterVertex : componentsWithClass(vertices, afterClass)) {
+ graph.addEdge(afterVertex, vertex);
+ }
+ }
+ }
+ }
+
+ private static <T> List<Vertex> componentsWithClass(List<ComponentVertex<T>> vertices,
+ Class<? extends T> beforeClass) {
+ List<Vertex> result = new ArrayList<>();
+ for (ComponentVertex<T> vertex : vertices) {
+ if (beforeClass.isInstance(vertex.component)) {
+ result.add(vertex);
+ }
+ }
+ return result;
+ }
+
+ private static <T> void addBeforeInstanceEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ IdentityHashMap<T, Vertex> componentToVertex = getComponentToVertex(vertices);
+ for (ComponentVertex<T> vertex : vertices) {
+ for (T before : dependencies.get(vertex.component).before.instances) {
+ Vertex beforeVertex = componentToVertex.get(before);
+ if (beforeVertex != null) {
+ graph.addEdge(vertex, beforeVertex);
+ }
+ }
+ }
+ }
+
+ private static <T> void addAfterInstanceEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ IdentityHashMap<T, Vertex> componentToVertex = getComponentToVertex(vertices);
+ for (ComponentVertex<T> vertex : vertices) {
+ for (T after : dependencies.get(vertex.component).after.instances) {
+ Vertex afterVertex = componentToVertex.get(after);
+ if (afterVertex != null) {
+ graph.addEdge(afterVertex, vertex);
+ }
+ }
+ }
+ }
+
+ private static <T> IdentityHashMap<T, Vertex> getComponentToVertex(List<ComponentVertex<T>> vertices) {
+ IdentityHashMap<T, Vertex> result = new IdentityHashMap<>();
+ for (ComponentVertex<T> vertex : vertices) {
+ result.put(vertex.component, vertex);
+ }
+ return result;
+ }
+
+ private static <T> void addBeforeProvidedEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ Map<String, Set<Vertex>> providedNames = getProvidedNames(vertices, dependencies);
+ for (ComponentVertex<T> vertex : vertices) {
+ for (String name : dependencies.get(vertex.component).before.providedNames) {
+ for (Vertex beforeVertex : emptyIfNull(providedNames.get(name))) {
+ graph.addEdge(vertex, beforeVertex);
+ }
+ }
+ }
+ }
+
+ private static <T> void addAfterProvidedEdges(DirectedGraph graph,
+ List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ Map<String, Set<Vertex>> providedNames = getProvidedNames(vertices, dependencies);
+ for (ComponentVertex<T> vertex : vertices) {
+ for (String name : dependencies.get(vertex.component).after.providedNames) {
+ for (Vertex afterVertex : emptyIfNull(providedNames.get(name))) {
+ graph.addEdge(afterVertex, vertex);
+ }
+ }
+ }
+ }
+
+ private static <T> Map<String, Set<Vertex>> getProvidedNames(List<ComponentVertex<T>> vertices,
+ IdentityHashMap<T, Dependencies<T>> dependencies) {
+ Map<String, Set<Vertex>> result = new HashMap<>();
+ for (ComponentVertex<T> vertex : vertices) {
+ for (String providedName : dependencies.get(vertex.component).provided) {
+ getIdentitySet(result, providedName).add(vertex);
+ }
+ addClassName(result, vertex);
+ }
+ return result;
+ }
+
+ private static void addClassName(Map<String, Set<Vertex>> providedNamesToVertex, ComponentVertex vertex) {
+ String className = vertex.component.getClass().getName();
+ getIdentitySet(providedNamesToVertex, className).add(vertex);
+ }
+
+ private static <T> Set<T> getIdentitySet(Map<String, Set<T>> map, String key) {
+ Set<T> result = map.get(key);
+ if (result == null) {
+ result = Collections.newSetFromMap(new IdentityHashMap<T, Boolean>());
+ map.put(key, result);
+ }
+ return result;
+ }
+
+ private static <T> Set<T> emptyIfNull(Set<T> set) {
+ return set != null ?
+ set :
+ Collections.<T>emptySet();
+ }
+
+ private static class ComponentVertex<T> implements Vertex {
+
+ final T component;
+
+ private ComponentVertex(T component) {
+ this.component = component;
+ }
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java b/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java
new file mode 100644
index 00000000000..b92df20ab8c
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java
@@ -0,0 +1,24 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author tonytv
+ */
+public class ChainCycleException extends RuntimeException {
+
+ private final List<?> components;
+
+ public ChainCycleException(List<?> components) {
+ this.components = ImmutableList.copyOf(components);
+ }
+
+ public List<?> components() {
+ return components;
+ }
+} \ No newline at end of file
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java b/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java
new file mode 100644
index 00000000000..ca59c373351
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java
@@ -0,0 +1,191 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author tonytv
+ * @author gjoranv
+ */
+public class Dependencies<T> {
+
+ final Order<T> before;
+ final Order<T> after;
+ final List<String> provided;
+
+ private Dependencies(Order<T> before, Order<T> after, String[] provided) {
+ this.before = before;
+ this.after = after;
+ this.provided = copyList(provided);
+ }
+
+ @SafeVarargs
+ public static <T> Dependencies<T> before(T... components) {
+ return new Dependencies<>(new Order<>(components, null, null), Order.<T>emptyOrder(), null);
+ }
+
+ @SafeVarargs
+ public static <T> Dependencies<T> before(Class<? extends T>... classes) {
+ return new Dependencies<>(new Order<>(null, classes, null), Order.<T>emptyOrder(), null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Dependencies before(String... providedNames) {
+ // Does not use type parameters due to Javas limited type inference.
+ return new Dependencies(new Order(null, null, providedNames), Order.emptyOrder(), null);
+ }
+
+ @SafeVarargs
+ public static <T> Dependencies<T> after(T... components) {
+ return new Dependencies<>(Order.<T>emptyOrder(), new Order<>(components, null, null), null);
+ }
+
+ @SafeVarargs
+ public static <T> Dependencies<T> after(Class<? extends T>... classes) {
+ return new Dependencies<>(Order.<T>emptyOrder(), new Order<>(null, classes, null), null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Dependencies after(String... providedNames) {
+ // Does not use type parameters due to Javas limited type inference.
+ return new Dependencies(Order.emptyOrder(), new Order(null, null, providedNames), null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Dependencies provides(String... names) {
+ // Does not use type parameters due to Javas limited type inference.
+ return new Dependencies(Order.emptyOrder(), Order.emptyOrder(), names);
+ }
+
+ public static <T> Dependencies<T> emptyDependencies() {
+ return new Dependencies<>(Order.<T>emptyOrder(), Order.<T>emptyOrder(), null);
+ }
+
+ @SuppressWarnings("unchecked")
+ static <T> Dependencies<T> union(List<Dependencies<? extends T>> dependenciesList) {
+ if (dependenciesList.size() > 1) {
+ Dependencies<T> result = emptyDependencies();
+ for (Dependencies<? extends T> dependencies : dependenciesList) {
+ result = result.union(dependencies);
+ }
+ return result;
+ } else if (dependenciesList.size() == 0) {
+ return emptyDependencies();
+ } else {
+ return (Dependencies<T>)dependenciesList.get(0); // Dependencies<T> is covariant for T, the cast is valid.
+ }
+ }
+
+ private Dependencies<T> union(Dependencies<? extends T> other) {
+ List<String> lst = listUnion(provided, other.provided);
+ return new Dependencies<>(before.union(other.before),
+ after.union(other.after),
+ lst.toArray(new String[lst.size()]));
+ }
+
+ private static <T> List<T> listUnion(List<? extends T> list1, List<? extends T> list2) {
+ List<T> union = new ArrayList<>(list1);
+ union.removeAll(list2);
+ union.addAll(list2);
+ return union;
+ }
+
+ static <T> Dependencies<T> getAnnotatedDependencies(T component) {
+ return new Dependencies<>(
+ new Order<T>(null, null, getSymbols(component, Before.class)),
+ new Order<T>(null, null, getSymbols(component, After.class)),
+ getProvidedSymbols(component));
+ }
+
+ private static <T> String[] getProvidedSymbols(T component) {
+ List<String> lst = allOf(getSymbols(component, Provides.class), component.getClass().getName());
+ return lst.toArray(new String[lst.size()]);
+ }
+
+ @SafeVarargs
+ static <T> List<T> allOf(List<T> elements, T... otherElements) {
+ List<T> result = new ArrayList<>(elements);
+ result.addAll(Arrays.asList(otherElements));
+ return result;
+ }
+
+ @SafeVarargs
+ static <T> List<T> allOf(T[] elements, T... otherElements) {
+ return allOf(Arrays.asList(elements), otherElements);
+ }
+
+ private static <T> List<String> getSymbols(T component, Class<? extends Annotation> annotationClass) {
+ List<String> result = new ArrayList<>();
+
+ result.addAll(annotationSymbols(component, annotationClass));
+ return result;
+ }
+
+ private static <T> Collection<String> annotationSymbols(T component, Class<? extends Annotation> annotationClass) {
+ try {
+ List<String> values = new ArrayList<>();
+
+ Class clazz = component.getClass();
+ while (clazz != null) {
+ Annotation annotation = clazz.getAnnotation(annotationClass);
+ if (annotation != null) {
+ values.addAll(Arrays.asList((String[])annotationClass.getMethod("value").invoke(annotation)));
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return values;
+ } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static <U> List<U> copyList(List<U> list) {
+ return list == null ?
+ Collections.<U>emptyList() :
+ new ArrayList<>(list);
+ }
+
+ private static <U> List<U> copyList(U[] array) {
+ return array == null ?
+ Collections.<U>emptyList() :
+ new ArrayList<>(Arrays.<U>asList(array));
+ }
+
+ static final class Order<T> {
+
+ final List<T> instances;
+ final List<Class<? extends T>> classes;
+ final List<String> providedNames;
+
+ private Order(T[] instances, Class<? extends T>[] classes, String[] providedNames) {
+ this.instances = copyList(instances);
+ this.classes = copyList(classes);
+ this.providedNames = copyList(providedNames);
+ }
+
+ private Order(List<T> instances, List<Class<? extends T>> classes, List<String> providedNames) {
+ this.instances = copyList(instances);
+ this.classes = copyList(classes);
+ this.providedNames = copyList(providedNames);
+ }
+
+ // TODO: unit test
+ private Order<T> union(Order<? extends T> other) {
+ return new Order<>(
+ listUnion(instances, other.instances),
+ listUnion(classes, other.classes),
+ listUnion(providedNames, other.providedNames));
+ }
+
+ // TODO: try to make it possible to use 'null' Order in Dependencies instead.
+ private static <U> Order<U> emptyOrder() {
+ return new Order<>((U[])null, null, null);
+ }
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java b/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java
new file mode 100644
index 00000000000..260ffe33b5f
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java
@@ -0,0 +1,106 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * TODO: prioritize vertices in edge map.
+ *
+ * @author tonytv
+ */
+class DirectedGraph {
+
+ private IdentityHashMap<Vertex, List<Vertex>> incommingEdges = new IdentityHashMap<>();
+ private List<Vertex> vertices = new ArrayList<>();
+ private List<Vertex> beginningVertices = new ArrayList<>();
+ private List<Vertex> endingVertices = new ArrayList<>();
+
+ public void addVertex(Vertex vertex) {
+ vertices.add(vertex);
+ }
+
+ public void addBeginningVertex(Vertex vertex) {
+ beginningVertices.add(vertex);
+ }
+
+ public void addEndingVertex(Vertex vertex) {
+ endingVertices.add(vertex);
+ }
+
+ public void addEdge(Vertex start, Vertex end) {
+ get(incommingEdges, end).add(start);
+ }
+
+ private static List<Vertex> get(Map<Vertex, List<Vertex>> edgeMap, Vertex key) {
+ List<Vertex> value = edgeMap.get(key);
+ return value == null ?
+ addEmptyList(edgeMap, key) :
+ value;
+ }
+
+ private static List<Vertex> addEmptyList(Map<Vertex, List<Vertex>> edgeMap, Vertex key) {
+ List<Vertex> value = new ArrayList<>();
+ edgeMap.put(key, value);
+ return value;
+ }
+
+ public List<Vertex> topologicalSort() {
+ EnumeratedIdentitySet<Vertex> visitedVertices = new EnumeratedIdentitySet<>();
+
+ for (Vertex v : beginningVertices) {
+ topologicalSortVisit(v, visitedVertices);
+ }
+
+ warnIfVisitedEndVertices(visitedVertices);
+
+ for (Vertex v : vertices) {
+ topologicalSortVisit(v, visitedVertices);
+ }
+
+ // TODO: review this!
+ for (Vertex v : endingVertices) {
+ topologicalSortVisit(v, visitedVertices);
+ }
+
+ return visitedVertices.insertionOrderedList();
+ }
+
+ private void warnIfVisitedEndVertices(EnumeratedIdentitySet<Vertex> visitedVertices) {
+ //TVT:
+ }
+
+ private void topologicalSortVisit(Vertex vertex, Set<Vertex> visitedVertices) {
+ topologicalSortVisitImpl(vertex, visitedVertices, new EnumeratedIdentitySet<Vertex>());
+ }
+
+ private void topologicalSortVisitImpl(Vertex vertex, Set<Vertex> visitedVertices,
+ EnumeratedIdentitySet<Vertex> cycleDetector) {
+ if (cycleDetector.contains(vertex)) {
+ throw new ChainCycleException(cycleDetector.insertionOrderedList());
+ }
+
+ if (visitedVertices.contains(vertex)) {
+ return;
+ }
+
+ cycleDetector.add(vertex);
+
+ for (Vertex endVertex : emptyIfNull(incommingEdges.get(vertex))) {
+ topologicalSortVisitImpl(endVertex, visitedVertices, cycleDetector);
+ }
+
+ visitedVertices.add(vertex);
+ cycleDetector.remove(vertex);
+ }
+
+ private <T> List<T> emptyIfNull(List<T> list) {
+ return list == null ?
+ Collections.<T>emptyList() :
+ list;
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java b/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java
new file mode 100644
index 00000000000..902391e7d24
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java
@@ -0,0 +1,183 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A set using identity comparison.
+ * Keeps track of insertion order, which is available by calling insertionOrderedList.
+ *
+ * @author tonytv
+ */
+class EnumeratedIdentitySet<T> implements Set<T> {
+
+ private int counter = 0;
+ private final Map<T, Integer> set = new IdentityHashMap<>();
+
+ public EnumeratedIdentitySet(Collection<? extends T> collection) {
+ addAll(collection);
+ }
+
+ public EnumeratedIdentitySet() {
+ // empty
+ }
+
+ @Override
+ public int size() {
+ return set.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return set.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return set.containsKey(o);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return set.keySet().iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return set.keySet().toArray();
+ }
+
+ @Override
+ public <T1> T1[] toArray(T1[] a) {
+ return set.keySet().toArray(a);
+ }
+
+ @Override
+ public boolean add(T t) {
+ if (set.containsKey(t)) {
+ return false;
+ } else {
+ set.put(t, counter++);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return set.remove(o) != null;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return set.keySet().containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> collection) {
+ boolean changed = false;
+
+ for (T t : collection) {
+ changed |= add(t);
+ }
+
+ return changed;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ return set.keySet().retainAll(collection);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ boolean changed = false;
+
+ for (Object o : collection) {
+ changed |= remove(o);
+ }
+
+ return changed;
+ }
+
+ @Override
+ public void clear() {
+ set.clear();
+ counter = 0;
+ }
+
+ public List<T> insertionOrderedList() {
+ if (set.isEmpty()) {
+ counter = 0;
+ return Collections.emptyList();
+ }
+
+ if (counter >= set.size() * 2 + 20) {
+ renumber();
+ }
+
+ return getKeysSortedByValue(set, counter);
+ }
+
+ private static <KEY> List<KEY> getKeysSortedByValue(Map<KEY, Integer> set, int maxValue) {
+ @SuppressWarnings("unchecked")
+ KEY[] result = (KEY[])Array.newInstance(headKey(set).getClass(), maxValue);
+
+ for (Map.Entry<KEY, Integer> entry : set.entrySet()) {
+ result[entry.getValue()] = entry.getKey();
+ }
+
+ return removeNulls(result);
+ }
+
+ private static <T> T headKey(Map<T, ?> map) {
+ return map.entrySet().iterator().next().getKey();
+ }
+
+ static <T> List<T> removeNulls(T[] list) {
+ int insertionSpot = 0;
+ for (int i = 0; i < list.length; i++) {
+ T element = list[i];
+ if (element != null) {
+ list[insertionSpot] = element;
+ insertionSpot++;
+ }
+ }
+ return Arrays.asList(list).subList(0, insertionSpot);
+ }
+
+ //only for testing
+ List<Integer> numbers() {
+ return new ArrayList<>(set.values());
+ }
+
+ private void renumber() {
+ SortedMap<Integer, T> invertedSet = invertedSortedMap(set);
+
+ int i = 0;
+ for (Map.Entry<Integer, T> entry : invertedSet.entrySet()) {
+ set.put(entry.getValue(), i++);
+ }
+ counter = i;
+ }
+
+ private static <K, V> SortedMap<V, K> invertedSortedMap(Map<K, V> map) {
+ SortedMap<V, K> result = new TreeMap<>();
+
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ result.put(entry.getValue(), entry.getKey());
+ }
+
+ return result;
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java b/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java
new file mode 100644
index 00000000000..99cfaf1fef2
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Mark this component as providing some named functionality. Other components can then mark themselves as "before"
+ * and "after" the string provided here, to impose constraints on ordering.</p>
+ *
+ * @author tonytv
+ * @since 5.1.18
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface Provides {
+
+ public abstract String[] value() default { };
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Vertex.java b/yolean/src/main/java/com/yahoo/yolean/chain/Vertex.java
new file mode 100644
index 00000000000..57de9c3d81c
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/Vertex.java
@@ -0,0 +1,9 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+/**
+ * @author tonytv
+ */
+interface Vertex {
+
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/package-info.java b/yolean/src/main/java/com/yahoo/yolean/chain/package-info.java
new file mode 100644
index 00000000000..a17b39169c7
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/chain/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi package com.yahoo.yolean.chain;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/yolean/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java b/yolean/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java
new file mode 100644
index 00000000000..38c6abf055b
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMap.java
@@ -0,0 +1,94 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>This is a thread hash map for small collections that are stable once built. Until it is stable there will be a
+ * race among all threads missing something in the map. They will then clone the map add the missing stuff and then put
+ * it back as active again. Here are no locks, but the cost is that inserts will happen a lot more than necessary. The
+ * map reference is volatile, but on most multi-cpu machines that has no cost unless modified.</p>
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @since 5.2
+ */
+public class CopyOnWriteHashMap<K, V> implements Map<K, V> {
+
+ private volatile HashMap<K, V> map = new HashMap<>();
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public V get(Object key) {
+ return map.get(key);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ HashMap<K, V> next = new HashMap<>(map);
+ V old = next.put(key, value);
+ map = next;
+ return old;
+ }
+
+ @Override
+ @SuppressWarnings("SuspiciousMethodCalls")
+ public V remove(Object key) {
+ HashMap<K, V> prev = map;
+ if (!prev.containsKey(key)) {
+ return null;
+ }
+ HashMap<K, V> next = new HashMap<>(prev);
+ V old = next.remove(key);
+ map = next;
+ return old;
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ HashMap<K, V> next = new HashMap<>(map);
+ next.putAll(m);
+ map = next;
+ }
+
+ @Override
+ public void clear() {
+ map = new HashMap<>();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return map.values();
+ }
+
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ return map.entrySet();
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java b/yolean/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java
new file mode 100644
index 00000000000..5060ed8ef6a
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/concurrent/ThreadRobustList.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>This class implements a thread-safe, lock-free list of Objects that supports multiple readers and a single writer.
+ * Because there are no locks or other memory barriers involved, there exists no <i>happens-before</i> relationship
+ * among calls to either methods of the <tt>ThreadRobustList</tt>. This means that there are no guarantees as to when
+ * (or even if) an item {@link #add(Object)}ed becomes visible through {@link #iterator()}. If visibility is required,
+ * either use explicit synchronization between reader and writer thread, or move to a different concurrent collection
+ * (e.g. <tt>CopyOnWriteArrayList</tt>).</p>
+ * <p>Because it is lock-free, the <tt>ThreadRobustList</tt> has minimal overhead to both reading and writing. The
+ * iterator offered by this class always observes the list in a consistent state, and it never throws a
+ * <tt>ConcurrentModificationException</tt>.</p>
+ * <p>The <tt>ThreadRobustList</tt> does not permit adding <tt>null</tt> items.</p>
+ * <p>The usage of <tt>ThreadRobustList</tt> has no memory consistency effects. </p>
+ *
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.15
+ */
+public class ThreadRobustList<T> implements Iterable<T> {
+
+ private Object[] items;
+ private int next = 0;
+
+ /**
+ * <p>Constructs a new instance of this class with an initial capacity of <tt>10</tt>.</p>
+ */
+ public ThreadRobustList() {
+ this(10);
+ }
+
+ /**
+ * <p>Constructs a new instance of this class with a given initial capacity.</p>
+ *
+ * @param initialCapacity the initial capacity of this list
+ */
+ public ThreadRobustList(int initialCapacity) {
+ items = new Object[initialCapacity];
+ }
+
+ /**
+ * <p>Returns whether or not this list is empty.</p>
+ *
+ * @return <tt>true</tt> if this list has zero items
+ */
+ public boolean isEmpty() {
+ return next == 0;
+ }
+
+ /**
+ * <p>Adds an item to this list. As opposed to <tt>CopyOnWriteArrayList</tt>, items added to this list may become
+ * visible to iterators created <em>before</em> a call to this method.</p>
+ *
+ * @param item the item to add
+ * @throws NullPointerException if <tt>item</tt> is <tt>null</tt>
+ */
+ public void add(T item) {
+ if (item == null) {
+ throw new NullPointerException();
+ }
+ Object[] workItems = items;
+ if (next >= items.length) {
+ workItems = Arrays.copyOf(workItems, 20 + items.length * 2);
+ workItems[next++] = item;
+ items = workItems;
+ } else {
+ workItems[next++] = item;
+ }
+ }
+
+ /**
+ * <p>Returns an iterator over the items in this list. As opposed to <tt>CopyOnWriteArrayList</tt>, this iterator
+ * may see items added to the <tt>ThreadRobustList</tt> even if they occur <em>after</em> a call to this method.</p>
+ * <p>The returned iterator does not support <tt>remove()</tt>.</p>
+ *
+ * @return an iterator over this list
+ */
+ @Override
+ public Iterator<T> iterator() {
+ return new ThreadRobustIterator<>(items);
+ }
+
+ private static class ThreadRobustIterator<T> implements Iterator<T> {
+
+ final Object[] items;
+ int nextIndex = 0;
+
+ ThreadRobustIterator(Object[] items) {
+ this.items = items;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ return (T)items[nextIndex++];
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (nextIndex >= items.length) {
+ return false;
+ }
+ if (items[nextIndex] == null) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/concurrent/package-info.java b/yolean/src/main/java/com/yahoo/yolean/concurrent/package-info.java
new file mode 100644
index 00000000000..2062e224f79
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/concurrent/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.yolean.concurrent;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/yolean/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java
new file mode 100644
index 00000000000..1ba28d27c4c
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingConsumer.java
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.function;
+
+import java.util.Objects;
+
+/**
+ * Functional interface that mirrors the Consumer interface, but allows for an
+ * exception to be thrown.
+ *
+ * @author oyving
+ */
+@FunctionalInterface
+public interface ThrowingConsumer<T, E extends Throwable> {
+ void accept(T input) throws E;
+
+ default ThrowingConsumer<T, E> andThen(ThrowingConsumer<? super T, ? extends E> after) {
+ Objects.requireNonNull(after);
+ return (T t) -> { accept(t); after.accept(t); };
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java
new file mode 100644
index 00000000000..3db84c6ba95
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingFunction.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.function;
+
+import java.util.Objects;
+
+/**
+ * Functional interface that mirrors the Function interface, but allows for an
+ * exception to be thrown.
+ *
+ * @author oyving
+ */
+@FunctionalInterface
+public interface ThrowingFunction<T, R, E extends Throwable> {
+ R apply(T input) throws E;
+
+ default <V> ThrowingFunction<T, V, E> andThen(ThrowingFunction<? super R, ? extends V, ? extends E> after) {
+ Objects.requireNonNull(after);
+ return (T t) -> after.apply(apply(t));
+ }
+
+ default <V> ThrowingFunction<V, R, E> compose(ThrowingFunction<? super V, ? extends T, ? extends E> before) {
+ Objects.requireNonNull(before);
+ return (V v) -> apply(before.apply(v));
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java
new file mode 100644
index 00000000000..bee32af563b
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/function/ThrowingSupplier.java
@@ -0,0 +1,13 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.function;
+
+/**
+ * Functional interface that mirrors the Supplier interface, but allows for an
+ * exception to be thrown.
+ *
+ * @author oyving
+ */
+@FunctionalInterface
+public interface ThrowingSupplier<R, E extends Throwable> {
+ R get() throws E;
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/function/package-info.java b/yolean/src/main/java/com/yahoo/yolean/function/package-info.java
new file mode 100644
index 00000000000..38f7effc2bb
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/function/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author oyving
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.yolean.function;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/yolean/src/main/java/com/yahoo/yolean/package-info.java b/yolean/src/main/java/com/yahoo/yolean/package-info.java
new file mode 100644
index 00000000000..444ed2496be
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.yolean;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/yolean/src/main/java/com/yahoo/yolean/trace/TraceNode.java b/yolean/src/main/java/com/yahoo/yolean/trace/TraceNode.java
new file mode 100644
index 00000000000..d7e9d128ebd
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/trace/TraceNode.java
@@ -0,0 +1,247 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import com.yahoo.yolean.concurrent.ThreadRobustList;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>This class represents a single node in a tree of <tt>TraceNodes</tt>. The trace forms a tree where there is a
+ * branch for each parallel execution, and a node within such a branch for each traced event. As each <tt>TraceNode</tt>
+ * may contain a payload of any type, the trace tree can be used to exchange any thread-safe state between producers and
+ * consumers in different threads, whether or not the shape of the trace tree is relevant to the particular
+ * information.</p>
+ * <p>This class uses a {@link ThreadRobustList} for its children. That list allows multiple threads to inspect the
+ * hierarchy of a <tt>TraceNode</tt> tree while there are other threads concurrently modifying it, without incurring the
+ * cost of memory synchronization. The only caveat being that for each <tt>TraceNode</tt> there can never be more than
+ * exactly one writer thread. If multiple threads need to mutate a single <tt>TraceNode</tt>, then the writer threads
+ * need to synchronize their access on the <tt>TraceNode</tt>.</p>
+ *
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.15
+ */
+public class TraceNode {
+
+ private final Object payload;
+ private final long timestamp;
+ private ThreadRobustList<TraceNode> children;
+ private TraceNode parent;
+
+ /**
+ * <p>Creates a new instance of this class.</p>
+ *
+ * @param payload the payload to assign to this, may be <tt>null</tt>
+ * @param timestamp the timestamp to assign to this
+ */
+ public TraceNode(Object payload, long timestamp) {
+ this.payload = payload;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * <p>Adds another <tt>TraceNode</tt> as a child to this.</p>
+ *
+ * @param child the TraceNode to add
+ * @return this, to allow chaining
+ * @throws IllegalArgumentException if <tt>child</tt> is not a root TraceNode
+ * @see #isRoot()
+ */
+ public TraceNode add(TraceNode child) {
+ if (child.parent != null) {
+ throw new IllegalArgumentException("Can not add " + child + " to " + this + "; it is not a root.");
+ }
+ child.parent = this;
+ if (children == null) {
+ children = new ThreadRobustList<>();
+ }
+ children.add(child);
+ return this;
+ }
+
+ /**
+ * <p>Returns a read-only iterable of all {@link #payload() payloads} that are instances of <tt>payloadType</tt>,
+ * in all its decendants. The payload of <em>this</em> <tt>TraceNode</tt> is ignored.</p>
+ * <p>The payloads are retrieved in depth-first, prefix order.</p>
+ *
+ * @param payloadType the type of payloads to retrieve
+ * @return the payloads, never <tt>null</tt>
+ */
+ public <PAYLOADTYPE> Iterable<PAYLOADTYPE> descendants(final Class<PAYLOADTYPE> payloadType) {
+ if (children == null) {
+ return Collections.emptyList();
+ }
+ return new Iterable<PAYLOADTYPE>() {
+
+ @Override
+ public Iterator<PAYLOADTYPE> iterator() {
+ return new PayloadIterator<>(TraceNode.this, payloadType);
+ }
+ };
+ }
+
+ /**
+ * <p>Returns the payload of this <tt>TraceNode</tt>, or null if none.</p>
+ *
+ * @return the payload
+ */
+ public Object payload() {
+ return payload;
+ }
+
+ /**
+ * <p>Returns the timestamp of this <tt>TraceNode</tt>.</p>
+ *
+ * @return the timestamp
+ */
+ public long timestamp() {
+ return timestamp;
+ }
+
+ /**
+ * <p>Returns the parent <tt>TraceNode</tt> of this.</p>
+ *
+ * @return the parent
+ */
+ public TraceNode parent() {
+ return parent;
+ }
+
+ /**
+ * <p>Returns the child <tt>TraceNodes</tt> of this.</p>
+ *
+ * @return the children
+ */
+ public Iterable<TraceNode> children() {
+ if (children == null) {
+ return Collections.emptyList();
+ }
+ return children;
+ }
+
+ /**
+ * <p>Returns whether or not this <tt>TraceNode</tt> is a root node (i.e. it has no parent).</p>
+ *
+ * @return <tt>true</tt> if {@link #parent()} returns <tt>null</tt>
+ */
+ public boolean isRoot() {
+ return parent == null;
+ }
+
+ /**
+ * <p>Returns the root <tt>TraceNode</tt> of the tree that this <tt>TraceNode</tt> belongs to.</p>
+ *
+ * @return the root
+ */
+ public TraceNode root() {
+ TraceNode node = this;
+ while (node.parent != null) {
+ node = node.parent;
+ }
+ return node;
+ }
+
+ /**
+ * <p>Visits this <tt>TraceNode</tt> and all of its descendants in depth-first, prefix order.</p>
+ *
+ * @param visitor The visitor to accept.
+ * @return The <code>visitor</code> parameter.
+ */
+ public <T extends TraceVisitor> T accept(T visitor) {
+ visitor.visit(this);
+ if (children == null || children.isEmpty()) {
+ return visitor;
+ }
+ visitor.entering(this);
+ for (TraceNode child : children) {
+ child.accept(visitor);
+ }
+ visitor.leaving(this);
+ return visitor;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder out = new StringBuilder("[ ");
+ accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ if (node.payload != null) {
+ out.append(node.payload).append(" ");
+ }
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ out.append("[ ");
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ out.append("] ");
+ }
+ });
+ return out.append("]").toString();
+ }
+
+ private static class PayloadIterator<PAYLOADTYPE> implements Iterator<PAYLOADTYPE> {
+
+ final List<TraceNode> unexploredNodes = new LinkedList<>();
+ final Class<PAYLOADTYPE> payloadType;
+ PAYLOADTYPE next;
+
+ PayloadIterator(TraceNode root, Class<PAYLOADTYPE> payloadType) {
+ payloadType.getClass(); // throws NullPointerException
+ this.payloadType = payloadType;
+ unexploredNodes.add(root);
+ next = advance();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public PAYLOADTYPE next() {
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ PAYLOADTYPE current = next;
+ next = advance();
+ return current;
+ }
+
+ PAYLOADTYPE advance() {
+ // Current node is depleted, find next
+ while (unexploredNodes.size() > 0) {
+ // Take the next node
+ TraceNode node = unexploredNodes.remove(0);
+
+ // Add its children to the list of nodes we1'll look at
+ if (node.children != null) {
+ int i = 0; // used to fabricate depth-first traversal order
+ for (TraceNode child : node.children) {
+ unexploredNodes.add(i++, child);
+ }
+ }
+
+ Object payload = node.payload();
+ if (payloadType.isInstance(payload)) {
+ return payloadType.cast(payload);
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java b/yolean/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java
new file mode 100644
index 00000000000..2cb48616e56
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/trace/TraceVisitor.java
@@ -0,0 +1,45 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+/**
+ * <p>This class is an abstract visitor of {@link TraceNode}. See {@link TraceNode#accept(TraceVisitor)}.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.15
+ */
+public abstract class TraceVisitor {
+
+ /**
+ * <p>Visits a {@link TraceNode}. Called by {@link TraceNode#accept(TraceVisitor)}, before visiting its
+ * children.</p>
+ *
+ * @param node the <tt>TraceNode</tt> being visited
+ * @see TraceNode#accept(TraceVisitor)
+ */
+ public abstract void visit(TraceNode node);
+
+ /**
+ * <p>Enters a {@link TraceNode}. This method is called after {@link #visit(TraceNode)}, but before visiting its
+ * children. Note that this method is NOT called if a <tt>TraceNode</tt> has zero children.</p>
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param node the <tt>TraceNode</tt> being entered
+ * @see #leaving(TraceNode)
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void entering(TraceNode node) {
+ // empty
+ }
+
+ /**
+ * <p>Leaves a {@link TraceNode}. This method is called after {@link #entering(TraceNode)}, and after visiting its
+ * children. Note that this method is NOT called if a <tt>TraceNode</tt> has zero children.</p>
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param node the <tt>TraceNode</tt> being left
+ */
+ @SuppressWarnings("UnusedParameters")
+ public void leaving(TraceNode node) {
+ // empty
+ }
+}
diff --git a/yolean/src/main/java/com/yahoo/yolean/trace/package-info.java b/yolean/src/main/java/com/yahoo/yolean/trace/package-info.java
new file mode 100644
index 00000000000..7032992628c
--- /dev/null
+++ b/yolean/src/main/java/com/yahoo/yolean/trace/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package com.yahoo.yolean.trace;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/yolean/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java b/yolean/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
new file mode 100644
index 00000000000..36c295ba7f4
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/ExceptionsTestCase.java
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ExceptionsTestCase extends junit.framework.TestCase {
+
+ public ExceptionsTestCase(String name) {
+ super(name);
+ }
+
+ public void testToMessageStrings() {
+ assertEquals("Blah",Exceptions.toMessageString(new Exception("Blah")));
+ assertEquals("Blah", Exceptions.toMessageString(new Exception(new Exception("Blah"))));
+ assertEquals("Exception",Exceptions.toMessageString(new Exception()));
+ assertEquals("Foo: Blah",Exceptions.toMessageString(new Exception("Foo",new Exception(new IllegalArgumentException("Blah")))));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception("Foo",new Exception("Foo"))));
+ assertEquals("Foo: Exception",Exceptions.toMessageString(new Exception("Foo",new Exception())));
+ assertEquals("Foo",Exceptions.toMessageString(new Exception(new Exception("Foo"))));
+ }
+
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/chain/ChainBuilderTest.java b/yolean/src/test/java/com/yahoo/yolean/chain/ChainBuilderTest.java
new file mode 100644
index 00000000000..86dba99e333
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/chain/ChainBuilderTest.java
@@ -0,0 +1,394 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.yahoo.yolean.chain.Dependencies.after;
+import static com.yahoo.yolean.chain.Dependencies.before;
+import static com.yahoo.yolean.chain.Dependencies.provides;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author tonytv
+ * @author gjoranv
+ */
+public class ChainBuilderTest {
+
+ static class Filter {
+
+ }
+
+ static class FilterA extends Filter {
+
+ }
+
+ static class FilterB extends Filter {
+
+ }
+
+ static class FilterExtendsA extends FilterA {
+
+ }
+
+ static class FilterExtendsB extends FilterB {
+
+ }
+
+ @Provides("A")
+ static class ProvidesA extends Filter {
+
+ }
+
+ @Provides("B")
+ static class ProvidesB extends Filter {
+
+ }
+
+ @Before("A")
+ static class BeforeA extends Filter {
+
+ }
+
+ @After("A")
+ static class AfterA extends Filter {
+
+ }
+
+ @Before("*")
+ @Provides("BeforeAll")
+ static class BeforeAll extends Filter {
+
+ }
+
+ @After("*")
+ @Provides("AfterAll")
+ static class AfterAll extends Filter {
+
+ }
+
+ @Before({ "BeforeAll", "*" })
+ static class BeforeBeforeAll extends Filter {
+
+ }
+
+ @After({ "AfterAll", "*" })
+ static class AfterAfterAll extends Filter {
+
+ }
+
+ static class ExtendsProvidesA extends ProvidesA {
+
+ }
+
+ @Provides("ExtendsA")
+ static class ProvidesA_and_ProvidesExtendsA extends ProvidesA {
+
+ }
+
+ @Before("B")
+ static class BeforeA_and_BeforeB extends BeforeA {
+
+ }
+
+ @Test
+ public void build_empty_chain() {
+ Chain<Filter> chain = getChain().build();
+ assertTrue(chain.isEmpty());
+ assertThat(chain.id(), is("myChain"));
+ }
+
+ @Test
+ public void filters_without_dependencies_are_not_reordered() {
+ List<Filter> filters = new ArrayList<>();
+
+ ChainBuilder<Filter> chain = new ChainBuilder<>("myChain");
+ for (int i = 0; i < 10; ++i) {
+ Filter filter = new Filter();
+ filters.add(filter);
+ chain.add(filter);
+ }
+
+ assertThat(chain.build(), contains(filters.toArray()));
+ }
+
+ @Test(expected = ChainCycleException.class)
+ public void cycles_are_detected() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ getChain().add(b, before(a)).add(a, before(b)).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void adding_same_instance_twice_is_illegal() {
+ Filter a = new Filter();
+
+ getChain().add(a).add(a).build();
+ }
+
+ @Test
+ public void before_instance() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b).add(a, before(b)).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void after_instance() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b, after(a)).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void before_class() {
+ Filter a = new FilterA();
+ Filter b = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(b).add(a, before(b.getClass())).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void after_class() {
+ Filter a = new FilterA();
+ Filter b = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(b, after(a.getClass())).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void before_subclass() {
+ Filter a = new FilterA();
+ Filter b = new FilterExtendsB();
+
+ Chain<Filter> chain = getChain().
+ add(b).add(a, before(FilterB.class)).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void after_subclass() {
+ Filter a = new FilterExtendsA();
+ Filter b = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(b, after(FilterA.class)).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void before_provided_name() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b, provides("B")).add(a, before("B")).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void after_provided_name() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b, after("A")).add(a, provides("A")).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void before_provided_name_in_annotations() {
+ Filter providesA = new ProvidesA();
+ Filter beforeA = new BeforeA();
+
+ Chain<Filter> chain = getChain().
+ add(providesA).add(beforeA).build();
+
+ assertThat(chain, is(new Chain<>("myChain", beforeA, providesA)));
+ }
+
+ @Test
+ public void after_provided_name_in_annotations() {
+ Filter providesA = new ProvidesA();
+ Filter afterA = new AfterA();
+
+ Chain<Filter> chain = getChain().
+ add(afterA).add(providesA).build();
+
+ assertThat(chain, is(new Chain<>("myChain", providesA, afterA)));
+ }
+
+ @Test
+ public void before_all() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b).add(a, before("*")).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void after_all() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(b, after("*")).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void before_all_annotation() {
+ Filter a = new Filter();
+ Filter beforeAll = new BeforeAll();
+
+ Chain<Filter> chain = getChain().
+ add(a).add(beforeAll).build();
+
+ assertThat(chain, is(new Chain<>("myChain", beforeAll, a)));
+ }
+
+ @Test
+ public void after_all_annotation() {
+ Filter a = new Filter();
+ Filter afterAll = new AfterAll();
+
+ Chain<Filter> chain = getChain().
+ add(afterAll).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, afterAll)));
+ }
+
+ @Test
+ public void before_all_annotated_component_can_be_before_another_component_that_is_also_before_all_annotated() {
+ Filter beforeAll = new BeforeAll();
+ Filter beforeBeforeAll = new BeforeBeforeAll();
+
+ Chain<Filter> chain = getChain().
+ add(beforeAll).add(beforeBeforeAll).build();
+
+ assertThat(chain, is(new Chain<>("myChain", beforeBeforeAll, beforeAll)));
+ }
+
+ @Test
+ public void after_all_annotated_component_can_be_after_another_component_that_is_also_after_all_annotated() {
+ Filter afterAll = new AfterAll();
+ Filter afterAfterAll = new AfterAfterAll();
+
+ Chain<Filter> chain = getChain().
+ add(afterAfterAll).add(afterAll).build();
+
+ assertThat(chain, is(new Chain<>("myChain", afterAll, afterAfterAll)));
+ }
+
+ @Test
+ public void component_that_is_not_annotated_can_be_before_a_before_all_annotated_component() {
+ Filter first = new Filter();
+ Filter beforeAll = new BeforeAll();
+
+ Chain<Filter> chain = getChain().
+ add(beforeAll).add(first, before("BeforeAll")).build();
+
+ assertThat(chain, is(new Chain<>("myChain", first, beforeAll)));
+ }
+
+ @Test
+ public void component_that_is_not_annotated_can_be_after_an_after_all_annotated_component() {
+ Filter last = new Filter();
+ Filter afterAll = new AfterAll();
+
+ Chain<Filter> chain = getChain().
+ add(last, after("AfterAll")).add(afterAll).build();
+
+ assertThat(chain, is(new Chain<>("myChain", afterAll, last)));
+ }
+
+ @Test
+ public void class_name_is_always_provided() {
+ Filter a = new FilterA();
+ Filter b = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(b, after(a.getClass().getName())).add(a).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b)));
+ }
+
+ @Test
+ public void provides_annotation_on_superclass_is_inherited_by_subclasses() {
+ Filter extendsA = new ExtendsProvidesA();
+ Filter first = new FilterA();
+ Filter last = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(last, after("A")).add(first, before("A")).add(extendsA).build();
+
+ assertThat(chain, is(new Chain<>("myChain", first, extendsA, last)));
+ }
+
+ @Test
+ public void provides_annotation_on_superclass_is_inherited_by_a_subclass_that_has_its_own_provides_annotation() {
+ Filter extendsA = new ProvidesA_and_ProvidesExtendsA();
+ Filter first = new FilterA();
+ Filter last = new FilterB();
+
+ Chain<Filter> chain = getChain().
+ add(last, after("A")).add(first, before("ExtendsA")).add(extendsA).build();
+
+ assertThat(chain, is(new Chain<>("myChain", first, extendsA, last)));
+ }
+
+ @Test
+ public void before_annotation_on_superclass_is_inherited_by_a_subclass_that_has_its_own_before_annotation() {
+ Filter beforeA_and_beforeB = new BeforeA_and_BeforeB();
+ Filter A = new ProvidesA();
+ Filter B = new ProvidesB();
+
+ Chain<Filter> chain = getChain().
+ add(A, before("*")).add(beforeA_and_beforeB).add(B).build();
+ assertThat(chain, is(new Chain<>("myChain", beforeA_and_beforeB, A, B)));
+ }
+
+ @Test
+ public void add_accepts_multiple_dependencies() {
+ Filter a = new Filter();
+ Filter b = new Filter();
+ Filter c = new Filter();
+
+ Chain<Filter> chain = getChain().
+ add(a).add(c).add(b, after(a), before(c)).build();
+
+ assertThat(chain, is(new Chain<>("myChain", a, b, c)));
+ }
+
+ private ChainBuilder<Filter> getChain() {
+ return new ChainBuilder<>("myChain");
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/chain/ChainTest.java b/yolean/src/test/java/com/yahoo/yolean/chain/ChainTest.java
new file mode 100644
index 00000000000..e8a8f9309b0
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/chain/ChainTest.java
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author tonytv
+ */
+public class ChainTest {
+
+ public static class Filter {
+
+ }
+
+ public static class OtherFilter extends Filter {
+
+ }
+
+ @Test
+ public void empty_chain_toString() {
+ Chain<Filter> c = new Chain<>("myChain");
+ assertThat(c.toString(), is("chain 'myChain'{}"));
+ }
+
+ @Test
+ public void singleton_chain_toString() {
+ Chain<Filter> c = new Chain<>("myChain", new Filter());
+ assertThat(c.toString(), is("chain 'myChain'{ Filter }"));
+ }
+
+ @Test
+ public void chain_toString() {
+ Chain<Filter> c = new Chain<>("myChain", new Filter(), new Filter(), new OtherFilter());
+ assertThat(c.toString(), is("chain 'myChain'{ Filter -> Filter -> OtherFilter }"));
+ }
+
+ @Test
+ public void non_equal_due_to_different_components() {
+ assertThat(new Chain<>("a", new Filter()), is(not(new Chain<>("a", new Filter()))));
+ }
+
+ @Test
+ public void non_equal_due_to_different_size_comopnents() {
+ assertThat(new Chain<>("a", new Filter()), is(not(new Chain<Filter>("a"))));
+ }
+
+ @Test
+ public void hashCode_equals() {
+ assertThat(new Chain<>("a").hashCode(), is(new Chain<Filter>("a").hashCode()));
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/chain/ContainsSameElements.java b/yolean/src/test/java/com/yahoo/yolean/chain/ContainsSameElements.java
new file mode 100644
index 00000000000..9eee34a45a3
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/chain/ContainsSameElements.java
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Set;
+
+import static java.util.Collections.sort;
+
+/**
+ * @author tonytv
+ */
+class ContainsSameElements<T> extends TypeSafeMatcher<Collection<? super T>> {
+
+ private final Set<T> identitySet;
+
+ public static <T> Matcher<Collection<? super T>> containsSameElements(Collection<T> collection) {
+ return new ContainsSameElements<>(collection);
+ }
+
+ public ContainsSameElements(Collection<T> collection) {
+ identitySet = toIdentitySet(collection);
+ }
+
+ @SuppressWarnings("SuspiciousMethodCalls")
+ @Override
+ protected boolean matchesSafely(Collection<? super T> collection2) {
+ for (Object elem : collection2) {
+ if (!identitySet.contains(elem)) {
+ return false;
+ }
+ }
+
+ return collection2.size() == identitySet.size();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("containsSameElements ");
+ appendCollection(description, identitySet);
+ }
+
+ private void appendCollection(Description description, Collection<?> collection) {
+ description.appendValueList("{", ", ", "}", elementsToStringSorted(collection));
+ }
+
+ private List<String> elementsToStringSorted(Collection<?> collection) {
+ List<String> result = new ArrayList<>();
+ for (Object o : collection) {
+ result.add(o.toString());
+ }
+ sort(result);
+ return result;
+ }
+
+ @Override
+ protected void describeMismatchSafely(Collection<? super T> collection, Description description) {
+ description.appendText("was ");
+ appendCollection(description, collection);
+ }
+
+ public static <T> Set<T> toIdentitySet(Collection<? extends T> collection) {
+ Set<T> identitySet = Collections.newSetFromMap(new IdentityHashMap<T, Boolean>());
+ identitySet.addAll(collection);
+ return identitySet;
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/chain/DirectedGraphTest.java b/yolean/src/test/java/com/yahoo/yolean/chain/DirectedGraphTest.java
new file mode 100644
index 00000000000..5097253f8e1
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/chain/DirectedGraphTest.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.junit.Assert.assertThat;
+
+public class DirectedGraphTest {
+
+ private DirectedGraph graph;
+ private Vertex[] v = new Vertex[10];
+
+ @Before
+ public void setup() {
+ for (int i = 0; i < v.length; i++) {
+ v[i] = new TestVertex(i);
+ }
+
+ graph = new DirectedGraph();
+ }
+
+ @Test
+ public void before_all_are_prioritized_first() {
+ graph.addVertex(v[0]);
+ graph.addBeginningVertex(v[1]);
+
+ assertThat(graph.topologicalSort(), contains(v[1], v[0]));
+ }
+
+ @Test
+ public void vertex_can_be_placed_before_before_all_vertices() {
+ graph.addVertex(v[0]);
+ graph.addBeginningVertex(v[1]);
+ graph.addEdge(v[0], v[1]);
+
+ assertThat(graph.topologicalSort(), contains(v[0], v[1]));
+ }
+
+ static class TestVertex implements Vertex {
+
+ private final int id;
+
+ TestVertex(int id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "Vertex{" + id + '}';
+ }
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/chain/EnumeratedIdentitySetTest.java b/yolean/src/test/java/com/yahoo/yolean/chain/EnumeratedIdentitySetTest.java
new file mode 100644
index 00000000000..8a55381cdef
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/chain/EnumeratedIdentitySetTest.java
@@ -0,0 +1,249 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.chain;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static com.yahoo.yolean.chain.ContainsSameElements.containsSameElements;
+import static com.yahoo.yolean.chain.ContainsSameElements.toIdentitySet;
+import static java.util.Collections.singleton;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for EnumeratedIdentitySet.
+ */
+public class EnumeratedIdentitySetTest {
+
+ private final List<Element> elements;
+
+ public EnumeratedIdentitySetTest() {
+ elements = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ elements.add(new Element());
+ }
+ }
+
+ @Test
+ public void size() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ assertThat(set.size(), is(elements.size()));
+
+ set.add(elements.get(0));
+ assertThat(set.size(), is(elements.size()));
+
+ set.remove(elements.get(0));
+ assertThat(set.size(), is(elements.size() - 1));
+ }
+
+ @Test
+ public void isEmpty() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>();
+ assertTrue(set.isEmpty());
+
+ set.add(elements.get(0));
+ assertFalse(set.isEmpty());
+ }
+
+ @Test
+ public void contains2() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ assertTrue(set.contains(elements.get(0)));
+ assertFalse(set.contains(new Element()));
+ }
+
+ @Test
+ public void iterator() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+
+ IdentityHashMap<Element, Void> collectedElements = new IdentityHashMap<>();
+ int count = 0;
+ for (Element element : set) {
+ collectedElements.put(element, null);
+ count++;
+ }
+
+ assertThat(collectedElements.size(), is(count));
+ assertThat(collectedElements.size(), is(elements.size()));
+
+ for (Element element : elements) {
+ assertTrue(collectedElements.containsKey(element));
+ }
+ }
+
+ @Test
+ public void toArray() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+
+ Object[] array = set.toArray();
+ Element[] array2 = set.toArray(new Element[0]);
+
+ assertThat(Arrays.asList(array), containsSameElements(set));
+ assertThat(Arrays.asList(array2), containsSameElements(set));
+ }
+
+ @Test
+ public void add() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>();
+ assertTrue(set.add(elements.get(0)));
+ assertFalse(set.add(elements.get(0)));
+ }
+
+ @Test
+ public void remove() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ assertTrue(set.remove(elements.get(0)));
+ assertFalse(set.remove(elements.get(0)));
+ }
+
+ @Test
+ public void containsAll() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ assertTrue(set.containsAll(elements.subList(0, 7)));
+ assertTrue(set.containsAll(elements));
+
+ List<Element> extendedElements = new ArrayList<>(elements);
+ extendedElements.add(new Element());
+ assertFalse(set.containsAll(extendedElements));
+ }
+
+ @Test
+ public void addAll() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>();
+ set.addAll(elements);
+
+ assertThat(set, containsSameElements(elements));
+ }
+
+ @Test
+ public void retainAll() {
+ Set<Element> set = new EnumeratedIdentitySet<>();
+
+ set.addAll(elements.subList(0, 5));
+ boolean changed = set.retainAll(toIdentitySet(elements.subList(3, 10)));
+
+ assertTrue(changed);
+ assertThat(set, containsSameElements(elements.subList(3, 5)));
+
+ changed = set.retainAll(toIdentitySet(elements));
+ assertFalse(changed);
+ }
+
+ @Test
+ public void removeAll() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ set.removeAll(elements.subList(0, 5));
+ assertThat(set, containsSameElements(elements.subList(5, 10)));
+ }
+
+ @Test
+ public void clear() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ set.clear();
+ assertThat(set, empty());
+ }
+
+ @Test
+ public void removeNulls() {
+ Element[] singletonArray = { null, elements.get(0), null };
+ assertThat(EnumeratedIdentitySet.removeNulls(singletonArray),
+ containsSameElements(Arrays.asList(elements.get(0))));
+
+ Element[] elementsWithNull = new Element[20];
+
+ Iterator<Element> iterator = elements.iterator();
+
+ copyElementsTo(iterator, elementsWithNull, 2, 1);
+ copyElementsTo(iterator, elementsWithNull, 4, 8);
+ copyElementsTo(iterator, elementsWithNull, 19, 1);
+
+ assertThat(EnumeratedIdentitySet.removeNulls(elementsWithNull), containsSameElements(elements));
+ }
+
+ private void copyElementsTo(Iterator<Element> iterator, Element[] array, int startIndex, int numItems) {
+ for (int i = 0; i < numItems; i++) {
+ array[i + startIndex] = iterator.next();
+ }
+ }
+
+ @Test
+ public void renumber_preserves_ordering() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>();
+
+ for (int i = 0; i < 200; i++) {
+ set.add(new Element());
+ }
+
+ set.addAll(elements);
+
+ EnumeratedIdentitySet<Element> elementsToPreserve = new EnumeratedIdentitySet<>(elements);
+
+ for (Iterator<Element> i = set.iterator(); i.hasNext(); ) {
+ if (!elementsToPreserve.contains(i.next())) {
+ i.remove();
+ }
+ }
+
+ List<Element> forceRenumber = set.insertionOrderedList();
+ assertThat(forceRenumber.size(), is(elements.size()));
+
+ for (int i = 0; i < elements.size(); i++) {
+ assertSame(forceRenumber.get(i), elements.get(i));
+ }
+
+ set.add(new Element());
+ assertThat(set.numbers(), containsSameElements(range(0, 10)));
+ }
+
+ @Test
+ public void renumber_when_empty() {
+ EnumeratedIdentitySet<Element> set = new EnumeratedIdentitySet<>(elements);
+ for (Iterator<Element> i = set.iterator(); i.hasNext(); ) {
+ i.next();
+ i.remove();
+ }
+
+ set.insertionOrderedList();
+ assertThat(set.numbers(), empty());
+
+ set.add(new Element());
+ assertThat(set.numbers(), containsSameElements(singleton(0)));
+ }
+
+ private List<Integer> range(int start, int endInclusive) {
+ List<Integer> result = new ArrayList<>();
+ for (int i = start; i <= endInclusive; i++) {
+ result.add(i);
+ }
+
+ return result;
+ }
+
+ static class Element {
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return "Element@" + System.identityHashCode(this);
+ }
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java b/yolean/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
new file mode 100644
index 00000000000..280fa2bd22f
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/concurrent/CopyOnWriteHashMapTest.java
@@ -0,0 +1,106 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @since 5.2
+ */
+public class CopyOnWriteHashMapTest {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ assertEquals(0, map.size());
+ assertEquals(true, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertNull(map.get("fooKey"));
+ assertNull(map.remove("fooKey"));
+ assertEquals(0, map.keySet().size());
+ assertEquals(0, map.entrySet().size());
+ assertEquals(0, map.values().size());
+
+ map.put("fooKey", "fooVal");
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+
+ map.put("barKey", "barVal");
+ assertEquals(2, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(true, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(true, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertEquals("fooVal", map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(2, map.keySet().size());
+ assertEquals(2, map.entrySet().size());
+ assertEquals(2, map.values().size());
+
+ assertEquals("fooVal", map.remove("fooKey"));
+ assertEquals(1, map.size());
+ assertEquals(false, map.isEmpty());
+ assertEquals(false, map.containsKey("fooKey"));
+ assertEquals(true, map.containsKey("barKey"));
+ assertEquals(false, map.containsValue("fooVal"));
+ assertEquals(true, map.containsValue("barVal"));
+ assertNull(map.get("fooKey"));
+ assertEquals("barVal", map.get("barKey"));
+ assertEquals(1, map.keySet().size());
+ assertEquals(1, map.entrySet().size());
+ assertEquals(1, map.values().size());
+ }
+
+ @Test
+ public void requireThatEntrySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ Map.Entry<String, String> entry = it.next();
+ assertEquals("fooKey", entry.getKey());
+ assertEquals("fooVal", entry.getValue());
+ }
+
+ @Test
+ public void requireThatKeySetDoesNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.keySet().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooKey", it.next());
+ }
+
+ @Test
+ public void requireThatValuesDoNotReflectConcurrentModifications() {
+ Map<String, String> map = new CopyOnWriteHashMap<>();
+ map.put("fooKey", "fooVal");
+
+ Iterator<String> it = map.values().iterator();
+ assertEquals("fooVal", map.remove("fooKey"));
+
+ assertTrue(it.hasNext());
+ assertEquals("fooVal", it.next());
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java b/yolean/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
new file mode 100644
index 00000000000..794a8f4e75f
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/concurrent/ThreadRobustListTestCase.java
@@ -0,0 +1,146 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.concurrent;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a>
+ */
+public class ThreadRobustListTestCase {
+
+ private final static int NUM_THREADS = 64;
+ private final static int NUM_ITEMS_TO_WRITE = 1000000;
+ private final static int NUM_TIMES_TO_READ = 10;
+
+ @Test
+ public void requireThatListIsThreadRobust() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(NUM_THREADS);
+ final ThreadRobustList<Integer> sharedList = new ThreadRobustList<>();
+
+ List<Callable<Boolean>> tasks = new ArrayList<>(NUM_THREADS);
+ tasks.add(new WriterTask(latch, sharedList));
+ for (int i = 1; i < NUM_THREADS; ++i) {
+ tasks.add(new ReaderTask(latch, sharedList));
+ }
+ for (Future<Boolean> result : Executors.newFixedThreadPool(NUM_THREADS).invokeAll(tasks)) {
+ assertTrue(result.get(60, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ assertTrue(lst.isEmpty());
+ assertFalse(lst.iterator().hasNext());
+
+ Object foo = new Object();
+ lst.add(foo);
+ assertFalse(lst.isEmpty());
+ Iterator<Object> it = lst.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertFalse(it.hasNext());
+
+ Object bar = new Object();
+ lst.add(bar);
+ assertFalse(lst.isEmpty());
+ assertNotNull(it = lst.iterator());
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatIteratorNextThrowsNoSuchElementExceptionWhenDone() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Iterator<Object> it = lst.iterator();
+ assertFalse(it.hasNext());
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatIteratorRemoveIsNotSupported() {
+ ThreadRobustList<Object> lst = new ThreadRobustList<>();
+ Object obj = new Object();
+ lst.add(obj);
+ Iterator<Object> it = lst.iterator();
+ assertTrue(it.hasNext());
+ assertSame(obj, it.next());
+ try {
+ it.remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ private static class WriterTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ WriterTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_ITEMS_TO_WRITE; ++i) {
+ sharedList.add(i);
+ }
+ return true;
+ }
+ }
+
+ private static class ReaderTask implements Callable<Boolean> {
+
+ final CountDownLatch latch;
+ final ThreadRobustList<Integer> sharedList;
+
+ ReaderTask(CountDownLatch latch, ThreadRobustList<Integer> sharedList) {
+ this.latch = latch;
+ this.sharedList = sharedList;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ latch.countDown();
+ assertTrue(latch.await(60, TimeUnit.SECONDS));
+ for (int i = 0; i < NUM_TIMES_TO_READ; ++i) {
+ Iterator<Integer> it = sharedList.iterator();
+ for (int j = 0; it.hasNext(); ++j) {
+ assertEquals(j, it.next().intValue());
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java b/yolean/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
new file mode 100644
index 00000000000..cc25ecf4cd0
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/trace/TraceNodeTestCase.java
@@ -0,0 +1,231 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TraceNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ assertEquals(6, node.timestamp());
+ assertFalse(node.children().iterator().hasNext());
+ assertFalse(node.descendants(Object.class).iterator().hasNext());
+ assertTrue(node.isRoot());
+ assertNull(node.parent());
+ assertSame(node, node.root());
+ }
+
+ @Test
+ public void requireThatToStringIsReadable() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 1))
+ .add(new TraceNode("b", 2)
+ .add(new TraceNode("c", 3)));
+ assertEquals("[ [ a b [ c ] ] ]", trace.toString());
+ }
+
+ @Test
+ public void requireThatPayloadMayBeNull() {
+ TraceNode node = new TraceNode(null, 6);
+ assertNull(node.payload());
+ }
+
+ @Test
+ public void requireThatRootNodesCanBeAdded() {
+ TraceNode parent = new TraceNode(null, 1);
+
+ TraceNode foo = new TraceNode(null, 2);
+ parent.add(foo);
+ assertSame(parent, foo.parent());
+
+ TraceNode bar = new TraceNode(null, 3);
+ parent.add(bar);
+ assertSame(parent, bar.parent());
+
+ Iterator<TraceNode> children = parent.children().iterator();
+ assertTrue(children.hasNext());
+ assertSame(foo, children.next());
+ assertTrue(children.hasNext());
+ assertSame(bar, children.next());
+ assertFalse(children.hasNext());
+
+ Iterator<Object> payloads = parent.descendants(Object.class).iterator();
+ assertFalse(payloads.hasNext());
+ }
+
+ @Test
+ public void requireThatNonRootNodeCanNotBeAdded() {
+ TraceNode foo = new TraceNode(null, 0);
+ TraceNode bar = new TraceNode(null, 0);
+ TraceNode baz = new TraceNode(null, 0);
+ bar.add(baz);
+ try {
+ foo.add(baz);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ assertSame(bar, baz.parent());
+ assertTrue(bar.children().iterator().hasNext());
+ assertFalse(foo.children().iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatChildrenIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).children());
+ }
+
+ @Test
+ public void requireThatDescendantsIsNeverNull() {
+ assertNotNull(new TraceNode(null, 69).descendants(Object.class));
+ }
+
+ @Test
+ public void requireThatDescendantsOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("f", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("g", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatDescendantsFilterPayloads() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode(69, 0))
+ .add(new TraceNode("b", 0)
+ .add(new TraceNode("c", 0))
+ .add(new TraceNode(new Object(), 0))))
+ .add(new TraceNode("d", 0)
+ .add(new TraceNode("e", 0)));
+
+ Iterator<String> it = trace.descendants(String.class).iterator();
+ assertTrue(it.hasNext());
+ assertEquals("a", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("b", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("c", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("d", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("e", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatVisitorOrderIsDepthFirstPrefix() {
+ TraceNode trace = new TraceNode(null, 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode(3, 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final List<Object> payloads = new ArrayList<>();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ payloads.add(node.payload());
+ }
+ });
+ assertEquals(Arrays.<Object>asList(null, "a", "b", "c", "d", 3, "f", "g"),
+ payloads);
+ }
+
+ @Test
+ public void requireThatVisitorDoesNotEnterOrLeaveNodesThatHaveNoChildren() {
+ TraceNode trace = new TraceNode(null, 0);
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ fail();
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ fail();
+ }
+ });
+ }
+
+ @Test
+ public void requireThatVisitorEntersAndLeavesNodesThatHaveChildren() {
+ TraceNode trace = new TraceNode("", 0)
+ .add(new TraceNode("a", 0)
+ .add(new TraceNode("b", 0))
+ .add(new TraceNode("c", 0)
+ .add(new TraceNode("d", 0))
+ .add(new TraceNode("e", 0))))
+ .add(new TraceNode("f", 0)
+ .add(new TraceNode("g", 0)));
+
+ final StringBuilder out = new StringBuilder();
+ trace.accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+ out.append(node.payload());
+ }
+
+ @Override
+ public void entering(TraceNode node) {
+ out.append("[");
+ }
+
+ @Override
+ public void leaving(TraceNode node) {
+ out.append("]");
+ }
+ });
+ assertEquals("[a[bc[de]]f[g]]", out.toString());
+ }
+}
diff --git a/yolean/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java b/yolean/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
new file mode 100644
index 00000000000..3b04c210c2f
--- /dev/null
+++ b/yolean/src/test/java/com/yahoo/yolean/trace/TraceVisitorTestCase.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.yolean.trace;
+
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class TraceVisitorTestCase {
+
+ @Test
+ public void requireThatTraceVisitorCompilesWithOnlyVisitImplemented() {
+ new TraceNode(null, 0).accept(new TraceVisitor() {
+
+ @Override
+ public void visit(TraceNode node) {
+
+ }
+ });
+ }
+}