net.yetamine.lang.functional.Traversing Maven / Gradle / Ivy
Show all versions of net.yetamine.lang Show documentation
/*
* Copyright 2016 Yetamine
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.yetamine.lang.functional;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
/**
* A specialized {@link Function} variant designed for {@code null}-safe
* traversal through data structures like trees and other kinds of graphs.
*
*
* This interface allows to use the pattern that following example demonstrates.
* Let's have a hierarchical structure of configuration data with named entries,
* that form natually a tree structure (e.g., from JSON, XML or another format):
*
*
* interface Node {
* Node child(String name);
* String value();
* }
*
*
* Getting a value from a node in depth is not very comfortable without using
* the pattern described further:
*
*
* // Let's get the address and port to listen at from network/listen/address
* // and network/listen.port entries:
*
* final String address;
* final int port;
*
* Node current = configuration.child("network");
* if (current != null) {
* current = current.child("listen");
* if (child != null) {
* Node node = current.child("address");
* if (node != null) {
* address = node.value();
* } else {
* address = "localhost";
* }
*
* node = current.child("port");
* if (node != null) {
* port = Integer.valueOf(node.value());
* } else {
* port = DEFAULT_PORT;
* }
* }
* }
*
*
* Implementing a utility method, that gets a path in the structure and finds
* the node if it exists, is definitely a solution for many cases, but solves
* just one particular problem. Still, there is the problem of missing values,
* getting and transforming them etc., specially when the node interface does
* not offer rich support for that.
*
*
* A solution is falling back to {@link Optional} and {@link Function}, which
* are ubiquitous, and rather adapting the use of a lean node interface than
* requiring the node interface to solve everything. This is especially useful
* in cases like the example above where particular entries in a possibly much
* richer structure are well-known and the code accesses them using constants.
* The constants could be more than just the names of the nodes: a node-specific
* access strategies.
*
*
* The example above could be solved then in the following way:
*
*
* static final Traversing<Node, Node> NETWORK = Traversing.of(n -> n.child("network"));
* static final Traversing<Node, Node> LISTEN = Traversing.of(n -> n.child("listen"));
* static final Traversing<Node, String> ADDRESS = Traversing.of(n -> n.child("address")).map(Node::value);
* static final Traversing<Node, Integer> PORT = Traversing.of(n -> n.child("port")).map(Node::value).map(Integer::valueOf);
*
*
* Of course, instead of such ad-hoc definitions, a set of definitions for the
* most commonly used types could be pre-defined. For instance, the {@code PORT}
* constant could be defined as:
*
*
* static final Traversing<Node, Integer> PORT = integerValue("port");
*
* // Where following definition of 'integerValue' would be in the scope:
* static Traversing<Node, Integer> integerValue(String name) {
* return Traversing.of(node -> node.child(name)).map(Node::value).map(Integer::valueOf);
* }
*
*
* Finally, when having such constants, the original code snippet for retrieving
* the address and port may look like this:
*
*
* final Optional<Node> listen = NETWORK.apply(configuration).flatMap(LISTEN);
* final String address = listen.flatMap(ADDRESS).orElse("localhost");
* final int port = listen.flatMap(PORT).orElse(DEFAULT_PORT);
*
*
* Hence, the traversal through the structure can be performed easily as a
* sequence of {@link Optional#flatMap(Function)} with elements of the path as
* the mapping functions: {@code root.flatMap(node1).flatMap(node2)}. And this
* is the actual sense of this interface: to enable this pattern and indicate
* for an implementation that the implementation supports and encourages it.
*
* @param
* the structure or container to traverse through using this instance
* @param
* the value provided as the result of asking the container for
* traversal
*/
@FunctionalInterface
public interface Traversing extends Function> {
/**
* @see java.util.function.Function#apply(java.lang.Object)
*/
Optional apply(C t);
/**
* Makes an instance that uses this instance to get a result to map then
* with the given mapping function.
*
*
* The implementation is equivalent to {@code apply(t).map(f)} where
* {@code t} is the parameter of resulting {@link Traversing} instance.
*
* @param
* the type of the value provided by the resulting
* {@link Traversing} instance
* @param f
* the given mapping function. It must not be {@code null}.
*
* @return a mapping composition of this instance and the given function
*/
default Traversing map(Function super V, ? extends T> f) {
Objects.requireNonNull(f);
return t -> apply(t).map(f);
}
/**
* Makes an instance that uses the given function to extract the root for
* the traversal.
*
* @param
* the type of the root function argument
* @param
* the type of the root function result
* @param f
* the traversal root extracting function. It must not be
* {@code null}.
*
* @return the new instance
*/
static Traversing of(Function super R, ? extends V> f) {
Objects.requireNonNull(f);
return t -> Optional.ofNullable(t).map(f);
}
}