[tree] Add TreeBuilder (#57)
* [tree] TreeBuilder: whenEmptyBuilderBuildThenTreeIsAnEmptyLeaf() * [tree] Provide default implementation of count() * [tree] Extract TreeMapper * [tree] MutableTree added * [tree] TreeBuilder: whenAddLeafThenTreeHasLeaf() * [tree] TreeBuilder: whenAddSubTreeThenTreeHasSubTree() * [maybe] Add Maybe.findFirst(Stream) and matchValue(Function, Supplier) * [tree] TreeBuilder: whenAddGrandChildThenTreeHasGrandChild() * [tree] TreeBuilder: whenAddMultipleChildrenThenTreeHasAllChildren() * [tree] Tree: avoid leaking MutableTree type as parameters * [changelog] update * [changelog][readme] Update to include TreeBuilder
This commit is contained in:
parent
73e59b0050
commit
8357d7f5d8
17 changed files with 986 additions and 36 deletions
|
@ -1,9 +1,13 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
1.2.1
|
2.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* [BREAKING] [tree] Tree.item() now returns a Maybe
|
||||||
|
* [tree] Add TreeBuilder
|
||||||
|
* [maybe] Add static Maybe.findFirst(Stream)
|
||||||
|
* [maybe] Add matchValue(Function, Supplier)
|
||||||
* Bump kemitix-parent from 5.1.1 to 5.2.0 (#55)
|
* Bump kemitix-parent from 5.1.1 to 5.2.0 (#55)
|
||||||
* Bump junit from 4.12 to 5.3.1 [#56)
|
* Bump junit from 4.12 to 5.3.1 [#56)
|
||||||
|
|
||||||
|
|
130
README.org
130
README.org
|
@ -255,7 +255,7 @@
|
||||||
have a =get()= method.
|
have a =get()= method.
|
||||||
|
|
||||||
Unlike =Optional=, when a =map()= results in a =null=, the =Maybe= will
|
Unlike =Optional=, when a =map()= results in a =null=, the =Maybe= will
|
||||||
continue to be a =Just=. =Optional= would switch to being empty. [[http://blog.vavr.io/the-agonizing-death-of-an-astronaut/][vavi.io
|
continue to be a =Just=. =Optional= would switch to being empty. [[http://blog.vavr.io/the-agonizing-death-of-an-astronaut/][vavr.io
|
||||||
follows the same behaviour as =Maybe=]].
|
follows the same behaviour as =Maybe=]].
|
||||||
|
|
||||||
#+BEGIN_SRC java
|
#+BEGIN_SRC java
|
||||||
|
@ -385,6 +385,16 @@
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =static <T> Maybe<T> findFirst(Stream<T> stream)=
|
||||||
|
|
||||||
|
Creates a Maybe from the first item in the stream, or nothing if the stream is empty.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final Maybe<Integer> just3 = Maybe.findFirst(Stream.of(3, 4, 2, 4));
|
||||||
|
final Maybe<Integer> nothing = Maybe.findFirst(Stream.empty());
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
*** Instance Methods
|
*** Instance Methods
|
||||||
|
|
||||||
**** =Maybe<T> filter(Predicate<T> predicate)=
|
**** =Maybe<T> filter(Predicate<T> predicate)=
|
||||||
|
@ -421,7 +431,8 @@
|
||||||
|
|
||||||
**** =void match(Consumer<T> just, Runnable nothing)=
|
**** =void match(Consumer<T> just, Runnable nothing)=
|
||||||
|
|
||||||
Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing.
|
Matches the Maybe, either just or nothing, and performs either the
|
||||||
|
Consumer, for Just, or Runnable for nothing.
|
||||||
|
|
||||||
#+BEGIN_SRC java
|
#+BEGIN_SRC java
|
||||||
Maybe.maybe(getValue())
|
Maybe.maybe(getValue())
|
||||||
|
@ -432,6 +443,20 @@
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =<R> R matchValue(Function<T, R> justMatcher, Supplier<R> nothingMatcher)=
|
||||||
|
|
||||||
|
Matches the Maybe, either just or nothing, and performs either the
|
||||||
|
Function, for Just, or Supplier for nothing.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final String value = Maybe.maybe(getValue())
|
||||||
|
.matchValue(
|
||||||
|
just -> Integer.toString(just),
|
||||||
|
() -> "nothing"
|
||||||
|
);
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
**** =T orElse(T otherValue)=
|
**** =T orElse(T otherValue)=
|
||||||
|
|
||||||
A value to use when Maybe is Nothing.
|
A value to use when Maybe is Nothing.
|
||||||
|
@ -907,6 +932,25 @@
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =static <B> TreeBuilder<B> builder(final Class<B> type)=
|
||||||
|
|
||||||
|
Create a new TreeBuilder starting with an empty tree.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final TreeBuilder<Integer> builder = Tree.builder(Integer.class);
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =static <B> TreeBuilder<B> builder(final Tree<B> tree)=
|
||||||
|
|
||||||
|
Create a new TreeBuilder for the given tree.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final Tree<Integer> tree = ...;
|
||||||
|
final TreeBuilder<Integer> builder = Tree.builder(tree);
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
*** Instance Methods
|
*** Instance Methods
|
||||||
|
|
||||||
**** =<R> Tree<R> map(Function<T, R> f)=
|
**** =<R> Tree<R> map(Function<T, R> f)=
|
||||||
|
@ -920,13 +964,13 @@
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
**** =Optional<T> item()=
|
**** =Maybe<T> item()=
|
||||||
|
|
||||||
Returns the contents of the Tree node within an Optional.
|
Returns the contents of the Tree node within a Maybe.
|
||||||
|
|
||||||
#+BEGIN_SRC java
|
#+BEGIN_SRC java
|
||||||
final Tree<Item> tree = ...;
|
final Tree<Item> tree = ...;
|
||||||
final Optional<Item> result = tree.item();
|
final Maybe<Item> result = tree.item();
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
@ -951,6 +995,82 @@
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
** TreeBuilder
|
||||||
|
|
||||||
|
A mutable builder for a Tree. Each TreeBuilder allows modification of a
|
||||||
|
single Tree node. You can use the =select(childItem)= method to get a
|
||||||
|
TreeBuilder for the subtree that has the given child item.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final TreeBuilder<Integer> builder = Tree.builder();
|
||||||
|
builder.set(12).addChildren(Arrays.asList(1, 3, 5, 7));
|
||||||
|
final TreeBuilder<Integer> builderFor3 = builder.select(3);
|
||||||
|
builderFor3.addChildren(Arrays.asList(2, 4));
|
||||||
|
final Tree<Integer> tree = builder.build();
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Will produce a tree like:
|
||||||
|
|
||||||
|
[[file:images/treebuilder-example.svg]]
|
||||||
|
|
||||||
|
|
||||||
|
*** Static Constructors
|
||||||
|
|
||||||
|
None. The TreeBuilder is instantiated by =Tree.builder()=.
|
||||||
|
|
||||||
|
*** Instance Methods
|
||||||
|
|
||||||
|
**** =Tree<T> build()=
|
||||||
|
|
||||||
|
Create the immutable Tree.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
final TreeBuilder<Integer> builder = Tree.builder();
|
||||||
|
final Tree<Integer> tree = builder.build();
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =TreeBuilder<T> item(T item)=
|
||||||
|
|
||||||
|
Set the current Tree's item and return the TreeBuilder.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =TreeBuilder<T> add(Tree<T> subtree)=
|
||||||
|
|
||||||
|
Adds the subtree to the current tree.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =TreeBuilder<T> addChild(T childItem)=
|
||||||
|
|
||||||
|
Add the Child item as a sub-Tree.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =TreeBuilder<T> addChildren(List<T> children)=
|
||||||
|
|
||||||
|
Add all the child items as subTrees.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
**** =Maybe<TreeBuilder<T>> select(T childItem)=
|
||||||
|
|
||||||
|
Create a TreeBuilder for the subTree of the current Tree that has the
|
||||||
|
childItem.
|
||||||
|
|
||||||
|
#+BEGIN_SRC java
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
** Lazy
|
** Lazy
|
||||||
|
|
||||||
A Lazy evaluated expression. Using a Supplier to provide the value, only
|
A Lazy evaluated expression. Using a Supplier to provide the value, only
|
||||||
|
|
8
images/treebuilder-example.dot
Normal file
8
images/treebuilder-example.dot
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
digraph G {
|
||||||
|
12 -> 1;
|
||||||
|
12 -> 3;
|
||||||
|
12 -> 5;
|
||||||
|
12 -> 7;
|
||||||
|
3 -> 2;
|
||||||
|
3 -> 4;
|
||||||
|
}
|
91
images/treebuilder-example.svg
Normal file
91
images/treebuilder-example.svg
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||||
|
-->
|
||||||
|
<!-- Title: G Pages: 1 -->
|
||||||
|
<svg width="278pt" height="188pt"
|
||||||
|
viewBox="0.00 0.00 278.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
|
||||||
|
<title>G</title>
|
||||||
|
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-184 274,-184 274,4 -4,4"/>
|
||||||
|
<!-- 12 -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>12</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="135" cy="-162" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="135" y="-158.3" font-family="Times,serif" font-size="14.00" fill="#000000">12</text>
|
||||||
|
</g>
|
||||||
|
<!-- 1 -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>1</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="27" cy="-90" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="27" y="-86.3" font-family="Times,serif" font-size="14.00" fill="#000000">1</text>
|
||||||
|
</g>
|
||||||
|
<!-- 12->1 -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>12->1</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M115.6918,-149.1278C98.6445,-137.763 73.5981,-121.0654 54.4656,-108.3104"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="56.4031,-105.3956 46.1411,-102.7607 52.5201,-111.2199 56.4031,-105.3956"/>
|
||||||
|
</g>
|
||||||
|
<!-- 3 -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>3</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="99" cy="-90" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="99" y="-86.3" font-family="Times,serif" font-size="14.00" fill="#000000">3</text>
|
||||||
|
</g>
|
||||||
|
<!-- 12->3 -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>12->3</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M126.2854,-144.5708C122.0403,-136.0807 116.8464,-125.6929 112.1337,-116.2674"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="115.237,-114.6477 107.6343,-107.2687 108.976,-117.7782 115.237,-114.6477"/>
|
||||||
|
</g>
|
||||||
|
<!-- 5 -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>5</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="171" cy="-90" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="171" y="-86.3" font-family="Times,serif" font-size="14.00" fill="#000000">5</text>
|
||||||
|
</g>
|
||||||
|
<!-- 12->5 -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>12->5</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M143.7146,-144.5708C147.9597,-136.0807 153.1536,-125.6929 157.8663,-116.2674"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="161.024,-117.7782 162.3657,-107.2687 154.763,-114.6477 161.024,-117.7782"/>
|
||||||
|
</g>
|
||||||
|
<!-- 7 -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>7</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="243" cy="-90" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="243" y="-86.3" font-family="Times,serif" font-size="14.00" fill="#000000">7</text>
|
||||||
|
</g>
|
||||||
|
<!-- 12->7 -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>12->7</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M154.3082,-149.1278C171.3555,-137.763 196.4019,-121.0654 215.5344,-108.3104"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="217.4799,-111.2199 223.8589,-102.7607 213.5969,-105.3956 217.4799,-111.2199"/>
|
||||||
|
</g>
|
||||||
|
<!-- 2 -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>2</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="63" cy="-18" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="63" y="-14.3" font-family="Times,serif" font-size="14.00" fill="#000000">2</text>
|
||||||
|
</g>
|
||||||
|
<!-- 3->2 -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>3->2</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M90.2854,-72.5708C86.0403,-64.0807 80.8464,-53.6929 76.1337,-44.2674"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="79.237,-42.6477 71.6343,-35.2687 72.976,-45.7782 79.237,-42.6477"/>
|
||||||
|
</g>
|
||||||
|
<!-- 4 -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>4</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="135" cy="-18" rx="27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="135" y="-14.3" font-family="Times,serif" font-size="14.00" fill="#000000">4</text>
|
||||||
|
</g>
|
||||||
|
<!-- 3->4 -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>3->4</title>
|
||||||
|
<path fill="none" stroke="#000000" d="M107.7146,-72.5708C111.9597,-64.0807 117.1536,-53.6929 121.8663,-44.2674"/>
|
||||||
|
<polygon fill="#000000" stroke="#000000" points="125.024,-45.7782 126.3657,-35.2687 118.763,-42.6477 125.024,-45.7782"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -118,6 +118,14 @@ final class Just<T> implements Maybe<T> {
|
||||||
justMatcher.accept(value);
|
justMatcher.accept(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R matchValue(
|
||||||
|
final Function<T, R> justMatcher,
|
||||||
|
final Supplier<R> nothingMatcher
|
||||||
|
) {
|
||||||
|
return justMatcher.apply(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Maybe<T> or(final Supplier<Maybe<T>> alternative) {
|
public Maybe<T> or(final Supplier<Maybe<T>> alternative) {
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -81,6 +81,19 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
|
||||||
return just(value);
|
return just(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Maybe from the first item in the stream, or nothing if the stream is empty.
|
||||||
|
*
|
||||||
|
* @param stream the Stream
|
||||||
|
* @param <T> the type of the stream
|
||||||
|
* @return a Maybe containing the first item in the stream
|
||||||
|
*/
|
||||||
|
public static <T> Maybe<T> findFirst(Stream<T> stream) {
|
||||||
|
return stream.findFirst()
|
||||||
|
.map(Maybe::just)
|
||||||
|
.orElse(Maybe.nothing());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the Maybe is a Just.
|
* Checks if the Maybe is a Just.
|
||||||
*
|
*
|
||||||
|
@ -173,11 +186,26 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
|
||||||
/**
|
/**
|
||||||
* Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing.
|
* Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing.
|
||||||
*
|
*
|
||||||
|
* <p>Unlike {@link #matchValue(Function, Supplier)}, this method does not return a value.</p>
|
||||||
|
*
|
||||||
* @param justMatcher the Consumer to pass the value of a Just to
|
* @param justMatcher the Consumer to pass the value of a Just to
|
||||||
* @param nothingMatcher the Runnable to call if the Maybe is a Nothing
|
* @param nothingMatcher the Runnable to call if the Maybe is a Nothing
|
||||||
*/
|
*/
|
||||||
public abstract void match(Consumer<T> justMatcher, Runnable nothingMatcher);
|
public abstract void match(Consumer<T> justMatcher, Runnable nothingMatcher);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches the Maybe, either just or nothing, and performs either the Function, for Just, or Supplier for nothing.
|
||||||
|
*
|
||||||
|
* <p>Unlike {@link #match(Consumer, Runnable)}, this method returns a value.</p>
|
||||||
|
*
|
||||||
|
* @param justMatcher the Function to pass the value of a Just to
|
||||||
|
* @param nothingMatcher the Supplier to call if the Maybe is a Nothing
|
||||||
|
* @param <R> the type of the matched result
|
||||||
|
*
|
||||||
|
* @return the matched result, from either the justMatcher or the nothingMatcher
|
||||||
|
*/
|
||||||
|
public abstract <R> R matchValue(Function<T, R> justMatcher, Supplier<R> nothingMatcher);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps the Maybe into another Maybe only when it is nothing.
|
* Maps the Maybe into another Maybe only when it is nothing.
|
||||||
*
|
*
|
||||||
|
|
|
@ -100,6 +100,14 @@ final class Nothing<T> implements Maybe<T> {
|
||||||
nothingMatcher.run();
|
nothingMatcher.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> R matchValue(
|
||||||
|
final Function<T, R> justMatcher,
|
||||||
|
final Supplier<R> nothingMatcher
|
||||||
|
) {
|
||||||
|
return nothingMatcher.get();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Maybe<T> or(final Supplier<Maybe<T>> alternative) {
|
public Maybe<T> or(final Supplier<Maybe<T>> alternative) {
|
||||||
return alternative.get();
|
return alternative.get();
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
package net.kemitix.mon.tree;
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic tree of trees and objects.
|
* A generic tree of trees and objects.
|
||||||
|
@ -37,7 +37,7 @@ import java.util.stream.Collectors;
|
||||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
*/
|
*/
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
class GeneralisedTree<T> implements Tree<T> {
|
class GeneralisedTree<T> implements Tree<T>, TreeMapper<T> {
|
||||||
|
|
||||||
private final T item;
|
private final T item;
|
||||||
private final List<Tree<T>> subTrees;
|
private final List<Tree<T>> subTrees;
|
||||||
|
@ -62,30 +62,12 @@ class GeneralisedTree<T> implements Tree<T> {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public <R> Tree<R> map(final Function<T, R> f) {
|
public <R> Tree<R> map(final Function<T, R> f) {
|
||||||
return Tree.of(f.apply(item), mapSubTrees(f));
|
return Tree.of(f.apply(item), mapTrees(f, subTrees()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<T> item() {
|
public Maybe<T> item() {
|
||||||
return Optional.ofNullable(item);
|
return Maybe.maybe(item);
|
||||||
}
|
|
||||||
|
|
||||||
private <R> List<Tree<R>> mapSubTrees(final Function<T, R> f) {
|
|
||||||
return subTrees.stream()
|
|
||||||
.map(subTree -> subTree.map(f::apply))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the number of items in the subtree.
|
|
||||||
*
|
|
||||||
* @return the sum of the subtrees, plus 1 if there is an item in this node
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("avoidinlineconditionals")
|
|
||||||
public int count() {
|
|
||||||
return (item != null ? 1 : 0)
|
|
||||||
+ subTrees.stream().mapToInt(Tree::count).sum();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
168
src/main/java/net/kemitix/mon/tree/MutableTree.java
Normal file
168
src/main/java/net/kemitix/mon/tree/MutableTree.java
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Paul Campbell
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies
|
||||||
|
* or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mutable {@link Tree}.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the objects help in the tree
|
||||||
|
*
|
||||||
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@SuppressWarnings("methodcount")
|
||||||
|
class MutableTree<T> implements Tree<T>, TreeMapper<T> {
|
||||||
|
|
||||||
|
private final List<MutableTree<T>> mySubTrees = new ArrayList<>();
|
||||||
|
|
||||||
|
private T item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link MutableTree}.
|
||||||
|
*
|
||||||
|
* @param <B> the type of the {@link Tree}.
|
||||||
|
*
|
||||||
|
* @return the MutableTree
|
||||||
|
*/
|
||||||
|
static <B> MutableTree<B> create() {
|
||||||
|
return new MutableTree<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new mutable tree to hold object of a type.
|
||||||
|
*
|
||||||
|
* @param item the item for the leaf node
|
||||||
|
* @param <B> the type of the object to be held in the tree
|
||||||
|
*
|
||||||
|
* @return a empty mutable tree
|
||||||
|
*/
|
||||||
|
static <B> MutableTree<B> leaf(final B item) {
|
||||||
|
return MutableTree.<B>create().set(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new mutable tree to hold the single item.
|
||||||
|
*
|
||||||
|
* @param item the item for the tree node
|
||||||
|
* @param subtrees the subtrees of the tree node
|
||||||
|
* @param <B> the type of the item
|
||||||
|
* @return a leaf node of a generalised tree
|
||||||
|
*/
|
||||||
|
static <B> MutableTree<B> of(final B item, final Collection<MutableTree<B>> subtrees) {
|
||||||
|
return MutableTree.<B>create().set(item).subTrees(subtrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate, or cast if possible, an existing Tree as a {@link MutableTree}.
|
||||||
|
* @param tree the tree to duplicate/cast
|
||||||
|
* @param <T> the type of the tree
|
||||||
|
* @return the mutable tree
|
||||||
|
*/
|
||||||
|
static <T> MutableTree<T> of(final Tree<T> tree) {
|
||||||
|
if (tree instanceof MutableTree) {
|
||||||
|
return (MutableTree<T>) tree;
|
||||||
|
}
|
||||||
|
final T item = tree.item().orElse(null);
|
||||||
|
final List<MutableTree<T>> subtrees = tree.subTrees()
|
||||||
|
.stream().map(MutableTree::of).collect(Collectors.toList());
|
||||||
|
return MutableTree.of(item, subtrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <R> MutableTree<R> map(final Function<T, R> f) {
|
||||||
|
final MutableTree<R> mutableTree = MutableTree.create();
|
||||||
|
final List<MutableTree<R>> trees = subTreesAsMutable().stream()
|
||||||
|
.map(subTree -> subTree.map(f))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return mutableTree
|
||||||
|
.set(f.apply(item))
|
||||||
|
.subTrees(trees);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Maybe<T> item() {
|
||||||
|
return Maybe.maybe(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the item of the tree.
|
||||||
|
*
|
||||||
|
* @param newItem the new item
|
||||||
|
*
|
||||||
|
* @return the tree
|
||||||
|
*/
|
||||||
|
MutableTree<T> set(final T newItem) {
|
||||||
|
this.item = newItem;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<Tree<T>> subTrees() {
|
||||||
|
final Stream<MutableTree<T>> mutableTreeStream = mySubTrees.stream();
|
||||||
|
final Stream<Tree<T>> treeStream = mutableTreeStream.map(Tree.class::cast);
|
||||||
|
final List<Tree<T>> treeList = treeStream.collect(Collectors.toList());
|
||||||
|
return treeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the subTrees of the tree.
|
||||||
|
*
|
||||||
|
* @param subTrees the subtrees
|
||||||
|
* @return the tree
|
||||||
|
*/
|
||||||
|
MutableTree<T> subTrees(final Collection<MutableTree<T>> subTrees) {
|
||||||
|
this.mySubTrees.clear();
|
||||||
|
this.mySubTrees.addAll(subTrees);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the subtree to the existing subtrees.
|
||||||
|
*
|
||||||
|
* @param subtree the subtree
|
||||||
|
* @return the current tree
|
||||||
|
*/
|
||||||
|
MutableTree<T> add(final Tree<T> subtree) {
|
||||||
|
mySubTrees.add(MutableTree.of(subtree));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mutable subtrees of the tree.
|
||||||
|
*
|
||||||
|
* @return a list of Trees
|
||||||
|
*/
|
||||||
|
List<MutableTree<T>> subTreesAsMutable() {
|
||||||
|
return mySubTrees;
|
||||||
|
}
|
||||||
|
}
|
91
src/main/java/net/kemitix/mon/tree/MutableTreeBuilder.java
Normal file
91
src/main/java/net/kemitix/mon/tree/MutableTreeBuilder.java
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Paul Campbell
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies
|
||||||
|
* or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for a {@link Tree}.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the tree
|
||||||
|
*
|
||||||
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
|
*/
|
||||||
|
class MutableTreeBuilder<T> implements TreeBuilder<T> {
|
||||||
|
|
||||||
|
private final MutableTree<T> root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create empty tree builder.
|
||||||
|
*/
|
||||||
|
MutableTreeBuilder() {
|
||||||
|
root = MutableTree.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tree builder to work with the given tree.
|
||||||
|
*
|
||||||
|
* @param tree the tree to build upon
|
||||||
|
*/
|
||||||
|
MutableTreeBuilder(final MutableTree<T> tree) {
|
||||||
|
root = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tree<T> build() {
|
||||||
|
return root.map(Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeBuilder<T> item(final T item) {
|
||||||
|
root.set(item);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeBuilder<T> add(final Tree<T> subtree) {
|
||||||
|
root.add(subtree);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TreeBuilder<T> addChild(final T childItem) {
|
||||||
|
root.add(MutableTree.leaf(childItem));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Maybe<TreeBuilder<T>> select(final T childItem) {
|
||||||
|
return Maybe.findFirst(
|
||||||
|
root.subTreesAsMutable()
|
||||||
|
.stream()
|
||||||
|
.filter((MutableTree<T> tree) -> matchesItem(childItem, tree))
|
||||||
|
.map(Tree::builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean matchesItem(final T childItem, final MutableTree<T> tree) {
|
||||||
|
return tree.item().map(childItem::equals).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,11 +22,11 @@
|
||||||
package net.kemitix.mon.tree;
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
import net.kemitix.mon.Functor;
|
import net.kemitix.mon.Functor;
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,22 +62,49 @@ public interface Tree<T> extends Functor<T, Tree<?>> {
|
||||||
return new GeneralisedTree<>(item, subtrees);
|
return new GeneralisedTree<>(item, subtrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link TreeBuilder} starting with an empty tree.
|
||||||
|
*
|
||||||
|
* @param type the type
|
||||||
|
* @param <B> the type of the tree
|
||||||
|
*
|
||||||
|
* @return a TreeBuilder
|
||||||
|
*/
|
||||||
|
public static <B> TreeBuilder<B> builder(final Class<B> type) {
|
||||||
|
return new MutableTreeBuilder<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link TreeBuilder} for the given tree.
|
||||||
|
*
|
||||||
|
* @param tree the tree to build upon
|
||||||
|
* @param <B> the type of the tree
|
||||||
|
*
|
||||||
|
* @return a TreeBuilder
|
||||||
|
*/
|
||||||
|
public static <B> TreeBuilder<B> builder(final Tree<B> tree) {
|
||||||
|
return new MutableTreeBuilder<>(MutableTree.of(tree));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract <R> Tree<R> map(Function<T, R> f);
|
public abstract <R> Tree<R> map(Function<T, R> f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the item within the node of the tree, if present.
|
* Return the item within the node of the tree, if present.
|
||||||
*
|
*
|
||||||
* @return an Optional containing the item
|
* @return a Maybe containing the item
|
||||||
*/
|
*/
|
||||||
public abstract Optional<T> item();
|
public abstract Maybe<T> item();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count the number of item in the tree, including subtrees.
|
* Count the number of item in the tree, including subtrees.
|
||||||
*
|
*
|
||||||
* @return the number of items
|
* @return the sum of the subtrees, plus 1 if there is an item in this node
|
||||||
*/
|
*/
|
||||||
public abstract int count();
|
public default int count() {
|
||||||
|
return item().matchValue(x -> 1, () -> 0)
|
||||||
|
+ subTrees().stream().mapToInt(Tree::count).sum();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The subtrees of the tree.
|
* The subtrees of the tree.
|
||||||
|
|
88
src/main/java/net/kemitix/mon/tree/TreeBuilder.java
Normal file
88
src/main/java/net/kemitix/mon/tree/TreeBuilder.java
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Paul Campbell
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies
|
||||||
|
* or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable builder for a {@link Tree}.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the tree to build
|
||||||
|
*
|
||||||
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
|
*/
|
||||||
|
public interface TreeBuilder<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the immutable {@link Tree}.
|
||||||
|
*
|
||||||
|
* @return a {@link Tree}
|
||||||
|
*/
|
||||||
|
public abstract Tree<T> build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current {@link Tree}'s item.
|
||||||
|
*
|
||||||
|
* @param item the item for the current {@link Tree}
|
||||||
|
*
|
||||||
|
* @return the TreeBuilder
|
||||||
|
*/
|
||||||
|
public abstract TreeBuilder<T> item(T item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the subtree to the current tree.
|
||||||
|
*
|
||||||
|
* @param subtree the tree to add
|
||||||
|
*
|
||||||
|
* @return the TreeBuilder
|
||||||
|
*/
|
||||||
|
public abstract TreeBuilder<T> add(Tree<T> subtree);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the Child item as a subTree.
|
||||||
|
*
|
||||||
|
* @param childItem the item to add as a subtree
|
||||||
|
* @return the TreeBuilder
|
||||||
|
*/
|
||||||
|
public abstract TreeBuilder<T> addChild(T childItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the child items as subTrees.
|
||||||
|
*
|
||||||
|
* @param children the items to add as a subtree
|
||||||
|
* @return the TreeBuilder
|
||||||
|
*/
|
||||||
|
public default TreeBuilder<T> addChildren(List<T> children) {
|
||||||
|
children.forEach(this::addChild);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TreeBuilder for the subTree of the current Tree that has the childItem.
|
||||||
|
*
|
||||||
|
* @param childItem the item of search the subtrees for
|
||||||
|
* @return a Maybe containing the TreeBuilder for the subtree, or Nothing if there child item is not found
|
||||||
|
*/
|
||||||
|
public abstract Maybe<TreeBuilder<T>> select(T childItem);
|
||||||
|
}
|
55
src/main/java/net/kemitix/mon/tree/TreeMapper.java
Normal file
55
src/main/java/net/kemitix/mon/tree/TreeMapper.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Paul Campbell
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||||
|
* and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies
|
||||||
|
* or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a list of Trees.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the objects help in the tree
|
||||||
|
*
|
||||||
|
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||||
|
*/
|
||||||
|
interface TreeMapper<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the Trees.
|
||||||
|
*
|
||||||
|
* @param f the mapping function
|
||||||
|
* @param trees the trees to map
|
||||||
|
* @param <R> the type of the resulting Trees
|
||||||
|
*
|
||||||
|
* @return a List of mapped sub-trees
|
||||||
|
*/
|
||||||
|
public default <R> List<Tree<R>> mapTrees(
|
||||||
|
final Function<T, R> f,
|
||||||
|
final List<Tree<T>> trees
|
||||||
|
) {
|
||||||
|
return trees.stream()
|
||||||
|
.map(subTree -> subTree.map(f))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package net.kemitix.mon;
|
package net.kemitix.mon;
|
||||||
|
|
||||||
import net.kemitix.mon.maybe.Maybe;
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
import org.assertj.core.api.AbstractIntegerAssert;
|
||||||
import org.assertj.core.api.WithAssertions;
|
import org.assertj.core.api.WithAssertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -8,7 +9,9 @@ import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static net.kemitix.mon.maybe.Maybe.*;
|
import static net.kemitix.mon.maybe.Maybe.*;
|
||||||
|
@ -252,6 +255,26 @@ class MaybeTest implements WithAssertions {
|
||||||
assertThat(flag).isTrue();
|
assertThat(flag).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void just_whenMatchValue_thenJustTriggers() {
|
||||||
|
//given
|
||||||
|
final Maybe<Integer> maybe = just(1);
|
||||||
|
//when
|
||||||
|
final String result = maybe.matchValue(integer -> "just", () -> "nothing");
|
||||||
|
//then
|
||||||
|
assertThat(result).isEqualTo("just");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nothing_whenMatchValue_thenNothingTriggers() {
|
||||||
|
//given
|
||||||
|
final Maybe<Integer> maybe = nothing();
|
||||||
|
//when
|
||||||
|
final String result = maybe.matchValue(integer -> "just", () -> "nothing");
|
||||||
|
//then
|
||||||
|
assertThat(result).isEqualTo("nothing");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void just_isJust_isTrue() {
|
void just_isJust_isTrue() {
|
||||||
//given
|
//given
|
||||||
|
@ -311,4 +334,36 @@ class MaybeTest implements WithAssertions {
|
||||||
//then
|
//then
|
||||||
assertThat(result.toOptional()).contains("two");
|
assertThat(result.toOptional()).contains("two");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void emptyStream_findFirst_isNothing() {
|
||||||
|
//given
|
||||||
|
final Stream<Object> stream = Stream.empty();
|
||||||
|
//when
|
||||||
|
final Maybe<Object> result = Maybe.findFirst(stream);
|
||||||
|
//then
|
||||||
|
assertThat(result.isNothing()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleItemStream_findFirst_isJustItem() {
|
||||||
|
//given
|
||||||
|
final String item = "item";
|
||||||
|
final Stream<String> stream = Stream.of(item);
|
||||||
|
//when
|
||||||
|
final Maybe<String> result = Maybe.findFirst(stream);
|
||||||
|
//then
|
||||||
|
assertThat(result.toOptional()).contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleItemStream_findFirst_isJustFirst() {
|
||||||
|
//given
|
||||||
|
final String first = "first";
|
||||||
|
final Stream<String> stream = Stream.of(first, "second");
|
||||||
|
//when
|
||||||
|
final Maybe<String> result = Maybe.findFirst(stream);
|
||||||
|
//then
|
||||||
|
assertThat(result.toOptional()).contains(first);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ class GeneralisedTreeTest implements WithAssertions {
|
||||||
//when
|
//when
|
||||||
final Tree<String> leaf = Tree.leaf(null);
|
final Tree<String> leaf = Tree.leaf(null);
|
||||||
//then
|
//then
|
||||||
assertThat(leaf.item()).isEmpty();
|
assertThat(leaf.item().isNothing()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -25,7 +25,7 @@ class GeneralisedTreeTest implements WithAssertions {
|
||||||
//when
|
//when
|
||||||
final Tree<String> leaf = Tree.leaf(item);
|
final Tree<String> leaf = Tree.leaf(item);
|
||||||
//then
|
//then
|
||||||
assertThat(leaf.item()).contains(item);
|
assertThat(leaf.item().toOptional()).contains(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
108
src/test/java/net/kemitix/mon/tree/MutableTreeTest.java
Normal file
108
src/test/java/net/kemitix/mon/tree/MutableTreeTest.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import org.assertj.core.api.WithAssertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
class MutableTreeTest implements WithAssertions {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canCreateAnEmptyLeaf() {
|
||||||
|
//when
|
||||||
|
final Tree<String> leaf = MutableTree.create();
|
||||||
|
//then
|
||||||
|
assertThat(leaf.item().isNothing()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canCreateANonEmptyLeaf() {
|
||||||
|
//given
|
||||||
|
final String item = "item";
|
||||||
|
//when
|
||||||
|
final MutableTree<String> leaf = MutableTree.create();
|
||||||
|
leaf.set(item);
|
||||||
|
//then
|
||||||
|
assertThat(leaf.item().toOptional()).contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void emptyLeafHasCountZero() {
|
||||||
|
//given
|
||||||
|
final Tree<Object> tree = MutableTree.create();
|
||||||
|
//when
|
||||||
|
final int count = tree.count();
|
||||||
|
//then
|
||||||
|
assertThat(count).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nonEmptyLeafHasCountOne() {
|
||||||
|
//given
|
||||||
|
final MutableTree<String> tree = MutableTree.create();
|
||||||
|
tree.set("value");
|
||||||
|
//when
|
||||||
|
final int count = tree.count();
|
||||||
|
//then
|
||||||
|
assertThat(count).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canCreateTreeWithSubTrees() {
|
||||||
|
//given
|
||||||
|
final String leafItem = "leaf";
|
||||||
|
final MutableTree<String> leaf = MutableTree.leaf(leafItem);
|
||||||
|
final MutableTree<String> tree = MutableTree.create();
|
||||||
|
//when
|
||||||
|
tree.subTrees(singletonList(leaf));
|
||||||
|
//then
|
||||||
|
assertThat(tree.subTrees()).containsExactly(leaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canMapNestedTrees() {
|
||||||
|
//given
|
||||||
|
final UUID uid1 = UUID.randomUUID();
|
||||||
|
final UUID uid2 = UUID.randomUUID();
|
||||||
|
final UUID uid3 = UUID.randomUUID();
|
||||||
|
final String sid1 = uid1.toString();
|
||||||
|
final String sid2 = uid2.toString();
|
||||||
|
final String sid3 = uid3.toString();
|
||||||
|
final MutableTree<UUID> tree = MutableTree.of(
|
||||||
|
uid1,
|
||||||
|
asList(
|
||||||
|
MutableTree.leaf(uid2),
|
||||||
|
MutableTree.leaf(uid3)));
|
||||||
|
//when
|
||||||
|
final Tree<String> result = tree.map(UUID::toString);
|
||||||
|
//then
|
||||||
|
final MutableTree<String> expectedTree = MutableTree.of(
|
||||||
|
sid1,
|
||||||
|
asList(
|
||||||
|
MutableTree.leaf(sid2),
|
||||||
|
MutableTree.leaf(sid3)));
|
||||||
|
assertThat(result).isEqualToComparingFieldByFieldRecursively(expectedTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void canCloneNonMutableTree() {
|
||||||
|
//given
|
||||||
|
final UUID rootItem = UUID.randomUUID();
|
||||||
|
final UUID leafItem = UUID.randomUUID();
|
||||||
|
final Tree<UUID> immutableTree = Tree.of(rootItem, singletonList(Tree.leaf(leafItem)));
|
||||||
|
//when
|
||||||
|
final MutableTree<UUID> mutableTree = MutableTree.of(immutableTree);
|
||||||
|
//then
|
||||||
|
assertThat(mutableTree.count()).isEqualTo(2);
|
||||||
|
assertThat(mutableTree.item().toOptional()).contains(rootItem);
|
||||||
|
final List<Tree<UUID>> subTrees = mutableTree.subTrees();
|
||||||
|
assertThat(subTrees).hasSize(1);
|
||||||
|
assertThat(subTrees.get(0).item().toOptional()).contains(leafItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java
Normal file
109
src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package net.kemitix.mon.tree;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.kemitix.mon.maybe.Maybe;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class TreeBuilderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenEmptyBuilderBuildThenTreeIsAnEmptyLeaf() {
|
||||||
|
//when
|
||||||
|
final Tree<Node> result = Tree.builder(Node.class).build();
|
||||||
|
//then
|
||||||
|
assertThat(result.count()).isZero();
|
||||||
|
assertThat(result.item().isNothing()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenAddLeafThenTreeHasLeaf() {
|
||||||
|
//given
|
||||||
|
final Node node = createANode();
|
||||||
|
//when
|
||||||
|
final Tree<Node> result =
|
||||||
|
Tree.builder(Node.class)
|
||||||
|
.item(node).build();
|
||||||
|
//then
|
||||||
|
assertThat(result.count()).isEqualTo(1);
|
||||||
|
assertThat(result.item().toOptional()).contains(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenAddSubTreeThenTreeHasSubTree() {
|
||||||
|
//given
|
||||||
|
final Tree<Node> subtree = MutableTree.leaf(createANode());
|
||||||
|
//when
|
||||||
|
final Tree<Node> result =
|
||||||
|
Tree.builder(Node.class)
|
||||||
|
.item(createANode())
|
||||||
|
.add(subtree)
|
||||||
|
.build();
|
||||||
|
//then
|
||||||
|
assertThat(result.count()).isEqualTo(2);
|
||||||
|
assertThat(result.subTrees()).contains(subtree);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenAddGrandChildThenTreeHasGrandChild() {
|
||||||
|
//given
|
||||||
|
final Node rootNode = new Node("root");
|
||||||
|
final Node childNode = new Node("child");
|
||||||
|
final Node grandchildNode = new Node("grandchild");
|
||||||
|
//when
|
||||||
|
final TreeBuilder<Node> rootBuilder =
|
||||||
|
Tree.builder(Node.class)
|
||||||
|
.item(rootNode)
|
||||||
|
.addChild(childNode);
|
||||||
|
rootBuilder.select(childNode)
|
||||||
|
.map(childBuilder -> childBuilder.addChild(grandchildNode));
|
||||||
|
final Tree<Node> result = rootBuilder.build();
|
||||||
|
//then
|
||||||
|
assertThat(result.count()).isEqualTo(3);
|
||||||
|
assertThat(result).isEqualToComparingFieldByFieldRecursively(
|
||||||
|
MutableTree.of(rootNode, Collections.singleton(
|
||||||
|
MutableTree.of(childNode, Collections.singleton(
|
||||||
|
MutableTree.leaf(grandchildNode))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenAddMultipleChildrenThenTreeHasAllChildren() {
|
||||||
|
//given
|
||||||
|
final Node rootNode = new Node("root");
|
||||||
|
final Node child1Node = createANode();
|
||||||
|
final Node child2Node = createANode();
|
||||||
|
final Node child3Node = createANode();
|
||||||
|
//when
|
||||||
|
final Tree<Node> result =
|
||||||
|
Tree.builder(Node.class)
|
||||||
|
.item(rootNode)
|
||||||
|
.addChildren(asList(child1Node, child2Node, child3Node))
|
||||||
|
.build();
|
||||||
|
//then
|
||||||
|
assertThat(result.count()).isEqualTo(4);
|
||||||
|
assertThat(result).isEqualToComparingFieldByFieldRecursively(
|
||||||
|
MutableTree.of(rootNode, asList(
|
||||||
|
MutableTree.leaf(child1Node),
|
||||||
|
MutableTree.leaf(child2Node),
|
||||||
|
MutableTree.leaf(child3Node))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node createANode() {
|
||||||
|
return new Node(createAName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createAName() {
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
private static class Node {
|
||||||
|
private final String name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue