org.apache.isis.commons.functional.Railway Maven / Gradle / Ivy
Show all versions of isis-commons Show documentation
/*
* 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);
}
}
}