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

org.apache.isis.commons.functional.Railway Maven / Gradle / Ivy

Go to download

Apache Isis Commons is a library with utilities, that are shared with the entire Apache Isis ecosystem.

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.isis.commons.functional;

import java.io.Serializable;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

import org.springframework.lang.Nullable;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;

/**
 * The {@link Railway} type represents a value of one of two possible types (a disjoint union)
 * of {@link Success} or {@link Failure}, where chaining follows the Railway Pattern,
 * that is, once failed, stays failed.
 * 

* Factory methods {@link Railway#success(Object)} and {@link Railway#failure(Object)} * correspond to the two possible values. * * @apiNote It is a common functional programming convention, to map the success value right. * * @since 2.0 {@index} */ public interface Railway { // -- FACTORIES public static Success success(final @Nullable S success) { return new Success<>(success); } public static Failure failure(final @NonNull F failure) { return new Failure<>(failure); } // -- PREDICATES boolean isSuccess(); boolean isFailure(); // -- ACCESSORS /** * Optionally returns the contained {@code value} based on presence, * that is, if this is a {@link Success}. */ Optional getSuccess(); default S getSuccessElseFail() { return getSuccess().orElseThrow(); } @SneakyThrows default S getSuccessElseFail(final Function toThrowable) { val successIfAny = getSuccess(); if(successIfAny.isPresent()) { return successIfAny.get(); } throw toThrowable.apply(getFailureElseFail()); } /** * Optionally returns the contained {@code failure} based on presence, * that is, if this is a {@link Failure}. */ Optional getFailure(); default F getFailureElseFail() { return getFailure().orElseThrow(); } // -- PEEKING /** * Peeks into the contained {@code success} if this is a {@link Success}. */ Railway ifSuccess(final @NonNull Consumer successConsumer); /** * Peeks into the contained {@code failure} if this is a {@link Failure}. */ Railway ifFailure(final @NonNull Consumer failureConsumer); // -- MAPPING /** * Maps this {@link Railway} to another if this is a {@link Success}. * Otherwise if this is a {@link Failure} acts as identity operator. */ Railway mapSuccess(final @NonNull Function successMapper); /** * Maps this {@link Railway} to another if this is a {@link Failure}. * Otherwise if this is a {@link Success} acts as identity operator. */ Railway mapFailure(final @NonNull Function failureMapper); // -- FOLDING /** * Maps the contained {@code success} or {@code failure} to a new value of type {@code R} * using according mapping function {@code successMapper} or {@code failureMapper}. */ R fold( final @NonNull Function failureMapper, final @NonNull Function successMapper); // -- CHAINING /** * Railway Pattern * If this is a {@link Success}, returns a new {@link Railway} as produced by the * chainingFunction, that receives the current success value as input. * Otherwise if this is a {@link Failure} acts as identity operator and * the chainingFunction is not executed. *

* In other words: if once failed stays failed */ Railway chain(@NonNull Function> chainingFunction); // -- SUCCESS @lombok.Value @RequiredArgsConstructor final class Success implements Railway, Serializable { private static final long serialVersionUID = 1L; private final @NonNull S success; @Override public boolean isSuccess() { return true; } @Override public boolean isFailure() { return false; } @Override public Optional getSuccess() { return Optional.of(success); } @Override public Optional getFailure() { return Optional.empty(); } @Override public Success ifSuccess(final @NonNull Consumer successConsumer) { successConsumer.accept(success); return this; } @Override public Success ifFailure(final @NonNull Consumer failureConsumer) { return this; } @Override public Success mapSuccess(final @NonNull Function successMapper) { return Railway.success(successMapper.apply(success)); } @Override public Success mapFailure(final @NonNull Function failureMapper) { return Railway.success(success); } @Override public R fold( final @NonNull Function failureMapper, final @NonNull Function successMapper) { return successMapper.apply(success); } @Override public Railway chain(final @NonNull Function> chainingFunction){ return chainingFunction.apply(success); } } // -- FAILURE @lombok.Value @RequiredArgsConstructor final class Failure implements Railway, Serializable { private static final long serialVersionUID = 1L; private final @NonNull F failure; @Override public boolean isSuccess() { return false; } @Override public boolean isFailure() { return true; } @Override public Optional getSuccess() { return Optional.empty(); } @Override public Optional getFailure() { return Optional.of(failure); } @Override public Failure ifSuccess(final @NonNull Consumer successConsumer) { return this; } @Override public Failure ifFailure(final @NonNull Consumer failureConsumer) { failureConsumer.accept(failure); return this; } @Override public Failure mapSuccess(final @NonNull Function successMapper) { return Railway.failure(failure); } @Override public Failure mapFailure(final @NonNull Function failureMapper) { return Railway.failure(failureMapper.apply(failure)); } @Override public R fold( final @NonNull Function failureMapper, final @NonNull Function successMapper) { return failureMapper.apply(failure); } @Override public Railway chain(final @NonNull Function> chainingFunction){ return this; } } // -- TYPE COMPOSITION @FunctionalInterface public static interface HasRailway extends Railway { Railway getRailway(); @Override default boolean isSuccess() { return getRailway().isSuccess(); } @Override default boolean isFailure() { return getRailway().isFailure(); } @Override default Optional getSuccess() { return getRailway().getSuccess(); } @Override default Optional getFailure() { return getRailway().getFailure(); } @Override default Railway ifSuccess(final @NonNull Consumer successConsumer) { return getRailway().ifSuccess(successConsumer); } @Override default Railway ifFailure(final @NonNull Consumer failureConsumer) { return getRailway().ifFailure(failureConsumer); } @Override default Railway mapSuccess(final @NonNull Function successMapper) { return getRailway().mapSuccess(successMapper); } @Override default Railway mapFailure(final @NonNull Function failureMapper) { return getRailway().mapFailure(failureMapper); } @Override default R fold( final @NonNull Function failureMapper, final @NonNull Function successMapper) { return getRailway().fold(failureMapper, successMapper); } @Override default public Railway chain(final @NonNull Function> chainingFunction){ return getRailway().chain(chainingFunction); } } }