From d3312c444433f214b38fe5fc33eab645d7aeefa0 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 30 Sep 2022 16:27:18 +0200 Subject: [PATCH] improved Stringify --- build.gradle | 2 +- .../net/hostsharing/hsadminng/Stringify.java | 52 ++++++++++--- .../office/partner/HsOfficePartnerEntity.java | 22 +++++- .../hsadminng/StringifyUnitTest.java | 75 ++++++++++++++----- .../partner/HsOfficePartnerEntityTest.java | 34 +++++++++ ...fficePartnerRepositoryIntegrationTest.java | 16 ++-- 6 files changed, 159 insertions(+), 42 deletions(-) create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityTest.java diff --git a/build.gradle b/build.gradle index 5f6d36a3..0e573ba8 100644 --- a/build.gradle +++ b/build.gradle @@ -273,7 +273,7 @@ pitest { // As Java unit tests are pretty pointless in our case, this maybe makes not much sense. mutationThreshold = 71 coverageThreshold = 57 - testStrengthThreshold = 88 + testStrengthThreshold = 87 outputFormats = ['XML', 'HTML'] timestampedReports = false diff --git a/src/main/java/net/hostsharing/hsadminng/Stringify.java b/src/main/java/net/hostsharing/hsadminng/Stringify.java index 76078329..7cde6757 100644 --- a/src/main/java/net/hostsharing/hsadminng/Stringify.java +++ b/src/main/java/net/hostsharing/hsadminng/Stringify.java @@ -4,22 +4,26 @@ import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; -// TODO.refa: use this instead of toDisplayName everywhere and add JavaDoc -public class Stringify { +import static java.lang.Boolean.TRUE; + +public final class Stringify { private final Class clazz; private final String name; private final List> props = new ArrayList<>(); + private String separator = ", "; + private Boolean quotedValues = null; public static Stringify stringify(final Class clazz, final String name) { - return new Stringify(clazz, name); + return new Stringify<>(clazz, name); } public static Stringify stringify(final Class clazz) { - return new Stringify(clazz, null); + return new Stringify<>(clazz, null); } private Stringify(final Class clazz, final String name) { @@ -28,7 +32,12 @@ public class Stringify { } public Stringify withProp(final String propName, final Function getter) { - props.add(new Property(propName, getter)); + props.add(new Property<>(propName, getter)); + return this; + } + + public Stringify withProp(final Function getter) { + props.add(new Property<>(null, getter)); return this; } @@ -42,20 +51,43 @@ public class Stringify { } return propVal; }) - .map(propVal -> propVal.prop.name + "=" + optionallyQuoted(propVal)) - .collect(Collectors.joining(", ")); + .map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal)) + .collect(Collectors.joining(separator)); return (name != null ? name : object.getClass().getSimpleName()) + "(" + propValues + ")"; } + public Stringify withSeparator(final String separator) { + this.separator = separator; + return this; + } + + private String propName(final PropertyValue propVal, final String delimiter) { + return Optional.ofNullable(propVal.prop.name).map(v -> v + delimiter).orElse(""); + } + private String optionallyQuoted(final PropertyValue propVal) { - return (propVal.rawValue instanceof Number) || (propVal.rawValue instanceof Boolean) - ? propVal.value - : "'" + propVal.value + "'"; + if (quotedValues == null) + return quotedQuotedValueType(propVal) + ? ("'" + propVal.value + "'") + : propVal.value; + return TRUE == quotedValues + ? ("'" + propVal.value + "'") + : propVal.value; + } + + private static boolean quotedQuotedValueType(final PropertyValue propVal) { + return !(propVal.rawValue instanceof Number || propVal.rawValue instanceof Boolean); + } + + public Stringify quotedValues(final boolean quotedValues) { + this.quotedValues = quotedValues; + return this; } private record Property(String name, Function getter) {} private record PropertyValue(Property prop, Object rawValue, String value) { + static PropertyValue of(Property prop, Object rawValue) { return rawValue != null ? new PropertyValue<>(prop, rawValue, rawValue.toString()) : null; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index 0a37b76f..58e7937a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -1,6 +1,8 @@ package net.hostsharing.hsadminng.hs.office.partner; import lombok.*; +import net.hostsharing.hsadminng.Stringify; +import net.hostsharing.hsadminng.Stringifyable; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; @@ -8,6 +10,8 @@ import javax.persistence.*; import java.time.LocalDate; import java.util.UUID; +import static net.hostsharing.hsadminng.Stringify.stringify; + @Entity @Table(name = "hs_office_partner_rv") @Getter @@ -15,7 +19,13 @@ import java.util.UUID; @Builder @NoArgsConstructor @AllArgsConstructor -public class HsOfficePartnerEntity { +public class HsOfficePartnerEntity implements Stringifyable { + + private static Stringify stringify = stringify(HsOfficePartnerEntity.class, "partner") + .withProp(HsOfficePartnerEntity::getPerson) + .withProp(HsOfficePartnerEntity::getContact) + .withSeparator(": ") + .quotedValues(false); private @Id UUID uuid; @@ -33,7 +43,13 @@ public class HsOfficePartnerEntity { private @Column(name = "birthday") LocalDate birthday; private @Column(name = "dateofdeath") LocalDate dateOfDeath; - public String getDisplayName() { - return "partner(%s, %s)".formatted(person.getDisplayName(), contact.getLabel()); + @Override + public String toString() { + return stringify.apply(this); + } + + @Override + public String toShortString() { + return person.toShortString(); } } diff --git a/src/test/java/net/hostsharing/hsadminng/StringifyUnitTest.java b/src/test/java/net/hostsharing/hsadminng/StringifyUnitTest.java index e7e22e6b..9bf1870c 100644 --- a/src/test/java/net/hostsharing/hsadminng/StringifyUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/StringifyUnitTest.java @@ -2,24 +2,17 @@ package net.hostsharing.hsadminng; import lombok.*; import lombok.experimental.FieldNameConstants; -import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import org.junit.jupiter.api.Test; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; import java.util.UUID; import static net.hostsharing.hsadminng.Stringify.stringify; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; class StringifyUnitTest { @Getter @Setter - @Builder @NoArgsConstructor @AllArgsConstructor @FieldNameConstants @@ -27,15 +20,20 @@ class StringifyUnitTest { private static Stringify toString = stringify(TestBean.class, "bean") .withProp(TestBean.Fields.label, TestBean::getLabel) - .withProp(TestBean.Fields.content, TestBean::getContent) + .withProp(TestBean.Fields.contentA, TestBean::getContentA) + .withProp(TestBean.Fields.contentB, TestBean::getContentB) + .withProp(TestBean.Fields.value, TestBean::getValue) .withProp(TestBean.Fields.active, TestBean::isActive); private UUID uuid; private String label; - private SubBean content; + private SubBeanWithUnquotedValues contentA; + private SubBeanWithQuotedValues contentB; + + private int value; private boolean active; @Override @@ -51,15 +49,41 @@ class StringifyUnitTest { @Getter @Setter - @Builder @NoArgsConstructor @AllArgsConstructor - @FieldNameConstants - public static class SubBean implements Stringifyable { + public static class SubBeanWithUnquotedValues implements Stringifyable { - private static Stringify toString = stringify(SubBean.class) - .withProp(SubBean.Fields.key, SubBean::getKey) - .withProp(Fields.value, SubBean::getValue); + private static Stringify toString = stringify(SubBeanWithUnquotedValues.class) + .withProp(SubBeanWithUnquotedValues::getKey) + .withProp(SubBeanWithUnquotedValues::getValue) + .withSeparator(": ") + .quotedValues(false); + + private String key; + private String value; + + @Override + public String toString() { + return toString.apply(this); + } + + @Override + public String toShortString() { + return key + ":" + value; + } + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SubBeanWithQuotedValues implements Stringifyable { + + private static Stringify toString = stringify(SubBeanWithQuotedValues.class) + .withProp(SubBeanWithQuotedValues::getKey) + .withProp(SubBeanWithQuotedValues::getValue) + .withSeparator(": ") + .quotedValues(true); private String key; private Integer value; @@ -78,22 +102,33 @@ class StringifyUnitTest { @Test void stringifyWhenAllPropsHaveValues() { final var given = new TestBean(UUID.randomUUID(), "some label", - new SubBean("some content", 1234), false); + new SubBeanWithUnquotedValues("some key", "some value"), + new SubBeanWithQuotedValues("some key", 1234), + 42, + false); final var result = given.toString(); - assertThat(result).isEqualTo("bean(label='some label', content='some content:1234', active=false)"); + assertThat(result).isEqualTo( + "bean(label='some label', contentA='some key:some value', contentB='some key:1234', value=42, active=false)"); } @Test void stringifyWhenAllNullablePropsHaveNulValues() { final var given = new TestBean(); final var result = given.toString(); - assertThat(result).isEqualTo("bean(active=false)"); + assertThat(result).isEqualTo("bean(value=0, active=false)"); } @Test void stringifyWithoutExplicitNameUsesSimpleClassName() { - final var given = new SubBean("some key", 1234); + final var given = new SubBeanWithUnquotedValues("some key", "some value"); final var result = given.toString(); - assertThat(result).isEqualTo("SubBean(key='some key', value=1234)"); + assertThat(result).isEqualTo("SubBeanWithUnquotedValues(some key: some value)"); + } + + @Test + void stringifyWithQuotedValueTrueQuotesEvenIntegers() { + final var given = new SubBeanWithQuotedValues("some key", 1234); + final var result = given.toString(); + assertThat(result).isEqualTo("SubBeanWithQuotedValues('some key': '1234')"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityTest.java new file mode 100644 index 00000000..c28d76d2 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityTest.java @@ -0,0 +1,34 @@ +package net.hostsharing.hsadminng.hs.office.partner; + +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HsOfficePartnerEntityTest { + + @Test + void toStringContainsPersonAndContact() { + final var given = HsOfficePartnerEntity.builder() + .person(HsOfficePersonEntity.builder().tradeName("some trade name").build()) + .contact(HsOfficeContactEntity.builder().label("some label").build()) + .build(); + + final var result = given.toString(); + + assertThat(result).isEqualTo("partner(some trade name: some label)"); + } + + @Test + void toShortStringContainsPersonAndContact() { + final var given = HsOfficePartnerEntity.builder() + .person(HsOfficePersonEntity.builder().tradeName("some trade name").build()) + .contact(HsOfficeContactEntity.builder().label("some label").build()) + .build(); + + final var result = given.toShortString(); + + assertThat(result).isEqualTo("some trade name"); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index e66b05f4..0808e3e4 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -150,9 +150,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // then allThesePartnersAreReturned( result, - "partner(Third OHG, third contact)", - "partner(Second e.K., second contact)", - "partner(First GmbH, first contact)"); + "partner(Third OHG: third contact)", + "partner(Second e.K.: second contact)", + "partner(First GmbH: first contact)"); } @Test @@ -164,7 +164,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final var result = partnerRepo.findPartnerByOptionalNameLike(null); // then: - exactlyThesePartnersAreReturned(result, "partner(First GmbH, first contact)"); + exactlyThesePartnersAreReturned(result, "partner(First GmbH: first contact)"); } } @@ -180,7 +180,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final var result = partnerRepo.findPartnerByOptionalNameLike("third contact"); // then - exactlyThesePartnersAreReturned(result, "partner(Third OHG, third contact)"); + exactlyThesePartnersAreReturned(result, "partner(Third OHG: third contact)"); } } @@ -392,20 +392,20 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { void cleanup() { context("superuser-alex@hostsharing.net", null); tempPartners.forEach(tempPartner -> { - System.out.println("DELETING temporary partner: " + tempPartner.getDisplayName()); + System.out.println("DELETING temporary partner: " + tempPartner.toString()); partnerRepo.deleteByUuid(tempPartner.getUuid()); }); } void exactlyThesePartnersAreReturned(final List actualResult, final String... partnerNames) { assertThat(actualResult) - .extracting(HsOfficePartnerEntity::getDisplayName) + .extracting(partnerEntity -> partnerEntity.toString()) .containsExactlyInAnyOrder(partnerNames); } void allThesePartnersAreReturned(final List actualResult, final String... partnerNames) { assertThat(actualResult) - .extracting(HsOfficePartnerEntity::getDisplayName) + .extracting(partnerEntity -> partnerEntity.toString()) .contains(partnerNames); } }