From b7a64d4b6f1dd175006bcbf628a999336b3f33dd Mon Sep 17 00:00:00 2001 From: Emil Koutanov Date: Sat, 21 Apr 2018 22:15:44 +1000 Subject: [PATCH] Argument transforms are now done by the Args utility --- README.md | 23 ++++++++-- .../com/obsidiandynamics/zerolog/Args.java | 33 +++++++++++++++ .../obsidiandynamics/zerolog/NopLogChain.java | 5 --- .../com/obsidiandynamics/zerolog/Zlg.java | 2 - .../com/obsidiandynamics/zerolog/ZlgImpl.java | 5 --- .../obsidiandynamics/zerolog/ArgsTest.java | 42 +++++++++++++++++++ .../zerolog/NopLogChainTest.java | 9 ++-- .../obsidiandynamics/zerolog/ZlgImplTest.java | 11 +++-- .../zerolog/sample/LazyLogSample.java | 19 +++++++-- 9 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/obsidiandynamics/zerolog/Args.java create mode 100644 src/test/java/com/obsidiandynamics/zerolog/ArgsTest.java diff --git a/README.md b/README.md index c6bc398..a8ea4ab 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ By simply changing `list.size()` to `list::size` we avoid a potentially superflu ## Transforms Often we won't have the luxury of invoking a single no-arg method on an object to obtain a nice, log-friendly representation. Zlg provides a convenient way of extracting a lazily-evaluated transform into a separate static method, taking a single argument — the object to transform. -In the next example, we are searching for a person's name from a list of people. If the name isn't found, we'd like to log the list's contents, but not reveal people's surnames. The transform in question is a static `tokeniseSurnames()` function, taking a collection of `Name` objects. To append the transform, we call the overloaded `arg(T value, Function transform)` method in the log chain, providing both the raw (untransformed) value and the transform method reference. The rest is Zlg's problem. +In the next example, we are searching for a person's name from a list of people. If the name isn't found, we'd like to log the list's contents, but not reveal people's surnames. The transform in question is a static `tokeniseSurnames()` function, taking a collection of `Name` objects. To append the transform, we use the `Args.map(Supplier, Function)` utility method, providing both the raw (untransformed) value reference and the transform method reference. The rest is Zlg's problem. ```java private static final Zlg zlg = Zlg.forDeclaringClass().get(); @@ -111,22 +111,37 @@ public static final class Name { } } -public static void logWithDescriber() { +public static void logWithTransform() { final List hackers = Arrays.asList(new Name("Kevin", "Flynn"), new Name("Thomas", "Anderson"), new Name("Angela", "Bennett")); final String surnameToFind = "Smith"; if (! hackers.stream().anyMatch(n -> n.surname.contains(surnameToFind))) { - zlg.i("%s not found among %s", z -> z.arg(surnameToFind).arg(hackers, LazyLogSample::tokeniseSurnames)); + zlg.i("%s not found among %s", + z -> z.arg(surnameToFind).arg(Args.map(Args.ref(hackers), LazyLogSample::tokeniseSurnames))); } } -public static List tokeniseSurnames(Collection names) { +private static List tokeniseSurnames(Collection names) { return names.stream().map(n -> n.forename + " " + n.surname.replaceAll(".", "X")).collect(toList()); } ``` +The value being transformed may itself be retrieved lazily. The example below prints the current time using a custom `DateFormat`; the `Date` object is conditionally instantiated. + +```java +private static final Zlg zlg = Zlg.forDeclaringClass().get(); + +public static void logWithSupplierAndTransform() { + zlg.i("The current time is %s", z -> z.arg(Args.map(Date::new, LazyLogSample::formatDate))); +} + +private static String formatDate(Date date) { + return new SimpleDateFormat("MMM dd HH:mm:ss").format(date); +} +``` + One thing to note about transforms and suppliers: they are code like any other and should be unit tested accordingly. You might have a buggy transform and, due to its lazy evaluation, fail to pick up on it when testing code that contains the log instruction (if logging was suppressed). Because transforms and suppliers are simple, single-responsibility 'pure' functions, unit testing them should be straightforward. # Tags diff --git a/src/main/java/com/obsidiandynamics/zerolog/Args.java b/src/main/java/com/obsidiandynamics/zerolog/Args.java new file mode 100644 index 0000000..72b730e --- /dev/null +++ b/src/main/java/com/obsidiandynamics/zerolog/Args.java @@ -0,0 +1,33 @@ +package com.obsidiandynamics.zerolog; + +import java.util.function.*; + +/** + * Utilities for lazily-invoked argument transformations. + */ +public final class Args { + private Args() {} + + /** + * Forms a supplier of the given object reference. + * + * @param Value type. + * @param value The value to supply. + * @return A {@link Supplier} of the given value. + */ + public static Supplier ref(T value) { + return () -> value; + } + + /** + * Maps the value returned by the given supplier to any type via the given transform. + * + * @param Value type. + * @param supplier The source of the value to transform. + * @param transform The transform function. + * @return A {@link Supplier} that will perform the transform when invoked. + */ + public static Supplier map(Supplier supplier, Function transform) { + return () -> transform.apply(supplier.get()); + } +} diff --git a/src/main/java/com/obsidiandynamics/zerolog/NopLogChain.java b/src/main/java/com/obsidiandynamics/zerolog/NopLogChain.java index 8227e5d..99c7b58 100644 --- a/src/main/java/com/obsidiandynamics/zerolog/NopLogChain.java +++ b/src/main/java/com/obsidiandynamics/zerolog/NopLogChain.java @@ -91,11 +91,6 @@ public LogChain arg(Supplier supplier) { return this; } - @Override - public LogChain arg(T value, Function transform) { - return this; - } - @Override public LogChain threw(Throwable throwable) { return this; diff --git a/src/main/java/com/obsidiandynamics/zerolog/Zlg.java b/src/main/java/com/obsidiandynamics/zerolog/Zlg.java index d50d07a..a384220 100644 --- a/src/main/java/com/obsidiandynamics/zerolog/Zlg.java +++ b/src/main/java/com/obsidiandynamics/zerolog/Zlg.java @@ -40,8 +40,6 @@ interface LogChain { LogChain arg(Supplier supplier); - LogChain arg(T value, Function transform); - LogChain threw(Throwable throwable); default void with(Consumer logChainConsumer) { diff --git a/src/main/java/com/obsidiandynamics/zerolog/ZlgImpl.java b/src/main/java/com/obsidiandynamics/zerolog/ZlgImpl.java index fe01fd2..8e2f9d7 100644 --- a/src/main/java/com/obsidiandynamics/zerolog/ZlgImpl.java +++ b/src/main/java/com/obsidiandynamics/zerolog/ZlgImpl.java @@ -119,11 +119,6 @@ public LogChain arg(Object arg) { public LogChain arg(Supplier supplier) { return appendArg(supplier.get()); } - - @Override - public LogChain arg(T value, Function transform) { - return appendArg(transform.apply(value)); - } private LogChain appendArg(Object arg) { if (argc == MAX_ARGS) throw new TooManyArgsException("Number of args cannot exceed " + MAX_ARGS); diff --git a/src/test/java/com/obsidiandynamics/zerolog/ArgsTest.java b/src/test/java/com/obsidiandynamics/zerolog/ArgsTest.java new file mode 100644 index 0000000..17634c9 --- /dev/null +++ b/src/test/java/com/obsidiandynamics/zerolog/ArgsTest.java @@ -0,0 +1,42 @@ +package com.obsidiandynamics.zerolog; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.function.*; + +import org.junit.*; + +import com.obsidiandynamics.assertion.*; +import com.obsidiandynamics.func.*; + +public final class ArgsTest { + @Test + public void testConformance() { + Assertions.assertUtilityClassWellDefined(Args.class); + } + + @Test + public void testRef() { + final Object obj = "obj"; + assertSame(obj, Args.ref(obj).get()); + } + + @Test + public void testMap() { + final Supplier supplier = Classes.cast(mock(Supplier.class)); + when(supplier.get()).thenReturn("str"); + + final Function transform = Classes.cast(mock(Function.class)); + when(transform.apply(any())).thenReturn("transformed"); + + final Supplier x = Args.map(supplier, transform); + verifyNoMoreInteractions(supplier); + verifyNoMoreInteractions(transform); + + assertEquals("transformed", x.get()); + verify(supplier).get(); + verify(transform).apply(eq("str")); + } +} diff --git a/src/test/java/com/obsidiandynamics/zerolog/NopLogChainTest.java b/src/test/java/com/obsidiandynamics/zerolog/NopLogChainTest.java index 398723d..3679748 100644 --- a/src/test/java/com/obsidiandynamics/zerolog/NopLogChainTest.java +++ b/src/test/java/com/obsidiandynamics/zerolog/NopLogChainTest.java @@ -39,8 +39,7 @@ public void testLazy() { final DoubleSupplier doubleSupplier = mock(DoubleSupplier.class); final IntSupplier intSupplier = mock(IntSupplier.class); final LongSupplier longSupplier = mock(LongSupplier.class); - final Supplier objectSupplier = mock(Supplier.class); - final Function transform = Classes.cast(mock(Function.class)); + final Supplier stringSupplier = Classes.cast(mock(Supplier.class)); final LogChain end = chain .format("format") @@ -48,8 +47,7 @@ public void testLazy() { .arg(doubleSupplier) .arg(intSupplier) .arg(longSupplier) - .arg(objectSupplier) - .arg("test", transform); + .arg(stringSupplier); assertSame(chain, end); end.done(); // does nothing @@ -58,7 +56,6 @@ public void testLazy() { verifyNoMoreInteractions(doubleSupplier); verifyNoMoreInteractions(intSupplier); verifyNoMoreInteractions(longSupplier); - verifyNoMoreInteractions(objectSupplier); - verifyNoMoreInteractions(transform); + verifyNoMoreInteractions(stringSupplier); } } diff --git a/src/test/java/com/obsidiandynamics/zerolog/ZlgImplTest.java b/src/test/java/com/obsidiandynamics/zerolog/ZlgImplTest.java index fc4dccd..264d30c 100644 --- a/src/test/java/com/obsidiandynamics/zerolog/ZlgImplTest.java +++ b/src/test/java/com/obsidiandynamics/zerolog/ZlgImplTest.java @@ -236,8 +236,9 @@ public void testLazy() { final DoubleSupplier doubleSupplier = mock(DoubleSupplier.class); final IntSupplier intSupplier = mock(IntSupplier.class); final LongSupplier longSupplier = mock(LongSupplier.class); - final Supplier objectSupplier = mock(Supplier.class); - final Function transform = Classes.cast(mock(Function.class)); + final Supplier stringSupplier = Classes.cast(mock(Supplier.class)); + when(stringSupplier.get()).thenReturn("suppliedString"); + zlg .level(LogLevel.INFO) .format("format") @@ -245,15 +246,13 @@ public void testLazy() { .arg(doubleSupplier) .arg(intSupplier) .arg(longSupplier) - .arg(objectSupplier) - .arg("test", transform) + .arg(stringSupplier) .done(); verify(booleanSupplier).getAsBoolean(); verify(doubleSupplier).getAsDouble(); verify(intSupplier).getAsInt(); verify(longSupplier).getAsLong(); - verify(objectSupplier).get(); - verify(transform).apply(eq("test")); + verify(stringSupplier).get(); } } diff --git a/src/test/java/com/obsidiandynamics/zerolog/sample/LazyLogSample.java b/src/test/java/com/obsidiandynamics/zerolog/sample/LazyLogSample.java index 2584a9d..ecde1d3 100644 --- a/src/test/java/com/obsidiandynamics/zerolog/sample/LazyLogSample.java +++ b/src/test/java/com/obsidiandynamics/zerolog/sample/LazyLogSample.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.*; +import java.text.*; import java.util.*; import com.obsidiandynamics.zerolog.*; @@ -28,23 +29,33 @@ public static final class Name { } } - public static void logWithDescriber() { + public static void logWithTransform() { final List hackers = Arrays.asList(new Name("Kevin", "Flynn"), new Name("Thomas", "Anderson"), new Name("Angela", "Bennett")); final String surnameToFind = "Smith"; if (! hackers.stream().anyMatch(n -> n.surname.contains(surnameToFind))) { - zlg.i("%s not found among %s", z -> z.arg(surnameToFind).arg(hackers, LazyLogSample::tokeniseSurnames)); + zlg.i("%s not found among %s", + z -> z.arg(surnameToFind).arg(Args.map(Args.ref(hackers), LazyLogSample::tokeniseSurnames))); } } - public static List tokeniseSurnames(Collection names) { + private static List tokeniseSurnames(Collection names) { return names.stream().map(n -> n.forename + " " + n.surname.replaceAll(".", "X")).collect(toList()); } + public static void logWithSupplierAndTransform() { + zlg.i("The current time is %s", z -> z.arg(Args.map(Date::new, LazyLogSample::formatDate))); + } + + private static String formatDate(Date date) { + return new SimpleDateFormat("MMM dd HH:mm:ss").format(date); + } + public static void main(String[] args) { logWithSupplier(); - logWithDescriber(); + logWithTransform(); + logWithSupplierAndTransform(); } }