Skip to content

Commit

Permalink
Argument transforms are now done by the Args utility
Browse files Browse the repository at this point in the history
  • Loading branch information
ekoutanov committed Apr 21, 2018
1 parent 4dc532e commit b7a64d4
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 32 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<? super T, ?> 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();
Expand All @@ -111,22 +111,37 @@ public static final class Name {
}
}

public static void logWithDescriber() {
public static void logWithTransform() {
final List<Name> 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<String> tokeniseSurnames(Collection<Name> names) {
private static List<String> tokeniseSurnames(Collection<Name> 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
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/obsidiandynamics/zerolog/Args.java
Original file line number Diff line number Diff line change
@@ -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 <T> Value type.
* @param value The value to supply.
* @return A {@link Supplier} of the given value.
*/
public static <T> Supplier<T> ref(T value) {
return () -> value;
}

/**
* Maps the value returned by the given supplier to any type via the given transform.
*
* @param <T> 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 <T> Supplier<?> map(Supplier<? extends T> supplier, Function<? super T, ?> transform) {
return () -> transform.apply(supplier.get());
}
}
5 changes: 0 additions & 5 deletions src/main/java/com/obsidiandynamics/zerolog/NopLogChain.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@ public LogChain arg(Supplier<?> supplier) {
return this;
}

@Override
public <T> LogChain arg(T value, Function<? super T, ?> transform) {
return this;
}

@Override
public LogChain threw(Throwable throwable) {
return this;
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/com/obsidiandynamics/zerolog/Zlg.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ interface LogChain {

LogChain arg(Supplier<?> supplier);

<T> LogChain arg(T value, Function<? super T, ?> transform);

LogChain threw(Throwable throwable);

default void with(Consumer<LogChain> logChainConsumer) {
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/com/obsidiandynamics/zerolog/ZlgImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ public LogChain arg(Object arg) {
public LogChain arg(Supplier<?> supplier) {
return appendArg(supplier.get());
}

@Override
public <T> LogChain arg(T value, Function<? super T, ?> 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);
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/com/obsidiandynamics/zerolog/ArgsTest.java
Original file line number Diff line number Diff line change
@@ -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<String> supplier = Classes.cast(mock(Supplier.class));
when(supplier.get()).thenReturn("str");

final Function<String, Object> 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"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,15 @@ 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<String, ?> transform = Classes.cast(mock(Function.class));
final Supplier<String> stringSupplier = Classes.cast(mock(Supplier.class));

final LogChain end = chain
.format("format")
.arg(booleanSupplier)
.arg(doubleSupplier)
.arg(intSupplier)
.arg(longSupplier)
.arg(objectSupplier)
.arg("test", transform);
.arg(stringSupplier);
assertSame(chain, end);

end.done(); // does nothing
Expand All @@ -58,7 +56,6 @@ public void testLazy() {
verifyNoMoreInteractions(doubleSupplier);
verifyNoMoreInteractions(intSupplier);
verifyNoMoreInteractions(longSupplier);
verifyNoMoreInteractions(objectSupplier);
verifyNoMoreInteractions(transform);
verifyNoMoreInteractions(stringSupplier);
}
}
11 changes: 5 additions & 6 deletions src/test/java/com/obsidiandynamics/zerolog/ZlgImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,24 +236,23 @@ 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<String, ?> transform = Classes.cast(mock(Function.class));
final Supplier<String> stringSupplier = Classes.cast(mock(Supplier.class));
when(stringSupplier.get()).thenReturn("suppliedString");

zlg
.level(LogLevel.INFO)
.format("format")
.arg(booleanSupplier)
.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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static java.util.stream.Collectors.*;

import java.text.*;
import java.util.*;

import com.obsidiandynamics.zerolog.*;
Expand All @@ -28,23 +29,33 @@ public static final class Name {
}
}

public static void logWithDescriber() {
public static void logWithTransform() {
final List<Name> 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<String> tokeniseSurnames(Collection<Name> names) {
private static List<String> tokeniseSurnames(Collection<Name> 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();
}
}

0 comments on commit b7a64d4

Please sign in to comment.