├── MAINTAINERS ├── docs └── spilled-coffee.jpg ├── .github ├── CODEOWNERS ├── no-response.yml ├── dependabot.yml ├── CONTRIBUTING.md ├── workflows │ ├── release-pull-request.yaml │ ├── release.yaml │ └── build.yaml ├── ISSUE_TEMPLATE │ ├── FEATURE.md │ └── BUG.md ├── PULL_REQUEST_TEMPLATE.md └── CODE_OF_CONDUCT.md ├── .gitignore ├── .zappr.yaml ├── cve-suppressions.xml ├── SECURITY.md ├── src ├── main │ └── java │ │ └── org │ │ └── zalando │ │ └── fauxpas │ │ ├── ThrowingUnaryOperator.java │ │ ├── ThrowingBinaryOperator.java │ │ ├── ThrowingRunnable.java │ │ ├── ThrowingSupplier.java │ │ ├── ThrowingConsumer.java │ │ ├── ThrowingFunction.java │ │ ├── ThrowingPredicate.java │ │ ├── ThrowingBiConsumer.java │ │ ├── ThrowingBiFunction.java │ │ ├── ThrowingBiPredicate.java │ │ ├── TryWith.java │ │ └── FauxPas.java └── test │ └── java │ └── org │ └── zalando │ └── fauxpas │ ├── ExceptionallyComposeTest.java │ ├── HandleComposeTest.java │ ├── FailedWithTest.java │ ├── TryWithConsumerTest.java │ ├── TryWithFunctionTest.java │ ├── TryWithBiConsumerTest.java │ ├── TryWithBiFunctionTest.java │ ├── DefaultImplementationTest.java │ └── ExceptionallyTest.java ├── release.sh ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE ├── mvnw.cmd ├── README.md ├── mvnw └── pom.xml /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Willi Schönborn 2 | -------------------------------------------------------------------------------- /docs/spilled-coffee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zalando/faux-pas/HEAD/docs/spilled-coffee.jpg -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @whiskeysierra @lukasniemeier-zalando @Semernitskaya @dingxiangfei2009 @joyfolk @gpradeepkrishna 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ ### 2 | *.iml 3 | .idea/ 4 | 5 | ### Maven ### 6 | target/ 7 | /.mvn/wrapper/*.jar 8 | 9 | *.log 10 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | responseRequiredLabel: More information needed 4 | 5 | -------------------------------------------------------------------------------- /.zappr.yaml: -------------------------------------------------------------------------------- 1 | approvals: 2 | pattern: "^(:\\+1:|👍|\\+1|:thumbsup:|[Ll][Gg][Tt][Mm])$" 3 | minimum: 1 4 | from: 5 | orgs: 6 | - zalando 7 | collaborators: true 8 | -------------------------------------------------------------------------------- /cve-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.slf4j:slf4j-api:1.7.25 5 | CVE-2018-8088 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: org.owasp:dependency-check-maven 11 | versions: 12 | - 6.1.0 13 | - 6.1.2 14 | - 6.1.5 15 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | We acknowledge that every line of code that we write may potentially contain security issues. 2 | 3 | We are trying to deal with it responsibly and provide patches as quickly as possible. If you have anything to report to 4 | us please use any of the following channels: 5 | 6 | - open an [issue](../../issues) 7 | - use our [security form](https://corporate.zalando.com/en/services-and-contact#security-form) 8 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingUnaryOperator.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.apiguardian.api.API; 4 | 5 | import java.util.function.UnaryOperator; 6 | 7 | import static org.apiguardian.api.API.Status.STABLE; 8 | 9 | @API(status = STABLE) 10 | @FunctionalInterface 11 | public interface ThrowingUnaryOperator extends ThrowingFunction, UnaryOperator { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingBinaryOperator.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.apiguardian.api.API; 4 | 5 | import java.util.function.BinaryOperator; 6 | 7 | import static org.apiguardian.api.API.Status.STABLE; 8 | 9 | @API(status = STABLE) 10 | @FunctionalInterface 11 | public interface ThrowingBinaryOperator extends ThrowingBiFunction, BinaryOperator { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Pull requests only 4 | 5 | **DON'T** push to the main branch directly. Always use feature branches and let people discuss changes in pull requests. 6 | Pull requests should only be merged after all discussions have been concluded and at least 1 reviewer has given their 7 | **approval**. 8 | 9 | ## Guidelines 10 | 11 | - **every change** needs a test 12 | - 100% code coverage 13 | - keep the current code style 14 | -------------------------------------------------------------------------------- /.github/workflows/release-pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: Release Pull Request 2 | 3 | on: 4 | push: 5 | branches: 6 | - release/* 7 | 8 | jobs: 9 | pull-request: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: pull-request-action 13 | uses: vsoch/pull-request-action@master 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | PULL_REQUEST_TITLE: Release ${{ github.ref }} 17 | PULL_REQUEST_BODY: Bump version 18 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingRunnable.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import static org.apiguardian.api.API.Status.STABLE; 7 | 8 | @API(status = STABLE) 9 | @FunctionalInterface 10 | public interface ThrowingRunnable extends Runnable { 11 | 12 | void tryRun() throws X; 13 | 14 | @Override 15 | @SneakyThrows 16 | default void run() { 17 | tryRun(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Create Release 15 | uses: softprops/action-gh-release@v2 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | tag_name: ${{ github.ref }} 20 | name: ${{ github.ref }} 21 | draft: false 22 | prerelease: ${{ contains(github.ref, 'RC') }} 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingSupplier.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.Supplier; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingSupplier extends Supplier { 13 | 14 | T tryGet() throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default T get() { 19 | return tryGet(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingConsumer.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.Consumer; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingConsumer extends Consumer { 13 | 14 | void tryAccept(T t) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default void accept(final T t) { 19 | tryAccept(t); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingFunction.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.Function; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingFunction extends Function { 13 | 14 | R tryApply(T t) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default R apply(final T t) { 19 | return tryApply(t); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingPredicate.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.Predicate; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingPredicate extends Predicate { 13 | 14 | boolean tryTest(T t) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default boolean test(final T t) { 19 | return tryTest(t); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingBiConsumer.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.BiConsumer; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingBiConsumer extends BiConsumer { 13 | 14 | void tryAccept(T t, U u) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default void accept(final T t, final U u) { 19 | tryAccept(t, u); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingBiFunction.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.BiFunction; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingBiFunction extends BiFunction { 13 | 14 | R tryApply(T t, U u) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default R apply(final T t, final U u) { 19 | return tryApply(t, u); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/ThrowingBiPredicate.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import java.util.function.BiPredicate; 7 | 8 | import static org.apiguardian.api.API.Status.STABLE; 9 | 10 | @API(status = STABLE) 11 | @FunctionalInterface 12 | public interface ThrowingBiPredicate extends BiPredicate { 13 | 14 | boolean tryTest(T t, U u) throws X; 15 | 16 | @Override 17 | @SneakyThrows 18 | default boolean test(final T t, final U u) { 19 | return tryTest(t, u); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | : ${1?"Usage: $0 <[pre]major|[pre]minor|[pre]patch|prerelease>"} 4 | 5 | ./mvnw scm:check-local-modification 6 | 7 | current=$(git describe --abbrev=0 || echo 0.0.0) 8 | release=$(semver ${current} -i $1 --preid RC) 9 | next=$(semver ${release} -i minor) 10 | 11 | git checkout -b release/${release} 12 | 13 | ./mvnw versions:set -D newVersion=${release} 14 | git commit -am "Release ${release}" 15 | ./mvnw clean deploy scm:tag -P release -D tag=${release} -D pushChanges=false -D skipTests -D dependency-check.skip 16 | 17 | ./mvnw versions:set -D newVersion=${next}-SNAPSHOT 18 | git commit -am "Development ${next}-SNAPSHOT" 19 | 20 | git push 21 | git push --tags 22 | 23 | git checkout main 24 | git branch -D release/${release} 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: Suggest an idea for this project 4 | labels: Feature 5 | --- 6 | 7 | 8 | 9 | ## Detailed Description 10 | 11 | 12 | ## Context 13 | 14 | 15 | 16 | ## Possible Implementation 17 | 18 | 19 | ## Your Environment 20 | 21 | * Version used: 22 | * Link to your project: 23 | 24 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/ExceptionallyComposeTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | import static java.util.concurrent.CompletableFuture.completedFuture; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.zalando.fauxpas.FauxPas.exceptionallyCompose; 10 | import static org.zalando.fauxpas.FauxPas.partially; 11 | 12 | class ExceptionallyComposeTest { 13 | 14 | @Test 15 | void shouldExceptionallyCompose() { 16 | final CompletableFuture original = new CompletableFuture<>(); 17 | final CompletableFuture unit = exceptionallyCompose(original, partially(e -> 18 | completedFuture("result"))); 19 | 20 | original.completeExceptionally(new RuntimeException()); 21 | 22 | assertThat(unit).isCompletedWithValue("result"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 6 * * *" 12 | 13 | env: 14 | # https://github.com/actions/virtual-environments/issues/1499#issuecomment-689467080 15 | MAVEN_OPTS: >- 16 | -Dhttp.keepAlive=false 17 | -Dmaven.wagon.http.pool=false 18 | -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | - name: Set up JDK 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: temurin 30 | java-version: 8 31 | cache: 'maven' 32 | - name: Compile 33 | run: ./mvnw clean test-compile -B 34 | - name: Test 35 | run: ./mvnw verify -B 36 | - name: Coverage 37 | if: github.event_name != 'pull_request' 38 | run: ./mvnw coveralls:report -B -D repoToken=${{ secrets.COVERALLS_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## Types of changes 11 | 12 | - [ ] Bug fix (non-breaking change which fixes an issue) 13 | - [ ] New feature (non-breaking change which adds functionality) 14 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 15 | 16 | ## Checklist: 17 | 18 | 19 | - [ ] My change requires a change to the documentation. 20 | - [ ] I have updated the documentation accordingly. 21 | - [ ] I have added tests to cover my changes. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41EBug report" 3 | about: Create a report to help us improve 4 | labels: Bug 5 | --- 6 | 7 | 8 | 9 | ## Description 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | ## Actual Behavior 16 | 17 | 18 | ## Possible Fix 19 | 20 | 21 | ## Steps to Reproduce 22 | 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 4. 28 | 29 | ## Context 30 | 31 | 32 | ## Your Environment 33 | 34 | * Version used: 35 | * Link to your project: 36 | 37 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zalando SE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/HandleComposeTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | import static java.util.concurrent.CompletableFuture.completedFuture; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.zalando.fauxpas.FauxPas.handleCompose; 10 | 11 | class HandleComposeTest { 12 | 13 | @Test 14 | void shouldHandleComposeResult() { 15 | final CompletableFuture original = new CompletableFuture<>(); 16 | final CompletableFuture unit = handleCompose(original, (s, throwable) -> 17 | completedFuture("result")); 18 | 19 | original.complete("foo"); 20 | 21 | assertThat(unit).isCompletedWithValue("result"); 22 | } 23 | 24 | @Test 25 | void shouldHandleComposeException() { 26 | final CompletableFuture original = new CompletableFuture<>(); 27 | final CompletableFuture unit = handleCompose(original, (s, throwable) -> 28 | completedFuture("result")); 29 | 30 | original.completeExceptionally(new RuntimeException()); 31 | 32 | assertThat(unit).isCompletedWithValue("result"); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/FailedWithTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.Mock; 6 | import org.mockito.junit.jupiter.MockitoExtension; 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.Mockito.verifyNoInteractions; 12 | import static org.zalando.fauxpas.FauxPas.failedWith; 13 | 14 | @ExtendWith(MockitoExtension.class) 15 | class FailedWithTest { 16 | 17 | @Mock 18 | private ThrowingConsumer action; 19 | 20 | @Test 21 | void shouldMatch() throws Throwable { 22 | final CompletableFuture original = new CompletableFuture<>(); 23 | original.whenComplete(failedWith(IllegalArgumentException.class, action)); 24 | 25 | final IllegalArgumentException e = new IllegalArgumentException(); 26 | original.completeExceptionally(e); 27 | 28 | verify(action).tryAccept(e); 29 | } 30 | 31 | @Test 32 | void shouldNotMatchIfCompletedSuccessfully() { 33 | final CompletableFuture original = new CompletableFuture<>(); 34 | original.whenComplete(failedWith(IllegalStateException.class, action)); 35 | 36 | original.complete("result"); 37 | 38 | verifyNoInteractions(action); 39 | } 40 | 41 | @Test 42 | void shouldNotMatchIfExceptionTypeIsDifferent() { 43 | final CompletableFuture original = new CompletableFuture<>(); 44 | original.whenComplete(failedWith(IllegalStateException.class, action)); 45 | 46 | original.completeExceptionally(new IllegalArgumentException()); 47 | 48 | verifyNoInteractions(action); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/TryWith.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apiguardian.api.API; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | import static org.apiguardian.api.API.Status.MAINTAINED; 9 | import static org.apiguardian.api.API.Status.STABLE; 10 | 11 | public final class TryWith { 12 | 13 | private TryWith() { 14 | 15 | } 16 | 17 | @API(status = MAINTAINED) 18 | public static void tryWith( 19 | @Nullable final O outer, @Nullable final I inner, final ThrowingBiConsumer consumer) throws X { 20 | 21 | tryWith(outer, a -> { 22 | tryWith(inner, b -> { 23 | consumer.tryAccept(a, b); 24 | }); 25 | }); 26 | } 27 | 28 | @API(status = STABLE) 29 | public static void tryWith(@Nullable final R resource, 30 | final ThrowingConsumer consumer) throws X { 31 | 32 | try { 33 | consumer.tryAccept(resource); 34 | } catch (final Throwable e) { 35 | throw tryClose(resource, TryWith.cast(e)); 36 | } 37 | 38 | tryClose(resource); 39 | } 40 | 41 | @API(status = MAINTAINED) 42 | public static T tryWith( 43 | @Nullable final O outer, @Nullable final I inner, final ThrowingBiFunction function) throws X { 44 | 45 | // not exactly sure why those explicit type parameters are needed 46 | return TryWith.tryWith(outer, a -> 47 | tryWith(inner, b -> { 48 | return function.tryApply(a, b); 49 | })); 50 | } 51 | 52 | @API(status = STABLE) 53 | public static T tryWith(@Nullable final R resource, 54 | final ThrowingFunction supplier) throws X { 55 | 56 | final T value; 57 | 58 | try { 59 | value = supplier.tryApply(resource); 60 | } catch (final Throwable e) { 61 | throw tryClose(resource, TryWith.cast(e)); 62 | } 63 | 64 | tryClose(resource); 65 | 66 | return value; 67 | } 68 | 69 | @SneakyThrows 70 | private static void tryClose(@Nullable final AutoCloseable resource) { 71 | if (resource == null) { 72 | return; 73 | } 74 | 75 | resource.close(); 76 | } 77 | 78 | @SuppressWarnings("unchecked") 79 | private static X cast(final Throwable e) { 80 | return (X) e; 81 | } 82 | 83 | private static X tryClose(@Nullable final AutoCloseable closeable, final X e) { 84 | if (closeable == null) { 85 | return e; 86 | } 87 | 88 | try { 89 | closeable.close(); 90 | } catch (final Throwable inner) { 91 | e.addSuppressed(inner); 92 | } 93 | 94 | return e; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/TryWithConsumerTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | import static org.mockito.ArgumentMatchers.any; 11 | import static org.mockito.Mockito.doThrow; 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.verify; 14 | import static org.zalando.fauxpas.TryWith.tryWith; 15 | 16 | final class TryWithConsumerTest { 17 | 18 | private final Closeable resource = mock(Closeable.class); 19 | 20 | @SuppressWarnings("unchecked") 21 | private final ThrowingConsumer consumer = mock(ThrowingConsumer.class); 22 | 23 | @Test 24 | void shouldPassResource() throws Exception { 25 | run(); 26 | verify(consumer).tryAccept(resource); 27 | } 28 | 29 | @Test 30 | void shouldCloseWithoutException() throws Exception { 31 | run(); 32 | verify(resource).close(); 33 | } 34 | 35 | @Test 36 | void shouldNotFailOnNullResource() throws Exception { 37 | tryWith(null, consumer); 38 | verify(consumer).tryAccept(null); 39 | } 40 | 41 | @Test 42 | void shouldNotFailOnNullResourceWithException() throws Exception { 43 | doThrow(new Exception()).when(consumer).tryAccept(any()); 44 | assertThrows(Exception.class, () -> tryWith(null, consumer)); 45 | } 46 | 47 | @Test 48 | void shouldThrowException() throws Exception { 49 | final Exception exception = new Exception(); 50 | doThrow(exception).when(consumer).tryAccept(any()); 51 | final Exception thrown = assertThrows(Exception.class, this::run); 52 | assertThat(thrown).isSameAs(exception); 53 | } 54 | 55 | @Test 56 | void shouldCloseWithException() throws Exception { 57 | doThrow(new Exception()).when(consumer).tryAccept(any()); 58 | assertThrows(Exception.class, this::run); 59 | verify(resource).close(); 60 | } 61 | 62 | @Test 63 | void shouldFailToClose() throws Exception { 64 | final IOException ioException = new IOException(); 65 | doThrow(ioException).when(resource).close(); 66 | final IOException thrown = assertThrows(IOException.class, this::run); 67 | assertThat(thrown).isSameAs(ioException); 68 | } 69 | 70 | @Test 71 | void shouldFailToCloseWithException() throws Exception { 72 | final Exception exception = new Exception(); 73 | final IOException ioException = new IOException(); 74 | doThrow(exception).when(consumer).tryAccept(any()); 75 | doThrow(ioException).when(resource).close(); 76 | 77 | final Exception thrown = assertThrows(Exception.class, this::run); 78 | 79 | assertThat(thrown) 80 | .isSameAs(exception) 81 | .hasSuppressedException(ioException); 82 | } 83 | 84 | private void run() throws Exception { 85 | tryWith(resource, consumer); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at team-balance [at] zalando [dot] de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/TryWithFunctionTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | import static org.mockito.ArgumentMatchers.any; 11 | import static org.mockito.Mockito.doReturn; 12 | import static org.mockito.Mockito.doThrow; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.verify; 15 | import static org.zalando.fauxpas.TryWith.tryWith; 16 | 17 | final class TryWithFunctionTest { 18 | 19 | private final Object value = new Object(); 20 | 21 | private final Closeable resource = mock(Closeable.class); 22 | 23 | @SuppressWarnings("unchecked") 24 | private final ThrowingFunction function = mock(ThrowingFunction.class); 25 | 26 | @Test 27 | void shouldPassResource() throws Exception { 28 | run(); 29 | verify(function).tryApply(resource); 30 | } 31 | 32 | @Test 33 | void shouldReturnWithoutException() throws Exception { 34 | doReturn(value).when(function).tryApply(any()); 35 | 36 | final Object actual = run(); 37 | assertThat(actual).isSameAs(value); 38 | } 39 | 40 | @Test 41 | void shouldCloseWithoutException() throws Exception { 42 | run(); 43 | verify(resource).close(); 44 | } 45 | 46 | @Test 47 | void shouldNotFailOnNullResource() throws Exception { 48 | tryWith(null, function); 49 | verify(function).tryApply(null); 50 | } 51 | 52 | @Test 53 | void shouldNotFailOnNullResourceWithException() throws Exception { 54 | doThrow(new Exception()).when(function).tryApply(any()); 55 | assertThrows(Exception.class, () -> tryWith(null, function)); 56 | } 57 | 58 | @Test 59 | void shouldThrowException() throws Exception { 60 | final Exception exception = new Exception(); 61 | doThrow(exception).when(function).tryApply(any()); 62 | final Exception thrown = assertThrows(Exception.class, this::run); 63 | assertThat(thrown).isSameAs(exception); 64 | } 65 | 66 | @Test 67 | void shouldCloseWithException() throws Exception { 68 | doThrow(new Exception()).when(function).tryApply(any()); 69 | assertThrows(Exception.class, this::run); 70 | verify(resource).close(); 71 | } 72 | 73 | @Test 74 | void shouldFailToClose() throws Exception { 75 | final IOException ioException = new IOException(); 76 | doThrow(ioException).when(resource).close(); 77 | final IOException thrown = assertThrows(IOException.class, this::run); 78 | assertThat(thrown).isSameAs(ioException); 79 | } 80 | 81 | @Test 82 | void shouldFailToCloseWithException() throws Exception { 83 | final Exception exception = new Exception(); 84 | final IOException ioException = new IOException(); 85 | doThrow(exception).when(function).tryApply(any()); 86 | doThrow(ioException).when(resource).close(); 87 | 88 | final Exception thrown = assertThrows(Exception.class, this::run); 89 | 90 | assertThat(thrown) 91 | .isSameAs(exception) 92 | .hasSuppressedException(ioException); 93 | } 94 | 95 | private Object run() throws Exception { 96 | return tryWith(resource, function); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/TryWithBiConsumerTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | import static org.mockito.ArgumentMatchers.any; 11 | import static org.mockito.Mockito.doThrow; 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.verify; 14 | import static org.zalando.fauxpas.TryWith.tryWith; 15 | 16 | final class TryWithBiConsumerTest { 17 | 18 | private final Closeable outer = mock(Closeable.class); 19 | private final Closeable inner = mock(Closeable.class); 20 | 21 | @SuppressWarnings("unchecked") 22 | private final ThrowingBiConsumer consumer = mock(ThrowingBiConsumer.class); 23 | 24 | @Test 25 | void shouldPassResources() throws Exception { 26 | run(); 27 | verify(consumer).tryAccept(outer, inner); 28 | } 29 | 30 | @Test 31 | void shouldCloseWithoutException() throws Exception { 32 | run(); 33 | verify(inner).close(); 34 | verify(outer).close(); 35 | } 36 | 37 | @Test 38 | void shouldNotFailOnNullResource() throws Exception { 39 | tryWith(null, null, consumer); 40 | verify(consumer).tryAccept(null, null); 41 | } 42 | 43 | @Test 44 | void shouldNotFailOnNullResourceWithException() throws Exception { 45 | doThrow(new Exception()).when(consumer).tryAccept(any(), any()); 46 | assertThrows(Exception.class, () -> tryWith(null, null, consumer)); 47 | } 48 | 49 | @Test 50 | void shouldThrowException() throws Exception { 51 | final Exception exception = new Exception(); 52 | doThrow(exception).when(consumer).tryAccept(any(), any()); 53 | final Exception thrown = assertThrows(Exception.class, this::run); 54 | assertThat(thrown).isSameAs(exception); 55 | } 56 | 57 | @Test 58 | void shouldCloseWithException() throws Exception { 59 | doThrow(new Exception()).when(consumer).tryAccept(any(), any()); 60 | assertThrows(Exception.class, this::run); 61 | verify(outer).close(); 62 | verify(inner).close(); 63 | } 64 | 65 | @Test 66 | void shouldFailToCloseOuter() throws Exception { 67 | shouldFailToClose(outer); 68 | } 69 | 70 | @Test 71 | void shouldFailToCloseInner() throws Exception { 72 | shouldFailToClose(inner); 73 | } 74 | 75 | private void shouldFailToClose(final Closeable resource) throws IOException { 76 | final IOException ioException = new IOException(); 77 | doThrow(ioException).when(resource).close(); 78 | final IOException thrown = assertThrows(IOException.class, this::run); 79 | assertThat(thrown).isSameAs(ioException); 80 | } 81 | 82 | @Test 83 | void shouldFailToCloseOuterWithException() throws Exception { 84 | shouldFailToCloseWithException(outer); 85 | } 86 | 87 | @Test 88 | void shouldFailToCloseInnerWithException() throws Exception { 89 | shouldFailToCloseWithException(inner); 90 | } 91 | 92 | private void shouldFailToCloseWithException(final Closeable resource) throws Exception { 93 | final Exception exception = new Exception(); 94 | final IOException ioException = new IOException(); 95 | 96 | doThrow(exception).when(consumer).tryAccept(any(), any()); 97 | doThrow(ioException).when(resource).close(); 98 | 99 | final Exception thrown = assertThrows(Exception.class, this::run); 100 | 101 | assertThat(thrown) 102 | .isSameAs(exception) 103 | .hasSuppressedException(ioException); 104 | } 105 | 106 | @Test 107 | void shouldFailToCloseOuterAndInnerWithException() throws Exception { 108 | final Exception exception = new Exception(); 109 | final IOException ioException = new IOException(); 110 | final IOException secondIOException = new IOException(); 111 | 112 | doThrow(exception).when(consumer).tryAccept(any(), any()); 113 | doThrow(ioException).when(inner).close(); 114 | doThrow(secondIOException).when(outer).close(); 115 | 116 | final Exception thrown = assertThrows(Exception.class, this::run); 117 | 118 | assertThat(thrown) 119 | .isSameAs(exception) 120 | .hasSuppressedException(ioException) 121 | .hasSuppressedException(secondIOException); 122 | } 123 | 124 | private void run() throws Exception { 125 | tryWith(outer, inner, consumer); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/TryWithBiFunctionTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | import static org.mockito.ArgumentMatchers.any; 11 | import static org.mockito.Mockito.doReturn; 12 | import static org.mockito.Mockito.doThrow; 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.verify; 15 | import static org.zalando.fauxpas.TryWith.tryWith; 16 | 17 | final class TryWithBiFunctionTest { 18 | 19 | private final Object value = new Object(); 20 | 21 | private final Closeable outer = mock(Closeable.class); 22 | private final Closeable inner = mock(Closeable.class); 23 | 24 | @SuppressWarnings("unchecked") 25 | private final ThrowingBiFunction function = mock(ThrowingBiFunction.class); 26 | 27 | @Test 28 | void shouldReturnWithoutException() throws Exception { 29 | doReturn(value).when(function).tryApply(any(), any()); 30 | 31 | final Object actual = run(); 32 | assertThat(actual).isSameAs(value); 33 | } 34 | 35 | @Test 36 | void shouldPassResources() throws Exception { 37 | run(); 38 | verify(function).tryApply(outer, inner); 39 | } 40 | 41 | @Test 42 | void shouldNotFailOnNullResource() throws Exception { 43 | tryWith(null, null, function); 44 | verify(function).tryApply(null, null); 45 | } 46 | 47 | @Test 48 | void shouldNotFailOnNullResourceWithException() throws Exception { 49 | doThrow(new Exception()).when(function).tryApply(any(), any()); 50 | assertThrows(Exception.class, () -> tryWith(null, null, function)); 51 | } 52 | 53 | @Test 54 | void shouldCloseWithoutException() throws Exception { 55 | run(); 56 | verify(inner).close(); 57 | verify(outer).close(); 58 | } 59 | 60 | @Test 61 | void shouldThrowException() throws Exception { 62 | final Exception exception = new Exception(); 63 | doThrow(exception).when(function).tryApply(any(), any()); 64 | final Exception thrown = assertThrows(Exception.class, this::run); 65 | assertThat(thrown).isSameAs(exception); 66 | } 67 | 68 | @Test 69 | void shouldCloseWithException() throws Exception { 70 | doThrow(new Exception()).when(function).tryApply(any(), any()); 71 | assertThrows(Exception.class, this::run); 72 | verify(outer).close(); 73 | verify(inner).close(); 74 | } 75 | 76 | @Test 77 | void shouldFailToCloseOuter() throws Exception { 78 | shouldFailToClose(outer); 79 | } 80 | 81 | @Test 82 | void shouldFailToCloseInner() throws Exception { 83 | shouldFailToClose(inner); 84 | } 85 | 86 | private void shouldFailToClose(final Closeable resource) throws IOException { 87 | final IOException ioException = new IOException(); 88 | doThrow(ioException).when(resource).close(); 89 | final IOException thrown = assertThrows(IOException.class, this::run); 90 | assertThat(thrown).isSameAs(ioException); 91 | } 92 | 93 | @Test 94 | void shouldFailToCloseOuterWithException() throws Exception { 95 | shouldFailToCloseWithException(outer); 96 | } 97 | 98 | @Test 99 | void shouldFailToCloseInnerWithException() throws Exception { 100 | shouldFailToCloseWithException(inner); 101 | } 102 | 103 | private void shouldFailToCloseWithException(final Closeable resource) throws Exception { 104 | final Exception exception = new Exception(); 105 | final IOException ioException = new IOException(); 106 | 107 | doThrow(exception).when(function).tryApply(any(), any()); 108 | doThrow(ioException).when(resource).close(); 109 | 110 | final Exception thrown = assertThrows(Exception.class, this::run); 111 | 112 | assertThat(thrown) 113 | .isSameAs(exception) 114 | .hasSuppressedException(ioException); 115 | } 116 | 117 | @Test 118 | void shouldFailToCloseOuterAndInnerWithException() throws Exception { 119 | final Exception exception = new Exception(); 120 | final IOException ioException = new IOException(); 121 | final IOException secondIOException = new IOException(); 122 | 123 | doThrow(exception).when(function).tryApply(any(), any()); 124 | doThrow(ioException).when(inner).close(); 125 | doThrow(secondIOException).when(outer).close(); 126 | 127 | final Exception thrown = assertThrows(Exception.class, this::run); 128 | 129 | assertThat(thrown) 130 | .isSameAs(exception) 131 | .hasSuppressedException(ioException) 132 | .hasSuppressedException(secondIOException); 133 | } 134 | 135 | private Object run() throws Exception { 136 | return tryWith(outer, inner, function); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/org/zalando/fauxpas/FauxPas.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.apiguardian.api.API; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.CompletionException; 7 | import java.util.function.BiFunction; 8 | import java.util.function.Function; 9 | 10 | import static java.util.Objects.nonNull; 11 | import static java.util.function.Function.identity; 12 | import static org.apiguardian.api.API.Status.EXPERIMENTAL; 13 | import static org.apiguardian.api.API.Status.MAINTAINED; 14 | import static org.apiguardian.api.API.Status.STABLE; 15 | 16 | @API(status = STABLE) 17 | public final class FauxPas { 18 | 19 | private FauxPas() { 20 | 21 | } 22 | 23 | public static ThrowingRunnable throwingRunnable( 24 | final ThrowingRunnable runnable) { 25 | return runnable; 26 | } 27 | 28 | public static ThrowingSupplier throwingSupplier( 29 | final ThrowingSupplier supplier) { 30 | return supplier; 31 | } 32 | 33 | public static ThrowingConsumer throwingConsumer( 34 | final ThrowingConsumer consumer) { 35 | return consumer; 36 | } 37 | 38 | public static ThrowingFunction throwingFunction( 39 | final ThrowingFunction function) { 40 | return function; 41 | } 42 | 43 | public static ThrowingUnaryOperator throwingUnaryOperator( 44 | final ThrowingUnaryOperator operator) { 45 | return operator; 46 | } 47 | 48 | public static ThrowingPredicate throwingPredicate( 49 | final ThrowingPredicate predicate) { 50 | return predicate; 51 | } 52 | 53 | public static ThrowingBiConsumer throwingBiConsumer( 54 | final ThrowingBiConsumer consumer) { 55 | return consumer; 56 | } 57 | 58 | public static ThrowingBiFunction throwingBiFunction( 59 | final ThrowingBiFunction function) { 60 | return function; 61 | } 62 | 63 | public static ThrowingBinaryOperator throwingBinaryOperator( 64 | final ThrowingBinaryOperator operator) { 65 | return operator; 66 | } 67 | 68 | public static ThrowingBiPredicate throwingBiPredicate( 69 | final ThrowingBiPredicate predicate) { 70 | return predicate; 71 | } 72 | 73 | @API(status = MAINTAINED) 74 | public static Function partially(final Class type, 75 | final ThrowingFunction function) { 76 | return partially(e -> { 77 | if (type.isInstance(e)) { 78 | return function.apply(type.cast(e)); 79 | } 80 | throw e; 81 | }); 82 | } 83 | 84 | @API(status = MAINTAINED) 85 | public static Function partially(final ThrowingFunction function) { 86 | return throwable -> { 87 | try { 88 | return function.tryApply(unpack(throwable)); 89 | } catch (final CompletionException e) { 90 | throw e; 91 | } catch (final Throwable e) { 92 | throw new CompletionException(e); 93 | } 94 | }; 95 | } 96 | 97 | @API(status = EXPERIMENTAL) 98 | public static ThrowingBiConsumer failedWith( 99 | final Class type, final ThrowingConsumer action) { 100 | return (result, throwable) -> { 101 | if (nonNull(throwable)) { 102 | final Throwable unpacked = unpack(throwable); 103 | if (type.isInstance(unpacked)) { 104 | action.tryAccept(type.cast(unpacked)); 105 | } 106 | } 107 | }; 108 | } 109 | 110 | private static Throwable unpack(final Throwable throwable) { 111 | final Throwable cause = throwable.getCause(); 112 | return throwable instanceof CompletionException && cause != null ? cause : throwable; 113 | } 114 | 115 | @API(status = EXPERIMENTAL) 116 | public static CompletableFuture handleCompose(final CompletableFuture future, 117 | final BiFunction> function) { 118 | return future 119 | .handle(function) 120 | .thenCompose(identity()); 121 | } 122 | 123 | @API(status = EXPERIMENTAL) 124 | public static CompletableFuture exceptionallyCompose(final CompletableFuture future, 125 | final Function> function) { 126 | return future 127 | .thenApply(CompletableFuture::completedFuture) 128 | .exceptionally(function) 129 | .thenCompose(identity()); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/DefaultImplementationTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.function.Function; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | import static org.zalando.fauxpas.FauxPas.throwingBiConsumer; 10 | import static org.zalando.fauxpas.FauxPas.throwingBiFunction; 11 | import static org.zalando.fauxpas.FauxPas.throwingBiPredicate; 12 | import static org.zalando.fauxpas.FauxPas.throwingBinaryOperator; 13 | import static org.zalando.fauxpas.FauxPas.throwingConsumer; 14 | import static org.zalando.fauxpas.FauxPas.throwingFunction; 15 | import static org.zalando.fauxpas.FauxPas.throwingPredicate; 16 | import static org.zalando.fauxpas.FauxPas.throwingRunnable; 17 | import static org.zalando.fauxpas.FauxPas.throwingSupplier; 18 | import static org.zalando.fauxpas.FauxPas.throwingUnaryOperator; 19 | 20 | /** 21 | * Tests the default implementation of e.g. {@link Function#apply(Object)} in {@link ThrowingFunction}. 22 | */ 23 | final class DefaultImplementationTest { 24 | 25 | @SuppressWarnings("ThrowableInstanceNeverThrown") // we're in fact throwing it, multiple times even... 26 | private final Exception exception = new Exception(); 27 | 28 | @Test 29 | void shouldRethrowExceptionFromRunnable() { 30 | final ThrowingRunnable runnable = throwingRunnable(() -> { 31 | throw exception; 32 | }); 33 | shouldThrow(runnable); 34 | } 35 | 36 | @Test 37 | void shouldNotRethrowExceptionFromRunnable() { 38 | final ThrowingRunnable runnable = throwingRunnable(() -> { 39 | }); 40 | shouldNotThrow(runnable); 41 | } 42 | 43 | @Test 44 | void shouldRethrowExceptionFromSupplier() { 45 | final ThrowingSupplier supplier = throwingSupplier(() -> { 46 | throw exception; 47 | }); 48 | shouldThrow(supplier::get); 49 | } 50 | 51 | @Test 52 | void shouldNotRethrowExceptionFromSupplier() { 53 | final ThrowingSupplier supplier = throwingSupplier(() -> null); 54 | shouldNotThrow(supplier::get); 55 | } 56 | 57 | @Test 58 | void shouldRethrowExceptionFromConsumer() { 59 | final ThrowingConsumer consumer = throwingConsumer($ -> { 60 | throw exception; 61 | }); 62 | shouldThrow(() -> consumer.accept(null)); 63 | } 64 | 65 | @Test 66 | void shouldNotRethrowExceptionFromConsumer() { 67 | final ThrowingConsumer consumer = throwingConsumer($ -> { 68 | }); 69 | shouldNotThrow(() -> consumer.accept(null)); 70 | } 71 | 72 | @Test 73 | void shouldRethrowExceptionFromFunction() { 74 | final ThrowingFunction function = throwingFunction($ -> { 75 | throw exception; 76 | }); 77 | shouldThrow(() -> function.apply(null)); 78 | } 79 | 80 | @Test 81 | void shouldNotRethrowExceptionFromFunction() { 82 | final ThrowingFunction function = throwingFunction((Void $) -> null); 83 | shouldNotThrow(() -> function.apply(null)); 84 | } 85 | 86 | @Test 87 | void shouldRethrowExceptionFromUnaryOperator() { 88 | final ThrowingUnaryOperator operator = throwingUnaryOperator($ -> { 89 | throw exception; 90 | }); 91 | shouldThrow(() -> operator.apply(null)); 92 | } 93 | 94 | @Test 95 | void shouldNotRethrowExceptionFromUnaryOperator() { 96 | final ThrowingUnaryOperator operator = throwingUnaryOperator((Void $) -> null); 97 | shouldNotThrow(() -> operator.apply(null)); 98 | } 99 | 100 | @Test 101 | void shouldRethrowExceptionFromPredicate() { 102 | final ThrowingPredicate predicate = throwingPredicate($ -> { 103 | throw exception; 104 | }); 105 | shouldThrow(() -> predicate.test(null)); 106 | } 107 | 108 | @Test 109 | void shouldNotRethrowExceptionFromPredicate() { 110 | final ThrowingPredicate predicate = throwingPredicate((Void $) -> false); 111 | shouldNotThrow(() -> predicate.test(null)); 112 | } 113 | 114 | @Test 115 | void shouldRethrowExceptionFromBiConsumer() { 116 | final ThrowingBiConsumer consumer = throwingBiConsumer(($, $2) -> { 117 | throw exception; 118 | }); 119 | shouldThrow(() -> consumer.accept(null, null)); 120 | } 121 | 122 | @Test 123 | void shouldNotRethrowExceptionFromBiConsumer() { 124 | final ThrowingBiConsumer consumer = throwingBiConsumer(($, $2) -> { 125 | }); 126 | shouldNotThrow(() -> consumer.accept(null, null)); 127 | } 128 | 129 | @Test 130 | void shouldRethrowExceptionFromBiFunction() { 131 | final ThrowingBiFunction function = throwingBiFunction(($, $2) -> { 132 | throw exception; 133 | }); 134 | shouldThrow(() -> function.apply(null, null)); 135 | } 136 | 137 | @Test 138 | void shouldNotRethrowExceptionFromBiFunction() { 139 | final ThrowingBiFunction function = throwingBiFunction(($, $2) -> null); 140 | shouldNotThrow(() -> function.apply(null, null)); 141 | } 142 | 143 | @Test 144 | void shouldRethrowExceptionFromBinaryOperator() { 145 | final ThrowingBinaryOperator operator = throwingBinaryOperator(($, $2) -> { 146 | throw exception; 147 | }); 148 | shouldThrow(() -> operator.apply(null, null)); 149 | } 150 | 151 | @Test 152 | void shouldNotRethrowExceptionFromBinaryOperator() { 153 | final ThrowingBinaryOperator operator = throwingBinaryOperator(($, $2) -> null); 154 | shouldNotThrow(() -> operator.apply(null, null)); 155 | } 156 | 157 | @Test 158 | void shouldRethrowExceptionFromBiPredicate() { 159 | final ThrowingBiPredicate predicate = throwingBiPredicate(($, $2) -> { 160 | throw exception; 161 | }); 162 | shouldThrow(() -> predicate.test(null, null)); 163 | } 164 | 165 | @Test 166 | void shouldNotRethrowExceptionFromBiPredicate() { 167 | final ThrowingBiPredicate predicate = throwingBiPredicate(($, $2) -> false); 168 | shouldNotThrow(() -> predicate.test(null, null)); 169 | } 170 | 171 | private void shouldNotThrow(final Runnable nonThrower) { 172 | nonThrower.run(); 173 | } 174 | 175 | private void shouldThrow(final Runnable thrower) { 176 | Exception thrown = assertThrows(Exception.class, thrower::run); 177 | assertThat(thrown).isSameAs(exception); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /src/test/java/org/zalando/fauxpas/ExceptionallyTest.java: -------------------------------------------------------------------------------- 1 | package org.zalando.fauxpas; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.net.NoRouteToHostException; 7 | import java.util.NoSuchElementException; 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.CompletionException; 10 | import java.util.function.Function; 11 | import java.util.function.Predicate; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.junit.jupiter.api.Assertions.assertThrows; 15 | import static org.junit.jupiter.api.Assertions.fail; 16 | import static org.zalando.fauxpas.FauxPas.partially; 17 | 18 | class ExceptionallyTest { 19 | 20 | @Test 21 | void shouldReturnResult() { 22 | final CompletableFuture original = new CompletableFuture<>(); 23 | final CompletableFuture unit = original.exceptionally(partially(throwable -> fail("Unexpected"))); 24 | 25 | original.complete("result"); 26 | 27 | assertThat(unit).isCompletedWithValue("result"); 28 | } 29 | 30 | @Test 31 | void shouldCascade() { 32 | final CompletableFuture original = new CompletableFuture<>(); 33 | final CompletableFuture unit = original.exceptionally(partially(e -> { 34 | throw new IllegalStateException(); 35 | })); 36 | 37 | original.completeExceptionally(new IllegalArgumentException()); 38 | 39 | final CompletionException thrown = assertThrows(CompletionException.class, unit::join); 40 | assertThat(thrown).getCause().isInstanceOf(IllegalStateException.class); 41 | } 42 | 43 | @Test 44 | void shouldUseFallbackWhenExplicitlyCompletedExceptionally() { 45 | final CompletableFuture original = new CompletableFuture<>(); 46 | final CompletableFuture unit = original.exceptionally( 47 | partially(fallbackIf(UnsupportedOperationException.class::isInstance))); 48 | 49 | original.completeExceptionally(new UnsupportedOperationException(new IOException())); 50 | 51 | assertThat(unit).isCompletedWithValue("fallback"); 52 | } 53 | 54 | @Test 55 | void shouldUseFallbackWhenImplicitlyCompletedExceptionally() { 56 | final CompletableFuture original = new CompletableFuture<>(); 57 | final CompletableFuture unit = original 58 | .thenApply(failWith(new CompletionException(new UnsupportedOperationException(new IOException())))) 59 | .exceptionally(partially(fallbackIf(UnsupportedOperationException.class::isInstance))); 60 | 61 | original.complete("unused"); 62 | 63 | assertThat(unit).isCompletedWithValue("fallback"); 64 | } 65 | 66 | @Test 67 | void shouldUseFallbackWhenImplicitlyCompletedExceptionallyWithNullCause() { 68 | final CompletableFuture original = new CompletableFuture<>(); 69 | final CompletableFuture unit = original 70 | .thenApply(failWith(new CompletionException(null))) 71 | .exceptionally(partially(fallbackIf(UnsupportedOperationException.class::isInstance))); 72 | 73 | original.complete("unused"); 74 | 75 | final CompletionException thrown = assertThrows(CompletionException.class, unit::join); 76 | assertThat(thrown).hasNoCause(); 77 | } 78 | 79 | @Test 80 | void shouldNotRethrowOriginalCompletionExceptionWhenImplicitlyCompletedExceptionally() { 81 | final RuntimeException exception = new CompletionException(new AssertionError()); 82 | final CompletionException thrown = shouldRethrowOriginalWhenImplicitlyCompletedExceptionally(exception, e -> { 83 | throw new CompletionException(e); 84 | }); 85 | assertThat(thrown) 86 | .isNotSameAs(exception) 87 | .getCause().isSameAs(exception.getCause()); 88 | } 89 | 90 | @Test 91 | void shouldRethrowOriginalRuntimeWhenImplicitlyCompletedExceptionally() { 92 | final RuntimeException exception = new IllegalStateException(); 93 | final CompletionException thrown = shouldRethrowOriginalWhenImplicitlyCompletedExceptionally(exception, rethrow()); 94 | assertThat(thrown).getCause().isSameAs(exception); 95 | } 96 | 97 | @Test 98 | void shouldRethrowOriginalThrowableWhenImplicitlyCompletedExceptionally() { 99 | final Exception exception = new IOException(); 100 | final CompletionException thrown = shouldRethrowOriginalWhenImplicitlyCompletedExceptionally( 101 | new CompletionException(exception), rethrow()); 102 | assertThat(thrown).getCause().isSameAs(exception); 103 | } 104 | 105 | private CompletionException shouldRethrowOriginalWhenImplicitlyCompletedExceptionally( 106 | final RuntimeException exception, final ThrowingFunction function) { 107 | final CompletableFuture original = new CompletableFuture<>(); 108 | final CompletableFuture unit = original 109 | .thenApply(failWith(exception)) 110 | .exceptionally(partially(function)); 111 | 112 | original.complete("unused"); 113 | 114 | return assertThrows(CompletionException.class, unit::join); 115 | } 116 | 117 | @Test 118 | void shouldRethrowPackedCompletionExceptionWhenImplicitlyCompletedExceptionally() { 119 | final Exception exception = new CompletionException(new UnsupportedOperationException()); 120 | final CompletionException thrown = shouldRethrowPackedWhenImplicitlyCompletedExceptionally(exception); 121 | assertThat(thrown).isSameAs(exception); 122 | } 123 | 124 | @Test 125 | void shouldRethrowPackedRuntimeExceptionWhenImplicitlyCompletedExceptionally() { 126 | final Exception exception = new UnsupportedOperationException(); 127 | final CompletionException thrown = shouldRethrowPackedWhenImplicitlyCompletedExceptionally(exception); 128 | assertThat(thrown).getCause().isSameAs(exception); 129 | } 130 | 131 | @Test 132 | void shouldRethrowPackedThrowableWhenImplicitlyCompletedExceptionally() { 133 | final Exception exception = new IOException(); 134 | final CompletionException thrown = shouldRethrowPackedWhenImplicitlyCompletedExceptionally(exception); 135 | assertThat(thrown).getCause().isSameAs(exception); 136 | } 137 | 138 | private CompletionException shouldRethrowPackedWhenImplicitlyCompletedExceptionally(final Exception exception) { 139 | final CompletableFuture original = new CompletableFuture<>(); 140 | final CompletableFuture unit = original 141 | .thenApply(failWith(new NoSuchElementException())) 142 | .exceptionally(partially(e -> { 143 | throw exception; 144 | })); 145 | 146 | original.complete("unused"); 147 | 148 | return assertThrows(CompletionException.class, unit::join); 149 | } 150 | 151 | @Test 152 | void shouldRethrowPackedRuntimeExceptionWhenExplicitlyCompletedExceptionally() { 153 | shouldRethrowPackedWhenExplicitlyCompletedExceptionally(new IllegalStateException()); 154 | } 155 | 156 | @Test 157 | void shouldRethrowPackedThrowableWhenExplicitlyCompletedExceptionally() { 158 | shouldRethrowPackedWhenExplicitlyCompletedExceptionally(new NoRouteToHostException()); 159 | } 160 | 161 | private void shouldRethrowPackedWhenExplicitlyCompletedExceptionally(final Exception exception) { 162 | final CompletableFuture original = new CompletableFuture<>(); 163 | final CompletableFuture unit = original.exceptionally(partially(rethrow())); 164 | 165 | original.completeExceptionally(exception); 166 | 167 | final CompletionException thrown = assertThrows(CompletionException.class, unit::join); 168 | assertThat(thrown).getCause().isSameAs(exception); 169 | } 170 | 171 | @Test 172 | void shouldHandleIfInstanceOf() { 173 | final CompletableFuture original = new CompletableFuture<>(); 174 | final CompletableFuture unit = original.exceptionally(partially( 175 | IllegalStateException.class, e -> "foo")); 176 | 177 | final IllegalStateException exception = new IllegalStateException(); 178 | original.completeExceptionally(exception); 179 | 180 | assertThat(unit).isCompletedWithValue("foo"); 181 | } 182 | 183 | @Test 184 | void shouldThrowIfNotInstanceOf() { 185 | final CompletableFuture original = new CompletableFuture<>(); 186 | final CompletableFuture unit = original.exceptionally(partially( 187 | IllegalArgumentException.class, e -> "foo")); 188 | 189 | final IllegalStateException exception = new IllegalStateException(); 190 | original.completeExceptionally(exception); 191 | 192 | final CompletionException thrown = assertThrows(CompletionException.class, unit::join); 193 | assertThat(thrown).getCause().isSameAs(exception); 194 | } 195 | 196 | private Function failWith(final RuntimeException e) { 197 | return result -> { 198 | throw e; 199 | }; 200 | } 201 | 202 | private ThrowingFunction fallbackIf(final Predicate predicate) { 203 | return throwable -> { 204 | if (predicate.test(throwable)) { 205 | return "fallback"; 206 | } 207 | 208 | throw throwable; 209 | }; 210 | } 211 | 212 | private ThrowingFunction rethrow() { 213 | return e -> { 214 | throw e; 215 | }; 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Faux Pas: Error handling in Functional Programming 2 | 3 | [![Spilled coffee](docs/spilled-coffee.jpg)](https://pixabay.com/en/mistake-spill-slip-up-accident-876597/) 4 | 5 | [![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html) 6 | ![Build Status](https://github.com/zalando/faux-pas/workflows/build/badge.svg) 7 | [![Coverage Status](https://img.shields.io/coveralls/zalando/faux-pas/main.svg)](https://coveralls.io/r/zalando/faux-pas) 8 | [![Code Quality](https://img.shields.io/codacy/grade/b3a619ff47574eb68f38bdf74906e91a/main.svg)](https://www.codacy.com/app/whiskeysierra/faux-pas) 9 | [![Javadoc](http://javadoc.io/badge/org.zalando/faux-pas.svg)](http://www.javadoc.io/doc/org.zalando/faux-pas) 10 | [![Release](https://img.shields.io/github/release/zalando/faux-pas.svg)](https://github.com/zalando/faux-pas/releases) 11 | [![Maven Central](https://img.shields.io/maven-central/v/org.zalando/faux-pas.svg)](https://maven-badges.herokuapp.com/maven-central/org.zalando/faux-pas) 12 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/zalando/faux-pas/main/LICENSE) 13 | 14 | > **Faux pas** *noun*, /fəʊ pɑː/: blunder; misstep, false step 15 | 16 | _**F**aux **P**as_ is a library that simplifies error handling for **F**unctional **P**rogramming in Java. It fixes the 17 | issue that none of the functional interfaces in the Java Runtime by default is allowed to throw checked exceptions. 18 | 19 | - **Technology stack**: Java 8+, functional interfaces 20 | - **Status**: 0.x, originally ported from [Riptide](https://www.github.com/zalando/riptide), used in production 21 | 22 | ## Example 23 | 24 | ```java 25 | interface Client { 26 | User read(final String name) throws IOException; 27 | } 28 | 29 | Function readUser = throwingFunction(client::read); 30 | readUser.apply("Bob"); // may throw IOException directly 31 | ``` 32 | 33 | ## Features 34 | 35 | - Checked exceptions for functional interfaces 36 | - Compatible with the JDK types 37 | 38 | ## Dependencies 39 | 40 | - Java 8 or higher 41 | - Lombok (no runtime dependency) 42 | 43 | ## Installation 44 | 45 | Add the following dependency to your project: 46 | 47 | ```xml 48 | 49 | org.zalando 50 | faux-pas 51 | ${faux-pas.version} 52 | 53 | ``` 54 | 55 | ## Usage 56 | 57 | ### Throwing functional interfaces 58 | 59 | *Faux Pas* has a variant of every major functional interface from the Java core: 60 | 61 | - [`ThrowingRunnable`](src/main/java/org/zalando/fauxpas/ThrowingRunnable.java) 62 | - [`ThrowingSupplier`](src/main/java/org/zalando/fauxpas/ThrowingSupplier.java) 63 | - [`ThrowingConsumer`](src/main/java/org/zalando/fauxpas/ThrowingConsumer.java) 64 | - [`ThrowingFunction`](src/main/java/org/zalando/fauxpas/ThrowingFunction.java) 65 | - [`ThrowingUnaryOperator`](src/main/java/org/zalando/fauxpas/ThrowingUnaryOperator.java) 66 | - [`ThrowingPredicate`](src/main/java/org/zalando/fauxpas/ThrowingPredicate.java) 67 | - [`ThrowingBiConsumer`](src/main/java/org/zalando/fauxpas/ThrowingBiConsumer.java) 68 | - [`ThrowingBiFunction`](src/main/java/org/zalando/fauxpas/ThrowingBiFunction.java) 69 | - [`ThrowingBinaryOperator`](src/main/java/org/zalando/fauxpas/ThrowingBinaryOperator.java) 70 | - [`ThrowingBiPredicate`](src/main/java/org/zalando/fauxpas/ThrowingBiPredicate.java) 71 | 72 | The followings statements apply to each of them: 73 | - extends the official interface, i.e. they are 100% compatible 74 | - [*sneakily throws*](https://projectlombok.org/features/SneakyThrows.html) the original exception 75 | 76 | #### Creation 77 | 78 | The way the Java runtime implemented functional interfaces always requires additional type information, either by 79 | using a cast or a local variable: 80 | 81 | ```java 82 | // compiler error 83 | client::read.apply(name); 84 | 85 | // too verbose 86 | ((ThrowingFunction) client::read).apply(name); 87 | 88 | // local variable may not always be desired 89 | ThrowingFunction readUser = client::read; 90 | readUser.apply(name); 91 | ``` 92 | 93 | As a workaround there is a static *factory* method for every interface type in`FauxPas`. All of them are called 94 | `throwingRunnable`, `throwingSupplier` and so forth. It allows for concise one-line statements: 95 | 96 | ```java 97 | List users = names.stream() 98 | .map(throwingFunction(client::read)) 99 | .collect(toList()); 100 | ``` 101 | 102 | ### Try-with-resources alternative 103 | 104 | Traditional `try-with-resources` statements are compiled into byte code that includes 105 | [unreachable parts](http://stackoverflow.com/a/17356707) and unfortunately JaCoCo has no 106 | [support for filtering](https://github.com/jacoco/jacoco/wiki/FilteringOptions) yet. That's why we came up with an 107 | alternative implementation. The [official example](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) 108 | for the `try-with-resources` statement looks like this: 109 | 110 | ```java 111 | try (BufferedReader br = 112 | new BufferedReader(new FileReader(path))) { 113 | return br.readLine(); 114 | } 115 | ``` 116 | 117 | Compared to ours: 118 | 119 | ```java 120 | return tryWith(new BufferedReader(new FileReader(path)), br -> 121 | br.readLine() 122 | ); 123 | ``` 124 | 125 | ### CompletableFuture.exceptionally(Function) 126 | 127 | [`CompletableFuture.exceptionally(..)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function-) 128 | is a very powerful but often overlooked tool. It allows to inject [partial exception handling](https://stackoverflow.com/questions/37032990/separated-exception-handling-of-a-completablefuture) 129 | into a `CompletableFuture`: 130 | 131 | ```java 132 | future.exceptionally(e -> { 133 | Throwable t = e instanceof CompletionException ? e.getCause() : e; 134 | 135 | if (t instanceof NoRouteToHostException) { 136 | return fallbackValueFor(e); 137 | } 138 | 139 | throw e instanceof CompletionException ? e : new CompletionException(t); 140 | }) 141 | ``` 142 | 143 | Unfortunately it has a contract that makes it harder to use than it needs to: 144 | 145 | - It takes a [`Throwable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html) as an argument, but 146 | doesn't allow to re-throw it *as-is*. This can be circumvented by optionally [wrapping it in a 147 | `CompletionException`](http://cs.oswego.edu/pipermail/concurrency-interest/2014-August/012910.html) before 148 | rethrowing it. 149 | - The throwable argument is [sometimes wrapped](https://stackoverflow.com/questions/27430255/surprising-behavior-of-java-8-completablefuture-exceptionally-method) 150 | inside a [`CompletionException`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionException.html) 151 | and sometimes it's not, depending on whether there is any other computation step before the `exceptionally(..)` call 152 | or not. 153 | 154 | In order to use the operation correctly one needs to follow these rules: 155 | 1. Unwrap given throwable if it's an instance of `CompletionException`. 156 | 2. Wrap checked exceptions in a `CompletionException` before throwing. 157 | 158 | `FauxPas.partially(..)` relives some of the pain by changing the interface and contract a bit to make it more usable. 159 | The following example is functionally equivalent to the one from above: 160 | 161 | ```java 162 | future.exceptionally(partially(e -> { 163 | if (e instanceof NoRouteToHostException) { 164 | return fallbackValueFor(e); 165 | } 166 | 167 | throw e; 168 | })) 169 | ``` 170 | 171 | 1. Takes a `ThrowingFunction`, i.e. it allows clients to 172 | - directly re-throw the throwable argument 173 | - throw any exception during exception handling *as-is* 174 | 2. Will automatically unwrap a `CompletionException` before passing it to the given function. 175 | I.e. the supplied function will never have to deal with `CompletionException` directly. Except for the rare occasion 176 | that the `CompletionException` has no cause, in which case it will be passed to the given function. 177 | 3. Will automatically wrap any thrown `Exception` inside a `CompletionException`, if needed. 178 | 179 | The last example is actually so common, that there is an overloaded version of `partially` that caters for this use 180 | particular case: 181 | 182 | ```java 183 | 184 | future.exceptionally(partially(NoRouteToHostException.class, this::fallbackValueFor)) 185 | ``` 186 | 187 | ### CompletableFuture.whenComplete(BiConsumer) 188 | 189 | ```java 190 | future.whenComplete(failedWith(TimeoutException.class, e -> { 191 | request.cancel(); 192 | })) 193 | ``` 194 | 195 | Other missing pieces in `CompletableFuture`'s API are `exceptionallyCompose` and `handleCompose`. Both can be seen as 196 | a combination of `exceptionally` + `compose` and `handle` + `compose` respectively. They basically allow to supply 197 | another `CompletableFuture` rather than concrete values directly. This is allows for asynchronous fallbacks: 198 | 199 | ```java 200 | exceptionallyCompose(users.find(name), e -> archive.find(name)) 201 | ``` 202 | 203 | ## Getting Help 204 | 205 | If you have questions, concerns, bug reports, etc., please file an issue in this repository's [Issue Tracker](../../issues). 206 | 207 | ## Getting Involved/Contributing 208 | 209 | To contribute, simply make a pull request and add a brief description (1-2 sentences) of your addition or change. For 210 | more details, check the [contribution guidelines](.github/CONTRIBUTING.md). 211 | 212 | ## Alternatives 213 | 214 | - [Lombok's `@SneakyThrows`](https://projectlombok.org/features/SneakyThrows.html) 215 | - [Durian's Errors](https://github.com/diffplug/durian) 216 | - [Spotify's Completable Futures](https://github.com/spotify/completable-futures) 217 | 218 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.zalando 8 | faux-pas 9 | 0.10.0-SNAPSHOT 10 | 11 | Faux Pas 12 | Error handling in Functional Programming 13 | https://github.com/zalando/faux-pas 14 | 15 | 16 | Zalando SE 17 | 18 | 19 | 2015 20 | 21 | 22 | 23 | MIT License 24 | https://opensource.org/licenses/MIT 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | Willi Schönborn 32 | willi.schoenborn@zalando.de 33 | Zalando SE 34 | https://tech.zalando.com/ 35 | 36 | 37 | 38 | 39 | https://github.com/zalando/faux-pas 40 | scm:git:git@github.com:zalando/faux-pas.git 41 | scm:git:git@github.com:zalando/faux-pas.git 42 | 43 | 44 | 45 | UTF-8 46 | 1.8 47 | 1.8 48 | 1.7.36 49 | 5.9.2 50 | 4.5.1 51 | 52 | 53 | 54 | 55 | org.apiguardian 56 | apiguardian-api 57 | 1.1.2 58 | 59 | 60 | com.google.code.findbugs 61 | jsr305 62 | 3.0.2 63 | 64 | 65 | com.google.gag 66 | gag 67 | 1.0.1 68 | provided 69 | 70 | 71 | org.slf4j 72 | slf4j-api 73 | ${slf4j.version} 74 | 75 | 76 | org.projectlombok 77 | lombok 78 | 1.18.26 79 | provided 80 | 81 | 82 | org.slf4j 83 | slf4j-nop 84 | ${slf4j.version} 85 | test 86 | 87 | 88 | org.junit.jupiter 89 | junit-jupiter-engine 90 | ${junit-jupiter.version} 91 | test 92 | 93 | 94 | org.assertj 95 | assertj-core 96 | 3.22.0 97 | test 98 | 99 | 100 | org.mockito 101 | mockito-core 102 | ${mockito.version} 103 | test 104 | 105 | 106 | org.mockito 107 | mockito-junit-jupiter 108 | ${mockito.version} 109 | test 110 | 111 | 112 | 113 | 114 | 115 | 116 | org.owasp 117 | dependency-check-maven 118 | 8.2.0 119 | 120 | 121 | 122 | check 123 | 124 | 125 | 126 | 127 | true 128 | 129 | cve-suppressions.xml 130 | 131 | 132 | 133 | 134 | org.basepom.maven 135 | duplicate-finder-maven-plugin 136 | 1.5.1 137 | 138 | 139 | validate 140 | 141 | check 142 | 143 | 144 | true 145 | false 146 | 147 | .*\.module-info 148 | 149 | 150 | .* 151 | 152 | 153 | 154 | 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-resources-plugin 159 | 3.3.0 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-compiler-plugin 164 | 3.11.0 165 | 166 | 167 | -Xlint:unchecked,deprecation 168 | -Werror 169 | -parameters 170 | 171 | 172 | 173 | 174 | org.apache.maven.plugins 175 | maven-surefire-plugin 176 | 3.0.0 177 | 178 | classesAndMethods 179 | 1 180 | true 181 | false 182 | 183 | 184 | 185 | org.jacoco 186 | jacoco-maven-plugin 187 | 0.8.8 188 | 189 | 190 | prepare-agent 191 | 192 | prepare-agent 193 | 194 | 195 | 196 | report 197 | 198 | report 199 | 200 | 201 | 202 | check 203 | 204 | check 205 | 206 | 207 | 208 | 209 | CLASS 210 | 211 | 212 | LINE 213 | COVEREDRATIO 214 | 1.0 215 | 216 | 217 | BRANCH 218 | COVEREDRATIO 219 | 1.0 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | org.eluder.coveralls 230 | coveralls-maven-plugin 231 | 4.3.0 232 | 233 | 234 | org.codehaus.mojo 235 | versions-maven-plugin 236 | 2.15.0 237 | 238 | false 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | release 247 | 248 | 249 | 250 | org.apache.maven.plugins 251 | maven-source-plugin 252 | 3.2.1 253 | 254 | 255 | attach-sources 256 | 257 | jar-no-fork 258 | 259 | 260 | 261 | 262 | 263 | org.apache.maven.plugins 264 | maven-javadoc-plugin 265 | 3.5.0 266 | 267 | 268 | attach-javadocs 269 | 270 | jar 271 | 272 | 273 | 274 | 275 | 276 | org.apache.maven.plugins 277 | maven-gpg-plugin 278 | 3.0.1 279 | 280 | 281 | sign-artifacts 282 | verify 283 | 284 | sign 285 | 286 | 287 | 288 | 289 | 290 | org.sonatype.central 291 | central-publishing-maven-plugin 292 | 0.8.0 293 | true 294 | 295 | central 296 | ${project.name}:${project.version} 297 | true 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | --------------------------------------------------------------------------------