All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.progrm_jarvis.javacommons.recursion.Recursions Maven / Gradle / Ivy

package ru.progrm_jarvis.javacommons.recursion;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import ru.progrm_jarvis.javacommons.primitive.wrapper.ReferenceWrapper;

import java.util.function.Function;
import java.util.stream.Stream;

/**
 * Utilities for performing common recursive operations.
 *
 * @author xdark
 * @author progrm_jarvis
 */
@UtilityClass
public class Recursions {

    /**
     * Recursively traverses the sources evaluating to the recursive hierarchy.
     * The provided {@link Stream} will attempt to be as lazy as possible.
     *
     * @param sources stream of sources which should be traversed recursively
     * @param digger function used to generate the stream of child elements from the base one
     * @param  type of source elements
     * @return stream of recursive hierarchy
     *
     * @throws NullPointerException if {@code sources} is {@code null}
     * @throws NullPointerException if {@code digger} is {@code null}
     * @see #recurse(Object, Function) equivalent method with single source
     */
    public  Stream recurse(
            final @NonNull Stream sources,
            final @NonNull Function> digger
    ) {
        return sources.flatMap(source -> lazyRecursiveStep(source, digger));
    }

    /**
     * 

Recursively traverses the source evaluating to the recursive hierarchy. * The provided {@link Stream} will attempt to be as lazy as possible.

*

An example providing the stream of class hierarchy of {@link String} class:

*
{@code
     * Recursions., Method>recurse(
     *         String.class,
     *         clazz -> {
     *             final Class superClass;
     *             return Stream.concat(
     *                     (superClass = clazz.getSuperclass()) == null
     *                             ? Stream.empty() : Stream.of(superClass),
     *                     Arrays.stream(clazz.getInterfaces())
     *             );
     *         }
     * )
     * }
* * @param source source element used * @param digger function used to generate the stream of child elements from the base one * @param type of source elements * @return stream of recursive hierarchy * * @throws NullPointerException if {@code digger} is {@code null} * @see #recurse(Stream, Function) equivalent method with {@link Stream} source */ public Stream recurse( final S source, final @NonNull Function> digger ) { return lazyRecursiveStep(source, digger); } /** * Recursively traverses the sources evaluating to the {@link Stream stream} of hierarchy members' components. * * @param sources stream of sources which should be traversed recursively * @param digger function used to generate the stream of child elements from the base one * @param elementGetter function used to get the elements from a source * @param type of source elements * @param type of resulting elements * @return stream of elements got from recursive hierarchy * * @throws NullPointerException if {@code sources} is {@code null} * @throws NullPointerException if {@code digger} is {@code null} * @throws NullPointerException if {@code elementGetter} is {@code null} * @see #recurseFully(Object, Function, Function) equivalent method with single source */ public Stream recurseFully( final @NonNull Stream sources, final @NonNull Function> digger, final @NonNull Function> elementGetter ) { return recurseFullyInternal(sources.map(SourceOrElement::source), digger, elementGetter); } /** *

Recursively traverses the source evaluating to the {@link Stream stream} hierarchy members' components.

*

An example providing the stream of all declared method in {@link String} class hierarchy:

*
{@code
     * Recursions., Method>recurseFully(
     *         String.class,
     *         clazz -> {
     *             final Class superClass;
     *             return Stream.concat(
     *                     (superClass = clazz.getSuperclass()) == null
     *                             ? Stream.empty() : Stream.of(superClass),
     *                     Arrays.stream(clazz.getInterfaces())
     *             );
     *         },
     *         clazz -> Arrays.stream(clazz.getDeclaredMethods())
     * )
     * }
* * @param source source element used * @param digger function used to generate the stream of child elements from the base one * @param elementGetter function used to get the elements from a source * @param type of source elements * @param type of resulting elements * @return stream of elements got from recursive hierarchy * * @throws NullPointerException if {@code digger} is {@code null} * @throws NullPointerException if {@code elementGetter} is {@code null} * @see #recurseFully(Stream, Function, Function) equivalent method with {@link Stream} source */ public Stream recurseFully( final S source, final @NonNull Function> digger, final @NonNull Function> elementGetter ) { return recurseFullyInternal(Stream.of(SourceOrElement.source(source)), digger, elementGetter); } /** * Performs a lazy recursive step. * * @param source source element used * @param digger function used to generate the stream of child elements from the base one * @param type of source elements * @return stream of elements in recursive hierarchy */ private Stream lazyRecursiveStep( final S source, final @NotNull Function> digger ) { return Stream.concat( Stream.of(source), Stream.of(source).flatMap(digger).flatMap(child -> lazyRecursiveStep(child, digger)) ); } /** * Recursively traverses the sources evaluating to the stream of results. * * @param source source element used * @param digger function used to generate the stream of child elements from the base one * @param elementGetter function used to get the elements from a source * @param type of source elements * @param type of resulting elements * @return stream of elements got from recursive hierarchy */ private @NotNull Stream recurseFullyInternal( final @NotNull Stream<@NotNull SourceOrElement> source, final @NotNull Function> digger, final @NotNull Function> elementGetter ) { final ReferenceWrapper, Stream>>> mapperReference; (mapperReference = ReferenceWrapper.create()) .set(sourceOrElement -> sourceOrElement.isSource() ? Stream.concat( // recursively put child sources digger.apply(sourceOrElement.asSource()) .>map(SourceOrElement::source) .flatMap(mapperReference.get()), // put finite elements being looked up elementGetter.apply(sourceOrElement.asSource()) .map(SourceOrElement::element) ) : /* no-op for finite elements in the stream */ Stream.of(sourceOrElement)); return source .flatMap(mapperReference.get()) // note: another `flatMap` could be used instead, // but filter + map is more reasonable here as it minimizes the amount of allocations // as they would be required per each element wrapped into a temporary single-element `Stream` .filter(SourceOrElement::isElement) .map(SourceOrElement::asElement); } /** * Either a source or an element. * * @param type of source element * @param type of target element */ private interface SourceOrElement { /** * Returns whether this is a source. * * @return {@code true} if this a source and {@code false} otherwise */ boolean isSource(); /** * Returns whether this is an element. * * @return {@code true} if this an element and {@code false} otherwise */ boolean isElement(); /** * Gets the value as a source. * * @return the value as a source * * @apiNote call on value which is not a {@link #isSource() source} is undefined behaviour */ S asSource(); /** * Gets the value as an element. * * @return the value as an element * * @apiNote call on value which is not an {@link #isElement() element} is undefined behaviour */ E asElement(); static Recursions.SourceOrElement source(final S source) { return new TaggedSourceOrElement<>(false, source); } static Recursions.SourceOrElement element(final E element) { return new TaggedSourceOrElement<>(true, element); } /** * {@link SourceOrElement} which identifies its type by having a tag field. * * @param type of source element * @param type of target element * @implNote unlike {@link ru.progrm_jarvis.javacommons.object.Result} which uses different classes for states, * tag-based implementation is more preferred here as there is no need gor guaranteeing the invariant * outside this class thus typed access is effectively just field-access wrapped in a method call * which can easily be monomorphized by JIT */ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) final class TaggedSourceOrElement implements SourceOrElement { @Getter boolean element; Object value; @Override public boolean isSource() { return !element; } @Override @SuppressWarnings("unchecked") public S asSource() { assert !element : "this is not a source"; return (S) value; } @Override @SuppressWarnings("unchecked") public E asElement() { assert element : "this is not an element"; return (E) value; } } } }