package dgm.trees;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import dgm.exceptions.UnreachableCodeReachedException;
import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.*;
/**
* Helper functions for {@link Tree}s
*
* @author wires
*
*/
public final class Trees
{
private Trees() {}
public static <A> void printTree(Tree<A> tree, PrintStream ps)
{
ps.printf("(%s (", tree.value());
for (Tree<A> t : tree.children())
{
printTree(t, ps);
ps.print(",");
}
ps.print("))");
}
/**
* Turn a {@code Tree} of {@code Optional}s into a {@code Optional}al {@code Tree}, meaning that if there is one
* absent value in the tree, the whole tree is absent.
*/
public static <A> Optional<Tree<A>> optional(Tree<Optional<A>> treeOfOptionals)
{
class ValueIsAbsent extends RuntimeException {}
final Function<Optional<A>, A> nonAbsent = new Function<Optional<A>, A>()
{
@Override
public A apply(Optional<A> input)
{
if(!input.isPresent())
throw new ValueIsAbsent();
return input.get();
}
};
try
{
return Optional.of(map(nonAbsent, treeOfOptionals));
}
catch (ValueIsAbsent e)
{
return Optional.absent();
}
}
public static final <_> boolean isLeaf(final Tree<_> tree)
{
return Iterables.isEmpty(tree.children());
}
/**
* Map a function over a tree
*
* @param fn Function
* @param tree Tree of {@code A}'s
* @return Tree of {@code B}'s
*/
public static <A,B> Tree<B> map(final Function<A,B> fn, Tree<A> tree)
{
final B value = fn.apply(tree.value());
if (isLeaf(tree))
return new ImmutableTree<B>(value);
else
{
final Function<Tree<A>,Tree<B>> tmap = new Function<Tree<A>,Tree<B>>()
{
public Tree<B> apply(Tree<A> tree)
{
return map(fn, tree);
}
};
final Iterable<Tree<B>> tb = Iterables.transform(tree.children(), tmap);
return new ImmutableTree<B>(value, tb);
}
}
/**
* Turn a {@link Function} into an {@link Callable}
*
* @param fn Function
* @return Callable
*/
public static <A,B> Function<A,Callable<B>> mkAsync(final Function<A,B> fn)
{
return new Function<A,Callable<B>>()
{
@Override
public Callable<B> apply(final A a)
{
return new Callable<B>()
{
@Override
public B call() throws Exception
{
return fn.apply(a);
}
};
}
};
}
/**
* In parallel, map a function over a tree
*
* @param executor Where to submit the jobs to
* @param fn The function applied to every node in the tree
* @param tree The tree to map
* @throws InterruptedException
*/
public static <A,B> Tree<B> pmap(final ExecutorService executor, final Function<A,B> fn, Tree<A> tree)
throws InterruptedException, ExecutionException
{
final class PmapException extends RuntimeException
{
public PmapException(Exception e)
{
super(e);
}
}
// convert the tree into jobs, and submit them to the executor
final Function<A,Future<B>> toJob = new Function<A,Future<B>>()
{
public Future<B> apply(final A a)
{
return executor.submit(new Callable<B>()
{
@Override
public B call() throws Exception
{
return fn.apply(a);
}
});
}
};
// get automatically waits for jobs to finish
final Function<Future<B>, B> waitDone = new Function<Future<B>, B>()
{
@Override
public B apply(final Future<B> b)
{
try
{
return b.get();
}
catch (InterruptedException e)
{
throw new PmapException(e);
}
catch (ExecutionException e)
{
throw new PmapException(e);
}
}
};
// for each node in the tree, start a job
final Tree<Future<B>> jobTree = map(toJob, tree);
try
{
// blocking wait for all jobs to finish
return map(waitDone, jobTree);
}
catch (final PmapException exception)
{
final Throwable inner = exception.getCause();
if(inner instanceof InterruptedException)
throw (InterruptedException)inner;
if(inner instanceof ExecutionException)
throw (ExecutionException)inner;
throw exception;
}
}
/**
* Convert a tree of JsonNode's into a Json tree like so:
*
* <pre>
* { "value": NODE_VALUE,
* "children: [
* {
* "value": CHILD1_VALUE,
* "children": [ ... ]
* },
* {
* "value": CHILD2_VALUE,
* "children": [....]
* }
* ]
* }
* </pre>
*/
// TODO all this is very memory inefficient and will lead to stack overflows for very deep trees
public static ObjectNode toJsonTree(ObjectMapper objectMapper, Tree<? extends JsonNode> tree)
{
final ObjectNode node = objectMapper.createObjectNode();
node.put("_value", tree.value());
final ArrayNode children = objectMapper.createArrayNode();
for (Tree<? extends JsonNode> child : tree.children())
children.add(toJsonTree(objectMapper, child));
node.put("_children", children);
return node;
}
/** Helper method to directly walk a TreeNode instance */
public static <T> Iterable<T> bfsWalk(Tree<T> root)
{
// view TreeNode as tree
final TreeViewer<Tree<T>> viewer = new TreeViewer<Tree<T>>()
{
@Override
public Iterable<Tree<T>> children(Tree<T> node)
{
return node.children();
}
};
// get value from a tree
final Function<Tree<T>,T> getValue = new Function<Tree<T>,T>()
{
@Override
public T apply(Tree<T> node)
{
return node.value();
}
};
final Iterable<Tree<T>> ti = bfsWalk(root, viewer);
return Iterables.transform(ti, getValue);
}
public static <A> Iterable<A> bfsWalk(final A root, final TreeViewer<A> viewer)
{
return new Iterable<A>()
{
@Override
public Iterator<A> iterator()
{
return new Iterator<A>()
{
final Queue<A> q = new LinkedList<A>(Collections.singleton(root));
@Override
public boolean hasNext()
{
return !q.isEmpty();
}
@Override
public A next()
{
final A a = q.poll();
// add children to end of the queue
for (A c : viewer.children(a))
if(!q.offer(c))
throw new UnreachableCodeReachedException();
return a;
}
@Override
public void remove()
{
// TODO use some sort of standard
// "NotImplemented" exception (how does guava do
// this?)
throw new RuntimeException("Not Implemented");
}
};
}
};
}
}