[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:
Paul Campbell 2018-10-14 08:55:09 +01:00 committed by GitHub
parent 73e59b0050
commit 8357d7f5d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 986 additions and 36 deletions

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,8 @@
digraph G {
12 -> 1;
12 -> 3;
12 -> 5;
12 -> 7;
3 -> 2;
3 -> 4;
}

View 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&#45;&gt;1 -->
<g id="edge1" class="edge">
<title>12&#45;&gt;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&#45;&gt;3 -->
<g id="edge2" class="edge">
<title>12&#45;&gt;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&#45;&gt;5 -->
<g id="edge3" class="edge">
<title>12&#45;&gt;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&#45;&gt;7 -->
<g id="edge4" class="edge">
<title>12&#45;&gt;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&#45;&gt;2 -->
<g id="edge5" class="edge">
<title>3&#45;&gt;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&#45;&gt;4 -->
<g id="edge6" class="edge">
<title>3&#45;&gt;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

View file

@ -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;

View file

@ -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.
* *

View file

@ -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();

View file

@ -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();
} }
/** /**

View 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;
}
}

View 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);
}
}

View file

@ -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.

View 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);
}

View 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());
}
}

View file

@ -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);
}
} }

View file

@ -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

View 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);
}
}

View 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;
}
}