improved Stringify

This commit is contained in:
Michael Hoennig 2022-09-30 16:27:18 +02:00
parent 31854bb838
commit d3312c4444
6 changed files with 159 additions and 42 deletions

View File

@ -273,7 +273,7 @@ pitest {
// As Java unit tests are pretty pointless in our case, this maybe makes not much sense. // As Java unit tests are pretty pointless in our case, this maybe makes not much sense.
mutationThreshold = 71 mutationThreshold = 71
coverageThreshold = 57 coverageThreshold = 57
testStrengthThreshold = 88 testStrengthThreshold = 87
outputFormats = ['XML', 'HTML'] outputFormats = ['XML', 'HTML']
timestampedReports = false timestampedReports = false

View File

@ -4,22 +4,26 @@ import javax.validation.constraints.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
// TODO.refa: use this instead of toDisplayName everywhere and add JavaDoc import static java.lang.Boolean.TRUE;
public class Stringify<B> {
public final class Stringify<B> {
private final Class<B> clazz; private final Class<B> clazz;
private final String name; private final String name;
private final List<Property<B>> props = new ArrayList<>(); private final List<Property<B>> props = new ArrayList<>();
private String separator = ", ";
private Boolean quotedValues = null;
public static <B> Stringify<B> stringify(final Class<B> clazz, final String name) { public static <B> Stringify<B> stringify(final Class<B> clazz, final String name) {
return new Stringify<B>(clazz, name); return new Stringify<>(clazz, name);
} }
public static <B> Stringify<B> stringify(final Class<B> clazz) { public static <B> Stringify<B> stringify(final Class<B> clazz) {
return new Stringify<B>(clazz, null); return new Stringify<>(clazz, null);
} }
private Stringify(final Class<B> clazz, final String name) { private Stringify(final Class<B> clazz, final String name) {
@ -28,7 +32,12 @@ public class Stringify<B> {
} }
public Stringify<B> withProp(final String propName, final Function<B, ?> getter) { public Stringify<B> withProp(final String propName, final Function<B, ?> getter) {
props.add(new Property<B>(propName, getter)); props.add(new Property<>(propName, getter));
return this;
}
public Stringify<B> withProp(final Function<B, ?> getter) {
props.add(new Property<>(null, getter));
return this; return this;
} }
@ -42,20 +51,43 @@ public class Stringify<B> {
} }
return propVal; return propVal;
}) })
.map(propVal -> propVal.prop.name + "=" + optionallyQuoted(propVal)) .map(propVal -> propName(propVal, "=") + optionallyQuoted(propVal))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(separator));
return (name != null ? name : object.getClass().getSimpleName()) + "(" + propValues + ")"; return (name != null ? name : object.getClass().getSimpleName()) + "(" + propValues + ")";
} }
public Stringify<B> withSeparator(final String separator) {
this.separator = separator;
return this;
}
private String propName(final PropertyValue<B> propVal, final String delimiter) {
return Optional.ofNullable(propVal.prop.name).map(v -> v + delimiter).orElse("");
}
private String optionallyQuoted(final PropertyValue<B> propVal) { private String optionallyQuoted(final PropertyValue<B> propVal) {
return (propVal.rawValue instanceof Number) || (propVal.rawValue instanceof Boolean) if (quotedValues == null)
? propVal.value return quotedQuotedValueType(propVal)
: "'" + propVal.value + "'"; ? ("'" + propVal.value + "'")
: propVal.value;
return TRUE == quotedValues
? ("'" + propVal.value + "'")
: propVal.value;
}
private static <B> boolean quotedQuotedValueType(final PropertyValue<B> propVal) {
return !(propVal.rawValue instanceof Number || propVal.rawValue instanceof Boolean);
}
public Stringify<B> quotedValues(final boolean quotedValues) {
this.quotedValues = quotedValues;
return this;
} }
private record Property<B>(String name, Function<B, ?> getter) {} private record Property<B>(String name, Function<B, ?> getter) {}
private record PropertyValue<B>(Property<B> prop, Object rawValue, String value) { private record PropertyValue<B>(Property<B> prop, Object rawValue, String value) {
static <B> PropertyValue<B> of(Property<B> prop, Object rawValue) { static <B> PropertyValue<B> of(Property<B> prop, Object rawValue) {
return rawValue != null ? new PropertyValue<>(prop, rawValue, rawValue.toString()) : null; return rawValue != null ? new PropertyValue<>(prop, rawValue, rawValue.toString()) : null;
} }

View File

@ -1,6 +1,8 @@
package net.hostsharing.hsadminng.hs.office.partner; package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*; 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.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
@ -8,6 +10,8 @@ import javax.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.Stringify.stringify;
@Entity @Entity
@Table(name = "hs_office_partner_rv") @Table(name = "hs_office_partner_rv")
@Getter @Getter
@ -15,7 +19,13 @@ import java.util.UUID;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class HsOfficePartnerEntity { public class HsOfficePartnerEntity implements Stringifyable {
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
.withProp(HsOfficePartnerEntity::getPerson)
.withProp(HsOfficePartnerEntity::getContact)
.withSeparator(": ")
.quotedValues(false);
private @Id UUID uuid; private @Id UUID uuid;
@ -33,7 +43,13 @@ public class HsOfficePartnerEntity {
private @Column(name = "birthday") LocalDate birthday; private @Column(name = "birthday") LocalDate birthday;
private @Column(name = "dateofdeath") LocalDate dateOfDeath; private @Column(name = "dateofdeath") LocalDate dateOfDeath;
public String getDisplayName() { @Override
return "partner(%s, %s)".formatted(person.getDisplayName(), contact.getLabel()); public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return person.toShortString();
} }
} }

View File

@ -2,24 +2,17 @@ package net.hostsharing.hsadminng;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import org.junit.jupiter.api.Test; 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 java.util.UUID;
import static net.hostsharing.hsadminng.Stringify.stringify; import static net.hostsharing.hsadminng.Stringify.stringify;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class StringifyUnitTest { class StringifyUnitTest {
@Getter @Getter
@Setter @Setter
@Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants @FieldNameConstants
@ -27,15 +20,20 @@ class StringifyUnitTest {
private static Stringify<TestBean> toString = stringify(TestBean.class, "bean") private static Stringify<TestBean> toString = stringify(TestBean.class, "bean")
.withProp(TestBean.Fields.label, TestBean::getLabel) .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); .withProp(TestBean.Fields.active, TestBean::isActive);
private UUID uuid; private UUID uuid;
private String label; private String label;
private SubBean content; private SubBeanWithUnquotedValues contentA;
private SubBeanWithQuotedValues contentB;
private int value;
private boolean active; private boolean active;
@Override @Override
@ -51,15 +49,41 @@ class StringifyUnitTest {
@Getter @Getter
@Setter @Setter
@Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants public static class SubBeanWithUnquotedValues implements Stringifyable {
public static class SubBean implements Stringifyable {
private static Stringify<SubBean> toString = stringify(SubBean.class) private static Stringify<SubBeanWithUnquotedValues> toString = stringify(SubBeanWithUnquotedValues.class)
.withProp(SubBean.Fields.key, SubBean::getKey) .withProp(SubBeanWithUnquotedValues::getKey)
.withProp(Fields.value, SubBean::getValue); .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<SubBeanWithQuotedValues> toString = stringify(SubBeanWithQuotedValues.class)
.withProp(SubBeanWithQuotedValues::getKey)
.withProp(SubBeanWithQuotedValues::getValue)
.withSeparator(": ")
.quotedValues(true);
private String key; private String key;
private Integer value; private Integer value;
@ -78,22 +102,33 @@ class StringifyUnitTest {
@Test @Test
void stringifyWhenAllPropsHaveValues() { void stringifyWhenAllPropsHaveValues() {
final var given = new TestBean(UUID.randomUUID(), "some label", 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(); 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 @Test
void stringifyWhenAllNullablePropsHaveNulValues() { void stringifyWhenAllNullablePropsHaveNulValues() {
final var given = new TestBean(); final var given = new TestBean();
final var result = given.toString(); final var result = given.toString();
assertThat(result).isEqualTo("bean(active=false)"); assertThat(result).isEqualTo("bean(value=0, active=false)");
} }
@Test @Test
void stringifyWithoutExplicitNameUsesSimpleClassName() { void stringifyWithoutExplicitNameUsesSimpleClassName() {
final var given = new SubBean("some key", 1234); final var given = new SubBeanWithUnquotedValues("some key", "some value");
final var result = given.toString(); 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')");
} }
} }

View File

@ -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");
}
}

View File

@ -150,9 +150,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
allThesePartnersAreReturned( allThesePartnersAreReturned(
result, result,
"partner(Third OHG, third contact)", "partner(Third OHG: third contact)",
"partner(Second e.K., second contact)", "partner(Second e.K.: second contact)",
"partner(First GmbH, first contact)"); "partner(First GmbH: first contact)");
} }
@Test @Test
@ -164,7 +164,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
final var result = partnerRepo.findPartnerByOptionalNameLike(null); final var result = partnerRepo.findPartnerByOptionalNameLike(null);
// then: // 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"); final var result = partnerRepo.findPartnerByOptionalNameLike("third contact");
// then // 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() { void cleanup() {
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
tempPartners.forEach(tempPartner -> { tempPartners.forEach(tempPartner -> {
System.out.println("DELETING temporary partner: " + tempPartner.getDisplayName()); System.out.println("DELETING temporary partner: " + tempPartner.toString());
partnerRepo.deleteByUuid(tempPartner.getUuid()); partnerRepo.deleteByUuid(tempPartner.getUuid());
}); });
} }
void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) { void exactlyThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
assertThat(actualResult) assertThat(actualResult)
.extracting(HsOfficePartnerEntity::getDisplayName) .extracting(partnerEntity -> partnerEntity.toString())
.containsExactlyInAnyOrder(partnerNames); .containsExactlyInAnyOrder(partnerNames);
} }
void allThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) { void allThesePartnersAreReturned(final List<HsOfficePartnerEntity> actualResult, final String... partnerNames) {
assertThat(actualResult) assertThat(actualResult)
.extracting(HsOfficePartnerEntity::getDisplayName) .extracting(partnerEntity -> partnerEntity.toString())
.contains(partnerNames); .contains(partnerNames);
} }
} }