From df2c2c627eeae211ccd7997d27f9dcf6039b7a30 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Wed, 23 Feb 2022 08:28:13 -0500 Subject: [PATCH] Support exclusion of empty collections using `experimentalExcludeEmptyCollections` (#1670) Support exclusion of empty collections using `experimentalExcludeEmptyCollections` --- changelog/@unreleased/pr-1670.v2.yml | 5 + ...liasOptionalDoubleAliasedBinaryResult.java | 6 + .../product/CollectionsTestAliasList.java | 55 +++ .../product/CollectionsTestAliasMap.java | 55 +++ .../product/CollectionsTestAliasSet.java | 55 +++ .../product/CollectionsTestObject.java | 315 ++++++++++++++++++ .../java/com/palantir/product/ListAlias.java | 6 + .../com/palantir/product/MapAliasExample.java | 6 + .../com/palantir/product/OptionalAlias.java | 6 + .../product/OptionalAliasExample.java | 2 +- .../product/OptionalListAliasExample.java | 6 + .../product/OptionalMapAliasExample.java | 6 + .../product/OptionalSetAliasExample.java | 6 + .../product/PrimitiveOptionalsExample.java | 10 +- .../java/com/palantir/product/SetAlias.java | 6 + .../com/palantir/product/StringAliasTwo.java | 6 + .../com/palantir/product/ListAlias.java | 6 + .../com/palantir/product/MapAliasExample.java | 6 + .../com/palantir/product/OptionalAlias.java | 6 + .../product/OptionalAliasExample.java | 2 +- .../product/OptionalListAliasExample.java | 6 + .../product/OptionalMapAliasExample.java | 6 + .../product/OptionalSetAliasExample.java | 6 + .../product/PrimitiveOptionalsExample.java | 10 +- .../prefix/com/palantir/product/SetAlias.java | 6 + .../com/palantir/product/StringAliasTwo.java | 6 + .../com/palantir/conjure/java/Options.java | 8 + .../conjure/java/types/AliasGenerator.java | 17 +- .../java/types/BeanBuilderGenerator.java | 13 + .../conjure/java/types/BeanGenerator.java | 12 + .../conjure/java/visitor/MoreVisitors.java | 18 + .../java/types/BeanSerdeIntegrationTests.java | 36 ++ .../java/types/ObjectGeneratorTests.java | 13 + .../resources/exclude-empty-collections.yml | 19 ++ .../conjure/java/cli/ConjureJavaCli.java | 9 + 35 files changed, 743 insertions(+), 13 deletions(-) create mode 100644 changelog/@unreleased/pr-1670.v2.yml create mode 100644 conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasList.java create mode 100644 conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasMap.java create mode 100644 conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasSet.java create mode 100644 conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestObject.java create mode 100644 conjure-java-core/src/test/resources/exclude-empty-collections.yml diff --git a/changelog/@unreleased/pr-1670.v2.yml b/changelog/@unreleased/pr-1670.v2.yml new file mode 100644 index 000000000..e80456d22 --- /dev/null +++ b/changelog/@unreleased/pr-1670.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Support exclusion of empty collections using `experimentalExcludeEmptyCollections` + links: + - https://github.com/palantir/conjure-java/pull/1670 diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/AliasOptionalDoubleAliasedBinaryResult.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/AliasOptionalDoubleAliasedBinaryResult.java index 3259d4975..f736fbb14 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/AliasOptionalDoubleAliasedBinaryResult.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/AliasOptionalDoubleAliasedBinaryResult.java @@ -9,6 +9,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class AliasOptionalDoubleAliasedBinaryResult { + private static final AliasOptionalDoubleAliasedBinaryResult EMPTY = new AliasOptionalDoubleAliasedBinaryResult(); + private final Optional value; private AliasOptionalDoubleAliasedBinaryResult(@Nonnull Optional value) { @@ -45,4 +47,8 @@ public int hashCode() { public static AliasOptionalDoubleAliasedBinaryResult of(@Nonnull Optional value) { return new AliasOptionalDoubleAliasedBinaryResult(value); } + + public static AliasOptionalDoubleAliasedBinaryResult empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasList.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasList.java new file mode 100644 index 000000000..1095b49a5 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasList.java @@ -0,0 +1,55 @@ +package com.palantir.product; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.palantir.logsafe.Preconditions; +import java.util.Collections; +import java.util.List; +import javax.annotation.Generated; +import javax.annotation.Nonnull; + +@Generated("com.palantir.conjure.java.types.AliasGenerator") +public final class CollectionsTestAliasList { + private static final CollectionsTestAliasList EMPTY = new CollectionsTestAliasList(); + + private final List value; + + private CollectionsTestAliasList(@Nonnull List value) { + this.value = Preconditions.checkNotNull(value, "value cannot be null"); + } + + private CollectionsTestAliasList() { + this(Collections.emptyList()); + } + + @JsonValue + public List get() { + return value; + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object other) { + return this == other + || (other instanceof CollectionsTestAliasList + && this.value.equals(((CollectionsTestAliasList) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static CollectionsTestAliasList of(@Nonnull List value) { + return new CollectionsTestAliasList(value); + } + + public static CollectionsTestAliasList empty() { + return EMPTY; + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasMap.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasMap.java new file mode 100644 index 000000000..2092b9a32 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasMap.java @@ -0,0 +1,55 @@ +package com.palantir.product; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.palantir.logsafe.Preconditions; +import java.util.Collections; +import java.util.Map; +import javax.annotation.Generated; +import javax.annotation.Nonnull; + +@Generated("com.palantir.conjure.java.types.AliasGenerator") +public final class CollectionsTestAliasMap { + private static final CollectionsTestAliasMap EMPTY = new CollectionsTestAliasMap(); + + private final Map value; + + private CollectionsTestAliasMap(@Nonnull Map value) { + this.value = Preconditions.checkNotNull(value, "value cannot be null"); + } + + private CollectionsTestAliasMap() { + this(Collections.emptyMap()); + } + + @JsonValue + public Map get() { + return value; + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object other) { + return this == other + || (other instanceof CollectionsTestAliasMap + && this.value.equals(((CollectionsTestAliasMap) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static CollectionsTestAliasMap of(@Nonnull Map value) { + return new CollectionsTestAliasMap(value); + } + + public static CollectionsTestAliasMap empty() { + return EMPTY; + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasSet.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasSet.java new file mode 100644 index 000000000..f0a8f8205 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestAliasSet.java @@ -0,0 +1,55 @@ +package com.palantir.product; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.palantir.logsafe.Preconditions; +import java.util.Collections; +import java.util.Set; +import javax.annotation.Generated; +import javax.annotation.Nonnull; + +@Generated("com.palantir.conjure.java.types.AliasGenerator") +public final class CollectionsTestAliasSet { + private static final CollectionsTestAliasSet EMPTY = new CollectionsTestAliasSet(); + + private final Set value; + + private CollectionsTestAliasSet(@Nonnull Set value) { + this.value = Preconditions.checkNotNull(value, "value cannot be null"); + } + + private CollectionsTestAliasSet() { + this(Collections.emptySet()); + } + + @JsonValue + public Set get() { + return value; + } + + @Override + public String toString() { + return value.toString(); + } + + @Override + public boolean equals(Object other) { + return this == other + || (other instanceof CollectionsTestAliasSet + && this.value.equals(((CollectionsTestAliasSet) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static CollectionsTestAliasSet of(@Nonnull Set value) { + return new CollectionsTestAliasSet(value); + } + + public static CollectionsTestAliasSet empty() { + return EMPTY; + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestObject.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestObject.java new file mode 100644 index 000000000..f767f4ae1 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/CollectionsTestObject.java @@ -0,0 +1,315 @@ +package com.palantir.product; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.palantir.conjure.java.lib.internal.ConjureCollections; +import com.palantir.logsafe.Preconditions; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Generated; +import javax.annotation.Nonnull; + +@JsonDeserialize(builder = CollectionsTestObject.Builder.class) +@Generated("com.palantir.conjure.java.types.BeanGenerator") +public final class CollectionsTestObject { + private final List items; + + private final Map itemsMap; + + private final Optional optionalItem; + + private final Set itemsSet; + + private final CollectionsTestAliasList alist; + + private final CollectionsTestAliasSet aset; + + private final CollectionsTestAliasMap amap; + + private int memoizedHashCode; + + private CollectionsTestObject( + List items, + Map itemsMap, + Optional optionalItem, + Set itemsSet, + CollectionsTestAliasList alist, + CollectionsTestAliasSet aset, + CollectionsTestAliasMap amap) { + validateFields(items, itemsMap, optionalItem, itemsSet, alist, aset, amap); + this.items = Collections.unmodifiableList(items); + this.itemsMap = Collections.unmodifiableMap(itemsMap); + this.optionalItem = optionalItem; + this.itemsSet = Collections.unmodifiableSet(itemsSet); + this.alist = alist; + this.aset = aset; + this.amap = amap; + } + + @JsonProperty("items") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public List getItems() { + return this.items; + } + + @JsonProperty("itemsMap") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public Map getItemsMap() { + return this.itemsMap; + } + + @JsonProperty("optionalItem") + public Optional getOptionalItem() { + return this.optionalItem; + } + + @JsonProperty("itemsSet") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public Set getItemsSet() { + return this.itemsSet; + } + + @JsonProperty("alist") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public CollectionsTestAliasList getAlist() { + return this.alist; + } + + @JsonProperty("aset") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public CollectionsTestAliasSet getAset() { + return this.aset; + } + + @JsonProperty("amap") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public CollectionsTestAliasMap getAmap() { + return this.amap; + } + + @Override + public boolean equals(Object other) { + return this == other || (other instanceof CollectionsTestObject && equalTo((CollectionsTestObject) other)); + } + + private boolean equalTo(CollectionsTestObject other) { + return this.items.equals(other.items) + && this.itemsMap.equals(other.itemsMap) + && this.optionalItem.equals(other.optionalItem) + && this.itemsSet.equals(other.itemsSet) + && this.alist.equals(other.alist) + && this.aset.equals(other.aset) + && this.amap.equals(other.amap); + } + + @Override + public int hashCode() { + int result = memoizedHashCode; + if (result == 0) { + int hash = 1; + hash = 31 * hash + this.items.hashCode(); + hash = 31 * hash + this.itemsMap.hashCode(); + hash = 31 * hash + this.optionalItem.hashCode(); + hash = 31 * hash + this.itemsSet.hashCode(); + hash = 31 * hash + this.alist.hashCode(); + hash = 31 * hash + this.aset.hashCode(); + hash = 31 * hash + this.amap.hashCode(); + result = hash; + memoizedHashCode = result; + } + return result; + } + + @Override + public String toString() { + return "CollectionsTestObject{items: " + items + ", itemsMap: " + itemsMap + ", optionalItem: " + optionalItem + + ", itemsSet: " + itemsSet + ", alist: " + alist + ", aset: " + aset + ", amap: " + amap + '}'; + } + + private static void validateFields( + List items, + Map itemsMap, + Optional optionalItem, + Set itemsSet, + CollectionsTestAliasList alist, + CollectionsTestAliasSet aset, + CollectionsTestAliasMap amap) { + List missingFields = null; + missingFields = addFieldIfMissing(missingFields, items, "items"); + missingFields = addFieldIfMissing(missingFields, itemsMap, "itemsMap"); + missingFields = addFieldIfMissing(missingFields, optionalItem, "optionalItem"); + missingFields = addFieldIfMissing(missingFields, itemsSet, "itemsSet"); + missingFields = addFieldIfMissing(missingFields, alist, "alist"); + missingFields = addFieldIfMissing(missingFields, aset, "aset"); + missingFields = addFieldIfMissing(missingFields, amap, "amap"); + if (missingFields != null) { + throw new SafeIllegalArgumentException( + "Some required fields have not been set", SafeArg.of("missingFields", missingFields)); + } + } + + private static List addFieldIfMissing(List prev, Object fieldValue, String fieldName) { + List missingFields = prev; + if (fieldValue == null) { + if (missingFields == null) { + missingFields = new ArrayList<>(7); + } + missingFields.add(fieldName); + } + return missingFields; + } + + public static Builder builder() { + return new Builder(); + } + + @Generated("com.palantir.conjure.java.types.BeanBuilderGenerator") + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + boolean _buildInvoked; + + private List items = new ArrayList<>(); + + private Map itemsMap = new LinkedHashMap<>(); + + private Optional optionalItem = Optional.empty(); + + private Set itemsSet = new LinkedHashSet<>(); + + private CollectionsTestAliasList alist = CollectionsTestAliasList.empty(); + + private CollectionsTestAliasSet aset = CollectionsTestAliasSet.empty(); + + private CollectionsTestAliasMap amap = CollectionsTestAliasMap.empty(); + + private Builder() {} + + public Builder from(CollectionsTestObject other) { + checkNotBuilt(); + items(other.getItems()); + itemsMap(other.getItemsMap()); + optionalItem(other.getOptionalItem()); + itemsSet(other.getItemsSet()); + alist(other.getAlist()); + aset(other.getAset()); + amap(other.getAmap()); + return this; + } + + @JsonSetter(value = "items", nulls = Nulls.SKIP) + public Builder items(@Nonnull Iterable items) { + checkNotBuilt(); + this.items.clear(); + ConjureCollections.addAll(this.items, Preconditions.checkNotNull(items, "items cannot be null")); + return this; + } + + public Builder addAllItems(@Nonnull Iterable items) { + checkNotBuilt(); + ConjureCollections.addAll(this.items, Preconditions.checkNotNull(items, "items cannot be null")); + return this; + } + + public Builder items(String items) { + checkNotBuilt(); + this.items.add(items); + return this; + } + + @JsonSetter(value = "itemsMap", nulls = Nulls.SKIP) + public Builder itemsMap(@Nonnull Map itemsMap) { + checkNotBuilt(); + this.itemsMap.clear(); + this.itemsMap.putAll(Preconditions.checkNotNull(itemsMap, "itemsMap cannot be null")); + return this; + } + + public Builder putAllItemsMap(@Nonnull Map itemsMap) { + checkNotBuilt(); + this.itemsMap.putAll(Preconditions.checkNotNull(itemsMap, "itemsMap cannot be null")); + return this; + } + + public Builder itemsMap(String key, int value) { + checkNotBuilt(); + this.itemsMap.put(key, value); + return this; + } + + @JsonSetter(value = "optionalItem", nulls = Nulls.SKIP) + public Builder optionalItem(@Nonnull Optional optionalItem) { + checkNotBuilt(); + this.optionalItem = Preconditions.checkNotNull(optionalItem, "optionalItem cannot be null"); + return this; + } + + public Builder optionalItem(@Nonnull String optionalItem) { + checkNotBuilt(); + this.optionalItem = Optional.of(Preconditions.checkNotNull(optionalItem, "optionalItem cannot be null")); + return this; + } + + @JsonSetter(value = "itemsSet", nulls = Nulls.SKIP) + public Builder itemsSet(@Nonnull Iterable itemsSet) { + checkNotBuilt(); + this.itemsSet.clear(); + ConjureCollections.addAll(this.itemsSet, Preconditions.checkNotNull(itemsSet, "itemsSet cannot be null")); + return this; + } + + public Builder addAllItemsSet(@Nonnull Iterable itemsSet) { + checkNotBuilt(); + ConjureCollections.addAll(this.itemsSet, Preconditions.checkNotNull(itemsSet, "itemsSet cannot be null")); + return this; + } + + public Builder itemsSet(String itemsSet) { + checkNotBuilt(); + this.itemsSet.add(itemsSet); + return this; + } + + @JsonSetter(value = "alist", nulls = Nulls.AS_EMPTY) + public Builder alist(@Nonnull CollectionsTestAliasList alist) { + checkNotBuilt(); + this.alist = Preconditions.checkNotNull(alist, "alist cannot be null"); + return this; + } + + @JsonSetter(value = "aset", nulls = Nulls.AS_EMPTY) + public Builder aset(@Nonnull CollectionsTestAliasSet aset) { + checkNotBuilt(); + this.aset = Preconditions.checkNotNull(aset, "aset cannot be null"); + return this; + } + + @JsonSetter(value = "amap", nulls = Nulls.AS_EMPTY) + public Builder amap(@Nonnull CollectionsTestAliasMap amap) { + checkNotBuilt(); + this.amap = Preconditions.checkNotNull(amap, "amap cannot be null"); + return this; + } + + public CollectionsTestObject build() { + checkNotBuilt(); + this._buildInvoked = true; + return new CollectionsTestObject(items, itemsMap, optionalItem, itemsSet, alist, aset, amap); + } + + private void checkNotBuilt() { + Preconditions.checkState(!_buildInvoked, "Build has already been called"); + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/ListAlias.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/ListAlias.java index 574f55c88..27bbfc7ea 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/ListAlias.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/ListAlias.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class ListAlias { + private static final ListAlias EMPTY = new ListAlias(); + private final List value; private ListAlias(@Nonnull List value) { @@ -44,4 +46,8 @@ public int hashCode() { public static ListAlias of(@Nonnull List value) { return new ListAlias(value); } + + public static ListAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/MapAliasExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/MapAliasExample.java index e166e68c1..83d984f37 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/MapAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/MapAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class MapAliasExample { + private static final MapAliasExample EMPTY = new MapAliasExample(); + private final Map value; private MapAliasExample(@Nonnull Map value) { @@ -45,4 +47,8 @@ public int hashCode() { public static MapAliasExample of(@Nonnull Map value) { return new MapAliasExample(value); } + + public static MapAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAlias.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAlias.java index 01291efd6..b855a78dc 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAlias.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAlias.java @@ -9,6 +9,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalAlias { + private static final OptionalAlias EMPTY = new OptionalAlias(); + private final Optional value; private OptionalAlias(@Nonnull Optional value) { @@ -43,4 +45,8 @@ public int hashCode() { public static OptionalAlias of(@Nonnull Optional value) { return new OptionalAlias(value); } + + public static OptionalAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAliasExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAliasExample.java index 8c8db6437..dafa214ee 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalAliasExample.java @@ -80,7 +80,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; - private OptionalAlias optionalAlias; + private OptionalAlias optionalAlias = OptionalAlias.empty(); private Builder() {} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalListAliasExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalListAliasExample.java index ba1519dcc..6e483d2eb 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalListAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalListAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalListAliasExample { + private static final OptionalListAliasExample EMPTY = new OptionalListAliasExample(); + private final Optional> value; private OptionalListAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalListAliasExample of(@Nonnull Optional> value) { return new OptionalListAliasExample(value); } + + public static OptionalListAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalMapAliasExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalMapAliasExample.java index 9e8385685..50c8f48eb 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalMapAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalMapAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalMapAliasExample { + private static final OptionalMapAliasExample EMPTY = new OptionalMapAliasExample(); + private final Optional> value; private OptionalMapAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalMapAliasExample of(@Nonnull Optional> value) { return new OptionalMapAliasExample(value); } + + public static OptionalMapAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalSetAliasExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalSetAliasExample.java index 831aa03b6..50512c006 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalSetAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/OptionalSetAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalSetAliasExample { + private static final OptionalSetAliasExample EMPTY = new OptionalSetAliasExample(); + private final Optional> value; private OptionalSetAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalSetAliasExample of(@Nonnull Optional> value) { return new OptionalSetAliasExample(value); } + + public static OptionalSetAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/PrimitiveOptionalsExample.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/PrimitiveOptionalsExample.java index 79dc6466b..2ba7d675d 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/PrimitiveOptionalsExample.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/PrimitiveOptionalsExample.java @@ -381,19 +381,19 @@ public static final class Builder { private Optional aliasOne = Optional.empty(); - private StringAliasTwo aliasTwo; + private StringAliasTwo aliasTwo = StringAliasTwo.empty(); private Optional aliasList = Optional.empty(); private Optional aliasMap = Optional.empty(); - private OptionalAlias aliasOptional; + private OptionalAlias aliasOptional = OptionalAlias.empty(); - private OptionalMapAliasExample aliasOptionalMap; + private OptionalMapAliasExample aliasOptionalMap = OptionalMapAliasExample.empty(); - private OptionalListAliasExample aliasOptionalList; + private OptionalListAliasExample aliasOptionalList = OptionalListAliasExample.empty(); - private OptionalSetAliasExample aliasOptionalSet; + private OptionalSetAliasExample aliasOptionalSet = OptionalSetAliasExample.empty(); private Builder() {} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/SetAlias.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/SetAlias.java index e6cbd5278..45475fe42 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/SetAlias.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/SetAlias.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class SetAlias { + private static final SetAlias EMPTY = new SetAlias(); + private final Set value; private SetAlias(@Nonnull Set value) { @@ -44,4 +46,8 @@ public int hashCode() { public static SetAlias of(@Nonnull Set value) { return new SetAlias(value); } + + public static SetAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/StringAliasTwo.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/StringAliasTwo.java index ba74107a4..da0a156bf 100644 --- a/conjure-java-core/src/integrationInput/java/com/palantir/product/StringAliasTwo.java +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/StringAliasTwo.java @@ -9,6 +9,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class StringAliasTwo { + private static final StringAliasTwo EMPTY = new StringAliasTwo(); + private final Optional value; private StringAliasTwo(@Nonnull Optional value) { @@ -43,4 +45,8 @@ public int hashCode() { public static StringAliasTwo of(@Nonnull Optional value) { return new StringAliasTwo(value); } + + public static StringAliasTwo empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/ListAlias.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/ListAlias.java index d58ebd677..e1611b82f 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/ListAlias.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/ListAlias.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class ListAlias { + private static final ListAlias EMPTY = new ListAlias(); + private final List value; private ListAlias(@Nonnull List value) { @@ -44,4 +46,8 @@ public int hashCode() { public static ListAlias of(@Nonnull List value) { return new ListAlias(value); } + + public static ListAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/MapAliasExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/MapAliasExample.java index f3e7710fb..6b8095aa9 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/MapAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/MapAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class MapAliasExample { + private static final MapAliasExample EMPTY = new MapAliasExample(); + private final Map value; private MapAliasExample(@Nonnull Map value) { @@ -45,4 +47,8 @@ public int hashCode() { public static MapAliasExample of(@Nonnull Map value) { return new MapAliasExample(value); } + + public static MapAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAlias.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAlias.java index ee456f8cc..046d7aab3 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAlias.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAlias.java @@ -9,6 +9,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalAlias { + private static final OptionalAlias EMPTY = new OptionalAlias(); + private final Optional value; private OptionalAlias(@Nonnull Optional value) { @@ -43,4 +45,8 @@ public int hashCode() { public static OptionalAlias of(@Nonnull Optional value) { return new OptionalAlias(value); } + + public static OptionalAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAliasExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAliasExample.java index 1f3845536..50c39f8a3 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalAliasExample.java @@ -82,7 +82,7 @@ public static Builder builder() { public static final class Builder { boolean _buildInvoked; - private OptionalAlias optionalAlias; + private OptionalAlias optionalAlias = OptionalAlias.empty(); private Builder() {} diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalListAliasExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalListAliasExample.java index 817891253..fb311fae3 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalListAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalListAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalListAliasExample { + private static final OptionalListAliasExample EMPTY = new OptionalListAliasExample(); + private final Optional> value; private OptionalListAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalListAliasExample of(@Nonnull Optional> value) { return new OptionalListAliasExample(value); } + + public static OptionalListAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalMapAliasExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalMapAliasExample.java index 7c931d1ae..2e4e6b462 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalMapAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalMapAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalMapAliasExample { + private static final OptionalMapAliasExample EMPTY = new OptionalMapAliasExample(); + private final Optional> value; private OptionalMapAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalMapAliasExample of(@Nonnull Optional> value) { return new OptionalMapAliasExample(value); } + + public static OptionalMapAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalSetAliasExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalSetAliasExample.java index 233e4d178..78a85a6a8 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalSetAliasExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/OptionalSetAliasExample.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class OptionalSetAliasExample { + private static final OptionalSetAliasExample EMPTY = new OptionalSetAliasExample(); + private final Optional> value; private OptionalSetAliasExample(@Nonnull Optional> value) { @@ -46,4 +48,8 @@ public int hashCode() { public static OptionalSetAliasExample of(@Nonnull Optional> value) { return new OptionalSetAliasExample(value); } + + public static OptionalSetAliasExample empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/PrimitiveOptionalsExample.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/PrimitiveOptionalsExample.java index fe0bf5a1e..b0c4be7b1 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/PrimitiveOptionalsExample.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/PrimitiveOptionalsExample.java @@ -383,19 +383,19 @@ public static final class Builder { private Optional aliasOne = Optional.empty(); - private StringAliasTwo aliasTwo; + private StringAliasTwo aliasTwo = StringAliasTwo.empty(); private Optional aliasList = Optional.empty(); private Optional aliasMap = Optional.empty(); - private OptionalAlias aliasOptional; + private OptionalAlias aliasOptional = OptionalAlias.empty(); - private OptionalMapAliasExample aliasOptionalMap; + private OptionalMapAliasExample aliasOptionalMap = OptionalMapAliasExample.empty(); - private OptionalListAliasExample aliasOptionalList; + private OptionalListAliasExample aliasOptionalList = OptionalListAliasExample.empty(); - private OptionalSetAliasExample aliasOptionalSet; + private OptionalSetAliasExample aliasOptionalSet = OptionalSetAliasExample.empty(); private Builder() {} diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/SetAlias.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/SetAlias.java index 67f624e64..3a691fc32 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/SetAlias.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/SetAlias.java @@ -10,6 +10,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class SetAlias { + private static final SetAlias EMPTY = new SetAlias(); + private final Set value; private SetAlias(@Nonnull Set value) { @@ -44,4 +46,8 @@ public int hashCode() { public static SetAlias of(@Nonnull Set value) { return new SetAlias(value); } + + public static SetAlias empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/StringAliasTwo.java b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/StringAliasTwo.java index 67159ce4f..75e7838b7 100644 --- a/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/StringAliasTwo.java +++ b/conjure-java-core/src/integrationInput/java/test/prefix/com/palantir/product/StringAliasTwo.java @@ -9,6 +9,8 @@ @Generated("com.palantir.conjure.java.types.AliasGenerator") public final class StringAliasTwo { + private static final StringAliasTwo EMPTY = new StringAliasTwo(); + private final Optional value; private StringAliasTwo(@Nonnull Optional value) { @@ -43,4 +45,8 @@ public int hashCode() { public static StringAliasTwo of(@Nonnull Optional value) { return new StringAliasTwo(value); } + + public static StringAliasTwo empty() { + return EMPTY; + } } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/Options.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/Options.java index 1fce61341..70331f5f9 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/Options.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/Options.java @@ -119,6 +119,14 @@ default boolean excludeEmptyOptionals() { return false; } + /** + * Generated objects exclude fields with empty collection (list, set, and map) values. + */ + @Value.Default + default boolean excludeEmptyCollections() { + return false; + } + /** * Instructs the object generator to generate union visitors that expose the values of unknowns in addition to their * types. diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/AliasGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/AliasGenerator.java index 8ac92fac3..65d9231e2 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/AliasGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/AliasGenerator.java @@ -37,6 +37,7 @@ import com.palantir.logsafe.Preconditions; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; @@ -118,7 +119,10 @@ public static JavaFile generateAliasType(TypeMapper typeMapper, AliasDefinition .build()); // Generate a default constructor so that Jackson can construct a default instance when coercing from null - typeDef.getAlias().accept(new DefaultConstructorVisitor(aliasTypeName)).ifPresent(spec::addMethod); + typeDef.getAlias().accept(new DefaultConstructorVisitor(aliasTypeName)).ifPresent(ctor -> { + spec.addMethod(ctor); + addEmptyMethod(spec, thisClass); + }); if (isAliasOfDouble(typeDef)) { CodeBlock longCastCodeBlock = CodeBlock.builder() @@ -196,6 +200,17 @@ public static JavaFile generateAliasType(TypeMapper typeMapper, AliasDefinition .build(); } + private static void addEmptyMethod(TypeSpec.Builder spec, TypeName thisClass) { + spec.addField(FieldSpec.builder(thisClass, "EMPTY", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer(CodeBlock.of("new $T()", thisClass)) + .build()); + spec.addMethod(MethodSpec.methodBuilder("empty") + .returns(thisClass) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addStatement("return $N", "EMPTY") + .build()); + } + private static boolean isAliasOfDouble(AliasDefinition typeDef) { return typeDef.getAlias().accept(TypeVisitor.IS_PRIMITIVE) && typeDef.getAlias().accept(TypeVisitor.PRIMITIVE).equals(PrimitiveType.DOUBLE); diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java index 7255eb84d..c098effad 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanBuilderGenerator.java @@ -29,6 +29,7 @@ import com.palantir.conjure.java.util.JavaNameSanitizer; import com.palantir.conjure.java.util.TypeFunctions; import com.palantir.conjure.java.visitor.DefaultableTypeVisitor; +import com.palantir.conjure.java.visitor.MoreVisitors; import com.palantir.conjure.spec.ExternalReference; import com.palantir.conjure.spec.FieldDefinition; import com.palantir.conjure.spec.FieldName; @@ -247,6 +248,18 @@ private EnrichedField createField(FieldName fieldName, FieldDefinition field) { spec.initializer("new $T<>()", LinkedHashMap.class); } else if (field.getType().accept(TypeVisitor.IS_OPTIONAL)) { spec.initializer("$T.empty()", asRawType(typeMapper.getClassName(field.getType()))); + } else if (field.getType().accept(MoreVisitors.IS_INTERNAL_REFERENCE)) { + com.palantir.conjure.spec.TypeName name = field.getType().accept(TypeVisitor.REFERENCE); + typeMapper + .getType(name) + .filter(definition -> definition.accept(TypeDefinitionVisitor.IS_ALIAS)) + .map(definition -> definition.accept(TypeDefinitionVisitor.ALIAS)) + .ifPresent(aliasDefinition -> { + Type aliasType = aliasDefinition.getAlias(); + if (aliasType.accept(MoreVisitors.IS_COLLECTION) || aliasType.accept(TypeVisitor.IS_OPTIONAL)) { + spec.initializer("$T.empty()", typeMapper.getClassName(field.getType())); + } + }); } // else no initializer diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java index 0c67daea7..38c285a60 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java @@ -34,6 +34,7 @@ import com.palantir.conjure.java.util.Packages; import com.palantir.conjure.java.util.TypeFunctions; import com.palantir.conjure.java.visitor.DefaultTypeVisitor; +import com.palantir.conjure.java.visitor.MoreVisitors; import com.palantir.conjure.spec.FieldDefinition; import com.palantir.conjure.spec.FieldName; import com.palantir.conjure.spec.ListType; @@ -462,6 +463,17 @@ private static MethodSpec createGetter( } } + if (featureFlags.excludeEmptyCollections()) { + Type dealiased = conjureDefType.accept(TypeVisitor.IS_REFERENCE) + ? TypeFunctions.toConjureTypeWithoutAliases(conjureDefType, typesMap) + : conjureDefType; + if (dealiased.accept(MoreVisitors.IS_COLLECTION)) { + getterBuilder.addAnnotation(AnnotationSpec.builder(JsonInclude.class) + .addMember("value", "$T.NON_EMPTY", JsonInclude.Include.class) + .build()); + } + } + if (conjureDefType.accept(TypeVisitor.IS_BINARY) && !featureFlags.useImmutableBytes()) { getterBuilder.addStatement("return this.$N.asReadOnlyBuffer()", field.poetSpec().name); } else { diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/visitor/MoreVisitors.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/visitor/MoreVisitors.java index 3c3d06c50..b8abc5a0d 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/visitor/MoreVisitors.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/visitor/MoreVisitors.java @@ -31,6 +31,7 @@ private MoreVisitors() {} public static final IsExternalType IS_EXTERNAL = new IsExternalType(); public static final ExternalType EXTERNAL = new ExternalType(); public static final IsInternalReference IS_INTERNAL_REFERENCE = new IsInternalReference(); + public static final IsCollection IS_COLLECTION = new IsCollection(); private static final class IsExternalType extends IsTypeVisitor { @Override @@ -95,4 +96,21 @@ public Boolean visitUnknown(String _unknownType) { return false; } } + + private static final class IsCollection extends IsTypeVisitor { + @Override + public Boolean visitList(ListType _value) { + return true; + } + + @Override + public Boolean visitSet(SetType _value) { + return true; + } + + @Override + public Boolean visitMap(MapType _value) { + return true; + } + } } diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/BeanSerdeIntegrationTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/BeanSerdeIntegrationTests.java index 68b7f508d..0f9f9b42c 100644 --- a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/BeanSerdeIntegrationTests.java +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/BeanSerdeIntegrationTests.java @@ -24,10 +24,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidNullException; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.palantir.conjure.java.serialization.ObjectMappers; import com.palantir.product.BearerTokenExample; import com.palantir.product.BooleanExample; +import com.palantir.product.CollectionsTestAliasList; +import com.palantir.product.CollectionsTestAliasMap; +import com.palantir.product.CollectionsTestAliasSet; +import com.palantir.product.CollectionsTestObject; import com.palantir.product.EnumExample; import com.palantir.product.ListExample; import com.palantir.product.MapExample; @@ -167,6 +174,35 @@ public void testEnumKeyDeserialization() throws IOException { assertThat(Iterables.getOnlyElement(value.keySet()).get()).isEqualTo(EnumExample.Value.TWO); } + @Test + public void testExcludeEmptyCollections_empty() throws IOException { + CollectionsTestObject initial = CollectionsTestObject.builder() + .alist(CollectionsTestAliasList.of(ImmutableList.of())) + .aset(CollectionsTestAliasSet.of(ImmutableSet.of())) + .amap(CollectionsTestAliasMap.of(ImmutableMap.of())) + .build(); + String stringValue = mapper.writeValueAsString(initial); + assertThat(stringValue) + .as("Type does not set the 'excludeEmptyOptionals' flag, only the optional should be serialized") + .isEqualTo("{\"optionalItem\":null}"); + assertThat(mapper.readValue(stringValue, CollectionsTestObject.class)).isEqualTo(initial); + } + + @Test + public void testExcludeEmptyCollections_nonempty() throws IOException { + CollectionsTestObject initial = CollectionsTestObject.builder() + .alist(CollectionsTestAliasList.of(ImmutableList.of(0))) + .aset(CollectionsTestAliasSet.of(ImmutableSet.of(0))) + .amap(CollectionsTestAliasMap.of(ImmutableMap.of("", 0))) + .items("") + .itemsMap("", 0) + .optionalItem("") + .itemsSet("") + .build(); + String stringValue = mapper.writeValueAsString(initial); + assertThat(mapper.readValue(stringValue, CollectionsTestObject.class)).isEqualTo(initial); + } + private static void testSerde(String json, Class clazz) throws Exception { T example = mapper.readValue(json, clazz); String serialized = mapper.writeValueAsString(example); diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java index 573908b91..8dfe40993 100644 --- a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java @@ -101,6 +101,19 @@ public void testObjectGenerator_stagedBuilder() throws IOException { assertThatFilesAreTheSame(files, REFERENCE_FILES_FOLDER); } + @Test + public void testObjectGenerator_excludeEmptyCollections() throws IOException { + ConjureDefinition def = + Conjure.parse(ImmutableList.of(new File("src/test/resources/exclude-empty-collections.yml"))); + List files = new GenerationCoordinator( + MoreExecutors.directExecutor(), + ImmutableSet.of(new ObjectGenerator( + Options.builder().excludeEmptyCollections(true).build()))) + .emit(def, tempDir); + + assertThatFilesAreTheSame(files, REFERENCE_FILES_FOLDER); + } + @Test public void testConjureImports() throws IOException { ConjureDefinition conjure = Conjure.parse(ImmutableList.of( diff --git a/conjure-java-core/src/test/resources/exclude-empty-collections.yml b/conjure-java-core/src/test/resources/exclude-empty-collections.yml new file mode 100644 index 000000000..6fd2fc0c6 --- /dev/null +++ b/conjure-java-core/src/test/resources/exclude-empty-collections.yml @@ -0,0 +1,19 @@ +types: + definitions: + default-package: com.palantir.product + objects: + CollectionsTestAliasList: + alias: list + CollectionsTestAliasSet: + alias: set + CollectionsTestAliasMap: + alias: map + CollectionsTestObject: + fields: + items: list + itemsMap: map + optionalItem: optional + itemsSet: set + alist: CollectionsTestAliasList + aset: CollectionsTestAliasSet + amap: CollectionsTestAliasMap diff --git a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java index 48e084990..826ab95de 100644 --- a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java +++ b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java @@ -195,6 +195,14 @@ public static final class GenerateCommand implements Runnable { description = "Objects exclude empty optionals in serialization based on the conjure spec.") private boolean excludeEmptyOptionals; + @CommandLine.Option( + names = "--experimentalExcludeEmptyCollections", + defaultValue = "false", + description = "Objects exclude empty collections in serialization based on the conjure spec. Note " + + "that this should not be enabled on servers due to shortcomings in the " + + "typescript implementation, and should only be used for clients with local codegen.") + private boolean excludeEmptyCollections; + @CommandLine.Option( names = "--unionsWithUnknownValues", defaultValue = "false", @@ -267,6 +275,7 @@ CliConfiguration getConfiguration() { .apiVersion(Optional.ofNullable(apiVersion)) .useStagedBuilders(useStagedBuilders) .excludeEmptyOptionals(excludeEmptyOptionals) + .excludeEmptyCollections(excludeEmptyCollections) .unionsWithUnknownValues(unionsWithUnknownValues) .build()) .build();