From fdc2148b918ac524cf18201d04c6a61456c4e8c1 Mon Sep 17 00:00:00 2001 From: George Tay Date: Mon, 4 Mar 2024 14:35:10 +0800 Subject: [PATCH 01/11] Implement FailableOptional monad --- .../util/function/FailableOptional.java | 392 ++++++++++++++++++ .../util/function/ThrowableConsumer.java | 6 + .../util/function/ThrowableFunction.java | 6 + .../util/function/ThrowableSupplier.java | 6 + 4 files changed, 410 insertions(+) create mode 100644 src/main/java/reposense/util/function/FailableOptional.java create mode 100644 src/main/java/reposense/util/function/ThrowableConsumer.java create mode 100644 src/main/java/reposense/util/function/ThrowableFunction.java create mode 100644 src/main/java/reposense/util/function/ThrowableSupplier.java diff --git a/src/main/java/reposense/util/function/FailableOptional.java b/src/main/java/reposense/util/function/FailableOptional.java new file mode 100644 index 0000000000..e80e4ebd66 --- /dev/null +++ b/src/main/java/reposense/util/function/FailableOptional.java @@ -0,0 +1,392 @@ +package reposense.util.function; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * {@code Optional} monad that enables both an empty and a + * fail option. + * + * @param Type T, unbounded to any type. + */ +public abstract class FailableOptional { + public static FailableOptional of(Supplier supplier) { + return of(supplier.get()); + } + + public static FailableOptional of(ThrowableSupplier supplier) { + try { + return of(supplier.produce()); + } catch (Exception e) { + return ofFailure(e); + } + } + + public static FailableOptional ofNullable(Supplier supplier) { + return ofNullable(supplier.get()); + } + + public static FailableOptional ofNullable(ThrowableSupplier supplier) { + try { + return ofNullable(supplier.produce()); + } catch (Exception e) { + return ofFailure(e); + } + } + + public static FailableOptional of(T item) { + return new Present<>(item); + } + + public static FailableOptional ofNullable(T item) { + return item == null ? ofAbsent() : of(item); + } + + public static FailableOptional ofAbsent() { + return new Absent<>(); + } + + public static FailableOptional ofFailure(Exception e) { + return new Fail<>(e); + } + + public abstract FailableOptional ifPresent(Runnable runner); + public abstract FailableOptional ifPresent(ThrowableConsumer consumer); + public abstract FailableOptional ifAbsent(Runnable runner); + public abstract FailableOptional ifAbsent(ThrowableConsumer consumer); + public abstract FailableOptional ifFail(Runnable runner); + public abstract FailableOptional ifFail( + ThrowableConsumer consumer); + + public abstract FailableOptional map(ThrowableFunction function); + public abstract FailableOptional flatMap( + ThrowableFunction, E> function); + public abstract FailableOptional filter(Predicate predicate); + public abstract T get() throws NoSuchElementException; + public abstract T orElse(T item); + public abstract T orElseThrow(Exception e) throws Exception; + public abstract boolean isPresent(); + public abstract boolean isAbsent(); + public abstract boolean isFail(); + public abstract FailableOptional ifFailOfType(List> exList); + public abstract FailableOptional recover(ThrowableSupplier supplier); + + private static class Present extends FailableOptional { + private final T item; + + private Present(T item) { + this.item = item; + } + + @Override + public FailableOptional ifPresent(Runnable runner) { + runner.run(); + return this; + } + + @Override + public FailableOptional ifPresent(ThrowableConsumer consumer) { + try { + consumer.consume(this.item); + } catch (Exception e) { + return FailableOptional.ofFailure(e); + } + + return this; + } + + @Override + public FailableOptional ifAbsent(Runnable runner) { + return this; + } + + @Override + public FailableOptional ifAbsent(ThrowableConsumer consumer) { + return null; + } + + @Override + public FailableOptional ifFail(Runnable runner) { + return this; + } + + public FailableOptional ifFail( + ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional map(ThrowableFunction function) { + try { + return FailableOptional.ofNullable(function.apply(this.item)); + } catch (Exception e) { + return FailableOptional.ofFailure(e); + } + } + + @Override + public FailableOptional flatMap(ThrowableFunction, E> function) { + try { + return function.apply(this.item); + } catch (Exception e) { + return FailableOptional.ofFailure(e); + } + } + + @Override + public FailableOptional filter(Predicate predicate) { + if (predicate.test(this.item)) { + return this; + } + + return FailableOptional.ofAbsent(); + } + + @Override + public T get() throws NoSuchElementException { + return this.item; + } + + @Override + public T orElse(T item) { + return this.item; + } + + @Override + public T orElseThrow(Exception e) { + return this.item; + } + + @Override + public boolean isPresent() { + return true; + } + + @Override + public boolean isAbsent() { + return false; + } + + @Override + public boolean isFail() { + return false; + } + + @Override + public final FailableOptional ifFailOfType(List> exList) { + return this; + } + + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + throw new NoSuchElementException(); + } + } + + private static class Absent extends FailableOptional { + + @Override + public FailableOptional ifPresent(Runnable runner) { + return this; + } + + @Override + public FailableOptional ifPresent(ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional ifAbsent(Runnable runner) { + runner.run(); + return this; + } + + @Override + public FailableOptional ifAbsent(ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional ifFail(Runnable runner) { + return this; + } + + public FailableOptional ifFail( + ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional map(ThrowableFunction function) { + @SuppressWarnings("unchecked") + FailableOptional failed = (FailableOptional) this; + return failed; + } + + @Override + public FailableOptional flatMap(ThrowableFunction, E> function) { + @SuppressWarnings("unchecked") + FailableOptional failed = (FailableOptional) this; + return failed; + } + + @Override + public FailableOptional filter(Predicate predicate) { + return this; + } + + @Override + public T get() throws NoSuchElementException { + throw new NoSuchElementException(); + } + + @Override + public T orElse(T item) { + return item; + } + + @Override + public T orElseThrow(Exception e) throws Exception { + throw e; + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public boolean isAbsent() { + return true; + } + + @Override + public boolean isFail() { + return false; + } + + @Override + public FailableOptional ifFailOfType(List> exList) { + return this; + } + + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + throw new NoSuchElementException(); + } + } + + private static class Fail extends FailableOptional { + private final Exception exception; + + private Fail(Exception e) { + this.exception = e; + } + + @Override + public FailableOptional ifPresent(Runnable runner) { + return this; + } + + @Override + public FailableOptional ifPresent(ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional ifAbsent(Runnable runner) { + return this; + } + + @Override + public FailableOptional ifAbsent(ThrowableConsumer consumer) { + return this; + } + + @Override + public FailableOptional ifFail(Runnable runner) { + runner.run(); + return this; + } + + public FailableOptional ifFail(ThrowableConsumer consumer) { + try { + @SuppressWarnings("unchecked") + E except = (E) this.exception; + consumer.consume(except); + } catch (Exception e) { + return FailableOptional.ofFailure(e); + } + + return this; + } + + @Override + public FailableOptional map(ThrowableFunction function) { + @SuppressWarnings("unchecked") + FailableOptional failed = (FailableOptional) this; + return failed; + } + + @Override + public FailableOptional flatMap(ThrowableFunction, E> function) { + @SuppressWarnings("unchecked") + FailableOptional failed = (FailableOptional) this; + return failed; + } + + @Override + public FailableOptional filter(Predicate predicate) { + return this; + } + + @Override + public T get() throws NoSuchElementException { + throw new NoSuchElementException(); + } + + @Override + public T orElse(T item) { + return item; + } + + @Override + public T orElseThrow(Exception e) throws Exception { + throw e; + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public boolean isAbsent() { + return false; + } + + @Override + public boolean isFail() { + return true; + } + + @Override + public FailableOptional ifFailOfType(List> exList) { + for (Class e : exList) { + if (this.exception.getClass().getSimpleName().equals(e.getSimpleName())) { + return this; + } + } + + return FailableOptional.ofAbsent(); + } + + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + return FailableOptional.of(supplier); + } + } +} diff --git a/src/main/java/reposense/util/function/ThrowableConsumer.java b/src/main/java/reposense/util/function/ThrowableConsumer.java new file mode 100644 index 0000000000..245497570a --- /dev/null +++ b/src/main/java/reposense/util/function/ThrowableConsumer.java @@ -0,0 +1,6 @@ +package reposense.util.function; + +@FunctionalInterface +public interface ThrowableConsumer { + void consume(T t) throws E; +} diff --git a/src/main/java/reposense/util/function/ThrowableFunction.java b/src/main/java/reposense/util/function/ThrowableFunction.java new file mode 100644 index 0000000000..53279e0d30 --- /dev/null +++ b/src/main/java/reposense/util/function/ThrowableFunction.java @@ -0,0 +1,6 @@ +package reposense.util.function; + +@FunctionalInterface +public interface ThrowableFunction { + U apply(T t) throws E; +} diff --git a/src/main/java/reposense/util/function/ThrowableSupplier.java b/src/main/java/reposense/util/function/ThrowableSupplier.java new file mode 100644 index 0000000000..d1419092fb --- /dev/null +++ b/src/main/java/reposense/util/function/ThrowableSupplier.java @@ -0,0 +1,6 @@ +package reposense.util.function; + +@FunctionalInterface +public interface ThrowableSupplier { + T produce() throws E; +} From b9aecd4242db09f5c465865e01a02e2ce8cf2b25 Mon Sep 17 00:00:00 2001 From: George Tay Date: Mon, 4 Mar 2024 14:35:35 +0800 Subject: [PATCH 02/11] Use FailableOptional in ConfigRunConfiguration --- .../model/ConfigRunConfiguration.java | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/main/java/reposense/model/ConfigRunConfiguration.java b/src/main/java/reposense/model/ConfigRunConfiguration.java index e63ca1ea8a..8fb9551ba4 100644 --- a/src/main/java/reposense/model/ConfigRunConfiguration.java +++ b/src/main/java/reposense/model/ConfigRunConfiguration.java @@ -3,7 +3,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @@ -13,6 +16,7 @@ import reposense.parser.exceptions.InvalidCsvException; import reposense.parser.exceptions.InvalidHeaderException; import reposense.system.LogsManager; +import reposense.util.function.FailableOptional; /** * Represents RepoSense run configured by config files. @@ -38,33 +42,25 @@ public ConfigRunConfiguration(CliArguments cliArguments) { public List getRepoConfigurations() throws IOException, InvalidCsvException, InvalidHeaderException { List repoConfigs = new RepoConfigCsvParser(cliArguments.getRepoConfigFilePath()).parse(); - List authorConfigs; - List groupConfigs; - Path authorConfigFilePath = cliArguments.getAuthorConfigFilePath(); - Path groupConfigFilePath = cliArguments.getGroupConfigFilePath(); + // parse the author config file path + FailableOptional.of(cliArguments.getAuthorConfigFilePath()) + .filter(Files::exists) + .map(x -> new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse()) + .ifPresent(x -> RepoConfiguration.merge(repoConfigs, x)) + .ifPresent(() -> RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true)) + .ifFailOfType(Arrays.asList(IOException.class, InvalidCsvException.class)) + .ifFail(x -> logger.log(Level.WARNING, x.getMessage(), x)) + .orElse(Collections.emptyList()); - - if (authorConfigFilePath != null && Files.exists(authorConfigFilePath)) { - try { - authorConfigs = new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse(); - RepoConfiguration.merge(repoConfigs, authorConfigs); - RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true); - } catch (IOException | InvalidCsvException e) { - // for all IO and invalid csv exceptions, log the error and continue - logger.log(Level.WARNING, e.getMessage(), e); - } - } - - if (groupConfigFilePath != null && Files.exists(groupConfigFilePath)) { - try { - groupConfigs = new GroupConfigCsvParser(cliArguments.getGroupConfigFilePath()).parse(); - RepoConfiguration.setGroupConfigsToRepos(repoConfigs, groupConfigs); - } catch (IOException | InvalidCsvException e) { - // for all IO and invalid csv exceptions, log the error and continue - logger.log(Level.WARNING, e.getMessage(), e); - } - } + // parse the group config file path + FailableOptional.of(cliArguments.getGroupConfigFilePath()) + .filter(Files::exists) + .map(x -> new GroupConfigCsvParser(x).parse()) + .ifPresent(x -> RepoConfiguration.setGroupConfigsToRepos(repoConfigs, x)) + .ifFailOfType(Arrays.asList(IOException.class, InvalidCsvException.class)) + .ifFail(x -> logger.log(Level.WARNING, x.getMessage(), x)) + .orElse(Collections.emptyList()); return repoConfigs; } From 1d9ce27ab1829b274443dc596e6ab3866e30f8ec Mon Sep 17 00:00:00 2001 From: George Tay Date: Mon, 4 Mar 2024 16:54:56 +0800 Subject: [PATCH 03/11] Add relevant JavaDoc documentation --- .../model/ConfigRunConfiguration.java | 2 - .../util/function/FailableOptional.java | 280 ++++++++++++++++-- .../util/function/ThrowableConsumer.java | 9 + .../util/function/ThrowableFunction.java | 9 + .../util/function/ThrowableSupplier.java | 9 + 5 files changed, 290 insertions(+), 19 deletions(-) diff --git a/src/main/java/reposense/model/ConfigRunConfiguration.java b/src/main/java/reposense/model/ConfigRunConfiguration.java index 8fb9551ba4..4026c69cbb 100644 --- a/src/main/java/reposense/model/ConfigRunConfiguration.java +++ b/src/main/java/reposense/model/ConfigRunConfiguration.java @@ -2,11 +2,9 @@ import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/src/main/java/reposense/util/function/FailableOptional.java b/src/main/java/reposense/util/function/FailableOptional.java index e80e4ebd66..1521c287ba 100644 --- a/src/main/java/reposense/util/function/FailableOptional.java +++ b/src/main/java/reposense/util/function/FailableOptional.java @@ -6,16 +6,48 @@ import java.util.function.Supplier; /** - * {@code Optional} monad that enables both an empty and a - * fail option. + * An {@code Optional} monad that enables both an empty and a fail option. * - * @param Type T, unbounded to any type. + * @param Generic Type {@code T}, unbounded to any type. */ public abstract class FailableOptional { + /** + * Creates a new {@code FailableOptional} object. {@code FailableOptional} + * can contain {@code null}. + * + * @param item Item of type {@code T}. + * @param Generic Type {@code T}. + * @return An instance of {@code FailableOptional} instance. + */ + public static FailableOptional of(T item) { + return new Present<>(item); + } + + /** + * Creates a new {@code FailableOptional} object. + * This method can allow null values to be stored within the {@code FailableOptional} object. + * + * @param supplier Produces an object of type {@code T}, which can also be {@code null}. + * This {@code Supplier} cannot throw any Exceptions. + * @param Generic type {@code T}. + * @return {@code FailableOptional} object wrapping an object of type {@code T}. + */ public static FailableOptional of(Supplier supplier) { return of(supplier.get()); } + /** + * Creates a new {@code FailableOptional} object. + * This method can allow null values to be stored within the {@code FailableOptional} object. + * + * @param supplier Produces an object of type {@code T}, which can also be {@code null}. + * This {@code ThrowableSupplier} can throw Exceptions and will be automatically + * converted into a failed instance of {@code ThrowaableSupplier}. + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded to {@code Exception}. + * @return {@code FailableOptional} object wrapping an object of type {@code T} or a failed + * instance of {@code FailableOptional}. + */ public static FailableOptional of(ThrowableSupplier supplier) { try { return of(supplier.produce()); @@ -24,10 +56,44 @@ public static FailableOptional of(ThrowableSupplier< } } + /** + * Creates a new {@code FailableOptional} object. + * {@code null} is automatically converted into an empty instance of {@code FailableOptional} object. + * + * @param item Item of type {@code T}. + * @param Generic Type {@code T}. + * @return An instance of {@code FailableOptional} instance. + */ + public static FailableOptional ofNullable(T item) { + return item == null ? ofAbsent() : of(item); + } + + /** + * Creates a new {@code FailableOptional} object. + * This method converts {@code null} into an empty instance of {@code FailableOptional}. + * + * @param supplier Produces an object of type {@code T}, which can also be {@code null}. + * This {@code Supplier} cannot throw any Exceptions. + * @param Generic type {@code T}. + * @return {@code FailableOptional} object wrapping an object of type {@code T} or an empty + * instance of {@code FailableOptional}. + */ public static FailableOptional ofNullable(Supplier supplier) { return ofNullable(supplier.get()); } + /** + * Creates a new {@code FailableOptional} object. + * This method converts {@code null} into an empty instance of {@code FailableOptional}. + * + * @param supplier Produces an object of type {@code T}, which can also be {@code null}. + * This {@code ThrowableSupplier} can throw Exceptions and will be automatically + * converted into a failed instance of {@code ThrowaableSupplier}. + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded to {@code Exception}. + * @return {@code FailableOptional} object wrapping an object of type {@code T} or an empty + * instance of {@code FailableOptional}. + */ public static FailableOptional ofNullable(ThrowableSupplier supplier) { try { return ofNullable(supplier.produce()); @@ -36,45 +102,210 @@ public static FailableOptional ofNullable(ThrowableS } } - public static FailableOptional of(T item) { - return new Present<>(item); - } - - public static FailableOptional ofNullable(T item) { - return item == null ? ofAbsent() : of(item); - } - + /** + * Creates an empty instance of {@code FailableOptional}. + * + * @param Generic Type {@code T}. + * @return Empty instance of {@code FailableOptional}. + */ public static FailableOptional ofAbsent() { return new Absent<>(); } + /** + * Creates a failed instance of {@code FailableOptional}. + * + * @param Generic Type {@code T}. + * @return Failed instance of {@code FailableOptional}. + */ public static FailableOptional ofFailure(Exception e) { return new Fail<>(e); } + /** + * Executes the provided {@code Runner} if this current instance of + * {@code FailableOptional} contains a value. + * + * @param runner {@code Runner} instance that executes if this {@code FailableOptional} + * contains a value + * @return This {@code FailableOptional} object + */ public abstract FailableOptional ifPresent(Runnable runner); + + /** + * Executes the provided {@code ThrowableConsumer} if this current instance of + * {@code FailableOptional} contains a value. + * + * @param consumer {@code ThrowableConsumer} instance that consumes the stored + * value if present. + * @param Generic Type {@code E} bounded by Exception. + * @return This {@code ThrowableConsumer} instance. + */ public abstract FailableOptional ifPresent(ThrowableConsumer consumer); + + /** + * Executes the provided {@code Runner} if this current instance of + * {@code FailableOptional} does not contains a value. + * + * @param runner {@code Runner} instance that executes if this {@code FailableOptional} + * does not contains a value. + * @return This {@code FailableOptional} object. + */ public abstract FailableOptional ifAbsent(Runnable runner); + + /** + * Executes the provided {@code ThrowableConsumer} if this current instance of + * {@code FailableOptional} does not contains a value. + * + * @param consumer {@code ThrowableConsumer} instance that attempts to consume + * an object of type {@code T}. + * @param Generic Type {@code E} bounded by Exception. + * @return This {@code ThrowableConsumer} instance. + */ public abstract FailableOptional ifAbsent(ThrowableConsumer consumer); + + /** + * Executes the provided {@code Runner} if this current instance of + * {@code FailableOptional} has failed. + * + * @param runner {@code Runner} instance that executes if this {@code FailableOptional} + * has failed. + * @return This {@code FailableOptional} object. + */ public abstract FailableOptional ifFail(Runnable runner); + + /** + * Executes the provided {@code ThrowableConsumer} if this current instance of + * {@code FailableOptional} has failed. + * + * @param consumer {@code ThrowableConsumer} instance that attempts to consume + * an object of type {@code T}. + * @param Generic Type {@code E} bounded by Exception. + * @return This {@code ThrowableConsumer} instance. + */ public abstract FailableOptional ifFail( ThrowableConsumer consumer); + /** + * Maps the stored value in this {@code FailableOptional} into another + * {@code FailableOptional} containing items of Type {@code U}. + * If this {@code FailableOptional} contains nothing or has failed, + * this instance is returned as is. + * + * @param function {@code ThrowableFunction} that takes an object of Type {@code T} + * and returns Type {@code U} and might throw + * Exception of Type {@code E}. + * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. + * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. + * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance + * of {@code FailableOptional}. + */ public abstract FailableOptional map(ThrowableFunction function); + + /** + * Maps the stored value into another new instance of {@code FailableOptional}. Unlike {@code map}, + * this method requires that functions itself return the new instance of {@code FailableOptional}. + * + * @param function {@code ThrowableFunction} that takes an object of Type {@code T} + * and returns Type {@code FailableOptional} and + * might throw Exception of Type {@code E}. + * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. + * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. + * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance + * of {@code FailableOptional}. + */ public abstract FailableOptional flatMap( ThrowableFunction, E> function); + + /** + * Checks if the stored item fulfills the predicate, and if so, return this instance, and if not, + * return an empty instance of {@code FailableOptional}. + * + * @param predicate {@code Predicate} that tests an item of Type {@code T}. + * @return This instance if the predicate evaluates to true, else an empty instance of + * {@code FailableOptional}. + */ public abstract FailableOptional filter(Predicate predicate); + + /** + * Returns the item stored in this {@code FailableOptional}. + * + * @return Stored item of Type {@code T} + * @throws NoSuchElementException if this {@code FailableOptional} does not contain any values + * or has failed. + */ public abstract T get() throws NoSuchElementException; + + /** + * Returns the item stored in this {@code FailableOptional}, and if + * there is no such value, return the input parameter {@code item}. + * + * @param item The value to return if this {@code FailableOptional} is empty or has failed. + * @return The stored item or the input item. + */ public abstract T orElse(T item); + + /** + * Returns the item stored in this {@code FailableOptional}, and if + * there is no such value, return the input parameter {@code item}. + * + * @param e The Exception to throw if this {@code FailableOptional} is empty or has failed. + * @return The stored item or throws an Exception. + * @throws Exception if there are no items stored in this {@code FailableOptional} instance. + */ public abstract T orElseThrow(Exception e) throws Exception; + + /** + * Checks if this {@code FailableOptional} instance contains a value. + * + * @return true if this instance of {@code FailableOptional} is not empty, false otherwise. + */ public abstract boolean isPresent(); + + /** + * Checks if this {@code FailableOptional} instance does not contains a value. + * + * @return true if this instance of {@code FailableOptional} is empty, false otherwise. + */ public abstract boolean isAbsent(); + + /** + * Checks if this {@code FailableOptional} instance has failed. + * + * @return true if this instance of {@code FailableOptional} has failed, false otherwise. + */ public abstract boolean isFail(); + + /** + * Verifies that a failed instance of {@code FailableOptional} has failed due to the + * input list of Exceptions to check. + * + * @param exList List of Exception Classes to verify the failure Exception cause. + * @return This instance if this instance has failed and is due to any one of the reasons specified + * in the {@code exList} argument, or an empty {@code FailableOptional} instance otherwise. + */ public abstract FailableOptional ifFailOfType(List> exList); + + /** + * Attempts to recover a failed instance by providing an item of Type {@code U}, which will be + * wrapped up in another {@code FailableOptional} object. + * + * @param supplier A {@code ThrowableSupplier} instance that provides an item of Type {@code U}, + * which may throw an Exception of Type {@code E}. + * @param Generic Type {@code U} represents the Type of the object returned by the + * {@code ThrowableSupplier}. + * @param Generic Type {@code E} bounded by Exception. + * @return A new {@code FailableOptional} object. + */ public abstract FailableOptional recover(ThrowableSupplier supplier); - private static class Present extends FailableOptional { + /** + * Represents a {@code ThrowableSupplier} that contains a value. + * + * @param Generic Type {@code T}. + */ + private static final class Present extends FailableOptional { private final T item; private Present(T item) { @@ -128,7 +359,8 @@ public FailableOptional map(ThrowableFunction FailableOptional flatMap(ThrowableFunction, E> function) { + public FailableOptional flatMap( + ThrowableFunction, E> function) { try { return function.apply(this.item); } catch (Exception e) { @@ -186,7 +418,12 @@ public FailableOptional recover(ThrowableSupplier extends FailableOptional { + /** + * Represents a {@code ThrowableSupplier} that does not contain any value. + * + * @param Generic Type {@code T}. + */ + private static final class Absent extends FailableOptional { @Override public FailableOptional ifPresent(Runnable runner) { @@ -227,7 +464,8 @@ public FailableOptional map(ThrowableFunction FailableOptional flatMap(ThrowableFunction, E> function) { + public FailableOptional flatMap( + ThrowableFunction, E> function) { @SuppressWarnings("unchecked") FailableOptional failed = (FailableOptional) this; return failed; @@ -279,6 +517,11 @@ public FailableOptional recover(ThrowableSupplier Generic Type {@code T}. + */ private static class Fail extends FailableOptional { private final Exception exception; @@ -312,7 +555,9 @@ public FailableOptional ifFail(Runnable runner) { return this; } - public FailableOptional ifFail(ThrowableConsumer consumer) { + @Override + public FailableOptional ifFail( + ThrowableConsumer consumer) { try { @SuppressWarnings("unchecked") E except = (E) this.exception; @@ -332,7 +577,8 @@ public FailableOptional map(ThrowableFunction FailableOptional flatMap(ThrowableFunction, E> function) { + public FailableOptional flatMap( + ThrowableFunction, E> function) { @SuppressWarnings("unchecked") FailableOptional failed = (FailableOptional) this; return failed; diff --git a/src/main/java/reposense/util/function/ThrowableConsumer.java b/src/main/java/reposense/util/function/ThrowableConsumer.java index 245497570a..6bf53dd4a5 100644 --- a/src/main/java/reposense/util/function/ThrowableConsumer.java +++ b/src/main/java/reposense/util/function/ThrowableConsumer.java @@ -1,5 +1,14 @@ package reposense.util.function; +/** + * Functional interface that defines a Consumer that can throw + * an Exception on execution. + * + * @param The Type of the item that this {@code ThrowableConsumer} can + * consume. + * @param The Type of the Exception that this {@code ThrowableConsumer} + * can throw. + */ @FunctionalInterface public interface ThrowableConsumer { void consume(T t) throws E; diff --git a/src/main/java/reposense/util/function/ThrowableFunction.java b/src/main/java/reposense/util/function/ThrowableFunction.java index 53279e0d30..5ef493373e 100644 --- a/src/main/java/reposense/util/function/ThrowableFunction.java +++ b/src/main/java/reposense/util/function/ThrowableFunction.java @@ -1,5 +1,14 @@ package reposense.util.function; +/** + * Functional interface that defines a Supplier that can throw + * an Exception on execution. + * + * @param The Input Type of the item that this {@code ThrowableFunction}. + * @param The Return Type of this {@code ThrowableFunction}. + * @param The Type of the Exception that this {@code ThrowableFunction}. + * can throw. + */ @FunctionalInterface public interface ThrowableFunction { U apply(T t) throws E; diff --git a/src/main/java/reposense/util/function/ThrowableSupplier.java b/src/main/java/reposense/util/function/ThrowableSupplier.java index d1419092fb..04d67baf15 100644 --- a/src/main/java/reposense/util/function/ThrowableSupplier.java +++ b/src/main/java/reposense/util/function/ThrowableSupplier.java @@ -1,5 +1,14 @@ package reposense.util.function; +/** + * Functional interface that defines a Supplier that can throw + * an Exception on execution. + * + * @param The Type of the item that this {@code ThrowableSupplier} can + * supply. + * @param The Type of the Exception that this {@code ThrowableSupplier} + * can throw. + */ @FunctionalInterface public interface ThrowableSupplier { T produce() throws E; From 270ea8774f979e8259356285cf682a8ef8d6ff14 Mon Sep 17 00:00:00 2001 From: George Tay Date: Sat, 9 Mar 2024 12:27:12 +0800 Subject: [PATCH 04/11] Use FailableOptionals to replace null usage --- .../authorship/AuthorshipReporter.java | 8 +- .../authorship/FileInfoAnalyzer.java | 96 ++++------ .../reposense/commits/CommitInfoAnalyzer.java | 22 +-- .../parser/GroupConfigCsvParser.java | 19 +- .../util/function/FailableOptional.java | 178 ++++++++++++------ .../authorship/AnnotatorAnalyzerTest.java | 38 +++- .../authorship/FileAnalyzerTest.java | 83 ++++++-- .../authorship/FileResultAggregatorTest.java | 6 +- .../reposense/template/GitTestTemplate.java | 3 +- 9 files changed, 282 insertions(+), 171 deletions(-) diff --git a/src/main/java/reposense/authorship/AuthorshipReporter.java b/src/main/java/reposense/authorship/AuthorshipReporter.java index 24f55ce8f4..bf4c00b4ec 100644 --- a/src/main/java/reposense/authorship/AuthorshipReporter.java +++ b/src/main/java/reposense/authorship/AuthorshipReporter.java @@ -1,7 +1,6 @@ package reposense.authorship; import java.util.List; -import java.util.Objects; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -10,6 +9,7 @@ import reposense.authorship.model.FileResult; import reposense.model.RepoConfiguration; import reposense.system.LogsManager; +import reposense.util.function.FailableOptional; /** @@ -47,14 +47,16 @@ public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config) { List fileResults = textFileInfos.stream() .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo)) - .filter(Objects::nonNull) + .filter(FailableOptional::isPresent) + .map(FailableOptional::get) .collect(Collectors.toList()); List binaryFileInfos = fileInfoExtractor.extractBinaryFileInfos(config); List binaryFileResults = binaryFileInfos.stream() .map(fileInfo -> fileInfoAnalyzer.analyzeBinaryFile(config, fileInfo)) - .filter(Objects::nonNull) + .filter(FailableOptional::isPresent) + .map(FailableOptional::get) .collect(Collectors.toList()); fileResults.addAll(binaryFileResults); diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index 4114b23f3b..fe27421791 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -7,7 +7,6 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.logging.Logger; @@ -22,6 +21,7 @@ import reposense.model.RepoConfiguration; import reposense.system.LogsManager; import reposense.util.FileUtil; +import reposense.util.function.FailableOptional; /** * Analyzes the target and information given in the {@link FileInfo}. @@ -44,50 +44,37 @@ public class FileInfoAnalyzer { /** * Analyzes the lines of the file, given in the {@code fileInfo}, that has changed in the time period provided * by {@code config}. - * Returns null if the file is missing from the local system, or none of the + * Returns empty {@code FailableOptional} if the file is missing from the local system, or none of the * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { - String relativePath = fileInfo.getPath(); - - if (Files.notExists(Paths.get(config.getRepoRoot(), relativePath))) { - logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath)); - return null; - } - - if (FileUtil.isEmptyFile(config.getRepoRoot(), relativePath)) { - return null; - } - - aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo); - fileInfo.setFileType(config.getFileType(fileInfo.getPath())); - - AnnotatorAnalyzer.aggregateAnnotationAuthorInfo(fileInfo, config.getAuthorConfig()); - - if (!config.getAuthorList().isEmpty() && fileInfo.isAllAuthorsIgnored(config.getAuthorList())) { - return null; - } - - return generateTextFileResult(fileInfo); + public FailableOptional analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { + // note that the predicates in filter() test for the negation of the previous failure conditions + return FailableOptional.ofNullable(fileInfo.getPath()) + .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x))) + .ifAbsent(x -> logger.severe(String.format(MESSAGE_FILE_MISSING, x))) + .filter(x -> !FileUtil.isEmptyFile(config.getRepoRoot(), x)) + .ifPresent(x -> { + aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo); + fileInfo.setFileType(config.getFileType(fileInfo.getPath())); + + AnnotatorAnalyzer.aggregateAnnotationAuthorInfo(fileInfo, config.getAuthorConfig()); + }) + .filter(x -> config.getAuthorList().isEmpty() || !fileInfo.isAllAuthorsIgnored(config.getAuthorList())) + .map(x -> generateTextFileResult(fileInfo)); } /** * Analyzes the binary file, given in the {@code fileInfo}, that has changed in the time period provided * by {@code config}. - * Returns null if the file is missing from the local system, or none of the + * Returns empty {@code FailableOptional} if the file is missing from the local system, or none of the * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public FileResult analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { - String relativePath = fileInfo.getPath(); - - if (Files.notExists(Paths.get(config.getRepoRoot(), relativePath))) { - logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath)); - return null; - } - - fileInfo.setFileType(config.getFileType(fileInfo.getPath())); - - return generateBinaryFileResult(config, fileInfo); + public FailableOptional analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { + return FailableOptional.ofNullable(fileInfo.getPath()) + .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x))) + .ifAbsent(x -> logger.severe(String.format(MESSAGE_FILE_MISSING, x))) + .ifPresent(x -> fileInfo.setFileType(config.getFileType(fileInfo.getPath()))) + .flatMap(x -> generateBinaryFileResult(config, fileInfo)); } /** @@ -108,29 +95,28 @@ private FileResult generateTextFileResult(FileInfo fileInfo) { /** * Generates and returns a {@link FileResult} with the authorship results from binary {@code fileInfo} consolidated. * Authorship results are indicated in the {@code authorContributionMap} as contributions with zero line counts. - * Returns {@code null} if none of the {@link Author} specified in {@code config} contributed to the file in - * {@code fileInfo}. + * Returns an empty {@code FailableOptional} if none of the {@link Author} specified in + * {@code config} contributed to the file in {@code fileInfo}. */ - private FileResult generateBinaryFileResult(RepoConfiguration config, FileInfo fileInfo) { - List authorsString = GitLog.getFileAuthors(config, fileInfo.getPath()); - if (authorsString.size() == 0) { - return null; - } - + private FailableOptional generateBinaryFileResult(RepoConfiguration config, FileInfo fileInfo) { Set authors = new HashSet<>(); HashMap authorContributionMap = new HashMap<>(); - for (String[] lineDetails : authorsString) { - String authorName = lineDetails[0]; - String authorEmail = lineDetails[1]; - authors.add(config.getAuthor(authorName, authorEmail)); - } - - for (Author author : authors) { - authorContributionMap.put(author, 0); - } - - return FileResult.createBinaryFileResult(fileInfo.getPath(), fileInfo.getFileType(), authorContributionMap); + return FailableOptional.ofNullable(GitLog.getFileAuthors(config, fileInfo.getPath())) + .filter(x -> !x.isEmpty()) + .ifPresent(x -> { + for (String[] lineDetails : x) { + String authorName = lineDetails[0]; + String authorEmail = lineDetails[1]; + authors.add(config.getAuthor(authorName, authorEmail)); + } + + for (Author author : authors) { + authorContributionMap.put(author, 0); + } + }) + .map(x -> FileResult + .createBinaryFileResult(fileInfo.getPath(), fileInfo.getFileType(), authorContributionMap)); } /** diff --git a/src/main/java/reposense/commits/CommitInfoAnalyzer.java b/src/main/java/reposense/commits/CommitInfoAnalyzer.java index 0730e21f7d..f1f56421a6 100644 --- a/src/main/java/reposense/commits/CommitInfoAnalyzer.java +++ b/src/main/java/reposense/commits/CommitInfoAnalyzer.java @@ -5,7 +5,6 @@ import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -25,6 +24,7 @@ import reposense.model.FileType; import reposense.model.RepoConfiguration; import reposense.system.LogsManager; +import reposense.util.function.FailableOptional; /** * Analyzes commit information found in the git log. @@ -89,15 +89,13 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi Boolean isMergeCommit = elements[PARENT_HASHES_INDEX].split(HASH_SPLITTER).length > 1; Author author = config.getAuthor(elements[AUTHOR_INDEX], elements[EMAIL_INDEX]); - ZonedDateTime date = null; - try { - date = ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT); - } catch (DateTimeParseException pe) { - logger.log(Level.WARNING, "Unable to parse the date from git log result for commit.", pe); - } - - // Commit date may be in a timezone different from the one given in the config. - LocalDateTime adjustedDate = date.withZoneSameInstant(config.getZoneId()).toLocalDateTime(); + // safe map since ZonedDateTime::now returns non-null + FailableOptional date = FailableOptional + .ofNullable(() -> ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT)) + .ifFail(x -> + logger.log(Level.WARNING, "Unable to parse the date from git log result for commit.", x)) + .recover(ZonedDateTime::now) + .map(x -> x.withZoneSameInstant(config.getZoneId()).toLocalDateTime()); String messageTitle = (elements.length > MESSAGE_TITLE_INDEX) ? elements[MESSAGE_TITLE_INDEX] : ""; String messageBody = (elements.length > MESSAGE_BODY_INDEX) @@ -114,7 +112,7 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi } if (statLine.isEmpty()) { // empty commit, no files changed - return new CommitResult(author, hash, isMergeCommit, adjustedDate, messageTitle, messageBody, tags); + return new CommitResult(author, hash, isMergeCommit, date.get(), messageTitle, messageBody, tags); } String[] statInfos = statLine.split(NEW_LINE_SPLITTER); @@ -122,7 +120,7 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi Map fileTypeAndContributionMap = getFileTypesAndContribution(fileTypeContributions, config); - return new CommitResult(author, hash, isMergeCommit, adjustedDate, messageTitle, messageBody, tags, + return new CommitResult(author, hash, isMergeCommit, date.get(), messageTitle, messageBody, tags, fileTypeAndContributionMap); } diff --git a/src/main/java/reposense/parser/GroupConfigCsvParser.java b/src/main/java/reposense/parser/GroupConfigCsvParser.java index 4a4509a70b..02aa62d676 100644 --- a/src/main/java/reposense/parser/GroupConfigCsvParser.java +++ b/src/main/java/reposense/parser/GroupConfigCsvParser.java @@ -10,6 +10,7 @@ import reposense.model.GroupConfiguration; import reposense.model.RepoLocation; import reposense.parser.exceptions.InvalidLocationException; +import reposense.util.function.FailableOptional; /** * Container for the values parsed from {@code group-config.csv} file. @@ -57,18 +58,16 @@ protected void processLine(List results, CSVRecord record) t String groupName = get(record, GROUP_NAME_HEADER); List globList = getAsList(record, FILES_GLOB_HEADER); - GroupConfiguration groupConfig = null; - groupConfig = findMatchingGroupConfiguration(results, location); - FileType group = new FileType(groupName, globList); - if (groupConfig.containsGroup(group)) { - logger.warning(String.format( - "Skipping group as %s has already been specified for the repository %s", - group.toString(), groupConfig.getLocation())); - return; - } - groupConfig.addGroup(group); + FailableOptional.of(findMatchingGroupConfiguration(results, location)) + .filter(x -> !x.containsGroup(group)) + .ifAbsent(x -> { + logger.warning(String.format( + "Skipping group as %s has already been specified for the repository %s", + group, x.getLocation())); + }) + .ifPresent(x -> x.addGroup(group)); } /** diff --git a/src/main/java/reposense/util/function/FailableOptional.java b/src/main/java/reposense/util/function/FailableOptional.java index 1521c287ba..dae0392a97 100644 --- a/src/main/java/reposense/util/function/FailableOptional.java +++ b/src/main/java/reposense/util/function/FailableOptional.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.function.Predicate; -import java.util.function.Supplier; /** * An {@code Optional} monad that enables both an empty and a fail option. @@ -11,6 +10,8 @@ * @param Generic Type {@code T}, unbounded to any type. */ public abstract class FailableOptional { + private static final Absent ABSENT = new Absent<>(); + /** * Creates a new {@code FailableOptional} object. {@code FailableOptional} * can contain {@code null}. @@ -23,19 +24,6 @@ public static FailableOptional of(T item) { return new Present<>(item); } - /** - * Creates a new {@code FailableOptional} object. - * This method can allow null values to be stored within the {@code FailableOptional} object. - * - * @param supplier Produces an object of type {@code T}, which can also be {@code null}. - * This {@code Supplier} cannot throw any Exceptions. - * @param Generic type {@code T}. - * @return {@code FailableOptional} object wrapping an object of type {@code T}. - */ - public static FailableOptional of(Supplier supplier) { - return of(supplier.get()); - } - /** * Creates a new {@code FailableOptional} object. * This method can allow null values to be stored within the {@code FailableOptional} object. @@ -68,20 +56,6 @@ public static FailableOptional ofNullable(T item) { return item == null ? ofAbsent() : of(item); } - /** - * Creates a new {@code FailableOptional} object. - * This method converts {@code null} into an empty instance of {@code FailableOptional}. - * - * @param supplier Produces an object of type {@code T}, which can also be {@code null}. - * This {@code Supplier} cannot throw any Exceptions. - * @param Generic type {@code T}. - * @return {@code FailableOptional} object wrapping an object of type {@code T} or an empty - * instance of {@code FailableOptional}. - */ - public static FailableOptional ofNullable(Supplier supplier) { - return ofNullable(supplier.get()); - } - /** * Creates a new {@code FailableOptional} object. * This method converts {@code null} into an empty instance of {@code FailableOptional}. @@ -109,7 +83,10 @@ public static FailableOptional ofNullable(ThrowableS * @return Empty instance of {@code FailableOptional}. */ public static FailableOptional ofAbsent() { - return new Absent<>(); + // safe as we can't do anything with an absent optional + @SuppressWarnings("unchecked") + FailableOptional absent = (FailableOptional) ABSENT; + return absent; } /** @@ -181,11 +158,21 @@ public static FailableOptional ofFailure(Exception e) { * @param consumer {@code ThrowableConsumer} instance that attempts to consume * an object of type {@code T}. * @param Generic Type {@code E} bounded by Exception. - * @return This {@code ThrowableConsumer} instance. + * @return A {@code FailableOptional} object. */ public abstract FailableOptional ifFail( ThrowableConsumer consumer); + + /** + * Recovers from a failed instance of {@code FailableOptional} with another object of type {@code T}. + * + * @param supplier Provides an object of type {@code T} to fail back to. + * @param Generic Type {@code E} bounded by Exception. + * @return A {@code FailableOptional} instance that may be present or failed. + */ + public abstract FailableOptional recover(ThrowableSupplier supplier); + /** * Maps the stored value in this {@code FailableOptional} into another * {@code FailableOptional} containing items of Type {@code U}. @@ -203,6 +190,9 @@ public abstract FailableOptional i public abstract FailableOptional map(ThrowableFunction function); + public abstract FailableOptional nullableMap( + ThrowableFunction function); + /** * Maps the stored value into another new instance of {@code FailableOptional}. Unlike {@code map}, * this method requires that functions itself return the new instance of {@code FailableOptional}. @@ -287,18 +277,35 @@ public abstract FailableOptional flatMap( */ public abstract FailableOptional ifFailOfType(List> exList); - /** - * Attempts to recover a failed instance by providing an item of Type {@code U}, which will be - * wrapped up in another {@code FailableOptional} object. - * - * @param supplier A {@code ThrowableSupplier} instance that provides an item of Type {@code U}, - * which may throw an Exception of Type {@code E}. - * @param Generic Type {@code U} represents the Type of the object returned by the - * {@code ThrowableSupplier}. - * @param Generic Type {@code E} bounded by Exception. - * @return A new {@code FailableOptional} object. - */ - public abstract FailableOptional recover(ThrowableSupplier supplier); + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + // short circuit if the object to check is not a subclass of this class + if (!(obj instanceof FailableOptional)) { + return false; + } + + // dispatch to the relevant equals methods + if (this instanceof Present) { + Present present = (Present) this; + return present.equals(obj); + } + + if (this instanceof Absent) { + Absent absent = (Absent) this; + return absent.equals(obj); + } + + if (this instanceof Fail) { + Fail fail = (Fail) this; + return fail.equals(obj); + } + + return false; + } /** * Represents a {@code ThrowableSupplier} that contains a value. @@ -336,7 +343,7 @@ public FailableOptional ifAbsent(Runnable runner) { @Override public FailableOptional ifAbsent(ThrowableConsumer consumer) { - return null; + return this; } @Override @@ -344,13 +351,29 @@ public FailableOptional ifFail(Runnable runner) { return this; } + @Override public FailableOptional ifFail( ThrowableConsumer consumer) { return this; } + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + return this; + } + @Override public FailableOptional map(ThrowableFunction function) { + try { + return FailableOptional.of(function.apply(this.item)); + } catch (Exception e) { + return FailableOptional.ofFailure(e); + } + } + + @Override + public FailableOptional nullableMap( + ThrowableFunction function) { try { return FailableOptional.ofNullable(function.apply(this.item)); } catch (Exception e) { @@ -408,14 +431,24 @@ public boolean isFail() { } @Override - public final FailableOptional ifFailOfType(List> exList) { + public FailableOptional ifFailOfType(List> exList) { return this; } @Override - public FailableOptional recover(ThrowableSupplier supplier) { - throw new NoSuchElementException(); + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Present) { + Present present = (Present) obj; + return this.item.equals(present.item); + } + + return false; } + } /** @@ -451,29 +484,37 @@ public FailableOptional ifFail(Runnable runner) { return this; } + @Override public FailableOptional ifFail( ThrowableConsumer consumer) { return this; } + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + return this; + } + @Override public FailableOptional map(ThrowableFunction function) { - @SuppressWarnings("unchecked") - FailableOptional failed = (FailableOptional) this; - return failed; + return FailableOptional.ofAbsent(); + } + + @Override + public FailableOptional nullableMap( + ThrowableFunction function) { + return FailableOptional.ofAbsent(); } @Override public FailableOptional flatMap( ThrowableFunction, E> function) { - @SuppressWarnings("unchecked") - FailableOptional failed = (FailableOptional) this; - return failed; + return FailableOptional.ofAbsent(); } @Override public FailableOptional filter(Predicate predicate) { - return this; + return FailableOptional.ofAbsent(); } @Override @@ -510,11 +551,6 @@ public boolean isFail() { public FailableOptional ifFailOfType(List> exList) { return this; } - - @Override - public FailableOptional recover(ThrowableSupplier supplier) { - throw new NoSuchElementException(); - } } /** @@ -569,6 +605,11 @@ public FailableOptional ifFail( return this; } + @Override + public FailableOptional recover(ThrowableSupplier supplier) { + return FailableOptional.of(supplier); + } + @Override public FailableOptional map(ThrowableFunction function) { @SuppressWarnings("unchecked") @@ -576,6 +617,14 @@ public FailableOptional map(ThrowableFunction FailableOptional nullableMap( + ThrowableFunction function) { + @SuppressWarnings("unchecked") + FailableOptional failed = (FailableOptional) this; + return failed; + } + @Override public FailableOptional flatMap( ThrowableFunction, E> function) { @@ -631,8 +680,17 @@ public FailableOptional ifFailOfType(List> exList) } @Override - public FailableOptional recover(ThrowableSupplier supplier) { - return FailableOptional.of(supplier); + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Fail) { + Fail fail = (Fail) obj; + return fail.exception.equals(this.exception); + } + + return false; } } } diff --git a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java index 092a23035f..aa8f5df699 100644 --- a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java +++ b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java @@ -13,7 +13,6 @@ import org.junit.jupiter.api.Test; import reposense.authorship.analyzer.AnnotatorAnalyzer; -import reposense.authorship.model.FileResult; import reposense.model.Author; import reposense.model.AuthorConfiguration; import reposense.model.RepoConfiguration; @@ -66,23 +65,48 @@ public void after() { @Test public void analyzeAnnotation_authorNamePresentInConfig_overrideAuthorship() { config.setAuthorList(new ArrayList<>(Arrays.asList(FAKE_AUTHOR))); - FileResult fileResult = getFileResult("annotationTest.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); + getFileResult("annotationTest.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test public void analyzeAnnotation_authorNameNotInConfigAndNoAuthorConfigFile_acceptTaggedAuthor() { config.setAuthorList(new ArrayList<>(Arrays.asList(FAKE_AUTHOR))); - FileResult fileResult = getFileResult("annotationTest.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); + getFileResult("annotationTest.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test public void analyzeAnnotation_authorNameNotInConfigAndHaveAuthorConfigFile_disownCode() { config.setAuthorList(new ArrayList<>(Arrays.asList(FAKE_AUTHOR))); config.setHasAuthorConfigFile(true); - FileResult fileResult = getFileResult("annotationTest.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_DISOWN_CODE_TEST)); + getFileResult("annotationTest.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_DISOWN_CODE_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); + } @Test diff --git a/src/test/java/reposense/authorship/FileAnalyzerTest.java b/src/test/java/reposense/authorship/FileAnalyzerTest.java index 77c5db5819..a4d0ccb434 100644 --- a/src/test/java/reposense/authorship/FileAnalyzerTest.java +++ b/src/test/java/reposense/authorship/FileAnalyzerTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.Test; import reposense.authorship.model.FileInfo; -import reposense.authorship.model.FileResult; import reposense.git.GitCheckout; import reposense.model.Author; import reposense.model.CommitHash; @@ -88,8 +87,16 @@ public void before() throws Exception { public void blameTest() { config.setSinceDate(BLAME_TEST_SINCE_DATE); config.setUntilDate(BLAME_TEST_UNTIL_DATE); - FileResult fileResult = getFileResult("blameTest.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); + getFileResult("blameTest.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test @@ -102,18 +109,33 @@ public void blameWithPreviousAuthorsTest() { GitCheckout.checkout(config.getRepoRoot(), TEST_REPO_BLAME_WITH_PREVIOUS_AUTHORS_BRANCH); createTestIgnoreRevsFile(AUTHOR_TO_IGNORE_BLAME_COMMIT_LIST_07082021); - FileResult fileResult = getFileResult("blameTest.java"); - removeTestIgnoreRevsFile(); - - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); + getFileResult("blameTest.java") + .ifPresent(x -> { + removeTestIgnoreRevsFile(); + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test public void movedFileBlameTest() { config.setSinceDate(MOVED_FILE_SINCE_DATE); config.setUntilDate(MOVED_FILE_UNTIL_DATE); - FileResult fileResult = getFileResult("newPos/movedFile.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); + getFileResult("newPos/movedFile.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test @@ -121,9 +143,16 @@ public void blameTestDateRange() throws Exception { GitCheckout.checkoutDate(config.getRepoRoot(), config.getBranch(), BLAME_TEST_UNTIL_DATE, config.getZoneId()); config.setSinceDate(BLAME_TEST_SINCE_DATE); config.setUntilDate(BLAME_TEST_UNTIL_DATE); - - FileResult fileResult = getFileResult("blameTest.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); + getFileResult("blameTest.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test @@ -138,10 +167,17 @@ public void blameWithPreviousAuthorsTestDateRange() throws Exception { config.getZoneId()); createTestIgnoreRevsFile(AUTHOR_TO_IGNORE_BLAME_COMMIT_LIST_07082021); - FileResult fileResult = getFileResult("blameTest.java"); - removeTestIgnoreRevsFile(); - - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); + getFileResult("blameTest.java") + .ifPresent(x -> { + removeTestIgnoreRevsFile(); + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test @@ -149,9 +185,16 @@ public void movedFileBlameTestDateRange() throws Exception { GitCheckout.checkoutDate(config.getRepoRoot(), config.getBranch(), MOVED_FILE_UNTIL_DATE, config.getZoneId()); config.setSinceDate(MOVED_FILE_SINCE_DATE); config.setUntilDate(MOVED_FILE_UNTIL_DATE); - - FileResult fileResult = getFileResult("newPos/movedFile.java"); - assertFileAnalysisCorrectness(fileResult, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); + getFileResult("newPos/movedFile.java") + .ifPresent(x -> { + assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); + }) + .ifFail(x -> { + throw x; + }) + .ifAbsent(x -> { + throw new AssertionError(); + }); } @Test @@ -372,7 +415,7 @@ public void analyzeBinaryFile_nonExistingFilePath_success() { new FileInfo("/nonExistingPngPicture.png")); for (FileInfo binaryFileInfo: binaryFileInfos) { - Assertions.assertNull(fileInfoAnalyzer.analyzeBinaryFile(config, binaryFileInfo)); + Assertions.assertTrue(fileInfoAnalyzer.analyzeBinaryFile(config, binaryFileInfo).isAbsent()); } } diff --git a/src/test/java/reposense/authorship/FileResultAggregatorTest.java b/src/test/java/reposense/authorship/FileResultAggregatorTest.java index 282265a6b1..a37ba1ee1f 100644 --- a/src/test/java/reposense/authorship/FileResultAggregatorTest.java +++ b/src/test/java/reposense/authorship/FileResultAggregatorTest.java @@ -4,7 +4,6 @@ import java.time.Month; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -18,6 +17,7 @@ import reposense.model.RepoConfiguration; import reposense.template.GitTestTemplate; import reposense.util.TestUtil; +import reposense.util.function.FailableOptional; public class FileResultAggregatorTest extends GitTestTemplate { @@ -50,9 +50,9 @@ public void aggregateFileResult_clearFileLines_success() { List fileResults = textFileInfos.stream() .filter(f -> !f.getPath().equals("annotationTest.java")) .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo)) - .filter(Objects::nonNull) + .filter(FailableOptional::isPresent) + .map(FailableOptional::get) .collect(Collectors.toList()); - // FileResultAggregator fileResultAggregator = new FileResultAggregator(); fileResultAggregator.aggregateFileResult(fileResults, config.getAuthorList(), config.getAllFileTypes()); diff --git a/src/test/java/reposense/template/GitTestTemplate.java b/src/test/java/reposense/template/GitTestTemplate.java index 1b9b853588..de8103fdde 100644 --- a/src/test/java/reposense/template/GitTestTemplate.java +++ b/src/test/java/reposense/template/GitTestTemplate.java @@ -30,6 +30,7 @@ import reposense.model.RepoLocation; import reposense.util.FileUtil; import reposense.util.TestRepoCloner; +import reposense.util.function.FailableOptional; /** * Contains templates for git testing. @@ -188,7 +189,7 @@ public void assertFileAnalysisCorrectness(FileResult fileResult, List ex } } - public FileResult getFileResult(String relativePath) { + public FailableOptional getFileResult(String relativePath) { FileInfo fileInfo = fileInfoExtractor.generateFileInfo(configs.get(), relativePath); return fileInfoAnalyzer.analyzeTextFile(configs.get(), fileInfo); } From 2e1b867c20225a50a49c22dcbe60b5822d6859e9 Mon Sep 17 00:00:00 2001 From: George Tay Date: Sat, 9 Mar 2024 14:50:15 +0800 Subject: [PATCH 05/11] Remove faulty method --- .../authorship/FileInfoAnalyzer.java | 14 +++-- .../parser/GroupConfigCsvParser.java | 7 +-- .../util/function/FailableOptional.java | 53 +++++++++---------- .../authorship/AnnotatorAnalyzerTest.java | 6 +-- .../authorship/FileAnalyzerTest.java | 12 ++--- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index fe27421791..78e446a7d2 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -48,10 +48,12 @@ public class FileInfoAnalyzer { * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ public FailableOptional analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { + String relativePath = fileInfo.getPath(); + // note that the predicates in filter() test for the negation of the previous failure conditions - return FailableOptional.ofNullable(fileInfo.getPath()) + return FailableOptional.ofNullable(relativePath) .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x))) - .ifAbsent(x -> logger.severe(String.format(MESSAGE_FILE_MISSING, x))) + .ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath))) .filter(x -> !FileUtil.isEmptyFile(config.getRepoRoot(), x)) .ifPresent(x -> { aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo); @@ -70,9 +72,11 @@ public FailableOptional analyzeTextFile(RepoConfiguration config, Fi * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ public FailableOptional analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { - return FailableOptional.ofNullable(fileInfo.getPath()) - .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x))) - .ifAbsent(x -> logger.severe(String.format(MESSAGE_FILE_MISSING, x))) + String relativePath = fileInfo.getPath(); + + return FailableOptional.ofNullable(relativePath) + .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), relativePath))) + .ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath))) .ifPresent(x -> fileInfo.setFileType(config.getFileType(fileInfo.getPath()))) .flatMap(x -> generateBinaryFileResult(config, fileInfo)); } diff --git a/src/main/java/reposense/parser/GroupConfigCsvParser.java b/src/main/java/reposense/parser/GroupConfigCsvParser.java index 02aa62d676..f41b99c432 100644 --- a/src/main/java/reposense/parser/GroupConfigCsvParser.java +++ b/src/main/java/reposense/parser/GroupConfigCsvParser.java @@ -59,13 +59,14 @@ protected void processLine(List results, CSVRecord record) t List globList = getAsList(record, FILES_GLOB_HEADER); FileType group = new FileType(groupName, globList); + GroupConfiguration groupConfig = findMatchingGroupConfiguration(results, location); - FailableOptional.of(findMatchingGroupConfiguration(results, location)) + FailableOptional.of(groupConfig) .filter(x -> !x.containsGroup(group)) - .ifAbsent(x -> { + .ifAbsent(() -> { logger.warning(String.format( "Skipping group as %s has already been specified for the repository %s", - group, x.getLocation())); + group, groupConfig.getLocation())); }) .ifPresent(x -> x.addGroup(group)); } diff --git a/src/main/java/reposense/util/function/FailableOptional.java b/src/main/java/reposense/util/function/FailableOptional.java index dae0392a97..5f93fdb615 100644 --- a/src/main/java/reposense/util/function/FailableOptional.java +++ b/src/main/java/reposense/util/function/FailableOptional.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.function.Predicate; /** @@ -130,17 +131,6 @@ public static FailableOptional ofFailure(Exception e) { */ public abstract FailableOptional ifAbsent(Runnable runner); - /** - * Executes the provided {@code ThrowableConsumer} if this current instance of - * {@code FailableOptional} does not contains a value. - * - * @param consumer {@code ThrowableConsumer} instance that attempts to consume - * an object of type {@code T}. - * @param Generic Type {@code E} bounded by Exception. - * @return This {@code ThrowableConsumer} instance. - */ - public abstract FailableOptional ifAbsent(ThrowableConsumer consumer); - /** * Executes the provided {@code Runner} if this current instance of * {@code FailableOptional} has failed. @@ -163,7 +153,6 @@ public static FailableOptional ofFailure(Exception e) { public abstract FailableOptional ifFail( ThrowableConsumer consumer); - /** * Recovers from a failed instance of {@code FailableOptional} with another object of type {@code T}. * @@ -190,6 +179,21 @@ public abstract FailableOptional i public abstract FailableOptional map(ThrowableFunction function); + /** + * Maps the stored value in this {@code FailableOptional} into another + * {@code FailableOptional} containing items of Type {@code U}. Note that this method + * convert nulls into empty {@code FailableOptional}. + * If this {@code FailableOptional} contains nothing or has failed, + * this instance is returned as is. + * + * @param function {@code ThrowableFunction} that takes an object of Type {@code T} + * and returns Type {@code U} and might throw + * Exception of Type {@code E}. + * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. + * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. + * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance + * of {@code FailableOptional}. + */ public abstract FailableOptional nullableMap( ThrowableFunction function); @@ -341,11 +345,6 @@ public FailableOptional ifAbsent(Runnable runner) { return this; } - @Override - public FailableOptional ifAbsent(ThrowableConsumer consumer) { - return this; - } - @Override public FailableOptional ifFail(Runnable runner) { return this; @@ -452,7 +451,7 @@ public boolean equals(Object obj) { } /** - * Represents a {@code ThrowableSupplier} that does not contain any value. + * Represents a {@code FailableOptional} that does not contain any value. * * @param Generic Type {@code T}. */ @@ -474,11 +473,6 @@ public FailableOptional ifAbsent(Runnable runner) { return this; } - @Override - public FailableOptional ifAbsent(ThrowableConsumer consumer) { - return this; - } - @Override public FailableOptional ifFail(Runnable runner) { return this; @@ -551,10 +545,15 @@ public boolean isFail() { public FailableOptional ifFailOfType(List> exList) { return this; } + + @Override + public boolean equals(Object obj) { + return this == obj; + } } /** - * Represents a {@code ThrowableSupplier} that has failed. + * Represents a {@code FailableOptional} that has failed. * * @param Generic Type {@code T}. */ @@ -562,6 +561,7 @@ private static class Fail extends FailableOptional { private final Exception exception; private Fail(Exception e) { + Objects.requireNonNull(e); this.exception = e; } @@ -580,11 +580,6 @@ public FailableOptional ifAbsent(Runnable runner) { return this; } - @Override - public FailableOptional ifAbsent(ThrowableConsumer consumer) { - return this; - } - @Override public FailableOptional ifFail(Runnable runner) { runner.run(); diff --git a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java index aa8f5df699..564ae2bf0c 100644 --- a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java +++ b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java @@ -72,7 +72,7 @@ public void analyzeAnnotation_authorNamePresentInConfig_overrideAuthorship() { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -87,7 +87,7 @@ public void analyzeAnnotation_authorNameNotInConfigAndNoAuthorConfigFile_acceptT .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -103,7 +103,7 @@ public void analyzeAnnotation_authorNameNotInConfigAndHaveAuthorConfigFile_disow .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); diff --git a/src/test/java/reposense/authorship/FileAnalyzerTest.java b/src/test/java/reposense/authorship/FileAnalyzerTest.java index a4d0ccb434..bed1ddeaad 100644 --- a/src/test/java/reposense/authorship/FileAnalyzerTest.java +++ b/src/test/java/reposense/authorship/FileAnalyzerTest.java @@ -94,7 +94,7 @@ public void blameTest() { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -117,7 +117,7 @@ public void blameWithPreviousAuthorsTest() { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -133,7 +133,7 @@ public void movedFileBlameTest() { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -150,7 +150,7 @@ public void blameTestDateRange() throws Exception { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -175,7 +175,7 @@ public void blameWithPreviousAuthorsTestDateRange() throws Exception { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } @@ -192,7 +192,7 @@ public void movedFileBlameTestDateRange() throws Exception { .ifFail(x -> { throw x; }) - .ifAbsent(x -> { + .ifAbsent(() -> { throw new AssertionError(); }); } From fb210491b70389608c70fa61729ff42d4407138f Mon Sep 17 00:00:00 2001 From: George Tay Date: Sat, 9 Mar 2024 14:50:35 +0800 Subject: [PATCH 06/11] Implement test cases for FailableOptional --- .../reposense/util/FailableOptionalTest.java | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 src/test/java/reposense/util/FailableOptionalTest.java diff --git a/src/test/java/reposense/util/FailableOptionalTest.java b/src/test/java/reposense/util/FailableOptionalTest.java new file mode 100644 index 0000000000..4b55a06058 --- /dev/null +++ b/src/test/java/reposense/util/FailableOptionalTest.java @@ -0,0 +1,289 @@ +package reposense.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import reposense.util.function.FailableOptional; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +public class FailableOptionalTest { + private static final FailableOptional PRESENT = FailableOptional.of(123); + private static final FailableOptional ABSENT = FailableOptional.ofAbsent(); + private static final FailableOptional FAIL = FailableOptional.ofFailure(new Exception()); + + @Test + public void isPresent_testAll_success() { + Assertions.assertTrue(PRESENT.isPresent()); + Assertions.assertFalse(ABSENT.isPresent()); + Assertions.assertFalse(FAIL.isPresent()); + } + + @Test + public void isAbsent_testAll_success() { + Assertions.assertFalse(PRESENT.isAbsent()); + Assertions.assertTrue(ABSENT.isAbsent()); + Assertions.assertFalse(FAIL.isAbsent()); + } + + @Test + public void isFail_testAll_success() { + Assertions.assertFalse(PRESENT.isFail()); + Assertions.assertFalse(ABSENT.isFail()); + Assertions.assertTrue(FAIL.isFail()); + } + + @Test + public void of_nullItem_success() { + Assertions.assertNull(FailableOptional.of(null).get()); + } + + @Test + public void of_nonNullItem_success() { + Assertions.assertEquals(FailableOptional.of("hello world").get(), "hello world"); + } + + @Test + public void of_nullItemSupplier_success() { + Assertions.assertNull(FailableOptional.of(() -> null).get()); + } + + @Test + public void of_nonNullItemSupplier_success() { + Assertions.assertEquals(FailableOptional.of(() -> 1234).get(), 1234); + } + + @Test + public void of_thrownException_becomesFailure() { + Assertions.assertTrue(FailableOptional.of(() -> { + throw new Exception(); + }).isFail()); + } + + @Test + public void ofNullable_nullItem_success() { + Assertions.assertEquals(FailableOptional.ofNullable(null), FailableOptional.ofAbsent()); + } + + @Test + public void ofNullable_nonNullItem_success() { + Assertions.assertEquals(FailableOptional.ofNullable("hello world").get(), "hello world"); + } + + @Test + public void ofNullable_nullItemSupplier_success() { + Assertions.assertEquals(FailableOptional.ofNullable(() -> null), FailableOptional.ofAbsent()); + } + + @Test + public void ofNullable_nonNullItemSupplier_success() { + Assertions.assertEquals(FailableOptional.ofNullable(() -> 1234).get(), 1234); + } + + @Test + public void ofNullable_thrownException_becomesFailure() { + Assertions.assertTrue(FailableOptional.ofNullable(() -> { + throw new Exception(); + }).isFail()); + } + + @Test + public void ofAbsent_returnsSameInstance_success() { + Assertions.assertSame(FailableOptional.ofAbsent(), FailableOptional.ofAbsent()); + } + + @Test + public void ofAbsent_noStoredValueCheck_success() { + Assertions.assertThrows(NoSuchElementException.class, () -> FailableOptional.ofAbsent().get()); + } + + @Test + public void ofFailure_invalidFailureNull_throwsException() { + Assertions.assertThrows(NullPointerException.class, () -> FailableOptional.ofFailure(null)); + } + + @Test + public void ofFailure_validFailureException_success() { + Assertions.assertDoesNotThrow(() -> FailableOptional.ofFailure(new Exception("should not be thrown"))); + } + + @Test + public void ifPresent_testAll_success() { + int[] testArray = {-1, -1, -1}; + + PRESENT.ifPresent(() -> testArray[0] = 1); + ABSENT.ifPresent(() -> testArray[1] = 1); + FAIL.ifPresent(() -> testArray[2] = 1); + + Assertions.assertArrayEquals(testArray, new int[]{1, -1, -1}); + } + + @Test + public void ifPresent_throwingConsumer_success() { + FailableOptional presentThrown = PRESENT.ifPresent(x -> { + throw new Exception(); + }); + FailableOptional absentThrown = ABSENT.ifPresent(x -> { + throw new Exception(); + }); + FailableOptional failThrown = FAIL.ifPresent(x -> { + throw new Exception(); + }); + + Assertions.assertTrue(presentThrown.isFail()); + Assertions.assertTrue(absentThrown.isAbsent()); + Assertions.assertTrue(failThrown.isFail()); + } + + @Test + public void ifAbsent_testAll_success() { + int[] testArray = {-1, -1, -1}; + + PRESENT.ifAbsent(() -> testArray[0] = 1); + ABSENT.ifAbsent(() -> testArray[1] = 1); + FAIL.ifAbsent(() -> testArray[2] = 1); + + System.out.println(Arrays.toString(testArray)); + + Assertions.assertArrayEquals(testArray, new int[]{-1, 1, -1}); + } + + @Test + public void ifFail_testAll_success() { + int[] testArray = {-1, -1, -1}; + + PRESENT.ifFail(x -> testArray[0] = 1); + ABSENT.ifFail(x -> testArray[1] = 1); + FAIL.ifFail(x -> testArray[2] = 1); + + Assertions.assertArrayEquals(testArray, new int[]{-1, -1, 1}); + } + + @Test + public void ifFail_throwingConsumer_success() { + FailableOptional presentThrown = PRESENT.ifFail(x -> { + throw new Exception(); + }); + FailableOptional absentThrown = ABSENT.ifFail(x -> { + throw new Exception(); + }); + FailableOptional failThrown = FAIL.ifFail(x -> { + throw new Exception(); + }); + + Assertions.assertTrue(presentThrown.isPresent()); + Assertions.assertTrue(absentThrown.isAbsent()); + Assertions.assertTrue(failThrown.isFail()); + } + + @Test + public void recover_presentFailableOptional_success() { + FailableOptional recovered = PRESENT.recover(() -> 1); + Assertions.assertEquals(recovered, PRESENT); + } + + @Test + public void recover_absentFailableOptional_success() { + FailableOptional recovered = ABSENT.recover(() -> 2); + Assertions.assertEquals(recovered, ABSENT); + } + + @Test + public void recover_failFailableOptional_success() { + FailableOptional recovered = FAIL.recover(() -> 9999); + Assertions.assertTrue(recovered.isPresent()); + Assertions.assertEquals(recovered.get(), 9999); + } + + @Test + public void map_testAll_success() { + Assertions.assertEquals(PRESENT.map(x -> "String").get(), "String"); + Assertions.assertSame(ABSENT.map(x -> "String"), ABSENT); + Assertions.assertSame(FAIL.map(x -> "String"), FAIL); + } + + @Test + public void nullableMap_testAll_success() { + Assertions.assertSame(PRESENT.nullableMap(x -> null), ABSENT); + Assertions.assertSame(ABSENT.nullableMap(x -> null), ABSENT); + Assertions.assertSame(FAIL.nullableMap(x -> null), FAIL); + } + + @Test + public void flatMap_testAll_success() { + Assertions.assertEquals(PRESENT.flatMap(x -> FailableOptional.of("String")).get(), "String"); + Assertions.assertSame(ABSENT.flatMap(x -> FailableOptional.of("String")), ABSENT); + Assertions.assertSame(FAIL.flatMap(x -> FailableOptional.of("String")), FAIL); + } + + @Test + public void filter_presentFailableOptional_passPredicate() { + Assertions.assertSame(PRESENT.filter(x -> x > 0), PRESENT); + } + + @Test + public void filter_absentFailableOptional_success() { + Assertions.assertSame(ABSENT.filter(x -> x < 0), ABSENT); + } + + @Test + public void filter_failedFailableOptional_success() { + Assertions.assertSame(FAIL.filter(x -> x < 0), FAIL); + } + + @Test + public void get_testAll_success() { + Assertions.assertEquals(PRESENT.get(), 123); + Assertions.assertThrows(NoSuchElementException.class, ABSENT::get); + Assertions.assertThrows(NoSuchElementException.class, FAIL::get); + } + + @Test + public void orElse_testAll_success() { + Assertions.assertEquals(PRESENT.orElse(999), 123); + Assertions.assertEquals(ABSENT.orElse(999), 999); + Assertions.assertEquals(FAIL.orElse(999), 999); + } + + @Test + public void orElseThrow_testAll_success() { + Assertions.assertEquals(PRESENT.orElse(999), 123); + Assertions.assertThrows(Exception.class, () -> ABSENT.orElseThrow(new Exception())); + Assertions.assertThrows(Exception.class, () -> FAIL.orElseThrow(new Exception())); + } + + @Test + public void ifFailOfType_testAll_failure() { + Assertions.assertSame(PRESENT.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), + PRESENT); + Assertions.assertSame(ABSENT.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), + ABSENT); + Assertions.assertSame(FAIL.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), FAIL); + Assertions.assertSame(FAIL.ifFailOfType(Arrays.asList(IllegalArgumentException.class)), ABSENT); + } + + @Test + public void equals_presentFailableOptional_multipleTests() { + Assertions.assertSame(PRESENT, PRESENT); + Assertions.assertEquals(PRESENT, PRESENT); + Assertions.assertNotEquals(PRESENT, ABSENT); + Assertions.assertNotEquals(PRESENT, FAIL); + Assertions.assertNotEquals(PRESENT, FailableOptional.of("String")); + } + + @Test + public void equals_absentFailableOptional_multipleTests() { + Assertions.assertSame(ABSENT, ABSENT); + Assertions.assertEquals(ABSENT, ABSENT); + Assertions.assertNotEquals(ABSENT, PRESENT); + Assertions.assertNotEquals(ABSENT, FAIL); + } + + @Test + public void equals_failedFailableOptional_multipleTests() { + Assertions.assertSame(FAIL, FAIL); + Assertions.assertEquals(FAIL, FAIL); + Assertions.assertNotEquals(FAIL, PRESENT); + Assertions.assertNotEquals(FAIL, ABSENT); + } +} \ No newline at end of file From 6d39525be4df547406ae5098cc371da0f32b1cf6 Mon Sep 17 00:00:00 2001 From: George Tay Date: Sat, 9 Mar 2024 14:53:08 +0800 Subject: [PATCH 07/11] Fix checkstyle errors --- src/test/java/reposense/util/FailableOptionalTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/reposense/util/FailableOptionalTest.java b/src/test/java/reposense/util/FailableOptionalTest.java index 4b55a06058..85984689d8 100644 --- a/src/test/java/reposense/util/FailableOptionalTest.java +++ b/src/test/java/reposense/util/FailableOptionalTest.java @@ -1,11 +1,12 @@ package reposense.util; +import java.util.Arrays; +import java.util.NoSuchElementException; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import reposense.util.function.FailableOptional; -import java.util.Arrays; -import java.util.NoSuchElementException; +import reposense.util.function.FailableOptional; public class FailableOptionalTest { private static final FailableOptional PRESENT = FailableOptional.of(123); @@ -286,4 +287,4 @@ public void equals_failedFailableOptional_multipleTests() { Assertions.assertNotEquals(FAIL, PRESENT); Assertions.assertNotEquals(FAIL, ABSENT); } -} \ No newline at end of file +} From 925cdee44facd48d4f98e66f7d5e883818ce2f4a Mon Sep 17 00:00:00 2001 From: George Tay Date: Thu, 21 Mar 2024 23:01:40 +0800 Subject: [PATCH 08/11] Implement Failable Monad --- .../authorship/AuthorshipReporter.java | 10 +- .../authorship/FileInfoAnalyzer.java | 30 +- .../reposense/commits/CommitInfoAnalyzer.java | 11 +- .../model/ConfigRunConfiguration.java | 13 +- .../parser/GroupConfigCsvParser.java | 4 +- .../util/function/CannotFailException.java | 7 + .../reposense/util/function/Failable.java | 695 ++++++++++++++++++ .../util/function/FailableOptional.java | 691 ----------------- .../util/function/ThrowableConsumer.java | 2 +- .../util/function/ThrowableFunction.java | 2 +- .../util/function/ThrowableSupplier.java | 2 +- .../authorship/AnnotatorAnalyzerTest.java | 6 +- .../authorship/FileAnalyzerTest.java | 12 +- .../authorship/FileResultAggregatorTest.java | 6 +- .../reposense/template/GitTestTemplate.java | 5 +- .../java/reposense/util/FailableTest.java | 293 ++++++++ 16 files changed, 1048 insertions(+), 741 deletions(-) create mode 100644 src/main/java/reposense/util/function/CannotFailException.java create mode 100644 src/main/java/reposense/util/function/Failable.java delete mode 100644 src/main/java/reposense/util/function/FailableOptional.java create mode 100644 src/test/java/reposense/util/FailableTest.java diff --git a/src/main/java/reposense/authorship/AuthorshipReporter.java b/src/main/java/reposense/authorship/AuthorshipReporter.java index bf4c00b4ec..ba27a1e89a 100644 --- a/src/main/java/reposense/authorship/AuthorshipReporter.java +++ b/src/main/java/reposense/authorship/AuthorshipReporter.java @@ -9,7 +9,7 @@ import reposense.authorship.model.FileResult; import reposense.model.RepoConfiguration; import reposense.system.LogsManager; -import reposense.util.function.FailableOptional; +import reposense.util.function.Failable; /** @@ -47,16 +47,16 @@ public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config) { List fileResults = textFileInfos.stream() .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo)) - .filter(FailableOptional::isPresent) - .map(FailableOptional::get) + .filter(Failable::isPresent) + .map(Failable::get) .collect(Collectors.toList()); List binaryFileInfos = fileInfoExtractor.extractBinaryFileInfos(config); List binaryFileResults = binaryFileInfos.stream() .map(fileInfo -> fileInfoAnalyzer.analyzeBinaryFile(config, fileInfo)) - .filter(FailableOptional::isPresent) - .map(FailableOptional::get) + .filter(Failable::isPresent) + .map(Failable::get) .collect(Collectors.toList()); fileResults.addAll(binaryFileResults); diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index 78e446a7d2..f657cf43be 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -21,7 +21,8 @@ import reposense.model.RepoConfiguration; import reposense.system.LogsManager; import reposense.util.FileUtil; -import reposense.util.function.FailableOptional; +import reposense.util.function.CannotFailException; +import reposense.util.function.Failable; /** * Analyzes the target and information given in the {@link FileInfo}. @@ -44,14 +45,14 @@ public class FileInfoAnalyzer { /** * Analyzes the lines of the file, given in the {@code fileInfo}, that has changed in the time period provided * by {@code config}. - * Returns empty {@code FailableOptional} if the file is missing from the local system, or none of the - * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. + * Returns empty {@code Failable} if the file is missing from the local system, + * or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public FailableOptional analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { + public Failable analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { String relativePath = fileInfo.getPath(); // note that the predicates in filter() test for the negation of the previous failure conditions - return FailableOptional.ofNullable(relativePath) + return Failable.ofNullable(() -> relativePath) .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), x))) .ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath))) .filter(x -> !FileUtil.isEmptyFile(config.getRepoRoot(), x)) @@ -68,13 +69,13 @@ public FailableOptional analyzeTextFile(RepoConfiguration config, Fi /** * Analyzes the binary file, given in the {@code fileInfo}, that has changed in the time period provided * by {@code config}. - * Returns empty {@code FailableOptional} if the file is missing from the local system, or none of the - * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. + * Returns empty {@code Failable} if the file is missing from the local system, + * or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public FailableOptional analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { + public Failable analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { String relativePath = fileInfo.getPath(); - return FailableOptional.ofNullable(relativePath) + return Failable.ofNullable(() -> relativePath) .filter(x -> Files.exists(Paths.get(config.getRepoRoot(), relativePath))) .ifAbsent(() -> logger.severe(String.format(MESSAGE_FILE_MISSING, relativePath))) .ifPresent(x -> fileInfo.setFileType(config.getFileType(fileInfo.getPath()))) @@ -99,14 +100,15 @@ private FileResult generateTextFileResult(FileInfo fileInfo) { /** * Generates and returns a {@link FileResult} with the authorship results from binary {@code fileInfo} consolidated. * Authorship results are indicated in the {@code authorContributionMap} as contributions with zero line counts. - * Returns an empty {@code FailableOptional} if none of the {@link Author} specified in + * Returns an empty {@code Failable} if none of the {@link Author} specified in * {@code config} contributed to the file in {@code fileInfo}. */ - private FailableOptional generateBinaryFileResult(RepoConfiguration config, FileInfo fileInfo) { + private Failable generateBinaryFileResult( + RepoConfiguration config, FileInfo fileInfo) { Set authors = new HashSet<>(); HashMap authorContributionMap = new HashMap<>(); - return FailableOptional.ofNullable(GitLog.getFileAuthors(config, fileInfo.getPath())) + return Failable.ofNullable(() -> GitLog.getFileAuthors(config, fileInfo.getPath())) .filter(x -> !x.isEmpty()) .ifPresent(x -> { for (String[] lineDetails : x) { @@ -149,14 +151,14 @@ private void aggregateBlameAuthorModifiedAndDateInfo(RepoConfiguration config, F String authorName = blameResultLines[lineCount + 1].substring(AUTHOR_NAME_OFFSET); String authorEmail = blameResultLines[lineCount + 2] .substring(AUTHOR_EMAIL_OFFSET).replaceAll("<|>", ""); - Long commitDateInMs = Long.parseLong(blameResultLines[lineCount + 3].substring(AUTHOR_TIME_OFFSET)) * 1000; + long commitDateInMs = Long.parseLong(blameResultLines[lineCount + 3].substring(AUTHOR_TIME_OFFSET)) * 1000; LocalDateTime commitDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(commitDateInMs), config.getZoneId()); Author author = config.getAuthor(authorName, authorEmail); if (!fileInfo.isFileLineTracked(lineCount / 5) || author.isIgnoringFile(filePath) || CommitHash.isInsideCommitList(commitHash, config.getIgnoreCommitList()) - || commitDate.compareTo(sinceDate) < 0 || commitDate.compareTo(untilDate) > 0) { + || commitDate.isBefore(sinceDate) || commitDate.isAfter(untilDate)) { author = Author.UNKNOWN_AUTHOR; } diff --git a/src/main/java/reposense/commits/CommitInfoAnalyzer.java b/src/main/java/reposense/commits/CommitInfoAnalyzer.java index f1f56421a6..1ee000029d 100644 --- a/src/main/java/reposense/commits/CommitInfoAnalyzer.java +++ b/src/main/java/reposense/commits/CommitInfoAnalyzer.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -24,7 +25,7 @@ import reposense.model.FileType; import reposense.model.RepoConfiguration; import reposense.system.LogsManager; -import reposense.util.function.FailableOptional; +import reposense.util.function.Failable; /** * Analyzes commit information found in the git log. @@ -90,11 +91,13 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi Author author = config.getAuthor(elements[AUTHOR_INDEX], elements[EMAIL_INDEX]); // safe map since ZonedDateTime::now returns non-null - FailableOptional date = FailableOptional + Failable date = Failable .ofNullable(() -> ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT)) - .ifFail(x -> + .ifFailed(x -> logger.log(Level.WARNING, "Unable to parse the date from git log result for commit.", x)) - .recover(ZonedDateTime::now) + .resolve(x -> { + assert x instanceof DateTimeParseException; + }, ZonedDateTime.now()) .map(x -> x.withZoneSameInstant(config.getZoneId()).toLocalDateTime()); String messageTitle = (elements.length > MESSAGE_TITLE_INDEX) ? elements[MESSAGE_TITLE_INDEX] : ""; diff --git a/src/main/java/reposense/model/ConfigRunConfiguration.java b/src/main/java/reposense/model/ConfigRunConfiguration.java index 4026c69cbb..2274014437 100644 --- a/src/main/java/reposense/model/ConfigRunConfiguration.java +++ b/src/main/java/reposense/model/ConfigRunConfiguration.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.nio.file.Files; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -14,7 +13,7 @@ import reposense.parser.exceptions.InvalidCsvException; import reposense.parser.exceptions.InvalidHeaderException; import reposense.system.LogsManager; -import reposense.util.function.FailableOptional; +import reposense.util.function.Failable; /** * Represents RepoSense run configured by config files. @@ -42,22 +41,20 @@ public List getRepoConfigurations() List repoConfigs = new RepoConfigCsvParser(cliArguments.getRepoConfigFilePath()).parse(); // parse the author config file path - FailableOptional.of(cliArguments.getAuthorConfigFilePath()) + Failable.of(cliArguments::getAuthorConfigFilePath) .filter(Files::exists) .map(x -> new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse()) .ifPresent(x -> RepoConfiguration.merge(repoConfigs, x)) .ifPresent(() -> RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true)) - .ifFailOfType(Arrays.asList(IOException.class, InvalidCsvException.class)) - .ifFail(x -> logger.log(Level.WARNING, x.getMessage(), x)) + .ifFailed(x -> logger.log(Level.WARNING, x.getMessage(), x)) .orElse(Collections.emptyList()); // parse the group config file path - FailableOptional.of(cliArguments.getGroupConfigFilePath()) + Failable.of(cliArguments::getGroupConfigFilePath) .filter(Files::exists) .map(x -> new GroupConfigCsvParser(x).parse()) .ifPresent(x -> RepoConfiguration.setGroupConfigsToRepos(repoConfigs, x)) - .ifFailOfType(Arrays.asList(IOException.class, InvalidCsvException.class)) - .ifFail(x -> logger.log(Level.WARNING, x.getMessage(), x)) + .ifFailed(x -> logger.log(Level.WARNING, x.getMessage(), x)) .orElse(Collections.emptyList()); return repoConfigs; diff --git a/src/main/java/reposense/parser/GroupConfigCsvParser.java b/src/main/java/reposense/parser/GroupConfigCsvParser.java index f41b99c432..cdb77dc752 100644 --- a/src/main/java/reposense/parser/GroupConfigCsvParser.java +++ b/src/main/java/reposense/parser/GroupConfigCsvParser.java @@ -10,7 +10,7 @@ import reposense.model.GroupConfiguration; import reposense.model.RepoLocation; import reposense.parser.exceptions.InvalidLocationException; -import reposense.util.function.FailableOptional; +import reposense.util.function.Failable; /** * Container for the values parsed from {@code group-config.csv} file. @@ -61,7 +61,7 @@ protected void processLine(List results, CSVRecord record) t FileType group = new FileType(groupName, globList); GroupConfiguration groupConfig = findMatchingGroupConfiguration(results, location); - FailableOptional.of(groupConfig) + Failable.success(groupConfig) .filter(x -> !x.containsGroup(group)) .ifAbsent(() -> { logger.warning(String.format( diff --git a/src/main/java/reposense/util/function/CannotFailException.java b/src/main/java/reposense/util/function/CannotFailException.java new file mode 100644 index 0000000000..fc72e855a5 --- /dev/null +++ b/src/main/java/reposense/util/function/CannotFailException.java @@ -0,0 +1,7 @@ +package reposense.util.function; + +/** + * Represents the error class for a {@code Either} or {@code Failable} that cannot fail. + */ +public class CannotFailException extends RuntimeException { +} diff --git a/src/main/java/reposense/util/function/Failable.java b/src/main/java/reposense/util/function/Failable.java new file mode 100644 index 0000000000..da035695c8 --- /dev/null +++ b/src/main/java/reposense/util/function/Failable.java @@ -0,0 +1,695 @@ +package reposense.util.function; + +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Represents a task type that encapsulates information about running a task with the possibility of + * throwing exception. + * + * @param Generic input type {@code T} + * @param Generic exception type {@code E} + */ +public abstract class Failable { + // Empty instance + private static final Failable EMPTY = new Empty<>(); + + /** + * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} + * and produces an item of type {@code T}. If it fails, then a failed instance of + * {@code Failable} is returned. This method allows {@code null} to be stored within + * it. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @param supplier Produces objects of type {@code T}. + * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} + * runs without failure or a failed instance of {@code Failable}. + */ + public static Failable of( + ThrowableSupplier supplier) { + try { + return Failable.success(supplier.produce()); + } catch (Throwable throwable) { + return Failable.ifFailElseThrow(throwable); + } + } + + /** + * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} + * and produces an item of type {@code T}. If it fails, then a failed instance of + * {@code Failable} is returned. This method converts {@code null} objects + * into empty instances of {@code Failable}. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @param supplier Produces objects of type {@code T}. + * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} + * runs without failure, or empty instance of {@code Failable} if {@code null} + * is produced, or a failed instance of {@code Failable}. + */ + public static Failable ofNullable( + ThrowableSupplier supplier) { + try { + T returns = supplier.produce(); + + if (returns == null) { + return Failable.empty(); + } + + return Failable.success(returns); + } catch (Throwable throwable) { + return Failable.ifFailElseThrow(throwable); + } + } + + /** + * Creates a successful instance of {@code Failable}. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @param item Item of type {@code T} to store. + * @return Successful instance of {@code Failable}. + */ + public static Failable success(T item) { + // can just cast as no exception thrown + @SuppressWarnings("unchecked") + Failable succ = (Failable) new Success<>(item); + return succ; + } + + /** + * Creates an empty instance of {@code Failable}. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @return Empty instance of {@code Failable}. + */ + public static Failable empty() { + // safe as empty contains nothing, and no monadic actions will cause it to turn into anything else + @SuppressWarnings("unchecked") + Failable failed = (Failable) Failable.EMPTY; + return failed; + } + + /** + * Creates a failed instance of {@code Failable}. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @param throwable Object of type {@code E} that represents the thrown exception this + * {@code Failable} encapsulates. + * @return Failed instance of {@code Failable}. + */ + public static Failable fail(E throwable) { + return new Fail<>(throwable); + } + + /** + * Checks if the thrown exception is of the correct generic type using Java Reflections. + * Experimental method that requires deeper inspection to ensure that it is correct; + * can be replaced by an unsafe cast if necessary. + * + * @param throwable {@code Throwable} object to check the type of. + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + * @return {@code Failable} instance if the exception type is verified. + * @throws ClassCastException if the function throws an exception that is not of the expected + * {@code Throwable} type. + */ + private static Failable ifFailElseThrow(Throwable throwable) { + if (new Fail<>(throwable).getExceptionClass().isInstance(throwable)) { + @SuppressWarnings("unchecked") + Failable failed = (Failable) new Fail<>(throwable); + return failed; + } + + throw new ClassCastException("Exception class does not match specifications"); + } + + /** + * Changes the {@code Throwable} exception type to a new type. + * + * @param exception {@code Throwable} type {@code Z} to change to. + * @param Generic type {@code Z} bounded by {@code Throwable}. + * @return {@code Failable} with new exception type {@code Z}. + */ + public abstract Failable failWith(Z exception); + + /** + * Returns the item stored in this {@code Failable} instance. + * + * @return Item of type {@code T}, or throws an exception if there is no item stored. + */ + public abstract T get(); + + /** + * Returns the item stored in this {@code Failable} instance, or the input item if this + * instance does not contain any items. + * + * @param item Item of type {@code T} to return if this instance has failed or is empty. + * @return This instance's item of type {@code T}, or the input item of type {@code T}. + */ + public abstract T orElse(T item); + + /** + * Tests the item stored in this instance against some {@code Predicate}. + * + * @param predicate {@code Predicate} to test this instance's item against. + * @return This instance if the predicate test passses, else an empty {@code Failable} instance. + */ + public abstract Failable filter(Predicate predicate); + + /** + * Maps this instance's item to a new item of type {@code U}. The mapping function cannot fail + * (throw exceptions). + * + * @param function {@code Function} that accepts an object of type {@code T} and returns a new + * object of type {@code U}. + * @param Generic type {@code U}. + * @return {@code Failable} instance + */ + public abstract Failable unfailableMap(Function function); + + /** + * Maps this instance's item to a new item of type {@code U}. The mapping function may fail + * and throw an exception. + * + * @param throwableFunction {@code Function} that accepts an object of type {@code T} and returns a new + * object of type {@code U}. This function may fail and throw an exception. + * @param Generic type {@code Z} bounded by {@code Throwable}. + * @param Generic type {@code U}. + * @return {@code Failable} instance + */ + public abstract Failable map( + ThrowableFunction throwableFunction); + + /** + * Maps this instance's item to a new {@code Failable} object. The mapping function cannot fail + * (throw exceptions). + * + * @param function {@code Function} that accepts an object of type {@code T} and returns a + * new {@code Failable} object. + * @param Generic type {@code U}. + * @return {@code Failable} instance + */ + public abstract Failable unfailableFlatMap( + Function> function); + + /** + * Maps this instance's item to a new {@code Failable} object. The mapping function may fail + * and throw an exception. + * + * @param throwableFunction {@code Function} that accepts an object of type {@code T} and returns a + * new {@code Failable} object. This function may fail and throw an exception. + * @param Generic type {@code Z} bounded by {@code Throwable}. + * @param Generic type {@code U}. + * @return {@code Failable} instance + */ + public abstract Failable flatMap( + ThrowableFunction, Z> throwableFunction); + + /** + * Attempts to resolve this instance to an empty instance of {@code Failable}. + * + * @param consumer {@code Consumer} object that consumes the possible exception thrown. + * @return Empty {@code Failable} instance. + */ + public abstract Failable resolve(Consumer consumer); + + /** + * Attempts to resolve this instance to a successful instance of {@code Failable}. + * + * @param consumer {@code Consumer} object that consumes the possible exception thrown. + * @param with Item of type {@code T} that the new resolved {@code Failable} will contain. + * @return Successful {@code Failable} instance. + */ + public abstract Failable resolve(Consumer consumer, T with); + + /** + * Checks if this instance is a present instance of {@code Failable}. + * + * @return true if this instance is an instance of {@code Failable} else false. + */ + public abstract boolean isPresent(); + + /** + * Checks if this instance is an absent instance of {@code Failable}. + * + * @return true if this instance is an absent instance of {@code Failable} else false. + */ + public abstract boolean isAbsent(); + + /** + * Checks if this instance is a failed instance of {@code Failable}. + * + * @return true if this instance is a failed instance of {@code Failable} else false. + */ + public abstract boolean isFailed(); + + /** + * Executes a {@code Runnable} object if this instance is a present instance of {@code Failable}. + * + * @param runner {@code Runnable} object to run. + * @return This instance. + */ + public abstract Failable ifPresent(Runnable runner); + + /** + * Consumes the item stored in this instance if this instance is a present instance of {@code Failable}. + * + * @param consumer {@code Consumer} object to consume the item stored in this instance. + * @return This instance. + */ + public abstract Failable ifPresent(Consumer consumer); + + /** + * Executes a {@code Runnable} object if this instance is a absent instance of {@code Failable}. + * + * @param runner {@code Runnable} object to run. + * @return This instance. + */ + public abstract Failable ifAbsent(Runnable runner); + + /** + * Consumes the exception stored in this instance if this instance is a failed instance of {@code Failable}. + * + * @param consumer {@code Consumer} object to consume the exception stored in this instance. + * @return This instance. + */ + public abstract Failable ifFailed(Consumer consumer); + + /** + * Executes a {@code Runnable} object if this instance is a failed instance of {@code Failable}. + * + * @param runner {@code Runnable} object to run. + * @return This instance. + */ + public abstract Failable ifFailed(Runnable runner); + + /** + * Throws the exception stored in this {@code Failable} object only if this instance is a failed + * instance, otherwise return this instance as is. + * + * @return This instance if this instance is not a failed instance. + * @throws E if this instance is a failed instance, else no exceptions are thrown. + */ + public abstract Failable ifFailThenThrow() throws E; + + /** + * Successful instance of {@code Failable}. + * + * @param Generic type {@code T}. + */ + private static final class Success extends Failable { + private final T item; + + private Success(T item) { + this.item = item; + } + + @Override + public Failable failWith(Z exception) { + return Failable.success(this.item); + } + + @Override + public T get() { + return this.item; + } + + @Override + public T orElse(T item) { + return this.item; + } + + @Override + public Failable unfailableMap(Function function) { + return Failable.of(() -> function.apply(this.item)); + } + + @Override + public Failable filter(Predicate predicate) { + if (predicate.test(this.item)) { + return this; + } + + return Failable.empty(); + } + + @Override + public Failable map( + ThrowableFunction throwableFunction) { + try { + return Failable.of(() -> throwableFunction.apply(this.item)); + } catch (Throwable throwable) { + return Failable.ifFailElseThrow(throwable); + } + } + + @Override + public Failable unfailableFlatMap( + Function> function) { + @SuppressWarnings("unchecked") + Failable current = (Failable) function.apply(this.item); + return current; + } + + @Override + public Failable flatMap( + ThrowableFunction, Z> throwableFunction) { + try { + @SuppressWarnings("unchecked") + Failable current = (Failable) throwableFunction.apply(this.item); + return current; + } catch (Throwable throwable) { + return Failable.ifFailElseThrow(throwable); + } + } + + @Override + public Failable resolve(Consumer consumer) { + return this; + } + + @Override + public Failable resolve(Consumer consumer, T with) { + return this; + } + + + @Override + public boolean isPresent() { + return true; + } + + @Override + public boolean isAbsent() { + return false; + } + + @Override + public boolean isFailed() { + return false; + } + + @Override + public Failable ifPresent(Runnable runner) { + runner.run(); + return this; + } + + @Override + public Failable ifPresent(Consumer consumer) { + consumer.accept(this.item); + return this; + } + + @Override + public Failable ifAbsent(Runnable runner) { + return this; + } + + @Override + public Failable ifFailed(Consumer consumer) { + return this; + } + + @Override + public Failable ifFailed(Runnable runner) { + return this; + } + + @Override + public Failable ifFailThenThrow() throws CannotFailException { + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Success) { + Success success = (Success) obj; + return success.item.equals(this.item); + } + + return false; + } + } + + /** + * Empty instance of {@code Failable}. + * + * @param Generic type {@code T}. + */ + private static final class Empty extends Failable { + private Empty() { + + } + + @Override + public Failable failWith(Z exception) { + return Failable.empty(); + } + + @Override + public T get() { + throw new NoSuchElementException("No element in Failable"); + } + + @Override + public T orElse(T item) { + return item; + } + + @Override + public Failable unfailableMap(Function function) { + return Failable.empty(); + } + + @Override + public Failable filter(Predicate predicate) { + return Failable.empty(); + } + + @Override + public Failable map( + ThrowableFunction throwableFunction) { + return Failable.empty(); + } + + @Override + public Failable unfailableFlatMap( + Function> function) { + return Failable.empty(); + } + + @Override + public Failable flatMap( + ThrowableFunction, Z> throwableFunction) { + return Failable.empty(); + } + + @Override + public Failable resolve(Consumer consumer) { + return Failable.empty(); + } + + @Override + public Failable resolve(Consumer consumer, T with) { + return Failable.empty(); + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public boolean isAbsent() { + return true; + } + + @Override + public boolean isFailed() { + return false; + } + + @Override + public Failable ifPresent(Runnable runner) { + return this; + } + + @Override + public Failable ifPresent(Consumer consumer) { + return this; + } + + @Override + public Failable ifAbsent(Runnable runner) { + runner.run(); + return this; + } + + @Override + public Failable ifFailed(Consumer consumer) { + return this; + } + + @Override + public Failable ifFailed(Runnable runner) { + return this; + } + + @Override + public Failable ifFailThenThrow() throws CannotFailException { + return this; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Empty; + } + } + + /** + * Failed instance of {@code Failable}. + * + * @param Generic type {@code T}. + * @param Generic type {@code E} bounded by {@code Throwable}. + */ + private static final class Fail extends Failable { + private final E exception; + + private Fail(E exception) { + this.exception = exception; + } + + private Class getExceptionClass() { + return exception.getClass(); + } + + @Override + public Failable failWith(Z exception) { + return Failable.fail(exception); + } + + @Override + public T get() { + throw new NoSuchElementException("No element in Failable"); + } + + @Override + public T orElse(T item) { + return item; + } + + @Override + public Failable unfailableMap(Function function) { + return Failable.fail(this.exception); + } + + @Override + public Failable filter(Predicate predicate) { + return Failable.fail(this.exception); + } + + @Override + public Failable map( + ThrowableFunction throwableFunction) { + return Failable.ifFailElseThrow(this.exception); + } + + @Override + public Failable unfailableFlatMap(Function> function) { + return Failable.fail(this.exception); + } + + public Failable flatMap( + ThrowableFunction, Z> throwableFunction) { + return Failable.ifFailElseThrow(this.exception); + } + + @Override + public Failable resolve(Consumer consumer) { + return Failable.empty(); + } + + @Override + public Failable resolve(Consumer consumer, T with) { + consumer.accept(this.exception); + return Failable.success(with); + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public boolean isAbsent() { + return false; + } + + @Override + public boolean isFailed() { + return true; + } + + @Override + public Failable ifPresent(Runnable runner) { + return this; + } + + @Override + public Failable ifPresent(Consumer consumer) { + return this; + } + + @Override + public Failable ifAbsent(Runnable runner) { + return this; + } + + @Override + public Failable ifFailed(Consumer consumer) { + consumer.accept(this.exception); + return this; + } + + @Override + public Failable ifFailed(Runnable runner) { + runner.run(); + return this; + } + + @Override + public Failable ifFailThenThrow() throws E { + throw this.exception; + } + + @Override + public String toString() { + return this.exception.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof Fail) { + Fail failed = (Fail) obj; + return failed.exception.equals(this.exception); + } + + return false; + } + } +} diff --git a/src/main/java/reposense/util/function/FailableOptional.java b/src/main/java/reposense/util/function/FailableOptional.java deleted file mode 100644 index 5f93fdb615..0000000000 --- a/src/main/java/reposense/util/function/FailableOptional.java +++ /dev/null @@ -1,691 +0,0 @@ -package reposense.util.function; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Predicate; - -/** - * An {@code Optional} monad that enables both an empty and a fail option. - * - * @param Generic Type {@code T}, unbounded to any type. - */ -public abstract class FailableOptional { - private static final Absent ABSENT = new Absent<>(); - - /** - * Creates a new {@code FailableOptional} object. {@code FailableOptional} - * can contain {@code null}. - * - * @param item Item of type {@code T}. - * @param Generic Type {@code T}. - * @return An instance of {@code FailableOptional} instance. - */ - public static FailableOptional of(T item) { - return new Present<>(item); - } - - /** - * Creates a new {@code FailableOptional} object. - * This method can allow null values to be stored within the {@code FailableOptional} object. - * - * @param supplier Produces an object of type {@code T}, which can also be {@code null}. - * This {@code ThrowableSupplier} can throw Exceptions and will be automatically - * converted into a failed instance of {@code ThrowaableSupplier}. - * @param Generic type {@code T}. - * @param Generic type {@code E} bounded to {@code Exception}. - * @return {@code FailableOptional} object wrapping an object of type {@code T} or a failed - * instance of {@code FailableOptional}. - */ - public static FailableOptional of(ThrowableSupplier supplier) { - try { - return of(supplier.produce()); - } catch (Exception e) { - return ofFailure(e); - } - } - - /** - * Creates a new {@code FailableOptional} object. - * {@code null} is automatically converted into an empty instance of {@code FailableOptional} object. - * - * @param item Item of type {@code T}. - * @param Generic Type {@code T}. - * @return An instance of {@code FailableOptional} instance. - */ - public static FailableOptional ofNullable(T item) { - return item == null ? ofAbsent() : of(item); - } - - /** - * Creates a new {@code FailableOptional} object. - * This method converts {@code null} into an empty instance of {@code FailableOptional}. - * - * @param supplier Produces an object of type {@code T}, which can also be {@code null}. - * This {@code ThrowableSupplier} can throw Exceptions and will be automatically - * converted into a failed instance of {@code ThrowaableSupplier}. - * @param Generic type {@code T}. - * @param Generic type {@code E} bounded to {@code Exception}. - * @return {@code FailableOptional} object wrapping an object of type {@code T} or an empty - * instance of {@code FailableOptional}. - */ - public static FailableOptional ofNullable(ThrowableSupplier supplier) { - try { - return ofNullable(supplier.produce()); - } catch (Exception e) { - return ofFailure(e); - } - } - - /** - * Creates an empty instance of {@code FailableOptional}. - * - * @param Generic Type {@code T}. - * @return Empty instance of {@code FailableOptional}. - */ - public static FailableOptional ofAbsent() { - // safe as we can't do anything with an absent optional - @SuppressWarnings("unchecked") - FailableOptional absent = (FailableOptional) ABSENT; - return absent; - } - - /** - * Creates a failed instance of {@code FailableOptional}. - * - * @param Generic Type {@code T}. - * @return Failed instance of {@code FailableOptional}. - */ - public static FailableOptional ofFailure(Exception e) { - return new Fail<>(e); - } - - /** - * Executes the provided {@code Runner} if this current instance of - * {@code FailableOptional} contains a value. - * - * @param runner {@code Runner} instance that executes if this {@code FailableOptional} - * contains a value - * @return This {@code FailableOptional} object - */ - public abstract FailableOptional ifPresent(Runnable runner); - - /** - * Executes the provided {@code ThrowableConsumer} if this current instance of - * {@code FailableOptional} contains a value. - * - * @param consumer {@code ThrowableConsumer} instance that consumes the stored - * value if present. - * @param Generic Type {@code E} bounded by Exception. - * @return This {@code ThrowableConsumer} instance. - */ - public abstract FailableOptional ifPresent(ThrowableConsumer consumer); - - /** - * Executes the provided {@code Runner} if this current instance of - * {@code FailableOptional} does not contains a value. - * - * @param runner {@code Runner} instance that executes if this {@code FailableOptional} - * does not contains a value. - * @return This {@code FailableOptional} object. - */ - public abstract FailableOptional ifAbsent(Runnable runner); - - /** - * Executes the provided {@code Runner} if this current instance of - * {@code FailableOptional} has failed. - * - * @param runner {@code Runner} instance that executes if this {@code FailableOptional} - * has failed. - * @return This {@code FailableOptional} object. - */ - public abstract FailableOptional ifFail(Runnable runner); - - /** - * Executes the provided {@code ThrowableConsumer} if this current instance of - * {@code FailableOptional} has failed. - * - * @param consumer {@code ThrowableConsumer} instance that attempts to consume - * an object of type {@code T}. - * @param Generic Type {@code E} bounded by Exception. - * @return A {@code FailableOptional} object. - */ - public abstract FailableOptional ifFail( - ThrowableConsumer consumer); - - /** - * Recovers from a failed instance of {@code FailableOptional} with another object of type {@code T}. - * - * @param supplier Provides an object of type {@code T} to fail back to. - * @param Generic Type {@code E} bounded by Exception. - * @return A {@code FailableOptional} instance that may be present or failed. - */ - public abstract FailableOptional recover(ThrowableSupplier supplier); - - /** - * Maps the stored value in this {@code FailableOptional} into another - * {@code FailableOptional} containing items of Type {@code U}. - * If this {@code FailableOptional} contains nothing or has failed, - * this instance is returned as is. - * - * @param function {@code ThrowableFunction} that takes an object of Type {@code T} - * and returns Type {@code U} and might throw - * Exception of Type {@code E}. - * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. - * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. - * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance - * of {@code FailableOptional}. - */ - public abstract FailableOptional map(ThrowableFunction function); - - /** - * Maps the stored value in this {@code FailableOptional} into another - * {@code FailableOptional} containing items of Type {@code U}. Note that this method - * convert nulls into empty {@code FailableOptional}. - * If this {@code FailableOptional} contains nothing or has failed, - * this instance is returned as is. - * - * @param function {@code ThrowableFunction} that takes an object of Type {@code T} - * and returns Type {@code U} and might throw - * Exception of Type {@code E}. - * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. - * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. - * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance - * of {@code FailableOptional}. - */ - public abstract FailableOptional nullableMap( - ThrowableFunction function); - - /** - * Maps the stored value into another new instance of {@code FailableOptional}. Unlike {@code map}, - * this method requires that functions itself return the new instance of {@code FailableOptional}. - * - * @param function {@code ThrowableFunction} that takes an object of Type {@code T} - * and returns Type {@code FailableOptional} and - * might throw Exception of Type {@code E}. - * @param Generic Type {@code U}, representing the return type of the new {@code FailableOptional}. - * @param Generic Type {@code E}, representing the Type of the Exception that might be thrown. - * @return This instance, if this instance is empty or has failed, otherwise, a new mapped instance - * of {@code FailableOptional}. - */ - public abstract FailableOptional flatMap( - ThrowableFunction, E> function); - - /** - * Checks if the stored item fulfills the predicate, and if so, return this instance, and if not, - * return an empty instance of {@code FailableOptional}. - * - * @param predicate {@code Predicate} that tests an item of Type {@code T}. - * @return This instance if the predicate evaluates to true, else an empty instance of - * {@code FailableOptional}. - */ - public abstract FailableOptional filter(Predicate predicate); - - /** - * Returns the item stored in this {@code FailableOptional}. - * - * @return Stored item of Type {@code T} - * @throws NoSuchElementException if this {@code FailableOptional} does not contain any values - * or has failed. - */ - public abstract T get() throws NoSuchElementException; - - /** - * Returns the item stored in this {@code FailableOptional}, and if - * there is no such value, return the input parameter {@code item}. - * - * @param item The value to return if this {@code FailableOptional} is empty or has failed. - * @return The stored item or the input item. - */ - public abstract T orElse(T item); - - /** - * Returns the item stored in this {@code FailableOptional}, and if - * there is no such value, return the input parameter {@code item}. - * - * @param e The Exception to throw if this {@code FailableOptional} is empty or has failed. - * @return The stored item or throws an Exception. - * @throws Exception if there are no items stored in this {@code FailableOptional} instance. - */ - public abstract T orElseThrow(Exception e) throws Exception; - - /** - * Checks if this {@code FailableOptional} instance contains a value. - * - * @return true if this instance of {@code FailableOptional} is not empty, false otherwise. - */ - public abstract boolean isPresent(); - - /** - * Checks if this {@code FailableOptional} instance does not contains a value. - * - * @return true if this instance of {@code FailableOptional} is empty, false otherwise. - */ - public abstract boolean isAbsent(); - - /** - * Checks if this {@code FailableOptional} instance has failed. - * - * @return true if this instance of {@code FailableOptional} has failed, false otherwise. - */ - public abstract boolean isFail(); - - /** - * Verifies that a failed instance of {@code FailableOptional} has failed due to the - * input list of Exceptions to check. - * - * @param exList List of Exception Classes to verify the failure Exception cause. - * @return This instance if this instance has failed and is due to any one of the reasons specified - * in the {@code exList} argument, or an empty {@code FailableOptional} instance otherwise. - */ - public abstract FailableOptional ifFailOfType(List> exList); - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - // short circuit if the object to check is not a subclass of this class - if (!(obj instanceof FailableOptional)) { - return false; - } - - // dispatch to the relevant equals methods - if (this instanceof Present) { - Present present = (Present) this; - return present.equals(obj); - } - - if (this instanceof Absent) { - Absent absent = (Absent) this; - return absent.equals(obj); - } - - if (this instanceof Fail) { - Fail fail = (Fail) this; - return fail.equals(obj); - } - - return false; - } - - /** - * Represents a {@code ThrowableSupplier} that contains a value. - * - * @param Generic Type {@code T}. - */ - private static final class Present extends FailableOptional { - private final T item; - - private Present(T item) { - this.item = item; - } - - @Override - public FailableOptional ifPresent(Runnable runner) { - runner.run(); - return this; - } - - @Override - public FailableOptional ifPresent(ThrowableConsumer consumer) { - try { - consumer.consume(this.item); - } catch (Exception e) { - return FailableOptional.ofFailure(e); - } - - return this; - } - - @Override - public FailableOptional ifAbsent(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifFail(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifFail( - ThrowableConsumer consumer) { - return this; - } - - @Override - public FailableOptional recover(ThrowableSupplier supplier) { - return this; - } - - @Override - public FailableOptional map(ThrowableFunction function) { - try { - return FailableOptional.of(function.apply(this.item)); - } catch (Exception e) { - return FailableOptional.ofFailure(e); - } - } - - @Override - public FailableOptional nullableMap( - ThrowableFunction function) { - try { - return FailableOptional.ofNullable(function.apply(this.item)); - } catch (Exception e) { - return FailableOptional.ofFailure(e); - } - } - - @Override - public FailableOptional flatMap( - ThrowableFunction, E> function) { - try { - return function.apply(this.item); - } catch (Exception e) { - return FailableOptional.ofFailure(e); - } - } - - @Override - public FailableOptional filter(Predicate predicate) { - if (predicate.test(this.item)) { - return this; - } - - return FailableOptional.ofAbsent(); - } - - @Override - public T get() throws NoSuchElementException { - return this.item; - } - - @Override - public T orElse(T item) { - return this.item; - } - - @Override - public T orElseThrow(Exception e) { - return this.item; - } - - @Override - public boolean isPresent() { - return true; - } - - @Override - public boolean isAbsent() { - return false; - } - - @Override - public boolean isFail() { - return false; - } - - @Override - public FailableOptional ifFailOfType(List> exList) { - return this; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (obj instanceof Present) { - Present present = (Present) obj; - return this.item.equals(present.item); - } - - return false; - } - - } - - /** - * Represents a {@code FailableOptional} that does not contain any value. - * - * @param Generic Type {@code T}. - */ - private static final class Absent extends FailableOptional { - - @Override - public FailableOptional ifPresent(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifPresent(ThrowableConsumer consumer) { - return this; - } - - @Override - public FailableOptional ifAbsent(Runnable runner) { - runner.run(); - return this; - } - - @Override - public FailableOptional ifFail(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifFail( - ThrowableConsumer consumer) { - return this; - } - - @Override - public FailableOptional recover(ThrowableSupplier supplier) { - return this; - } - - @Override - public FailableOptional map(ThrowableFunction function) { - return FailableOptional.ofAbsent(); - } - - @Override - public FailableOptional nullableMap( - ThrowableFunction function) { - return FailableOptional.ofAbsent(); - } - - @Override - public FailableOptional flatMap( - ThrowableFunction, E> function) { - return FailableOptional.ofAbsent(); - } - - @Override - public FailableOptional filter(Predicate predicate) { - return FailableOptional.ofAbsent(); - } - - @Override - public T get() throws NoSuchElementException { - throw new NoSuchElementException(); - } - - @Override - public T orElse(T item) { - return item; - } - - @Override - public T orElseThrow(Exception e) throws Exception { - throw e; - } - - @Override - public boolean isPresent() { - return false; - } - - @Override - public boolean isAbsent() { - return true; - } - - @Override - public boolean isFail() { - return false; - } - - @Override - public FailableOptional ifFailOfType(List> exList) { - return this; - } - - @Override - public boolean equals(Object obj) { - return this == obj; - } - } - - /** - * Represents a {@code FailableOptional} that has failed. - * - * @param Generic Type {@code T}. - */ - private static class Fail extends FailableOptional { - private final Exception exception; - - private Fail(Exception e) { - Objects.requireNonNull(e); - this.exception = e; - } - - @Override - public FailableOptional ifPresent(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifPresent(ThrowableConsumer consumer) { - return this; - } - - @Override - public FailableOptional ifAbsent(Runnable runner) { - return this; - } - - @Override - public FailableOptional ifFail(Runnable runner) { - runner.run(); - return this; - } - - @Override - public FailableOptional ifFail( - ThrowableConsumer consumer) { - try { - @SuppressWarnings("unchecked") - E except = (E) this.exception; - consumer.consume(except); - } catch (Exception e) { - return FailableOptional.ofFailure(e); - } - - return this; - } - - @Override - public FailableOptional recover(ThrowableSupplier supplier) { - return FailableOptional.of(supplier); - } - - @Override - public FailableOptional map(ThrowableFunction function) { - @SuppressWarnings("unchecked") - FailableOptional failed = (FailableOptional) this; - return failed; - } - - @Override - public FailableOptional nullableMap( - ThrowableFunction function) { - @SuppressWarnings("unchecked") - FailableOptional failed = (FailableOptional) this; - return failed; - } - - @Override - public FailableOptional flatMap( - ThrowableFunction, E> function) { - @SuppressWarnings("unchecked") - FailableOptional failed = (FailableOptional) this; - return failed; - } - - @Override - public FailableOptional filter(Predicate predicate) { - return this; - } - - @Override - public T get() throws NoSuchElementException { - throw new NoSuchElementException(); - } - - @Override - public T orElse(T item) { - return item; - } - - @Override - public T orElseThrow(Exception e) throws Exception { - throw e; - } - - @Override - public boolean isPresent() { - return false; - } - - @Override - public boolean isAbsent() { - return false; - } - - @Override - public boolean isFail() { - return true; - } - - @Override - public FailableOptional ifFailOfType(List> exList) { - for (Class e : exList) { - if (this.exception.getClass().getSimpleName().equals(e.getSimpleName())) { - return this; - } - } - - return FailableOptional.ofAbsent(); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (obj instanceof Fail) { - Fail fail = (Fail) obj; - return fail.exception.equals(this.exception); - } - - return false; - } - } -} diff --git a/src/main/java/reposense/util/function/ThrowableConsumer.java b/src/main/java/reposense/util/function/ThrowableConsumer.java index 6bf53dd4a5..3b57fdabf2 100644 --- a/src/main/java/reposense/util/function/ThrowableConsumer.java +++ b/src/main/java/reposense/util/function/ThrowableConsumer.java @@ -10,6 +10,6 @@ * can throw. */ @FunctionalInterface -public interface ThrowableConsumer { +public interface ThrowableConsumer { void consume(T t) throws E; } diff --git a/src/main/java/reposense/util/function/ThrowableFunction.java b/src/main/java/reposense/util/function/ThrowableFunction.java index 5ef493373e..b0d828bf13 100644 --- a/src/main/java/reposense/util/function/ThrowableFunction.java +++ b/src/main/java/reposense/util/function/ThrowableFunction.java @@ -10,6 +10,6 @@ * can throw. */ @FunctionalInterface -public interface ThrowableFunction { +public interface ThrowableFunction { U apply(T t) throws E; } diff --git a/src/main/java/reposense/util/function/ThrowableSupplier.java b/src/main/java/reposense/util/function/ThrowableSupplier.java index 04d67baf15..afad6da1a2 100644 --- a/src/main/java/reposense/util/function/ThrowableSupplier.java +++ b/src/main/java/reposense/util/function/ThrowableSupplier.java @@ -10,6 +10,6 @@ * can throw. */ @FunctionalInterface -public interface ThrowableSupplier { +public interface ThrowableSupplier { T produce() throws E; } diff --git a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java index 564ae2bf0c..052918786e 100644 --- a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java +++ b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java @@ -69,7 +69,7 @@ public void analyzeAnnotation_authorNamePresentInConfig_overrideAuthorship() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -84,7 +84,7 @@ public void analyzeAnnotation_authorNameNotInConfigAndNoAuthorConfigFile_acceptT .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -100,7 +100,7 @@ public void analyzeAnnotation_authorNameNotInConfigAndHaveAuthorConfigFile_disow .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_DISOWN_CODE_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { diff --git a/src/test/java/reposense/authorship/FileAnalyzerTest.java b/src/test/java/reposense/authorship/FileAnalyzerTest.java index bed1ddeaad..1295810048 100644 --- a/src/test/java/reposense/authorship/FileAnalyzerTest.java +++ b/src/test/java/reposense/authorship/FileAnalyzerTest.java @@ -91,7 +91,7 @@ public void blameTest() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -114,7 +114,7 @@ public void blameWithPreviousAuthorsTest() { removeTestIgnoreRevsFile(); assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -130,7 +130,7 @@ public void movedFileBlameTest() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -147,7 +147,7 @@ public void blameTestDateRange() throws Exception { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -172,7 +172,7 @@ public void blameWithPreviousAuthorsTestDateRange() throws Exception { removeTestIgnoreRevsFile(); assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { @@ -189,7 +189,7 @@ public void movedFileBlameTestDateRange() throws Exception { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); }) - .ifFail(x -> { + .ifFailed(x -> { throw x; }) .ifAbsent(() -> { diff --git a/src/test/java/reposense/authorship/FileResultAggregatorTest.java b/src/test/java/reposense/authorship/FileResultAggregatorTest.java index a37ba1ee1f..e43ae32fe0 100644 --- a/src/test/java/reposense/authorship/FileResultAggregatorTest.java +++ b/src/test/java/reposense/authorship/FileResultAggregatorTest.java @@ -17,7 +17,7 @@ import reposense.model.RepoConfiguration; import reposense.template.GitTestTemplate; import reposense.util.TestUtil; -import reposense.util.function.FailableOptional; +import reposense.util.function.Failable; public class FileResultAggregatorTest extends GitTestTemplate { @@ -50,8 +50,8 @@ public void aggregateFileResult_clearFileLines_success() { List fileResults = textFileInfos.stream() .filter(f -> !f.getPath().equals("annotationTest.java")) .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo)) - .filter(FailableOptional::isPresent) - .map(FailableOptional::get) + .filter(Failable::isPresent) + .map(Failable::get) .collect(Collectors.toList()); FileResultAggregator fileResultAggregator = new FileResultAggregator(); diff --git a/src/test/java/reposense/template/GitTestTemplate.java b/src/test/java/reposense/template/GitTestTemplate.java index de8103fdde..b277367196 100644 --- a/src/test/java/reposense/template/GitTestTemplate.java +++ b/src/test/java/reposense/template/GitTestTemplate.java @@ -30,7 +30,8 @@ import reposense.model.RepoLocation; import reposense.util.FileUtil; import reposense.util.TestRepoCloner; -import reposense.util.function.FailableOptional; +import reposense.util.function.CannotFailException; +import reposense.util.function.Failable; /** * Contains templates for git testing. @@ -189,7 +190,7 @@ public void assertFileAnalysisCorrectness(FileResult fileResult, List ex } } - public FailableOptional getFileResult(String relativePath) { + public Failable getFileResult(String relativePath) { FileInfo fileInfo = fileInfoExtractor.generateFileInfo(configs.get(), relativePath); return fileInfoAnalyzer.analyzeTextFile(configs.get(), fileInfo); } diff --git a/src/test/java/reposense/util/FailableTest.java b/src/test/java/reposense/util/FailableTest.java new file mode 100644 index 0000000000..84133114ea --- /dev/null +++ b/src/test/java/reposense/util/FailableTest.java @@ -0,0 +1,293 @@ +package reposense.util; + +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import reposense.util.function.CannotFailException; +import reposense.util.function.Failable; + +public class FailableTest { + private static final Failable PRESENT = Failable.success(123); + private static final Failable ABSENT = Failable.empty(); + private static final Failable FAIL = Failable.fail(new Exception()); + + @Test + public void isPresent_testAll_success() { + Assertions.assertTrue(PRESENT.isPresent()); + Assertions.assertFalse(ABSENT.isPresent()); + Assertions.assertFalse(FAIL.isPresent()); + } + + @Test + public void isAbsent_testAll_success() { + Assertions.assertFalse(PRESENT.isAbsent()); + Assertions.assertTrue(ABSENT.isAbsent()); + Assertions.assertFalse(FAIL.isAbsent()); + } + + @Test + public void isFail_testAll_success() { + Assertions.assertFalse(PRESENT.isFailed()); + Assertions.assertFalse(ABSENT.isFailed()); + Assertions.assertTrue(FAIL.isFailed()); + } + + @Test + public void of_nullItem_success() { + Assertions.assertNull(Failable.success(null).get()); + } + + @Test + public void of_nonNullItem_success() { + Assertions.assertEquals(Failable.success("hello world").get(), "hello world"); + } + + @Test + public void of_nullItemSupplier_success() { + Assertions.assertNull(Failable.success(null).get()); + } + + @Test + public void of_nonNullItemSupplier_success() { + Assertions.assertEquals(Failable.success(1234).get(), 1234); + } + + @Test + public void of_thrownException_becomesFailure() { + Assertions.assertTrue(Failable.of(() -> { + throw new Exception(); + }).isFailed()); + } + + @Test + public void ofNullable_nullItem_success() { + Assertions.assertEquals(Failable.ofNullable(() -> null), Failable.empty()); + } + + @Test + public void ofNullable_nonNullItem_success() { + Assertions.assertEquals(Failable.ofNullable(() -> "hello world").get(), "hello world"); + } + + @Test + public void ofNullable_nullItemSupplier_success() { + Assertions.assertEquals(Failable.ofNullable(() -> null), Failable.empty()); + } + + @Test + public void ofNullable_nonNullItemSupplier_success() { + Assertions.assertEquals(Failable.ofNullable(() -> 1234).get(), 1234); + } + + @Test + public void ofNullable_thrownException_becomesFailure() { + Assertions.assertTrue(Failable.ofNullable(() -> { + throw new Exception(); + }).isFailed()); + } + + @Test + public void ofAbsent_returnsSameInstance_success() { + Assertions.assertSame(Failable.empty(), Failable.empty()); + } + + @Test + public void ofAbsent_noStoredValueCheck_success() { + Assertions.assertThrows(NoSuchElementException.class, () -> Failable.empty().get()); + } + + @Test + public void ofFailure_invalidFailureNull_throwsException() { + Assertions.assertDoesNotThrow(() -> Failable.fail(null)); + } + + @Test + public void ofFailure_validFailureException_success() { + Assertions.assertDoesNotThrow(() -> Failable.fail(new Exception("should not be thrown"))); + } + + @Test + public void ifPresent_testAll_success() { + int[] testArray = {-1, -1, -1}; + + PRESENT.ifPresent(() -> testArray[0] = 1); + ABSENT.ifPresent(() -> testArray[1] = 1); + FAIL.ifPresent(() -> testArray[2] = 1); + + Assertions.assertArrayEquals(testArray, new int[]{1, -1, -1}); + } + + @Test + public void ifAbsent_testAll_success() { + int[] testArray = {-1, -1, -1}; + + PRESENT.ifAbsent(() -> testArray[0] = 1); + ABSENT.ifAbsent(() -> testArray[1] = 1); + FAIL.ifAbsent(() -> testArray[2] = 1); + + Assertions.assertArrayEquals(testArray, new int[]{-1, 1, -1}); + } + + @Test + public void ifFailed_testAll_success() { + int[] testArray = {-1, -1, -1, -1}; + + PRESENT.ifFailed(() -> testArray[0] = 1); + PRESENT.ifFailed(x -> testArray[0] = 2); + ABSENT.ifFailed(() -> testArray[1] = 1); + ABSENT.ifFailed(x -> testArray[1] = 2); + FAIL.ifFailed(() -> testArray[2] = 1); + FAIL.ifFailed(x -> { + if (x instanceof Exception) { + testArray[3] = 2; + } + }); + + Assertions.assertArrayEquals(testArray, new int[]{-1, -1, 1, 2}); + } + + @Test + public void map_testAll_success() { + Assertions.assertEquals(PRESENT.map(x -> "String").get(), "String"); + Assertions.assertEquals(ABSENT.map(x -> "String"), ABSENT); + Assertions.assertEquals(FAIL.map(x -> "String"), FAIL); + } + + @Test + public void map_throwingFunctionTestAll_success() { + Assertions.assertInstanceOf(FAIL.getClass(), PRESENT.map(x -> { + throw new Exception(); + })); + Assertions.assertInstanceOf(ABSENT.getClass(), ABSENT.map(x -> { + throw new Exception(); + })); + + Failable failedMapping = FAIL.map(x -> { + throw new Exception(); + }); + Assertions.assertInstanceOf(FAIL.getClass(), failedMapping); + Assertions.assertThrows(Exception.class, failedMapping::ifFailThenThrow); + } + + @Test + public void unfailableMap_testAll_success() { + Assertions.assertEquals(PRESENT.unfailableMap(x -> "String").get(), "String"); + Assertions.assertEquals(ABSENT.unfailableMap(x -> "String"), ABSENT); + Assertions.assertThrows(Exception.class, () -> FAIL.unfailableMap(x -> "String").ifFailThenThrow()); + } + + @Test + public void flatMap_testAll_success() { + Assertions.assertEquals(PRESENT.flatMap(x -> Failable.success("String")).get(), "String"); + Assertions.assertEquals(ABSENT.flatMap(x -> Failable.success("String")), ABSENT); + Assertions.assertEquals(FAIL.flatMap(x -> Failable.success("String")), FAIL); + } + + @Test + public void flatMap_throwingFunctionTestAll_success() throws Throwable { + Assertions.assertInstanceOf(FAIL.getClass(), PRESENT.flatMap(x -> { + throw new Exception(); + })); + Assertions.assertInstanceOf(ABSENT.getClass(), ABSENT.map(x -> { + throw new Exception(); + })); + + // mapping turns the failed instance to the new exception class, + // BUT it will only contain the first exception + Failable failedMapping = FAIL.flatMap(x -> { + throw new IllegalAccessError(); + }); + Assertions.assertInstanceOf(FAIL.getClass(), failedMapping); + + // users must be aware of what exceptions that can cause it to fail before considering + // what to catch/throw + Assertions.assertThrows(Exception.class, failedMapping::ifFailThenThrow); + } + + @Test + public void unfailableFlatMap_testAll_success() { + Assertions.assertEquals(PRESENT.unfailableFlatMap(x -> Failable.success("String")).get(), "String"); + Assertions.assertEquals(ABSENT.unfailableFlatMap(x -> Failable.success("String")), ABSENT); + Assertions.assertEquals(FAIL.unfailableFlatMap(x -> Failable.success("String")), FAIL); + } + + @Test + public void filter_presentFailable_passPredicate() { + Assertions.assertSame(PRESENT.filter(x -> x > 0), PRESENT); + } + + @Test + public void filter_absentFailable_success() { + Assertions.assertSame(ABSENT.filter(x -> x < 0), ABSENT); + } + + @Test + public void filter_failedFailable_success() { + Assertions.assertEquals(FAIL.filter(x -> x < 0), FAIL); + } + + @Test + public void get_testAll_success() { + Assertions.assertEquals(PRESENT.get(), 123); + Assertions.assertThrows(NoSuchElementException.class, ABSENT::get); + Assertions.assertThrows(NoSuchElementException.class, FAIL::get); + } + + @Test + public void orElse_testAll_success() { + Assertions.assertEquals(PRESENT.orElse(999), 123); + Assertions.assertEquals(ABSENT.orElse(999), 999); + Assertions.assertEquals(FAIL.orElse(999), 999); + } + + @Test + public void ifFailThenThrow_testAll_success() { + Assertions.assertDoesNotThrow(PRESENT::ifFailThenThrow); + Assertions.assertDoesNotThrow(ABSENT::ifFailThenThrow); + Assertions.assertThrows(Exception.class, FAIL::ifFailThenThrow); + } + + @Test + public void failWith_testAll_success() { + Assertions.assertDoesNotThrow(() -> PRESENT.failWith(new Throwable()).ifFailThenThrow()); + Assertions.assertDoesNotThrow(() -> ABSENT.failWith(new Throwable()).ifFailThenThrow()); + Assertions.assertThrows(Throwable.class, () -> FAIL.failWith(new Throwable()).ifFailThenThrow()); + } + + @Test + public void resolve_withoutReplacingValue_success() { + Assertions.assertEquals(FAIL.resolve(x -> Assertions.assertInstanceOf(Exception.class, x)), ABSENT); + } + + @Test + public void resolve_withReplacingValue_success() { + Assertions.assertEquals(FAIL.resolve(x -> Assertions.assertInstanceOf(Exception.class, x), 1).get(), 1); + } + + @Test + public void equals_presentFailable_multipleTests() { + Assertions.assertSame(PRESENT, PRESENT); + Assertions.assertEquals(PRESENT, PRESENT); + Assertions.assertNotEquals(PRESENT, ABSENT); + Assertions.assertNotEquals(PRESENT, FAIL); + Assertions.assertNotEquals(PRESENT, Failable.success("String")); + } + + @Test + public void equals_absentFailable_multipleTests() { + Assertions.assertSame(ABSENT, ABSENT); + Assertions.assertEquals(ABSENT, ABSENT); + Assertions.assertNotEquals(ABSENT, PRESENT); + Assertions.assertNotEquals(ABSENT, FAIL); + } + + @Test + public void equals_failedFailable_multipleTests() { + Assertions.assertSame(FAIL, FAIL); + Assertions.assertEquals(FAIL, FAIL); + Assertions.assertNotEquals(FAIL, PRESENT); + Assertions.assertNotEquals(FAIL, ABSENT); + } +} From 3b6b054e3fdc735dadf5c42ced6a88e7f11dc19e Mon Sep 17 00:00:00 2001 From: George Tay Date: Thu, 21 Mar 2024 23:07:54 +0800 Subject: [PATCH 09/11] Remove old test files --- .../reposense/util/FailableOptionalTest.java | 290 ------------------ 1 file changed, 290 deletions(-) delete mode 100644 src/test/java/reposense/util/FailableOptionalTest.java diff --git a/src/test/java/reposense/util/FailableOptionalTest.java b/src/test/java/reposense/util/FailableOptionalTest.java deleted file mode 100644 index 85984689d8..0000000000 --- a/src/test/java/reposense/util/FailableOptionalTest.java +++ /dev/null @@ -1,290 +0,0 @@ -package reposense.util; - -import java.util.Arrays; -import java.util.NoSuchElementException; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import reposense.util.function.FailableOptional; - -public class FailableOptionalTest { - private static final FailableOptional PRESENT = FailableOptional.of(123); - private static final FailableOptional ABSENT = FailableOptional.ofAbsent(); - private static final FailableOptional FAIL = FailableOptional.ofFailure(new Exception()); - - @Test - public void isPresent_testAll_success() { - Assertions.assertTrue(PRESENT.isPresent()); - Assertions.assertFalse(ABSENT.isPresent()); - Assertions.assertFalse(FAIL.isPresent()); - } - - @Test - public void isAbsent_testAll_success() { - Assertions.assertFalse(PRESENT.isAbsent()); - Assertions.assertTrue(ABSENT.isAbsent()); - Assertions.assertFalse(FAIL.isAbsent()); - } - - @Test - public void isFail_testAll_success() { - Assertions.assertFalse(PRESENT.isFail()); - Assertions.assertFalse(ABSENT.isFail()); - Assertions.assertTrue(FAIL.isFail()); - } - - @Test - public void of_nullItem_success() { - Assertions.assertNull(FailableOptional.of(null).get()); - } - - @Test - public void of_nonNullItem_success() { - Assertions.assertEquals(FailableOptional.of("hello world").get(), "hello world"); - } - - @Test - public void of_nullItemSupplier_success() { - Assertions.assertNull(FailableOptional.of(() -> null).get()); - } - - @Test - public void of_nonNullItemSupplier_success() { - Assertions.assertEquals(FailableOptional.of(() -> 1234).get(), 1234); - } - - @Test - public void of_thrownException_becomesFailure() { - Assertions.assertTrue(FailableOptional.of(() -> { - throw new Exception(); - }).isFail()); - } - - @Test - public void ofNullable_nullItem_success() { - Assertions.assertEquals(FailableOptional.ofNullable(null), FailableOptional.ofAbsent()); - } - - @Test - public void ofNullable_nonNullItem_success() { - Assertions.assertEquals(FailableOptional.ofNullable("hello world").get(), "hello world"); - } - - @Test - public void ofNullable_nullItemSupplier_success() { - Assertions.assertEquals(FailableOptional.ofNullable(() -> null), FailableOptional.ofAbsent()); - } - - @Test - public void ofNullable_nonNullItemSupplier_success() { - Assertions.assertEquals(FailableOptional.ofNullable(() -> 1234).get(), 1234); - } - - @Test - public void ofNullable_thrownException_becomesFailure() { - Assertions.assertTrue(FailableOptional.ofNullable(() -> { - throw new Exception(); - }).isFail()); - } - - @Test - public void ofAbsent_returnsSameInstance_success() { - Assertions.assertSame(FailableOptional.ofAbsent(), FailableOptional.ofAbsent()); - } - - @Test - public void ofAbsent_noStoredValueCheck_success() { - Assertions.assertThrows(NoSuchElementException.class, () -> FailableOptional.ofAbsent().get()); - } - - @Test - public void ofFailure_invalidFailureNull_throwsException() { - Assertions.assertThrows(NullPointerException.class, () -> FailableOptional.ofFailure(null)); - } - - @Test - public void ofFailure_validFailureException_success() { - Assertions.assertDoesNotThrow(() -> FailableOptional.ofFailure(new Exception("should not be thrown"))); - } - - @Test - public void ifPresent_testAll_success() { - int[] testArray = {-1, -1, -1}; - - PRESENT.ifPresent(() -> testArray[0] = 1); - ABSENT.ifPresent(() -> testArray[1] = 1); - FAIL.ifPresent(() -> testArray[2] = 1); - - Assertions.assertArrayEquals(testArray, new int[]{1, -1, -1}); - } - - @Test - public void ifPresent_throwingConsumer_success() { - FailableOptional presentThrown = PRESENT.ifPresent(x -> { - throw new Exception(); - }); - FailableOptional absentThrown = ABSENT.ifPresent(x -> { - throw new Exception(); - }); - FailableOptional failThrown = FAIL.ifPresent(x -> { - throw new Exception(); - }); - - Assertions.assertTrue(presentThrown.isFail()); - Assertions.assertTrue(absentThrown.isAbsent()); - Assertions.assertTrue(failThrown.isFail()); - } - - @Test - public void ifAbsent_testAll_success() { - int[] testArray = {-1, -1, -1}; - - PRESENT.ifAbsent(() -> testArray[0] = 1); - ABSENT.ifAbsent(() -> testArray[1] = 1); - FAIL.ifAbsent(() -> testArray[2] = 1); - - System.out.println(Arrays.toString(testArray)); - - Assertions.assertArrayEquals(testArray, new int[]{-1, 1, -1}); - } - - @Test - public void ifFail_testAll_success() { - int[] testArray = {-1, -1, -1}; - - PRESENT.ifFail(x -> testArray[0] = 1); - ABSENT.ifFail(x -> testArray[1] = 1); - FAIL.ifFail(x -> testArray[2] = 1); - - Assertions.assertArrayEquals(testArray, new int[]{-1, -1, 1}); - } - - @Test - public void ifFail_throwingConsumer_success() { - FailableOptional presentThrown = PRESENT.ifFail(x -> { - throw new Exception(); - }); - FailableOptional absentThrown = ABSENT.ifFail(x -> { - throw new Exception(); - }); - FailableOptional failThrown = FAIL.ifFail(x -> { - throw new Exception(); - }); - - Assertions.assertTrue(presentThrown.isPresent()); - Assertions.assertTrue(absentThrown.isAbsent()); - Assertions.assertTrue(failThrown.isFail()); - } - - @Test - public void recover_presentFailableOptional_success() { - FailableOptional recovered = PRESENT.recover(() -> 1); - Assertions.assertEquals(recovered, PRESENT); - } - - @Test - public void recover_absentFailableOptional_success() { - FailableOptional recovered = ABSENT.recover(() -> 2); - Assertions.assertEquals(recovered, ABSENT); - } - - @Test - public void recover_failFailableOptional_success() { - FailableOptional recovered = FAIL.recover(() -> 9999); - Assertions.assertTrue(recovered.isPresent()); - Assertions.assertEquals(recovered.get(), 9999); - } - - @Test - public void map_testAll_success() { - Assertions.assertEquals(PRESENT.map(x -> "String").get(), "String"); - Assertions.assertSame(ABSENT.map(x -> "String"), ABSENT); - Assertions.assertSame(FAIL.map(x -> "String"), FAIL); - } - - @Test - public void nullableMap_testAll_success() { - Assertions.assertSame(PRESENT.nullableMap(x -> null), ABSENT); - Assertions.assertSame(ABSENT.nullableMap(x -> null), ABSENT); - Assertions.assertSame(FAIL.nullableMap(x -> null), FAIL); - } - - @Test - public void flatMap_testAll_success() { - Assertions.assertEquals(PRESENT.flatMap(x -> FailableOptional.of("String")).get(), "String"); - Assertions.assertSame(ABSENT.flatMap(x -> FailableOptional.of("String")), ABSENT); - Assertions.assertSame(FAIL.flatMap(x -> FailableOptional.of("String")), FAIL); - } - - @Test - public void filter_presentFailableOptional_passPredicate() { - Assertions.assertSame(PRESENT.filter(x -> x > 0), PRESENT); - } - - @Test - public void filter_absentFailableOptional_success() { - Assertions.assertSame(ABSENT.filter(x -> x < 0), ABSENT); - } - - @Test - public void filter_failedFailableOptional_success() { - Assertions.assertSame(FAIL.filter(x -> x < 0), FAIL); - } - - @Test - public void get_testAll_success() { - Assertions.assertEquals(PRESENT.get(), 123); - Assertions.assertThrows(NoSuchElementException.class, ABSENT::get); - Assertions.assertThrows(NoSuchElementException.class, FAIL::get); - } - - @Test - public void orElse_testAll_success() { - Assertions.assertEquals(PRESENT.orElse(999), 123); - Assertions.assertEquals(ABSENT.orElse(999), 999); - Assertions.assertEquals(FAIL.orElse(999), 999); - } - - @Test - public void orElseThrow_testAll_success() { - Assertions.assertEquals(PRESENT.orElse(999), 123); - Assertions.assertThrows(Exception.class, () -> ABSENT.orElseThrow(new Exception())); - Assertions.assertThrows(Exception.class, () -> FAIL.orElseThrow(new Exception())); - } - - @Test - public void ifFailOfType_testAll_failure() { - Assertions.assertSame(PRESENT.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), - PRESENT); - Assertions.assertSame(ABSENT.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), - ABSENT); - Assertions.assertSame(FAIL.ifFailOfType(Arrays.asList(Exception.class, IllegalArgumentException.class)), FAIL); - Assertions.assertSame(FAIL.ifFailOfType(Arrays.asList(IllegalArgumentException.class)), ABSENT); - } - - @Test - public void equals_presentFailableOptional_multipleTests() { - Assertions.assertSame(PRESENT, PRESENT); - Assertions.assertEquals(PRESENT, PRESENT); - Assertions.assertNotEquals(PRESENT, ABSENT); - Assertions.assertNotEquals(PRESENT, FAIL); - Assertions.assertNotEquals(PRESENT, FailableOptional.of("String")); - } - - @Test - public void equals_absentFailableOptional_multipleTests() { - Assertions.assertSame(ABSENT, ABSENT); - Assertions.assertEquals(ABSENT, ABSENT); - Assertions.assertNotEquals(ABSENT, PRESENT); - Assertions.assertNotEquals(ABSENT, FAIL); - } - - @Test - public void equals_failedFailableOptional_multipleTests() { - Assertions.assertSame(FAIL, FAIL); - Assertions.assertEquals(FAIL, FAIL); - Assertions.assertNotEquals(FAIL, PRESENT); - Assertions.assertNotEquals(FAIL, ABSENT); - } -} From a5d4460c1a9b614acdfeca470a4d7b1ba059bd79 Mon Sep 17 00:00:00 2001 From: George Tay Date: Mon, 25 Mar 2024 15:58:08 +0800 Subject: [PATCH 10/11] Remove exception typing for Failable --- .../authorship/FileInfoAnalyzer.java | 7 +- .../reposense/commits/CommitInfoAnalyzer.java | 15 +- .../model/ConfigRunConfiguration.java | 14 +- .../reposense/model/RepoConfiguration.java | 2 +- .../ConfigurationBuildException.java | 2 +- .../util/function/CannotFailException.java | 7 - .../reposense/util/function/Failable.java | 548 +++++------------- .../authorship/AnnotatorAnalyzerTest.java | 9 - .../authorship/FileAnalyzerTest.java | 18 - .../model/RepoConfigurationTest.java | 2 +- .../reposense/template/GitTestTemplate.java | 3 +- .../java/reposense/util/FailableTest.java | 156 +---- 12 files changed, 196 insertions(+), 587 deletions(-) rename src/main/java/reposense/parser/{ => exceptions}/ConfigurationBuildException.java (81%) delete mode 100644 src/main/java/reposense/util/function/CannotFailException.java diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index f657cf43be..df1637c667 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -21,7 +21,6 @@ import reposense.model.RepoConfiguration; import reposense.system.LogsManager; import reposense.util.FileUtil; -import reposense.util.function.CannotFailException; import reposense.util.function.Failable; /** @@ -48,7 +47,7 @@ public class FileInfoAnalyzer { * Returns empty {@code Failable} if the file is missing from the local system, * or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public Failable analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { + public Failable analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { String relativePath = fileInfo.getPath(); // note that the predicates in filter() test for the negation of the previous failure conditions @@ -72,7 +71,7 @@ public Failable analyzeTextFile(RepoConfigurati * Returns empty {@code Failable} if the file is missing from the local system, * or none of the {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public Failable analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { + public Failable analyzeBinaryFile(RepoConfiguration config, FileInfo fileInfo) { String relativePath = fileInfo.getPath(); return Failable.ofNullable(() -> relativePath) @@ -103,7 +102,7 @@ private FileResult generateTextFileResult(FileInfo fileInfo) { * Returns an empty {@code Failable} if none of the {@link Author} specified in * {@code config} contributed to the file in {@code fileInfo}. */ - private Failable generateBinaryFileResult( + private Failable generateBinaryFileResult( RepoConfiguration config, FileInfo fileInfo) { Set authors = new HashSet<>(); HashMap authorContributionMap = new HashMap<>(); diff --git a/src/main/java/reposense/commits/CommitInfoAnalyzer.java b/src/main/java/reposense/commits/CommitInfoAnalyzer.java index 1ee000029d..764ed1f122 100644 --- a/src/main/java/reposense/commits/CommitInfoAnalyzer.java +++ b/src/main/java/reposense/commits/CommitInfoAnalyzer.java @@ -5,7 +5,6 @@ import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -91,13 +90,13 @@ public CommitResult analyzeCommit(CommitInfo commitInfo, RepoConfiguration confi Author author = config.getAuthor(elements[AUTHOR_INDEX], elements[EMAIL_INDEX]); // safe map since ZonedDateTime::now returns non-null - Failable date = Failable - .ofNullable(() -> ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT)) - .ifFailed(x -> - logger.log(Level.WARNING, "Unable to parse the date from git log result for commit.", x)) - .resolve(x -> { - assert x instanceof DateTimeParseException; - }, ZonedDateTime.now()) + Failable date = Failable + .ofNullable(() -> ZonedDateTime.parse(elements[DATE_INDEX], GIT_STRICT_ISO_DATE_FORMAT), + x -> { + logger.log(Level.WARNING, + "Unable to parse the date from git log result for commit.", x); + return ZonedDateTime.now(); + }) .map(x -> x.withZoneSameInstant(config.getZoneId()).toLocalDateTime()); String messageTitle = (elements.length > MESSAGE_TITLE_INDEX) ? elements[MESSAGE_TITLE_INDEX] : ""; diff --git a/src/main/java/reposense/model/ConfigRunConfiguration.java b/src/main/java/reposense/model/ConfigRunConfiguration.java index 2274014437..52a4c84981 100644 --- a/src/main/java/reposense/model/ConfigRunConfiguration.java +++ b/src/main/java/reposense/model/ConfigRunConfiguration.java @@ -43,18 +43,24 @@ public List getRepoConfigurations() // parse the author config file path Failable.of(cliArguments::getAuthorConfigFilePath) .filter(Files::exists) - .map(x -> new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse()) + .map(x -> new AuthorConfigCsvParser(cliArguments.getAuthorConfigFilePath()).parse(), + exception -> { + logger.log(Level.WARNING, exception.getMessage(), exception); + return Collections.emptyList(); + }) .ifPresent(x -> RepoConfiguration.merge(repoConfigs, x)) .ifPresent(() -> RepoConfiguration.setHasAuthorConfigFileToRepoConfigs(repoConfigs, true)) - .ifFailed(x -> logger.log(Level.WARNING, x.getMessage(), x)) .orElse(Collections.emptyList()); // parse the group config file path Failable.of(cliArguments::getGroupConfigFilePath) .filter(Files::exists) - .map(x -> new GroupConfigCsvParser(x).parse()) + .map(x -> new GroupConfigCsvParser(x).parse(), + exception -> { + logger.log(Level.WARNING, exception.getMessage(), exception); + return Collections.emptyList(); + }) .ifPresent(x -> RepoConfiguration.setGroupConfigsToRepos(repoConfigs, x)) - .ifFailed(x -> logger.log(Level.WARNING, x.getMessage(), x)) .orElse(Collections.emptyList()); return repoConfigs; diff --git a/src/main/java/reposense/model/RepoConfiguration.java b/src/main/java/reposense/model/RepoConfiguration.java index 8466cfb52b..bc367b04a2 100644 --- a/src/main/java/reposense/model/RepoConfiguration.java +++ b/src/main/java/reposense/model/RepoConfiguration.java @@ -13,7 +13,7 @@ import reposense.git.GitBranch; import reposense.git.exception.GitBranchException; -import reposense.parser.ConfigurationBuildException; +import reposense.parser.exceptions.ConfigurationBuildException; import reposense.system.LogsManager; import reposense.util.FileUtil; diff --git a/src/main/java/reposense/parser/ConfigurationBuildException.java b/src/main/java/reposense/parser/exceptions/ConfigurationBuildException.java similarity index 81% rename from src/main/java/reposense/parser/ConfigurationBuildException.java rename to src/main/java/reposense/parser/exceptions/ConfigurationBuildException.java index fd1f43fea8..bb3b302662 100644 --- a/src/main/java/reposense/parser/ConfigurationBuildException.java +++ b/src/main/java/reposense/parser/exceptions/ConfigurationBuildException.java @@ -1,4 +1,4 @@ -package reposense.parser; +package reposense.parser.exceptions; /** * Signals that there was an issue building a Configuration (missing parameters, etc.). diff --git a/src/main/java/reposense/util/function/CannotFailException.java b/src/main/java/reposense/util/function/CannotFailException.java deleted file mode 100644 index fc72e855a5..0000000000 --- a/src/main/java/reposense/util/function/CannotFailException.java +++ /dev/null @@ -1,7 +0,0 @@ -package reposense.util.function; - -/** - * Represents the error class for a {@code Either} or {@code Failable} that cannot fail. - */ -public class CannotFailException extends RuntimeException { -} diff --git a/src/main/java/reposense/util/function/Failable.java b/src/main/java/reposense/util/function/Failable.java index da035695c8..e7393f2d77 100644 --- a/src/main/java/reposense/util/function/Failable.java +++ b/src/main/java/reposense/util/function/Failable.java @@ -4,54 +4,73 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; /** * Represents a task type that encapsulates information about running a task with the possibility of * throwing exception. * * @param Generic input type {@code T} - * @param Generic exception type {@code E} */ -public abstract class Failable { +public abstract class Failable { // Empty instance - private static final Failable EMPTY = new Empty<>(); + private static final Failable EMPTY = new Empty(); /** - * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} + * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} * and produces an item of type {@code T}. If it fails, then a failed instance of - * {@code Failable} is returned. This method allows {@code null} to be stored within + * {@code Failable} is returned. This method allows {@code null} to be stored within * it. * * @param Generic type {@code T}. * @param Generic type {@code E} bounded by {@code Throwable}. * @param supplier Produces objects of type {@code T}. - * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} - * runs without failure or a failed instance of {@code Failable}. + * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} + * runs without failure or a failed instance of {@code Failable}. */ - public static Failable of( - ThrowableSupplier supplier) { + public static Failable of( + ThrowableSupplier supplier, Function exceptionHandler) { try { return Failable.success(supplier.produce()); } catch (Throwable throwable) { - return Failable.ifFailElseThrow(throwable); + // ensured by type declaration above + @SuppressWarnings("unchecked") + E exception = (E) throwable; + T recoveredItem = exceptionHandler.apply(exception); + return Failable.success(recoveredItem); } } /** - * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} + * Returns an instance of {@code Failable}. Accepts a {@code Supplier} + * and produces an item of type {@code T}. This method allows {@code null} to + * be stored within it. + * + * @param Generic type {@code T}. + * @param supplier Produces objects of type {@code T}. + * @return Successful instance of {@code Failable} if + * the {@code ThrowableSupplier} runs without failure or a failed instance + * of {@code Failable}. + */ + public static Failable of(Supplier supplier) { + return Failable.success(supplier.get()); + } + + /** + * Returns an instance of {@code Failable}. Accepts a {@code ThrowableSupplier} * and produces an item of type {@code T}. If it fails, then a failed instance of - * {@code Failable} is returned. This method converts {@code null} objects - * into empty instances of {@code Failable}. + * {@code Failable} is returned. This method converts {@code null} objects + * into empty instances of {@code Failable}. * * @param Generic type {@code T}. * @param Generic type {@code E} bounded by {@code Throwable}. * @param supplier Produces objects of type {@code T}. - * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} - * runs without failure, or empty instance of {@code Failable} if {@code null} - * is produced, or a failed instance of {@code Failable}. + * @return Successful instance of {@code Failable} if the {@code ThrowableSupplier} + * runs without failure, or empty instance of {@code Failable} if {@code null} + * is produced, or a failed instance of {@code Failable}. */ - public static Failable ofNullable( - ThrowableSupplier supplier) { + public static Failable ofNullable( + ThrowableSupplier supplier, Function exceptionHandler) { try { T returns = supplier.produce(); @@ -61,92 +80,78 @@ public static Failable ofNullable( return Failable.success(returns); } catch (Throwable throwable) { - return Failable.ifFailElseThrow(throwable); + // safe as type guaranteed by function declaration + @SuppressWarnings("unchecked") + E exception = (E) throwable; + T item = exceptionHandler.apply(exception); + + return Failable.ofNullable(item); } } /** - * Creates a successful instance of {@code Failable}. + * Creates a successful instance of {@code Failable}, or returns an empty instance + * if item is {@code null}. * * @param Generic type {@code T}. - * @param Generic type {@code E} bounded by {@code Throwable}. - * @param item Item of type {@code T} to store. - * @return Successful instance of {@code Failable}. + * @param supplier Provides an item of type {@code T} to store. + * @return Successful instance of {@code Failable} if item is not {@code null} else + * empty instance of {@code Failable}. */ - public static Failable success(T item) { - // can just cast as no exception thrown - @SuppressWarnings("unchecked") - Failable succ = (Failable) new Success<>(item); - return succ; + public static Failable ofNullable(Supplier supplier) { + return Failable.ofNullable(supplier.get()); } /** - * Creates an empty instance of {@code Failable}. + * Creates a successful instance of {@code Failable}, or returns an empty instance + * if item is {@code null}. * * @param Generic type {@code T}. - * @param Generic type {@code E} bounded by {@code Throwable}. - * @return Empty instance of {@code Failable}. + * @param item Item of type {@code T} to store. + * @return Successful instance of {@code Failable} if item is not {@code null} else + * empty instance of {@code Failable}. */ - public static Failable empty() { - // safe as empty contains nothing, and no monadic actions will cause it to turn into anything else - @SuppressWarnings("unchecked") - Failable failed = (Failable) Failable.EMPTY; - return failed; + public static Failable ofNullable(T item) { + if (item == null) { + return Failable.empty(); + } + + return Failable.success(item); } /** - * Creates a failed instance of {@code Failable}. + * Creates a successful instance of {@code Failable}. * * @param Generic type {@code T}. - * @param Generic type {@code E} bounded by {@code Throwable}. - * @param throwable Object of type {@code E} that represents the thrown exception this - * {@code Failable} encapsulates. - * @return Failed instance of {@code Failable}. + * @param item Item of type {@code T} to store. + * @return Successful instance of {@code Failable}. */ - public static Failable fail(E throwable) { - return new Fail<>(throwable); + public static Failable success(T item) { + return new Success<>(item); } /** - * Checks if the thrown exception is of the correct generic type using Java Reflections. - * Experimental method that requires deeper inspection to ensure that it is correct; - * can be replaced by an unsafe cast if necessary. + * Creates an empty instance of {@code Failable}. * - * @param throwable {@code Throwable} object to check the type of. * @param Generic type {@code T}. - * @param Generic type {@code E} bounded by {@code Throwable}. - * @return {@code Failable} instance if the exception type is verified. - * @throws ClassCastException if the function throws an exception that is not of the expected - * {@code Throwable} type. + * @return Empty instance of {@code Failable}. */ - private static Failable ifFailElseThrow(Throwable throwable) { - if (new Fail<>(throwable).getExceptionClass().isInstance(throwable)) { - @SuppressWarnings("unchecked") - Failable failed = (Failable) new Fail<>(throwable); - return failed; - } - - throw new ClassCastException("Exception class does not match specifications"); + public static Failable empty() { + // safe as empty contains nothing, and no monadic actions will cause it to turn into anything else + @SuppressWarnings("unchecked") + Failable failed = (Failable) Failable.EMPTY; + return failed; } /** - * Changes the {@code Throwable} exception type to a new type. - * - * @param exception {@code Throwable} type {@code Z} to change to. - * @param Generic type {@code Z} bounded by {@code Throwable}. - * @return {@code Failable} with new exception type {@code Z}. - */ - public abstract Failable failWith(Z exception); - - /** - * Returns the item stored in this {@code Failable} instance. + * Returns the item stored in this {@code Failable} instance. * * @return Item of type {@code T}, or throws an exception if there is no item stored. */ public abstract T get(); /** - * Returns the item stored in this {@code Failable} instance, or the input item if this + * Returns the item stored in this {@code Failable} instance, or the input item if this * instance does not contain any items. * * @param item Item of type {@code T} to return if this instance has failed or is empty. @@ -158,9 +163,9 @@ private static Failable ifFailElseThrow(Throwable * Tests the item stored in this instance against some {@code Predicate}. * * @param predicate {@code Predicate} to test this instance's item against. - * @return This instance if the predicate test passses, else an empty {@code Failable} instance. + * @return This instance if the predicate test passses, else an empty {@code Failable} instance. */ - public abstract Failable filter(Predicate predicate); + public abstract Failable filter(Predicate predicate); /** * Maps this instance's item to a new item of type {@code U}. The mapping function cannot fail @@ -169,9 +174,9 @@ private static Failable ifFailElseThrow(Throwable * @param function {@code Function} that accepts an object of type {@code T} and returns a new * object of type {@code U}. * @param Generic type {@code U}. - * @return {@code Failable} instance + * @return {@code Failable} instance */ - public abstract Failable unfailableMap(Function function); + public abstract Failable map(Function function); /** * Maps this instance's item to a new item of type {@code U}. The mapping function may fail @@ -181,140 +186,87 @@ private static Failable ifFailElseThrow(Throwable * object of type {@code U}. This function may fail and throw an exception. * @param Generic type {@code Z} bounded by {@code Throwable}. * @param Generic type {@code U}. - * @return {@code Failable} instance + * @return {@code Failable} instance */ - public abstract Failable map( - ThrowableFunction throwableFunction); + public abstract Failable map( + ThrowableFunction throwableFunction, + Function exceptionHandler); /** - * Maps this instance's item to a new {@code Failable} object. The mapping function cannot fail + * Maps this instance's item to a new {@code Failable} object. The mapping function cannot fail * (throw exceptions). * * @param function {@code Function} that accepts an object of type {@code T} and returns a - * new {@code Failable} object. + * new {@code Failable} object. * @param Generic type {@code U}. - * @return {@code Failable} instance + * @return {@code Failable} instance */ - public abstract Failable unfailableFlatMap( - Function> function); + public abstract Failable flatMap( + Function> function); /** - * Maps this instance's item to a new {@code Failable} object. The mapping function may fail - * and throw an exception. + * Maps this instance's item to a new {@code Failable} object. The mapping function may fail + * and throw an exception of the same type {@code E}. * * @param throwableFunction {@code Function} that accepts an object of type {@code T} and returns a - * new {@code Failable} object. This function may fail and throw an exception. - * @param Generic type {@code Z} bounded by {@code Throwable}. + * new {@code Failable} object. This function may fail and throw an exception. * @param Generic type {@code U}. - * @return {@code Failable} instance - */ - public abstract Failable flatMap( - ThrowableFunction, Z> throwableFunction); - - /** - * Attempts to resolve this instance to an empty instance of {@code Failable}. - * - * @param consumer {@code Consumer} object that consumes the possible exception thrown. - * @return Empty {@code Failable} instance. - */ - public abstract Failable resolve(Consumer consumer); - - /** - * Attempts to resolve this instance to a successful instance of {@code Failable}. - * - * @param consumer {@code Consumer} object that consumes the possible exception thrown. - * @param with Item of type {@code T} that the new resolved {@code Failable} will contain. - * @return Successful {@code Failable} instance. + * @return {@code Failable} instance */ - public abstract Failable resolve(Consumer consumer, T with); + public abstract Failable flatMap( + ThrowableFunction, E> throwableFunction, + Function> exceptionHandler); /** - * Checks if this instance is a present instance of {@code Failable}. + * Checks if this instance is a present instance of {@code Failable}. * - * @return true if this instance is an instance of {@code Failable} else false. + * @return true if this instance is an instance of {@code Failable} else false. */ public abstract boolean isPresent(); /** - * Checks if this instance is an absent instance of {@code Failable}. + * Checks if this instance is an absent instance of {@code Failable}. * - * @return true if this instance is an absent instance of {@code Failable} else false. + * @return true if this instance is an absent instance of {@code Failable} else false. */ public abstract boolean isAbsent(); /** - * Checks if this instance is a failed instance of {@code Failable}. - * - * @return true if this instance is a failed instance of {@code Failable} else false. - */ - public abstract boolean isFailed(); - - /** - * Executes a {@code Runnable} object if this instance is a present instance of {@code Failable}. + * Executes a {@code Runnable} object if this instance is a present instance of {@code Failable}. * * @param runner {@code Runnable} object to run. * @return This instance. */ - public abstract Failable ifPresent(Runnable runner); + public abstract Failable ifPresent(Runnable runner); /** - * Consumes the item stored in this instance if this instance is a present instance of {@code Failable}. + * Consumes the item stored in this instance if this instance is a present instance of {@code Failable}. * * @param consumer {@code Consumer} object to consume the item stored in this instance. * @return This instance. */ - public abstract Failable ifPresent(Consumer consumer); + public abstract Failable ifPresent(Consumer consumer); /** - * Executes a {@code Runnable} object if this instance is a absent instance of {@code Failable}. + * Executes a {@code Runnable} object if this instance is a absent instance of {@code Failable}. * * @param runner {@code Runnable} object to run. * @return This instance. */ - public abstract Failable ifAbsent(Runnable runner); - - /** - * Consumes the exception stored in this instance if this instance is a failed instance of {@code Failable}. - * - * @param consumer {@code Consumer} object to consume the exception stored in this instance. - * @return This instance. - */ - public abstract Failable ifFailed(Consumer consumer); + public abstract Failable ifAbsent(Runnable runner); /** - * Executes a {@code Runnable} object if this instance is a failed instance of {@code Failable}. - * - * @param runner {@code Runnable} object to run. - * @return This instance. - */ - public abstract Failable ifFailed(Runnable runner); - - /** - * Throws the exception stored in this {@code Failable} object only if this instance is a failed - * instance, otherwise return this instance as is. - * - * @return This instance if this instance is not a failed instance. - * @throws E if this instance is a failed instance, else no exceptions are thrown. - */ - public abstract Failable ifFailThenThrow() throws E; - - /** - * Successful instance of {@code Failable}. + * Successful instance of {@code Failable}. * * @param Generic type {@code T}. */ - private static final class Success extends Failable { + private static final class Success extends Failable { private final T item; private Success(T item) { this.item = item; } - @Override - public Failable failWith(Z exception) { - return Failable.success(this.item); - } - @Override public T get() { return this.item; @@ -326,12 +278,7 @@ public T orElse(T item) { } @Override - public Failable unfailableMap(Function function) { - return Failable.of(() -> function.apply(this.item)); - } - - @Override - public Failable filter(Predicate predicate) { + public Failable filter(Predicate predicate) { if (predicate.test(this.item)) { return this; } @@ -340,46 +287,35 @@ public Failable filter(Predicate predicate) { } @Override - public Failable map( - ThrowableFunction throwableFunction) { - try { - return Failable.of(() -> throwableFunction.apply(this.item)); - } catch (Throwable throwable) { - return Failable.ifFailElseThrow(throwable); - } - } - - @Override - public Failable unfailableFlatMap( - Function> function) { - @SuppressWarnings("unchecked") - Failable current = (Failable) function.apply(this.item); - return current; + public Failable map(Function function) { + return Failable.of(() -> function.apply(this.item)); } @Override - public Failable flatMap( - ThrowableFunction, Z> throwableFunction) { - try { - @SuppressWarnings("unchecked") - Failable current = (Failable) throwableFunction.apply(this.item); - return current; - } catch (Throwable throwable) { - return Failable.ifFailElseThrow(throwable); - } + public Failable map( + ThrowableFunction throwableFunction, + Function exceptionHandler) { + return Failable.of(() -> throwableFunction.apply(this.item), exceptionHandler); } @Override - public Failable resolve(Consumer consumer) { - return this; + public Failable flatMap(Function> function) { + return function.apply(this.item); } @Override - public Failable resolve(Consumer consumer, T with) { - return this; + public Failable flatMap( + ThrowableFunction, E> throwableFunction, + Function> exceptionHandler) { + try { + return throwableFunction.apply(this.item); + } catch (Throwable throwable) { + @SuppressWarnings("unchecked") + E exception = (E) throwable; + return (exceptionHandler.apply(exception)); + } } - @Override public boolean isPresent() { return true; @@ -391,39 +327,19 @@ public boolean isAbsent() { } @Override - public boolean isFailed() { - return false; - } - - @Override - public Failable ifPresent(Runnable runner) { + public Failable ifPresent(Runnable runner) { runner.run(); return this; } @Override - public Failable ifPresent(Consumer consumer) { + public Failable ifPresent(Consumer consumer) { consumer.accept(this.item); return this; } @Override - public Failable ifAbsent(Runnable runner) { - return this; - } - - @Override - public Failable ifFailed(Consumer consumer) { - return this; - } - - @Override - public Failable ifFailed(Runnable runner) { - return this; - } - - @Override - public Failable ifFailThenThrow() throws CannotFailException { + public Failable ifAbsent(Runnable runner) { return this; } @@ -443,65 +359,49 @@ public boolean equals(Object obj) { } /** - * Empty instance of {@code Failable}. - * - * @param Generic type {@code T}. + * Empty instance of {@code Failable}. */ - private static final class Empty extends Failable { + private static final class Empty extends Failable { private Empty() { } @Override - public Failable failWith(Z exception) { - return Failable.empty(); - } - - @Override - public T get() { - throw new NoSuchElementException("No element in Failable"); + public Object get() { + throw new NoSuchElementException("Empty instance of Failable contains no items"); } @Override - public T orElse(T item) { + public Object orElse(Object item) { return item; } @Override - public Failable unfailableMap(Function function) { - return Failable.empty(); - } - - @Override - public Failable filter(Predicate predicate) { - return Failable.empty(); - } - - @Override - public Failable map( - ThrowableFunction throwableFunction) { + public Failable filter(Predicate predicate) { return Failable.empty(); } @Override - public Failable unfailableFlatMap( - Function> function) { + public Failable map(Function function) { return Failable.empty(); } @Override - public Failable flatMap( - ThrowableFunction, Z> throwableFunction) { + public Failable map( + ThrowableFunction throwableFunction, + Function exceptionHandler) { return Failable.empty(); } @Override - public Failable resolve(Consumer consumer) { + public Failable flatMap(Function> function) { return Failable.empty(); } @Override - public Failable resolve(Consumer consumer, T with) { + public Failable flatMap( + ThrowableFunction, E> throwableFunction, + Function> exceptionHandler) { return Failable.empty(); } @@ -516,180 +416,24 @@ public boolean isAbsent() { } @Override - public boolean isFailed() { - return false; - } - - @Override - public Failable ifPresent(Runnable runner) { + public Failable ifPresent(Runnable runner) { return this; } @Override - public Failable ifPresent(Consumer consumer) { + public Failable ifPresent(Consumer consumer) { return this; } @Override - public Failable ifAbsent(Runnable runner) { + public Failable ifAbsent(Runnable runner) { runner.run(); return this; } - @Override - public Failable ifFailed(Consumer consumer) { - return this; - } - - @Override - public Failable ifFailed(Runnable runner) { - return this; - } - - @Override - public Failable ifFailThenThrow() throws CannotFailException { - return this; - } - @Override public boolean equals(Object obj) { return obj instanceof Empty; } } - - /** - * Failed instance of {@code Failable}. - * - * @param Generic type {@code T}. - * @param Generic type {@code E} bounded by {@code Throwable}. - */ - private static final class Fail extends Failable { - private final E exception; - - private Fail(E exception) { - this.exception = exception; - } - - private Class getExceptionClass() { - return exception.getClass(); - } - - @Override - public Failable failWith(Z exception) { - return Failable.fail(exception); - } - - @Override - public T get() { - throw new NoSuchElementException("No element in Failable"); - } - - @Override - public T orElse(T item) { - return item; - } - - @Override - public Failable unfailableMap(Function function) { - return Failable.fail(this.exception); - } - - @Override - public Failable filter(Predicate predicate) { - return Failable.fail(this.exception); - } - - @Override - public Failable map( - ThrowableFunction throwableFunction) { - return Failable.ifFailElseThrow(this.exception); - } - - @Override - public Failable unfailableFlatMap(Function> function) { - return Failable.fail(this.exception); - } - - public Failable flatMap( - ThrowableFunction, Z> throwableFunction) { - return Failable.ifFailElseThrow(this.exception); - } - - @Override - public Failable resolve(Consumer consumer) { - return Failable.empty(); - } - - @Override - public Failable resolve(Consumer consumer, T with) { - consumer.accept(this.exception); - return Failable.success(with); - } - - @Override - public boolean isPresent() { - return false; - } - - @Override - public boolean isAbsent() { - return false; - } - - @Override - public boolean isFailed() { - return true; - } - - @Override - public Failable ifPresent(Runnable runner) { - return this; - } - - @Override - public Failable ifPresent(Consumer consumer) { - return this; - } - - @Override - public Failable ifAbsent(Runnable runner) { - return this; - } - - @Override - public Failable ifFailed(Consumer consumer) { - consumer.accept(this.exception); - return this; - } - - @Override - public Failable ifFailed(Runnable runner) { - runner.run(); - return this; - } - - @Override - public Failable ifFailThenThrow() throws E { - throw this.exception; - } - - @Override - public String toString() { - return this.exception.toString(); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (obj instanceof Fail) { - Fail failed = (Fail) obj; - return failed.exception.equals(this.exception); - } - - return false; - } - } } diff --git a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java index 052918786e..9b2f66ee0a 100644 --- a/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java +++ b/src/test/java/reposense/authorship/AnnotatorAnalyzerTest.java @@ -69,9 +69,6 @@ public void analyzeAnnotation_authorNamePresentInConfig_overrideAuthorship() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -84,9 +81,6 @@ public void analyzeAnnotation_authorNameNotInConfigAndNoAuthorConfigFile_acceptT .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_OVERRIDE_AUTHORSHIP_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -100,9 +94,6 @@ public void analyzeAnnotation_authorNameNotInConfigAndHaveAuthorConfigFile_disow .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_DISOWN_CODE_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); diff --git a/src/test/java/reposense/authorship/FileAnalyzerTest.java b/src/test/java/reposense/authorship/FileAnalyzerTest.java index 1295810048..638474cab7 100644 --- a/src/test/java/reposense/authorship/FileAnalyzerTest.java +++ b/src/test/java/reposense/authorship/FileAnalyzerTest.java @@ -91,9 +91,6 @@ public void blameTest() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -114,9 +111,6 @@ public void blameWithPreviousAuthorsTest() { removeTestIgnoreRevsFile(); assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -130,9 +124,6 @@ public void movedFileBlameTest() { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -147,9 +138,6 @@ public void blameTestDateRange() throws Exception { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_BLAME_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -172,9 +160,6 @@ public void blameWithPreviousAuthorsTestDateRange() throws Exception { removeTestIgnoreRevsFile(); assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_PREVIOUS_AUTHORS_BLAME_TEST)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); @@ -189,9 +174,6 @@ public void movedFileBlameTestDateRange() throws Exception { .ifPresent(x -> { assertFileAnalysisCorrectness(x, Arrays.asList(EXPECTED_LINE_AUTHORS_MOVED_FILE)); }) - .ifFailed(x -> { - throw x; - }) .ifAbsent(() -> { throw new AssertionError(); }); diff --git a/src/test/java/reposense/model/RepoConfigurationTest.java b/src/test/java/reposense/model/RepoConfigurationTest.java index 60b2892660..6723a0d641 100644 --- a/src/test/java/reposense/model/RepoConfigurationTest.java +++ b/src/test/java/reposense/model/RepoConfigurationTest.java @@ -18,9 +18,9 @@ import reposense.parser.ArgsParser; import reposense.parser.AuthorConfigCsvParser; -import reposense.parser.ConfigurationBuildException; import reposense.parser.GroupConfigCsvParser; import reposense.parser.RepoConfigCsvParser; +import reposense.parser.exceptions.ConfigurationBuildException; import reposense.report.ReportGenerator; import reposense.util.InputBuilder; import reposense.util.TestRepoCloner; diff --git a/src/test/java/reposense/template/GitTestTemplate.java b/src/test/java/reposense/template/GitTestTemplate.java index b277367196..aec2ea61a4 100644 --- a/src/test/java/reposense/template/GitTestTemplate.java +++ b/src/test/java/reposense/template/GitTestTemplate.java @@ -30,7 +30,6 @@ import reposense.model.RepoLocation; import reposense.util.FileUtil; import reposense.util.TestRepoCloner; -import reposense.util.function.CannotFailException; import reposense.util.function.Failable; /** @@ -190,7 +189,7 @@ public void assertFileAnalysisCorrectness(FileResult fileResult, List ex } } - public Failable getFileResult(String relativePath) { + public Failable getFileResult(String relativePath) { FileInfo fileInfo = fileInfoExtractor.generateFileInfo(configs.get(), relativePath); return fileInfoAnalyzer.analyzeTextFile(configs.get(), fileInfo); } diff --git a/src/test/java/reposense/util/FailableTest.java b/src/test/java/reposense/util/FailableTest.java index 84133114ea..01012f7d0a 100644 --- a/src/test/java/reposense/util/FailableTest.java +++ b/src/test/java/reposense/util/FailableTest.java @@ -5,33 +5,22 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import reposense.util.function.CannotFailException; import reposense.util.function.Failable; public class FailableTest { - private static final Failable PRESENT = Failable.success(123); - private static final Failable ABSENT = Failable.empty(); - private static final Failable FAIL = Failable.fail(new Exception()); + private static final Failable PRESENT = Failable.success(123); + private static final Failable ABSENT = Failable.empty(); @Test public void isPresent_testAll_success() { Assertions.assertTrue(PRESENT.isPresent()); Assertions.assertFalse(ABSENT.isPresent()); - Assertions.assertFalse(FAIL.isPresent()); } @Test public void isAbsent_testAll_success() { Assertions.assertFalse(PRESENT.isAbsent()); Assertions.assertTrue(ABSENT.isAbsent()); - Assertions.assertFalse(FAIL.isAbsent()); - } - - @Test - public void isFail_testAll_success() { - Assertions.assertFalse(PRESENT.isFailed()); - Assertions.assertFalse(ABSENT.isFailed()); - Assertions.assertTrue(FAIL.isFailed()); } @Test @@ -55,10 +44,10 @@ public void of_nonNullItemSupplier_success() { } @Test - public void of_thrownException_becomesFailure() { - Assertions.assertTrue(Failable.of(() -> { - throw new Exception(); - }).isFailed()); + public void of_thrownException_recovered() { + Assertions.assertEquals(Failable.of(() -> { + throw new IllegalAccessError(); + }, x -> 1).get(), 1); } @Test @@ -82,10 +71,10 @@ public void ofNullable_nonNullItemSupplier_success() { } @Test - public void ofNullable_thrownException_becomesFailure() { - Assertions.assertTrue(Failable.ofNullable(() -> { - throw new Exception(); - }).isFailed()); + public void ofNullable_thrownException_recovered() { + Assertions.assertEquals(Failable.ofNullable(() -> { + throw new IllegalAccessError(); + }, x -> 1).get(), 1); } @Test @@ -98,119 +87,67 @@ public void ofAbsent_noStoredValueCheck_success() { Assertions.assertThrows(NoSuchElementException.class, () -> Failable.empty().get()); } - @Test - public void ofFailure_invalidFailureNull_throwsException() { - Assertions.assertDoesNotThrow(() -> Failable.fail(null)); - } - - @Test - public void ofFailure_validFailureException_success() { - Assertions.assertDoesNotThrow(() -> Failable.fail(new Exception("should not be thrown"))); - } - @Test public void ifPresent_testAll_success() { - int[] testArray = {-1, -1, -1}; + int[] testArray = {-1, -1}; PRESENT.ifPresent(() -> testArray[0] = 1); ABSENT.ifPresent(() -> testArray[1] = 1); - FAIL.ifPresent(() -> testArray[2] = 1); - Assertions.assertArrayEquals(testArray, new int[]{1, -1, -1}); + Assertions.assertArrayEquals(testArray, new int[]{1, -1}); } @Test public void ifAbsent_testAll_success() { - int[] testArray = {-1, -1, -1}; + int[] testArray = {-1, -1}; PRESENT.ifAbsent(() -> testArray[0] = 1); ABSENT.ifAbsent(() -> testArray[1] = 1); - FAIL.ifAbsent(() -> testArray[2] = 1); - - Assertions.assertArrayEquals(testArray, new int[]{-1, 1, -1}); - } - @Test - public void ifFailed_testAll_success() { - int[] testArray = {-1, -1, -1, -1}; - - PRESENT.ifFailed(() -> testArray[0] = 1); - PRESENT.ifFailed(x -> testArray[0] = 2); - ABSENT.ifFailed(() -> testArray[1] = 1); - ABSENT.ifFailed(x -> testArray[1] = 2); - FAIL.ifFailed(() -> testArray[2] = 1); - FAIL.ifFailed(x -> { - if (x instanceof Exception) { - testArray[3] = 2; - } - }); - - Assertions.assertArrayEquals(testArray, new int[]{-1, -1, 1, 2}); + Assertions.assertArrayEquals(testArray, new int[]{-1, 1}); } - @Test public void map_testAll_success() { Assertions.assertEquals(PRESENT.map(x -> "String").get(), "String"); Assertions.assertEquals(ABSENT.map(x -> "String"), ABSENT); - Assertions.assertEquals(FAIL.map(x -> "String"), FAIL); } @Test public void map_throwingFunctionTestAll_success() { - Assertions.assertInstanceOf(FAIL.getClass(), PRESENT.map(x -> { + Assertions.assertInstanceOf(PRESENT.getClass(), PRESENT.map(x -> { throw new Exception(); - })); + }, x -> 1000)); Assertions.assertInstanceOf(ABSENT.getClass(), ABSENT.map(x -> { throw new Exception(); - })); - - Failable failedMapping = FAIL.map(x -> { - throw new Exception(); - }); - Assertions.assertInstanceOf(FAIL.getClass(), failedMapping); - Assertions.assertThrows(Exception.class, failedMapping::ifFailThenThrow); + }, x -> 1)); } @Test public void unfailableMap_testAll_success() { - Assertions.assertEquals(PRESENT.unfailableMap(x -> "String").get(), "String"); - Assertions.assertEquals(ABSENT.unfailableMap(x -> "String"), ABSENT); - Assertions.assertThrows(Exception.class, () -> FAIL.unfailableMap(x -> "String").ifFailThenThrow()); + Assertions.assertEquals(PRESENT.map(x -> "String").get(), "String"); + Assertions.assertEquals(ABSENT.map(x -> "String"), ABSENT); } @Test public void flatMap_testAll_success() { Assertions.assertEquals(PRESENT.flatMap(x -> Failable.success("String")).get(), "String"); Assertions.assertEquals(ABSENT.flatMap(x -> Failable.success("String")), ABSENT); - Assertions.assertEquals(FAIL.flatMap(x -> Failable.success("String")), FAIL); } @Test - public void flatMap_throwingFunctionTestAll_success() throws Throwable { - Assertions.assertInstanceOf(FAIL.getClass(), PRESENT.flatMap(x -> { - throw new Exception(); - })); + public void flatMap_throwingFunctionTestAll_success() { + Assertions.assertInstanceOf(PRESENT.getClass(), PRESENT.flatMap(x -> Failable.success(2))); + Assertions.assertInstanceOf(ABSENT.getClass(), PRESENT.flatMap(x -> Failable.empty())); + Assertions.assertInstanceOf(ABSENT.getClass(), ABSENT.map(x -> { throw new Exception(); - })); - - // mapping turns the failed instance to the new exception class, - // BUT it will only contain the first exception - Failable failedMapping = FAIL.flatMap(x -> { - throw new IllegalAccessError(); - }); - Assertions.assertInstanceOf(FAIL.getClass(), failedMapping); - - // users must be aware of what exceptions that can cause it to fail before considering - // what to catch/throw - Assertions.assertThrows(Exception.class, failedMapping::ifFailThenThrow); + }, x -> 100)); } @Test public void unfailableFlatMap_testAll_success() { - Assertions.assertEquals(PRESENT.unfailableFlatMap(x -> Failable.success("String")).get(), "String"); - Assertions.assertEquals(ABSENT.unfailableFlatMap(x -> Failable.success("String")), ABSENT); - Assertions.assertEquals(FAIL.unfailableFlatMap(x -> Failable.success("String")), FAIL); + Assertions.assertEquals(PRESENT.flatMap(x -> Failable.success("String")).get(), "String"); + Assertions.assertEquals(ABSENT.flatMap(x -> Failable.success("String")), ABSENT); } @Test @@ -223,47 +160,16 @@ public void filter_absentFailable_success() { Assertions.assertSame(ABSENT.filter(x -> x < 0), ABSENT); } - @Test - public void filter_failedFailable_success() { - Assertions.assertEquals(FAIL.filter(x -> x < 0), FAIL); - } - @Test public void get_testAll_success() { Assertions.assertEquals(PRESENT.get(), 123); Assertions.assertThrows(NoSuchElementException.class, ABSENT::get); - Assertions.assertThrows(NoSuchElementException.class, FAIL::get); } @Test public void orElse_testAll_success() { Assertions.assertEquals(PRESENT.orElse(999), 123); Assertions.assertEquals(ABSENT.orElse(999), 999); - Assertions.assertEquals(FAIL.orElse(999), 999); - } - - @Test - public void ifFailThenThrow_testAll_success() { - Assertions.assertDoesNotThrow(PRESENT::ifFailThenThrow); - Assertions.assertDoesNotThrow(ABSENT::ifFailThenThrow); - Assertions.assertThrows(Exception.class, FAIL::ifFailThenThrow); - } - - @Test - public void failWith_testAll_success() { - Assertions.assertDoesNotThrow(() -> PRESENT.failWith(new Throwable()).ifFailThenThrow()); - Assertions.assertDoesNotThrow(() -> ABSENT.failWith(new Throwable()).ifFailThenThrow()); - Assertions.assertThrows(Throwable.class, () -> FAIL.failWith(new Throwable()).ifFailThenThrow()); - } - - @Test - public void resolve_withoutReplacingValue_success() { - Assertions.assertEquals(FAIL.resolve(x -> Assertions.assertInstanceOf(Exception.class, x)), ABSENT); - } - - @Test - public void resolve_withReplacingValue_success() { - Assertions.assertEquals(FAIL.resolve(x -> Assertions.assertInstanceOf(Exception.class, x), 1).get(), 1); } @Test @@ -271,7 +177,6 @@ public void equals_presentFailable_multipleTests() { Assertions.assertSame(PRESENT, PRESENT); Assertions.assertEquals(PRESENT, PRESENT); Assertions.assertNotEquals(PRESENT, ABSENT); - Assertions.assertNotEquals(PRESENT, FAIL); Assertions.assertNotEquals(PRESENT, Failable.success("String")); } @@ -280,14 +185,5 @@ public void equals_absentFailable_multipleTests() { Assertions.assertSame(ABSENT, ABSENT); Assertions.assertEquals(ABSENT, ABSENT); Assertions.assertNotEquals(ABSENT, PRESENT); - Assertions.assertNotEquals(ABSENT, FAIL); - } - - @Test - public void equals_failedFailable_multipleTests() { - Assertions.assertSame(FAIL, FAIL); - Assertions.assertEquals(FAIL, FAIL); - Assertions.assertNotEquals(FAIL, PRESENT); - Assertions.assertNotEquals(FAIL, ABSENT); } } From 967b37040c6386264e33e0950fcffca1faf57a53 Mon Sep 17 00:00:00 2001 From: George Tay Date: Thu, 4 Apr 2024 11:10:51 +0800 Subject: [PATCH 11/11] Fix wrong imports --- src/main/java/reposense/authorship/FileInfoAnalyzer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index 5caf972060..20bb4ae531 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -21,6 +21,7 @@ import reposense.model.RepoConfiguration; import reposense.system.LogsManager; import reposense.util.FileUtil; +import reposense.util.StringsUtil; import reposense.util.function.Failable; /**