From 51809fac5f8a131af49632d5ba129068d416abf5 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 6 Sep 2024 11:30:05 +0300 Subject: [PATCH 01/19] Add failing test case --- ...BaseCollectionPaginationCountResponse.java | 127 ++++++++++++++++++ .../java/com/microsoft/kiota/TestEntity.java | 6 + .../kiota/TestEntityCollectionResponse.java | 65 +++++++++ .../kiota/store/InMemoryBackingStoreTest.java | 32 +++++ 4 files changed, 230 insertions(+) create mode 100644 components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java create mode 100644 components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java new file mode 100644 index 000000000..a2ee6fd6e --- /dev/null +++ b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java @@ -0,0 +1,127 @@ +package com.microsoft.kiota; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.microsoft.kiota.serialization.AdditionalDataHolder; +import com.microsoft.kiota.serialization.Parsable; +import com.microsoft.kiota.serialization.ParseNode; +import com.microsoft.kiota.serialization.SerializationWriter; +import com.microsoft.kiota.store.BackedModel; +import com.microsoft.kiota.store.BackingStore; +import com.microsoft.kiota.store.BackingStoreFactorySingleton; + +public class BaseCollectionPaginationCountResponse implements AdditionalDataHolder, BackedModel, Parsable { + + /** + * Stores model information. + */ + @jakarta.annotation.Nonnull + protected BackingStore backingStore; + /** + * Instantiates a new {@link BaseCollectionPaginationCountResponse} and sets the default values. + */ + public BaseCollectionPaginationCountResponse() { + this.backingStore = BackingStoreFactorySingleton.instance.createBackingStore(); + this.setAdditionalData(new HashMap<>()); + } + /** + * Creates a new instance of the appropriate class based on discriminator value + * @param parseNode The parse node to use to read the discriminator value and create the object + * @return a {@link BaseCollectionPaginationCountResponse} + */ + @jakarta.annotation.Nonnull + public static BaseCollectionPaginationCountResponse createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + Objects.requireNonNull(parseNode); + return new BaseCollectionPaginationCountResponse(); + } + /** + * Gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + * @return a {@link Map} + */ + @jakarta.annotation.Nonnull + public Map getAdditionalData() { + Map value = this.backingStore.get("additionalData"); + if(value == null) { + value = new HashMap<>(); + this.setAdditionalData(value); + } + return value; + } + /** + * Gets the backingStore property value. Stores model information. + * @return a {@link BackingStore} + */ + @jakarta.annotation.Nonnull + public BackingStore getBackingStore() { + return this.backingStore; + } + /** + * The deserialization information for the current model + * @return a {@link Map>} + */ + @jakarta.annotation.Nonnull + public Map> getFieldDeserializers() { + final HashMap> deserializerMap = new HashMap>(2); + deserializerMap.put("@odata.count", (n) -> { this.setOdataCount(n.getLongValue()); }); + deserializerMap.put("@odata.nextLink", (n) -> { this.setOdataNextLink(n.getStringValue()); }); + return deserializerMap; + } + /** + * Gets the @odata.count property value. The OdataCount property + * @return a {@link Long} + */ + @jakarta.annotation.Nullable + public Long getOdataCount() { + return this.backingStore.get("odataCount"); + } + /** + * Gets the @odata.nextLink property value. The OdataNextLink property + * @return a {@link String} + */ + @jakarta.annotation.Nullable + public String getOdataNextLink() { + return this.backingStore.get("odataNextLink"); + } + /** + * Serializes information the current object + * @param writer Serialization writer to use to serialize this model + */ + public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writer) { + Objects.requireNonNull(writer); + writer.writeLongValue("@odata.count", this.getOdataCount()); + writer.writeStringValue("@odata.nextLink", this.getOdataNextLink()); + writer.writeAdditionalData(this.getAdditionalData()); + } + /** + * Sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + * @param value Value to set for the AdditionalData property. + */ + public void setAdditionalData(@jakarta.annotation.Nullable final Map value) { + this.backingStore.set("additionalData", value); + } + /** + * Sets the backingStore property value. Stores model information. + * @param value Value to set for the backingStore property. + */ + public void setBackingStore(@jakarta.annotation.Nonnull final BackingStore value) { + Objects.requireNonNull(value); + this.backingStore = value; + } + /** + * Sets the @odata.count property value. The OdataCount property + * @param value Value to set for the @odata.count property. + */ + public void setOdataCount(@jakarta.annotation.Nullable final Long value) { + this.backingStore.set("odataCount", value); + } + /** + * Sets the @odata.nextLink property value. The OdataNextLink property + * @param value Value to set for the @odata.nextLink property. + */ + public void setOdataNextLink(@jakarta.annotation.Nullable final String value) { + this.backingStore.set("odataNextLink", value); + } + +} diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java index 03d5111c1..7f2174c4f 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; public class TestEntity implements Parsable, AdditionalDataHolder, BackedModel { @@ -107,4 +108,9 @@ public void serialize(@Nonnull SerializationWriter writer) { // TODO Auto-generated method stub } + + public static TestEntity createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + Objects.requireNonNull(parseNode); + return new TestEntity(); + } } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java new file mode 100644 index 000000000..6bafe66a0 --- /dev/null +++ b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java @@ -0,0 +1,65 @@ +package com.microsoft.kiota; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + + +import com.microsoft.kiota.serialization.Parsable; +import com.microsoft.kiota.serialization.ParseNode; +import com.microsoft.kiota.serialization.SerializationWriter; + +public class TestEntityCollectionResponse extends BaseCollectionPaginationCountResponse implements Parsable { + + /** + * Instantiates a new {@link TestEntityCollectionResponse} and sets the default values. + */ + public TestEntityCollectionResponse() { + super(); + } + /** + * Creates a new instance of the appropriate class based on discriminator value + * @param parseNode The parse node to use to read the discriminator value and create the object + * @return a {@link TestEntityCollectionResponse} + */ + @jakarta.annotation.Nonnull + public static TestEntityCollectionResponse createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + Objects.requireNonNull(parseNode); + return new TestEntityCollectionResponse(); + } + /** + * The deserialization information for the current model + * @return a {@link Map>} + */ + @jakarta.annotation.Nonnull + public Map> getFieldDeserializers() { + final HashMap> deserializerMap = new HashMap>(super.getFieldDeserializers()); + deserializerMap.put("value", (n) -> { this.setValue(n.getCollectionOfObjectValues(TestEntity::createFromDiscriminatorValue)); }); + return deserializerMap; + } + /** + * Gets the value property value. The value property + * @return a {@link java.util.List} + */ + @jakarta.annotation.Nullable + public java.util.List getValue() { + return this.backingStore.get("value"); + } + /** + * Serializes information the current object + * @param writer Serialization writer to use to serialize this model + */ + public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writer) { + Objects.requireNonNull(writer); + super.serialize(writer); + writer.writeCollectionOfObjectValues("value", this.getValue()); + } + /** + * Sets the value property value. The value property + * @param value Value to set for the value property. + */ + public void setValue(@jakarta.annotation.Nullable final java.util.List value) { + this.backingStore.set("value", value); + } + +} diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index 081dc0f47..817aa5f71 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import com.microsoft.kiota.TestEntity; +import com.microsoft.kiota.TestEntityCollectionResponse; import org.junit.jupiter.api.Test; @@ -422,4 +423,35 @@ void TestsBackingStoreEmbeddedWithMultipleNestedModelsCollectionsAndAdditionalDa assertEquals(2, invocationCount.get()); // only for setting the id and the colleagues } + + @Test + void TestsBackingStoreUpdateToItemInNestedCollectionWithAnotherBackedModel() { + // Arrange dummy user with initialized backing store + var colleagues = new ArrayList(); + for (int i = 0; i < 10; i++) { + var colleague = new TestEntity(); + colleague.setId(UUID.randomUUID().toString()); + colleague.setBusinessPhones(Arrays.asList(new String[] {"+1 234 567 891"})); + colleague.getAdditionalData().put("count", i); + colleagues.add(colleague); + colleague.getBackingStore().setIsInitializationCompleted(true); + } + + var testUserCollectionResponse = new TestEntityCollectionResponse(); + testUserCollectionResponse.setValue(colleagues); + testUserCollectionResponse.getBackingStore().setIsInitializationCompleted(true); + + // Act on the data by making a change + var manager = new TestEntity(); + manager.setId("2fe22fe5-1132-42cf-90f9-1dc17e325a74"); + manager.getBackingStore().setIsInitializationCompleted(true); + testUserCollectionResponse.getValue().get(0).setManager(manager); + + // Assert by retrieving only changed values + var changedValues = testUserCollectionResponse.getBackingStore().enumerate(); + assertEquals(1, changedValues.size()); + assertEquals("value", changedValues.keySet().toArray()[0]); + assertEquals(10, ((List) changedValues.values().toArray()[0]).size()); + assertTrue(((TestEntity)((List)changedValues.values().toArray()[0]).get(0)).getBackingStore().enumerate().containsKey("manager")); + } } From 71aab440bb155d9545afef7731e78f73fe82b4ae Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 6 Sep 2024 12:25:34 +0300 Subject: [PATCH 02/19] Fix style issue --- ...BaseCollectionPaginationCountResponse.java | 63 ++++++++++++------- .../java/com/microsoft/kiota/TestEntity.java | 3 +- .../kiota/TestEntityCollectionResponse.java | 40 +++++++----- .../kiota/store/InMemoryBackingStoreTest.java | 6 +- 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java index a2ee6fd6e..109ab3f95 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java @@ -1,9 +1,5 @@ package com.microsoft.kiota; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - import com.microsoft.kiota.serialization.AdditionalDataHolder; import com.microsoft.kiota.serialization.Parsable; import com.microsoft.kiota.serialization.ParseNode; @@ -12,13 +8,18 @@ import com.microsoft.kiota.store.BackingStore; import com.microsoft.kiota.store.BackingStoreFactorySingleton; -public class BaseCollectionPaginationCountResponse implements AdditionalDataHolder, BackedModel, Parsable { +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class BaseCollectionPaginationCountResponse + implements AdditionalDataHolder, BackedModel, Parsable { /** * Stores model information. */ - @jakarta.annotation.Nonnull - protected BackingStore backingStore; + @jakarta.annotation.Nonnull protected BackingStore backingStore; + /** * Instantiates a new {@link BaseCollectionPaginationCountResponse} and sets the default values. */ @@ -26,64 +27,75 @@ public BaseCollectionPaginationCountResponse() { this.backingStore = BackingStoreFactorySingleton.instance.createBackingStore(); this.setAdditionalData(new HashMap<>()); } + /** * Creates a new instance of the appropriate class based on discriminator value * @param parseNode The parse node to use to read the discriminator value and create the object * @return a {@link BaseCollectionPaginationCountResponse} */ - @jakarta.annotation.Nonnull - public static BaseCollectionPaginationCountResponse createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + @jakarta.annotation.Nonnull public static BaseCollectionPaginationCountResponse createFromDiscriminatorValue( + @jakarta.annotation.Nonnull final ParseNode parseNode) { Objects.requireNonNull(parseNode); return new BaseCollectionPaginationCountResponse(); } + /** * Gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. * @return a {@link Map} */ - @jakarta.annotation.Nonnull - public Map getAdditionalData() { + @jakarta.annotation.Nonnull public Map getAdditionalData() { Map value = this.backingStore.get("additionalData"); - if(value == null) { + if (value == null) { value = new HashMap<>(); this.setAdditionalData(value); } return value; } + /** * Gets the backingStore property value. Stores model information. * @return a {@link BackingStore} */ - @jakarta.annotation.Nonnull - public BackingStore getBackingStore() { + @jakarta.annotation.Nonnull public BackingStore getBackingStore() { return this.backingStore; } + /** * The deserialization information for the current model * @return a {@link Map>} */ - @jakarta.annotation.Nonnull - public Map> getFieldDeserializers() { - final HashMap> deserializerMap = new HashMap>(2); - deserializerMap.put("@odata.count", (n) -> { this.setOdataCount(n.getLongValue()); }); - deserializerMap.put("@odata.nextLink", (n) -> { this.setOdataNextLink(n.getStringValue()); }); + @jakarta.annotation.Nonnull public Map> getFieldDeserializers() { + final HashMap> deserializerMap = + new HashMap>(2); + deserializerMap.put( + "@odata.count", + (n) -> { + this.setOdataCount(n.getLongValue()); + }); + deserializerMap.put( + "@odata.nextLink", + (n) -> { + this.setOdataNextLink(n.getStringValue()); + }); return deserializerMap; } + /** * Gets the @odata.count property value. The OdataCount property * @return a {@link Long} */ - @jakarta.annotation.Nullable - public Long getOdataCount() { + @jakarta.annotation.Nullable public Long getOdataCount() { return this.backingStore.get("odataCount"); } + /** * Gets the @odata.nextLink property value. The OdataNextLink property * @return a {@link String} */ - @jakarta.annotation.Nullable - public String getOdataNextLink() { + @jakarta.annotation.Nullable public String getOdataNextLink() { return this.backingStore.get("odataNextLink"); } + /** * Serializes information the current object * @param writer Serialization writer to use to serialize this model @@ -94,6 +106,7 @@ public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writ writer.writeStringValue("@odata.nextLink", this.getOdataNextLink()); writer.writeAdditionalData(this.getAdditionalData()); } + /** * Sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. * @param value Value to set for the AdditionalData property. @@ -101,6 +114,7 @@ public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writ public void setAdditionalData(@jakarta.annotation.Nullable final Map value) { this.backingStore.set("additionalData", value); } + /** * Sets the backingStore property value. Stores model information. * @param value Value to set for the backingStore property. @@ -109,6 +123,7 @@ public void setBackingStore(@jakarta.annotation.Nonnull final BackingStore value Objects.requireNonNull(value); this.backingStore = value; } + /** * Sets the @odata.count property value. The OdataCount property * @param value Value to set for the @odata.count property. @@ -116,6 +131,7 @@ public void setBackingStore(@jakarta.annotation.Nonnull final BackingStore value public void setOdataCount(@jakarta.annotation.Nullable final Long value) { this.backingStore.set("odataCount", value); } + /** * Sets the @odata.nextLink property value. The OdataNextLink property * @param value Value to set for the @odata.nextLink property. @@ -123,5 +139,4 @@ public void setOdataCount(@jakarta.annotation.Nullable final Long value) { public void setOdataNextLink(@jakarta.annotation.Nullable final String value) { this.backingStore.set("odataNextLink", value); } - } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java index 7f2174c4f..68a7d36d6 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntity.java @@ -109,7 +109,8 @@ public void serialize(@Nonnull SerializationWriter writer) { } - public static TestEntity createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + public static TestEntity createFromDiscriminatorValue( + @jakarta.annotation.Nonnull final ParseNode parseNode) { Objects.requireNonNull(parseNode); return new TestEntity(); } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java index 6bafe66a0..b5b87f9ce 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java @@ -1,15 +1,15 @@ package com.microsoft.kiota; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - - import com.microsoft.kiota.serialization.Parsable; import com.microsoft.kiota.serialization.ParseNode; import com.microsoft.kiota.serialization.SerializationWriter; -public class TestEntityCollectionResponse extends BaseCollectionPaginationCountResponse implements Parsable { +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class TestEntityCollectionResponse extends BaseCollectionPaginationCountResponse + implements Parsable { /** * Instantiates a new {@link TestEntityCollectionResponse} and sets the default values. @@ -17,34 +17,44 @@ public class TestEntityCollectionResponse extends BaseCollectionPaginationCountR public TestEntityCollectionResponse() { super(); } + /** * Creates a new instance of the appropriate class based on discriminator value * @param parseNode The parse node to use to read the discriminator value and create the object * @return a {@link TestEntityCollectionResponse} */ - @jakarta.annotation.Nonnull - public static TestEntityCollectionResponse createFromDiscriminatorValue(@jakarta.annotation.Nonnull final ParseNode parseNode) { + @jakarta.annotation.Nonnull public static TestEntityCollectionResponse createFromDiscriminatorValue( + @jakarta.annotation.Nonnull final ParseNode parseNode) { Objects.requireNonNull(parseNode); return new TestEntityCollectionResponse(); } + /** * The deserialization information for the current model * @return a {@link Map>} */ - @jakarta.annotation.Nonnull - public Map> getFieldDeserializers() { - final HashMap> deserializerMap = new HashMap>(super.getFieldDeserializers()); - deserializerMap.put("value", (n) -> { this.setValue(n.getCollectionOfObjectValues(TestEntity::createFromDiscriminatorValue)); }); + @jakarta.annotation.Nonnull public Map> getFieldDeserializers() { + final HashMap> deserializerMap = + new HashMap>( + super.getFieldDeserializers()); + deserializerMap.put( + "value", + (n) -> { + this.setValue( + n.getCollectionOfObjectValues( + TestEntity::createFromDiscriminatorValue)); + }); return deserializerMap; } + /** * Gets the value property value. The value property * @return a {@link java.util.List} */ - @jakarta.annotation.Nullable - public java.util.List getValue() { + @jakarta.annotation.Nullable public java.util.List getValue() { return this.backingStore.get("value"); } + /** * Serializes information the current object * @param writer Serialization writer to use to serialize this model @@ -54,6 +64,7 @@ public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writ super.serialize(writer); writer.writeCollectionOfObjectValues("value", this.getValue()); } + /** * Sets the value property value. The value property * @param value Value to set for the value property. @@ -61,5 +72,4 @@ public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writ public void setValue(@jakarta.annotation.Nullable final java.util.List value) { this.backingStore.set("value", value); } - } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index 817aa5f71..36ce86005 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -452,6 +452,10 @@ void TestsBackingStoreUpdateToItemInNestedCollectionWithAnotherBackedModel() { assertEquals(1, changedValues.size()); assertEquals("value", changedValues.keySet().toArray()[0]); assertEquals(10, ((List) changedValues.values().toArray()[0]).size()); - assertTrue(((TestEntity)((List)changedValues.values().toArray()[0]).get(0)).getBackingStore().enumerate().containsKey("manager")); + assertTrue( + ((TestEntity) ((List) changedValues.values().toArray()[0]).get(0)) + .getBackingStore() + .enumerate() + .containsKey("manager")); } } From 8e9d05156c34127a8e66f4fa4edd1de29cb755dc Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Fri, 6 Sep 2024 16:33:19 +0300 Subject: [PATCH 03/19] Fix spotbugs issues --- components/abstractions/spotBugsExcludeFilter.xml | 5 ++++- .../kiota/BaseCollectionPaginationCountResponse.java | 9 --------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/components/abstractions/spotBugsExcludeFilter.xml b/components/abstractions/spotBugsExcludeFilter.xml index c35270c2d..e2dbb38ce 100644 --- a/components/abstractions/spotBugsExcludeFilter.xml +++ b/components/abstractions/spotBugsExcludeFilter.xml @@ -52,7 +52,10 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu - + + + + diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java index 109ab3f95..e07f54dee 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java @@ -115,15 +115,6 @@ public void setAdditionalData(@jakarta.annotation.Nullable final Map Date: Fri, 6 Sep 2024 16:34:43 +0300 Subject: [PATCH 04/19] Reduce redundant calls to check for collection consistency --- .../kiota/store/InMemoryBackingStore.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 37d540755..daf2a8273 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -61,9 +61,12 @@ public void setIsInitializationCompleted(final boolean value) { backedModel .getBackingStore() .setIsInitializationCompleted(value); // propagate initialization + } else { + // setIsInitializationCompleted() called above already checks for collection + // consistency for BackedModels + ensureCollectionPropertyIsConsistent( + entry.getKey(), this.store.get(entry.getKey()).getValue1()); } - ensureCollectionPropertyIsConsistent( - entry.getKey(), this.store.get(entry.getKey()).getValue1()); final Pair updatedValue = wrapper.setValue0(!value); entry.setValue(updatedValue); } @@ -89,10 +92,10 @@ public void clear() { final Map result = new HashMap<>(); for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); - final Object value = this.getValueFromWrapper(entry.getKey(), wrapper); + final Object value = this.get(entry.getKey()); if (value != null) { - result.put(entry.getKey(), wrapper.getValue1()); + result.put(entry.getKey(), value); } else if (Boolean.TRUE.equals(wrapper.getValue0())) { result.put(entry.getKey(), null); } @@ -242,9 +245,8 @@ private void touchNestedProperties(final Object nestedObject) { // Call Get<>() on nested properties so that this method may be called recursively to // ensure collections are consistent final BackedModel backedModel = (BackedModel) nestedObject; - for (final String itemKey : backedModel.getBackingStore().enumerate().keySet()) { - backedModel.getBackingStore().get(itemKey); - } + // enumerate() calls get<>() on all properties + backedModel.getBackingStore().enumerate(); } } } From a03bdd86d71bcc896dcea7746c91f5971a850cb1 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 9 Sep 2024 15:57:48 +0300 Subject: [PATCH 05/19] feat: Update collection size using map entry to prevent undefined behaviour --- .../kiota/store/InMemoryBackingStore.java | 24 +++++++++++++++---- .../kiota/store/InMemoryBackingStoreTest.java | 6 ++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index daf2a8273..51635e0ea 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -56,18 +56,34 @@ public void setIsInitializationCompleted(final boolean value) { this.isInitializationCompleted = value; for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); + final Pair updatedValue = wrapper.setValue0(!value); + if (wrapper.getValue1() instanceof BackedModel) { BackedModel backedModel = (BackedModel) wrapper.getValue1(); backedModel .getBackingStore() .setIsInitializationCompleted(value); // propagate initialization - } else { + } + if (wrapper.getValue1() instanceof Pair) { // setIsInitializationCompleted() called above already checks for collection // consistency for BackedModels - ensureCollectionPropertyIsConsistent( - entry.getKey(), this.store.get(entry.getKey()).getValue1()); + final Pair collectionTuple = (Pair) wrapper.getValue1(); + Object[] items; + if (collectionTuple.getValue0() instanceof Collection) { + items = ((Collection) collectionTuple.getValue0()).toArray(); + } else { // it is a map + items = ((Map) collectionTuple.getValue0()).values().toArray(); + } + + for (final Object item : items) { + touchNestedProperties(item); // call get on nested properties + } + + if (collectionTuple.getValue1() + != items.length) { // and the size has changed since we last updated + updatedValue.setValue1(collectionTuple.setValue1(items.length)); + } } - final Pair updatedValue = wrapper.setValue0(!value); entry.setValue(updatedValue); } } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index 36ce86005..a847fbde6 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -439,15 +439,19 @@ void TestsBackingStoreUpdateToItemInNestedCollectionWithAnotherBackedModel() { var testUserCollectionResponse = new TestEntityCollectionResponse(); testUserCollectionResponse.setValue(colleagues); + // After set(), while adding nested subscriptions, all values in the collection now have + // initializationCompleted=false & their properties are all dirty testUserCollectionResponse.getBackingStore().setIsInitializationCompleted(true); // Act on the data by making a change var manager = new TestEntity(); manager.setId("2fe22fe5-1132-42cf-90f9-1dc17e325a74"); manager.getBackingStore().setIsInitializationCompleted(true); - testUserCollectionResponse.getValue().get(0).setManager(manager); + var collectionValues = testUserCollectionResponse.getValue(); + collectionValues.get(0).setManager(manager); // Assert by retrieving only changed values + testUserCollectionResponse.getBackingStore().setReturnOnlyChangedValues(true); var changedValues = testUserCollectionResponse.getBackingStore().enumerate(); assertEquals(1, changedValues.size()); assertEquals("value", changedValues.keySet().toArray()[0]); From 90c576c3aadaa5c4506b1e8d76f37bce578b6f4a Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 9 Sep 2024 20:43:20 +0300 Subject: [PATCH 06/19] Fix bug where if returnOnlyChangedValues is true and collection has not changed, collection consistency would not be checked --- .../kiota/store/InMemoryBackingStore.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 51635e0ea..b0248ad45 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -56,7 +56,8 @@ public void setIsInitializationCompleted(final boolean value) { this.isInitializationCompleted = value; for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); - final Pair updatedValue = wrapper.setValue0(!value); + Pair updatedValue = + new Pair(!value, wrapper.getValue1()); if (wrapper.getValue1() instanceof BackedModel) { BackedModel backedModel = (BackedModel) wrapper.getValue1(); @@ -81,7 +82,9 @@ public void setIsInitializationCompleted(final boolean value) { if (collectionTuple.getValue1() != items.length) { // and the size has changed since we last updated - updatedValue.setValue1(collectionTuple.setValue1(items.length)); + updatedValue = + new Pair( + !value, new Pair<>(collectionTuple.getValue0(), items.length)); } } entry.setValue(updatedValue); @@ -131,20 +134,13 @@ public void clear() { return result; } - private Object getValueFromWrapper(final String entryKey, final Pair wrapper) { + private Object getValueFromWrapper(final Pair wrapper) { if (wrapper != null) { - final Boolean hasChanged = wrapper.getValue0(); - if (!this.returnOnlyChangedValues || Boolean.TRUE.equals(hasChanged)) { - if (Boolean.FALSE.equals( - hasChanged)) { // no need property has already been flagged. - ensureCollectionPropertyIsConsistent(entryKey, wrapper.getValue1()); - } - if (wrapper.getValue1() instanceof Pair) { - Pair collectionTuple = (Pair) wrapper.getValue1(); - return collectionTuple.getValue0(); - } - return wrapper.getValue1(); + if (wrapper.getValue1() instanceof Pair) { + Pair collectionTuple = (Pair) wrapper.getValue1(); + return collectionTuple.getValue0(); } + return wrapper.getValue1(); } return null; } @@ -153,7 +149,17 @@ private Object getValueFromWrapper(final String entryKey, final Pair T get(@Nonnull final String key) { Objects.requireNonNull(key); final Pair wrapper = this.store.get(key); - final Object value = this.getValueFromWrapper(key, wrapper); + final Object value = this.getValueFromWrapper(wrapper); + + boolean hasChanged = wrapper.getValue0(); + if (getReturnOnlyChangedValues() && !hasChanged) { + ensureCollectionPropertyIsConsistent(key, wrapper.getValue1()); + hasChanged = this.store.get(key).getValue0(); + if (!hasChanged) { + return null; + } + } + try { return (T) value; } catch (ClassCastException ex) { From 5fe4b4538ad6434a03b6d3d63c55b478ab67866c Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Mon, 9 Sep 2024 21:07:29 +0300 Subject: [PATCH 07/19] Fix SonarCloud reliability warning --- .../java/com/microsoft/kiota/store/InMemoryBackingStore.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index b0248ad45..9aaa17e88 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -149,6 +149,9 @@ private Object getValueFromWrapper(final Pair wrapper) { @Nullable public T get(@Nonnull final String key) { Objects.requireNonNull(key); final Pair wrapper = this.store.get(key); + if (wrapper == null) { + return null; + } final Object value = this.getValueFromWrapper(wrapper); boolean hasChanged = wrapper.getValue0(); From f859bf58691c9c137f00b135c908296e3fc86a2b Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 00:20:44 +0300 Subject: [PATCH 08/19] Fix SonarCloud issues --- .../com/microsoft/kiota/store/InMemoryBackingStore.java | 8 +++++--- .../kiota/BaseCollectionPaginationCountResponse.java | 4 ++-- .../com/microsoft/kiota/TestEntityCollectionResponse.java | 4 +++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 9aaa17e88..00e60f9bf 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -57,7 +57,7 @@ public void setIsInitializationCompleted(final boolean value) { for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); Pair updatedValue = - new Pair(!value, wrapper.getValue1()); + new Pair<>(!value, wrapper.getValue1()); if (wrapper.getValue1() instanceof BackedModel) { BackedModel backedModel = (BackedModel) wrapper.getValue1(); @@ -83,8 +83,10 @@ public void setIsInitializationCompleted(final boolean value) { if (collectionTuple.getValue1() != items.length) { // and the size has changed since we last updated updatedValue = - new Pair( - !value, new Pair<>(collectionTuple.getValue0(), items.length)); + new Pair<>( + !value, + new Pair<>(collectionTuple.getValue0(), items.length) + ); } } entry.setValue(updatedValue); diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java index e07f54dee..39c448691 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/BaseCollectionPaginationCountResponse.java @@ -69,12 +69,12 @@ public BaseCollectionPaginationCountResponse() { new HashMap>(2); deserializerMap.put( "@odata.count", - (n) -> { + n -> { this.setOdataCount(n.getLongValue()); }); deserializerMap.put( "@odata.nextLink", - (n) -> { + n -> { this.setOdataNextLink(n.getStringValue()); }); return deserializerMap; diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java index b5b87f9ce..bed02a3e7 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/TestEntityCollectionResponse.java @@ -33,13 +33,14 @@ public TestEntityCollectionResponse() { * The deserialization information for the current model * @return a {@link Map>} */ + @Override @jakarta.annotation.Nonnull public Map> getFieldDeserializers() { final HashMap> deserializerMap = new HashMap>( super.getFieldDeserializers()); deserializerMap.put( "value", - (n) -> { + n -> { this.setValue( n.getCollectionOfObjectValues( TestEntity::createFromDiscriminatorValue)); @@ -59,6 +60,7 @@ public TestEntityCollectionResponse() { * Serializes information the current object * @param writer Serialization writer to use to serialize this model */ + @Override public void serialize(@jakarta.annotation.Nonnull final SerializationWriter writer) { Objects.requireNonNull(writer); super.serialize(writer); From fe63563935a1b77a73f5d9b40250e4cc97ded9eb Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 00:24:56 +0300 Subject: [PATCH 09/19] Bump version & update CHANGELOG --- CHANGELOG.md | 6 ++++++ .../com/microsoft/kiota/store/InMemoryBackingStore.java | 7 ++----- .../http/middleware/options/UserAgentHandlerOption.java | 2 +- gradle.properties | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e126c46ac..3313f9620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.4.0] - 2024-09-11 + +### Changed + +- Fix InMemoryBackingStore by preventing updates to underlying store's Map while iterating over it [#2106](https://github.com/microsoftgraph/msgraph-sdk-java/issues/2106) + ## [1.3.0] - 2024-08-22 ### Changed diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 00e60f9bf..fd4ecd6ff 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -56,8 +56,7 @@ public void setIsInitializationCompleted(final boolean value) { this.isInitializationCompleted = value; for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); - Pair updatedValue = - new Pair<>(!value, wrapper.getValue1()); + Pair updatedValue = new Pair<>(!value, wrapper.getValue1()); if (wrapper.getValue1() instanceof BackedModel) { BackedModel backedModel = (BackedModel) wrapper.getValue1(); @@ -84,9 +83,7 @@ public void setIsInitializationCompleted(final boolean value) { != items.length) { // and the size has changed since we last updated updatedValue = new Pair<>( - !value, - new Pair<>(collectionTuple.getValue0(), items.length) - ); + !value, new Pair<>(collectionTuple.getValue0(), items.length)); } } entry.setValue(updatedValue); diff --git a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java index 2eb271beb..c8dba6011 100644 --- a/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java +++ b/components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/options/UserAgentHandlerOption.java @@ -13,7 +13,7 @@ public UserAgentHandlerOption() {} private boolean enabled = true; @Nonnull private String productName = "kiota-java"; - @Nonnull private String productVersion = "1.3.0"; + @Nonnull private String productVersion = "1.4.0"; /** * Gets the product name to be used in the user agent header diff --git a/gradle.properties b/gradle.properties index ff6dd0baf..434f746a4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,7 +25,7 @@ org.gradle.caching=true mavenGroupId = com.microsoft.kiota mavenMajorVersion = 1 -mavenMinorVersion = 3 +mavenMinorVersion = 4 mavenPatchVersion = 0 mavenArtifactSuffix = From b337cbe44732b0bdb10159e8d38b6e0d251addd8 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 12:31:27 +0300 Subject: [PATCH 10/19] Refactor check for collection size consistency --- .../kiota/store/InMemoryBackingStore.java | 88 ++++++++++--------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index fd4ecd6ff..819b6f2bf 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -54,9 +54,11 @@ public Pair setValue1(B value1) { public void setIsInitializationCompleted(final boolean value) { this.isInitializationCompleted = value; + ensureCollectionPropertiesAreConsistent(); for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); Pair updatedValue = new Pair<>(!value, wrapper.getValue1()); + entry.setValue(updatedValue); if (wrapper.getValue1() instanceof BackedModel) { BackedModel backedModel = (BackedModel) wrapper.getValue1(); @@ -65,28 +67,18 @@ public void setIsInitializationCompleted(final boolean value) { .setIsInitializationCompleted(value); // propagate initialization } if (wrapper.getValue1() instanceof Pair) { - // setIsInitializationCompleted() called above already checks for collection - // consistency for BackedModels final Pair collectionTuple = (Pair) wrapper.getValue1(); - Object[] items; - if (collectionTuple.getValue0() instanceof Collection) { - items = ((Collection) collectionTuple.getValue0()).toArray(); - } else { // it is a map - items = ((Map) collectionTuple.getValue0()).values().toArray(); - } + Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); for (final Object item : items) { - touchNestedProperties(item); // call get on nested properties - } + if (!(item instanceof BackedModel)) break; - if (collectionTuple.getValue1() - != items.length) { // and the size has changed since we last updated - updatedValue = - new Pair<>( - !value, new Pair<>(collectionTuple.getValue0(), items.length)); + BackedModel backedModel = (BackedModel) item; + backedModel + .getBackingStore() + .setIsInitializationCompleted(value); // propagate initialization } } - entry.setValue(updatedValue); } } @@ -155,7 +147,7 @@ private Object getValueFromWrapper(final Pair wrapper) { boolean hasChanged = wrapper.getValue0(); if (getReturnOnlyChangedValues() && !hasChanged) { - ensureCollectionPropertyIsConsistent(key, wrapper.getValue1()); + ensureCollectionPropertiesAreConsistent(); hasChanged = this.store.get(key).getValue0(); if (!hasChanged) { return null; @@ -239,38 +231,50 @@ private void setupNestedSubscriptions( } } - private void ensureCollectionPropertyIsConsistent(final String key, final Object storeItem) { - if (storeItem instanceof Pair) { // check if we put in a collection annotated with the size - final Pair collectionTuple = (Pair) storeItem; - Object[] items; - if (collectionTuple.getValue0() instanceof Collection) { - items = ((Collection) collectionTuple.getValue0()).toArray(); - } else { // it is a map - items = ((Map) collectionTuple.getValue0()).values().toArray(); - } + private void ensureCollectionPropertiesAreConsistent() { + HashMap currentStoreDirtyCollections = new HashMap<>(); + List nestedBackedModelsToEnumerate = new ArrayList<>(); - for (final Object item : items) { - touchNestedProperties(item); // call get on nested properties + for (final Map.Entry> entry : this.store.entrySet()) { + final Pair wrapper = entry.getValue(); + if (wrapper.getValue1() instanceof Pair) { + final Pair collectionTuple = (Pair) wrapper.getValue1(); + Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); + for (Object item : items) { + if (!(item instanceof BackedModel)) break; + nestedBackedModelsToEnumerate.add((BackedModel) item); + } + if (collectionTuple.getValue1() + != items.length) { // and the size has changed since we last updated + currentStoreDirtyCollections.put(entry.getKey(), collectionTuple.getValue0()); + } } + } + + // Enumerate nested backed models first since they may trigger the parent to be dirty + for (BackedModel nestedBackedModel : nestedBackedModelsToEnumerate) { + nestedBackedModel.getBackingStore().enumerate(); + } - if (collectionTuple.getValue1() - != items.length) { // and the size has changed since we last updated - set( - key, - collectionTuple.getValue0()); // ensure the store is notified the collection - // property is "dirty" + // Only update parent properties that haven't been marked as dirty by the nested models + for (Map.Entry entry : currentStoreDirtyCollections.entrySet()) { + // Always set() if there were no nested models + if (nestedBackedModelsToEnumerate.isEmpty()) { + set(entry.getKey(), entry.getValue()); + continue; + } + boolean hasChanged = this.store.get(entry.getKey()).getValue0(); + if (!hasChanged) { + set(entry.getKey(), entry.getValue()); } } - touchNestedProperties(storeItem); // call get on nested properties } - private void touchNestedProperties(final Object nestedObject) { - if (nestedObject instanceof BackedModel) { - // Call Get<>() on nested properties so that this method may be called recursively to - // ensure collections are consistent - final BackedModel backedModel = (BackedModel) nestedObject; - // enumerate() calls get<>() on all properties - backedModel.getBackingStore().enumerate(); + private Object[] getObjectArrayFromCollectionWrapper(final Pair collectionTuple) { + if (collectionTuple.getValue0() instanceof Collection) { + return ((Collection) collectionTuple.getValue0()).toArray(); + } else { // it is a map + return ((Map) collectionTuple.getValue0()).values().toArray(); } } } From 0c19f7a16c4edefc690d832448ea17e43bc64504 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 13:29:03 +0300 Subject: [PATCH 11/19] Use concurrent dictionary for In memory backing store registry to avoid race conditions --- CHANGELOG.md | 1 + .../java/com/microsoft/kiota/store/InMemoryBackingStore.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3313f9620..e22ac6659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fix InMemoryBackingStore by preventing updates to underlying store's Map while iterating over it [#2106](https://github.com/microsoftgraph/msgraph-sdk-java/issues/2106) + - Use concurrent HashMap for In memory backing store registry to avoid race conditions. ## [1.3.0] - 2024-08-22 diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 819b6f2bf..0345a42ca 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** In-memory implementation of the backing store. Allows for dirty tracking of changes. */ public class InMemoryBackingStore implements BackingStore { @@ -48,9 +49,9 @@ public Pair setValue1(B value1) { private boolean isInitializationCompleted = true; private boolean returnOnlyChangedValues; - private final Map> store = new HashMap<>(); + private final Map> store = new ConcurrentHashMap<>(); private final Map> subscriptionStore = - new HashMap<>(); + new ConcurrentHashMap<>(); public void setIsInitializationCompleted(final boolean value) { this.isInitializationCompleted = value; From 0e73c97f30c8372dfa8b87d38305391a52b408ae Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 13:31:30 +0300 Subject: [PATCH 12/19] Add large array perf test --- CHANGELOG.md | 2 +- .../kiota/store/InMemoryBackingStoreTest.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e22ac6659..fdaa6be84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fix InMemoryBackingStore by preventing updates to underlying store's Map while iterating over it [#2106](https://github.com/microsoftgraph/msgraph-sdk-java/issues/2106) - - Use concurrent HashMap for In memory backing store registry to avoid race conditions. +- Use concurrent HashMap for In memory backing store registry to avoid race conditions. ## [1.3.0] - 2024-08-22 diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index a847fbde6..e2e418201 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -462,4 +462,20 @@ void TestsBackingStoreUpdateToItemInNestedCollectionWithAnotherBackedModel() { .enumerate() .containsKey("manager")); } + + @Test + void testLargeArraysPerformsWell() { + // Arrange + var testBackingStore = new InMemoryBackingStore(); + // Act + assertTrue(testBackingStore.enumerate().isEmpty()); + var testArray = new int[100000000]; + testBackingStore.set("values", testArray); + long startTimeMillis = System.currentTimeMillis(); + testBackingStore.setIsInitializationCompleted(true); + long timeTakenMillis = System.currentTimeMillis() - startTimeMillis; + + // Assert + assertTrue(timeTakenMillis < 1); + } } From f23d504802febd9a88297559559209358e8baa81 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 15:04:09 +0300 Subject: [PATCH 13/19] Update components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java Co-authored-by: Vincent Biret --- .../java/com/microsoft/kiota/store/InMemoryBackingStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 0345a42ca..49a66688c 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -58,7 +58,7 @@ public void setIsInitializationCompleted(final boolean value) { ensureCollectionPropertiesAreConsistent(); for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); - Pair updatedValue = new Pair<>(!value, wrapper.getValue1()); + final Pair updatedValue = new Pair<>(!value, wrapper.getValue1()); entry.setValue(updatedValue); if (wrapper.getValue1() instanceof BackedModel) { From bb43852f3abe771aaefb7177b7e334e2023c73f2 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Tue, 10 Sep 2024 17:03:25 +0300 Subject: [PATCH 14/19] Apply code review feedback --- .../kiota/store/InMemoryBackingStore.java | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 49a66688c..c2931bfe3 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -67,7 +67,7 @@ public void setIsInitializationCompleted(final boolean value) { .getBackingStore() .setIsInitializationCompleted(value); // propagate initialization } - if (wrapper.getValue1() instanceof Pair) { + if (isCollectionValue(wrapper)) { final Pair collectionTuple = (Pair) wrapper.getValue1(); Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); @@ -127,14 +127,14 @@ public void clear() { } private Object getValueFromWrapper(final Pair wrapper) { - if (wrapper != null) { - if (wrapper.getValue1() instanceof Pair) { - Pair collectionTuple = (Pair) wrapper.getValue1(); - return collectionTuple.getValue0(); - } - return wrapper.getValue1(); + if (wrapper == null) { + return null; } - return null; + if (isCollectionValue(wrapper)) { + Pair collectionTuple = (Pair) wrapper.getValue1(); + return collectionTuple.getValue0(); + } + return wrapper.getValue1(); } @SuppressWarnings("unchecked") @@ -148,8 +148,10 @@ private Object getValueFromWrapper(final Pair wrapper) { boolean hasChanged = wrapper.getValue0(); if (getReturnOnlyChangedValues() && !hasChanged) { - ensureCollectionPropertiesAreConsistent(); - hasChanged = this.store.get(key).getValue0(); + if (isCollectionValue(wrapper)) { + ensureCollectionPropertiesAreConsistent(); + hasChanged = this.store.get(key).getValue0(); + } if (!hasChanged) { return null; } @@ -238,7 +240,7 @@ private void ensureCollectionPropertiesAreConsistent() { for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); - if (wrapper.getValue1() instanceof Pair) { + if (isCollectionValue(wrapper)) { final Pair collectionTuple = (Pair) wrapper.getValue1(); Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); for (Object item : items) { @@ -272,10 +274,21 @@ private void ensureCollectionPropertiesAreConsistent() { } private Object[] getObjectArrayFromCollectionWrapper(final Pair collectionTuple) { + if (collectionTuple == null) { + throw new IllegalArgumentException("collectionTuple cannot be null"); + } if (collectionTuple.getValue0() instanceof Collection) { - return ((Collection) collectionTuple.getValue0()).toArray(); - } else { // it is a map - return ((Map) collectionTuple.getValue0()).values().toArray(); + final Collection items = (Collection) collectionTuple.getValue0(); + return items.toArray(); + } + if (collectionTuple.getValue0() instanceof Map) { + final Map items = (Map) collectionTuple.getValue0(); + return items.values().toArray(); } + throw new IllegalArgumentException("collectionTuple must be a Collection or a Map"); + } + + private boolean isCollectionValue(final Pair wrapper) { + return wrapper.getValue1() instanceof Pair; } } From 0155fb91b86562bc8bb167fdb5d8a30c9a863a48 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 10 Sep 2024 12:51:29 -0400 Subject: [PATCH 15/19] Apply suggestions from code review --- .../com/microsoft/kiota/store/InMemoryBackingStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index c2931bfe3..321b26b69 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -235,8 +235,8 @@ private void setupNestedSubscriptions( } private void ensureCollectionPropertiesAreConsistent() { - HashMap currentStoreDirtyCollections = new HashMap<>(); - List nestedBackedModelsToEnumerate = new ArrayList<>(); + final HashMap currentStoreDirtyCollections = new HashMap<>(); + final List nestedBackedModelsToEnumerate = new ArrayList<>(); for (final Map.Entry> entry : this.store.entrySet()) { final Pair wrapper = entry.getValue(); @@ -260,7 +260,7 @@ private void ensureCollectionPropertiesAreConsistent() { } // Only update parent properties that haven't been marked as dirty by the nested models - for (Map.Entry entry : currentStoreDirtyCollections.entrySet()) { + for (final Map.Entry entry : currentStoreDirtyCollections.entrySet()) { // Always set() if there were no nested models if (nestedBackedModelsToEnumerate.isEmpty()) { set(entry.getKey(), entry.getValue()); From 36083aa421534c8893cef13dee72ffdb1bea21db Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 10 Sep 2024 12:52:19 -0400 Subject: [PATCH 16/19] Update components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java --- .../java/com/microsoft/kiota/store/InMemoryBackingStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 321b26b69..cfd76437f 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -243,7 +243,7 @@ private void ensureCollectionPropertiesAreConsistent() { if (isCollectionValue(wrapper)) { final Pair collectionTuple = (Pair) wrapper.getValue1(); Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); - for (Object item : items) { + for (final Object item : items) { if (!(item instanceof BackedModel)) break; nestedBackedModelsToEnumerate.add((BackedModel) item); } From cde4c396de16eab5f1f78e2da3cdded1f5767ab2 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 14:25:43 +0300 Subject: [PATCH 17/19] Add failing test cases for collection consistency checks on maps --- .../kiota/store/InMemoryBackingStoreTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index e2e418201..c1bf48961 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -9,7 +9,9 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -478,4 +480,121 @@ void testLargeArraysPerformsWell() { // Assert assertTrue(timeTakenMillis < 1); } + + @Test + void testInitializationCompletedIsPropagatedToMapItems() { + var colleagues = new HashMap(); + for (int i = 0; i < 10; i++) { + var colleague = new TestEntity(); + colleague.setId(UUID.randomUUID().toString()); + colleague.setBusinessPhones(Arrays.asList(new String[] {"+1 234 567 891"})); + colleague.getAdditionalData().put("count", i); + colleagues.put(colleague.getId(), colleague); + colleague.getBackingStore().setIsInitializationCompleted(false); + } + + var testUser = new TestEntity(); + testUser.setId("1"); + testUser.setAdditionalData(colleagues); + + testUser.getBackingStore().setIsInitializationCompleted(true); + for (Map.Entry colleague : testUser.getAdditionalData().entrySet()) { + var backedModel = (BackedModel) colleague.getValue(); + assertTrue(backedModel.getBackingStore().getIsInitializationCompleted()); + } + } + + @Test + void testInitializationCompletedIsPropagatedToCollectionItems() { + var colleagues = new ArrayList(); + for (int i = 0; i < 10; i++) { + var colleague = new TestEntity(); + colleague.setId(UUID.randomUUID().toString()); + colleague.setBusinessPhones(Arrays.asList(new String[] {"+1 234 567 891"})); + colleague.getAdditionalData().put("count", i); + colleagues.add(colleague); + colleague.getBackingStore().setIsInitializationCompleted(false); + } + + var testUser = new TestEntityCollectionResponse(); + testUser.setValue(colleagues); + + testUser.getBackingStore().setIsInitializationCompleted(true); + for (TestEntity colleague : testUser.getValue()) { + assertTrue(colleague.getBackingStore().getIsInitializationCompleted()); + } + + } + + @Test + void testCollectionPropertyConsistencyChecksSizeChangesInAllNestedItemsInCollection() { + var colleagues = new ArrayList(); + for (int i = 0; i < 10; i++) { + var colleague = new TestEntity(); + colleague.setId(UUID.randomUUID().toString()); + colleague.setBusinessPhones(Arrays.asList(new String[] {"+1 234 567 891"})); + var manager = new TestEntity(); + manager.setId(UUID.randomUUID().toString()); + colleague.getAdditionalData().put("count", i); + colleague.getAdditionalData().put("random", "randomString"); + colleague.getAdditionalData().put("manager", manager); + colleagues.add(colleague); + colleague.getBackingStore().setIsInitializationCompleted(true); + } + + var testUser = new TestEntity(); + testUser.setId(UUID.randomUUID().toString()); + testUser.setColleagues(colleagues); + testUser.getBackingStore().setIsInitializationCompleted(true); + + testUser.getBackingStore().setReturnOnlyChangedValues(true); + assertEquals(0, testUser.getBackingStore().enumerate().size()); + assertNull(testUser.getColleagues()); // null since value is not dirty + + // Update nested backed model + testUser.getBackingStore().setReturnOnlyChangedValues(false); + testUser.getColleagues().get(9).getAdditionalData().put("moreRandom", 123); + + // collection consistency should loop through all nested backed models in the collection and find one with a dirty additional data map + testUser.getBackingStore().setReturnOnlyChangedValues(true); + assertNotNull(testUser.getColleagues()); + var changedValues = testUser.getBackingStore().enumerate(); + assertEquals(1, changedValues.size()); + } + + @Test + void testCollectionPropertyConsistencyChecksEnumeratesNestedBackedModelsInAllNestedCollections() { + var colleagues = new ArrayList(); + for (int i = 0; i < 10; i++) { + var colleague = new TestEntity(); + colleague.setId(UUID.randomUUID().toString()); + colleague.setBusinessPhones(Arrays.asList(new String[] {"+1 234 567 891"})); + var manager = new TestEntity(); + manager.setId(UUID.randomUUID().toString()); + colleague.getAdditionalData().put("count", i); + colleague.getAdditionalData().put("random", "randomString"); + colleague.getAdditionalData().put("manager", manager); + colleagues.add(colleague); + colleague.getBackingStore().setIsInitializationCompleted(true); + } + + var testUser = new TestEntity(); + testUser.setId(UUID.randomUUID().toString()); + testUser.setColleagues(colleagues); + testUser.getBackingStore().setIsInitializationCompleted(true); + + testUser.getBackingStore().setReturnOnlyChangedValues(true); + assertEquals(0, testUser.getBackingStore().enumerate().size()); + assertNull(testUser.getColleagues()); // null since value is not dirty + + // Update nested backed model + testUser.getBackingStore().setReturnOnlyChangedValues(false); + ((TestEntity) testUser.getColleagues().get(9).getAdditionalData().get("manager")).getAdditionalData().put("moreRandom", 123); + + // collection consistency should loop through all nested backed models in the collection and find one with a dirty additional data map + testUser.getBackingStore().setReturnOnlyChangedValues(true); + var changedValues = testUser.getBackingStore().enumerate(); + assertNotNull(testUser.getColleagues()); + assertEquals(1, changedValues.size()); + } } From 70bc8888113dd210ec957053716bb880c4a87f0b Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 14:27:00 +0300 Subject: [PATCH 18/19] Fix issue with consistency checks on maps and propagate returnOnlyChangedValues to nested backed models --- .../kiota/store/InMemoryBackingStore.java | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index cfd76437f..0eec9b953 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -70,14 +70,19 @@ public void setIsInitializationCompleted(final boolean value) { if (isCollectionValue(wrapper)) { final Pair collectionTuple = (Pair) wrapper.getValue1(); Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); - - for (final Object item : items) { - if (!(item instanceof BackedModel)) break; - - BackedModel backedModel = (BackedModel) item; - backedModel - .getBackingStore() - .setIsInitializationCompleted(value); // propagate initialization + final boolean isCollection = collectionTuple.getValue0() instanceof Collection; + + // No need to iterate over collection if first item is not BackedModel + if ((isCollection && items.length != 0 && items[0] instanceof BackedModel) + || !isCollection) { + for (final Object item : items) { + if (item instanceof BackedModel) { + BackedModel backedModel = (BackedModel) item; + backedModel + .getBackingStore() + .setIsInitializationCompleted(value); // propagate initialization + } + } } } } @@ -89,6 +94,32 @@ public boolean getIsInitializationCompleted() { public void setReturnOnlyChangedValues(final boolean value) { this.returnOnlyChangedValues = value; + // propagate to nested backed models + for (final Map.Entry> entry : this.store.entrySet()) { + final Pair wrapper = entry.getValue(); + if (wrapper.getValue1() instanceof BackedModel) { + final BackedModel item = (BackedModel) wrapper.getValue1(); + item.getBackingStore().setReturnOnlyChangedValues(value); + } + if (isCollectionValue(wrapper)) { + final Pair collectionTuple = (Pair) wrapper.getValue1(); + Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); + final boolean isCollection = collectionTuple.getValue0() instanceof Collection; + + // No need to iterate over collection if first item is not BackedModel + if ((isCollection && items.length != 0 && items[0] instanceof BackedModel) + || !isCollection) { + for (final Object item : items) { + if (item instanceof BackedModel) { + BackedModel backedModel = (BackedModel) item; + backedModel + .getBackingStore() + .setReturnOnlyChangedValues(value); + } + } + } + } + } } public boolean getReturnOnlyChangedValues() { @@ -243,9 +274,16 @@ private void ensureCollectionPropertiesAreConsistent() { if (isCollectionValue(wrapper)) { final Pair collectionTuple = (Pair) wrapper.getValue1(); Object[] items = getObjectArrayFromCollectionWrapper(collectionTuple); - for (final Object item : items) { - if (!(item instanceof BackedModel)) break; - nestedBackedModelsToEnumerate.add((BackedModel) item); + final boolean isCollection = collectionTuple.getValue0() instanceof Collection; + // No need to iterate over collection if first item is not BackedModel + if ((isCollection && items.length != 0 && items[0] instanceof BackedModel) + || !isCollection) { + for (final Object item : items) { + if (item instanceof BackedModel) { + final BackedModel backedModel = (BackedModel) item; + nestedBackedModelsToEnumerate.add(backedModel); + } + } } if (collectionTuple.getValue1() != items.length) { // and the size has changed since we last updated From 631f1cf6b2eae60f1b80bf6d2966cf4d86d2ac89 Mon Sep 17 00:00:00 2001 From: Philip Gichuhi Date: Wed, 11 Sep 2024 14:28:13 +0300 Subject: [PATCH 19/19] Apply style suggestions --- .../kiota/store/InMemoryBackingStore.java | 7 ++--- .../kiota/store/InMemoryBackingStoreTest.java | 30 ++++++------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 0eec9b953..43d49fb27 100644 --- a/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/components/abstractions/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -80,7 +80,8 @@ public void setIsInitializationCompleted(final boolean value) { BackedModel backedModel = (BackedModel) item; backedModel .getBackingStore() - .setIsInitializationCompleted(value); // propagate initialization + .setIsInitializationCompleted( + value); // propagate initialization } } } @@ -112,9 +113,7 @@ public void setReturnOnlyChangedValues(final boolean value) { for (final Object item : items) { if (item instanceof BackedModel) { BackedModel backedModel = (BackedModel) item; - backedModel - .getBackingStore() - .setReturnOnlyChangedValues(value); + backedModel.getBackingStore().setReturnOnlyChangedValues(value); } } } diff --git a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java index c1bf48961..39f97e711 100644 --- a/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java +++ b/components/abstractions/src/test/java/com/microsoft/kiota/store/InMemoryBackingStoreTest.java @@ -465,22 +465,6 @@ void TestsBackingStoreUpdateToItemInNestedCollectionWithAnotherBackedModel() { .containsKey("manager")); } - @Test - void testLargeArraysPerformsWell() { - // Arrange - var testBackingStore = new InMemoryBackingStore(); - // Act - assertTrue(testBackingStore.enumerate().isEmpty()); - var testArray = new int[100000000]; - testBackingStore.set("values", testArray); - long startTimeMillis = System.currentTimeMillis(); - testBackingStore.setIsInitializationCompleted(true); - long timeTakenMillis = System.currentTimeMillis() - startTimeMillis; - - // Assert - assertTrue(timeTakenMillis < 1); - } - @Test void testInitializationCompletedIsPropagatedToMapItems() { var colleagues = new HashMap(); @@ -523,7 +507,6 @@ void testInitializationCompletedIsPropagatedToCollectionItems() { for (TestEntity colleague : testUser.getValue()) { assertTrue(colleague.getBackingStore().getIsInitializationCompleted()); } - } @Test @@ -555,7 +538,8 @@ void testCollectionPropertyConsistencyChecksSizeChangesInAllNestedItemsInCollect testUser.getBackingStore().setReturnOnlyChangedValues(false); testUser.getColleagues().get(9).getAdditionalData().put("moreRandom", 123); - // collection consistency should loop through all nested backed models in the collection and find one with a dirty additional data map + // collection consistency should loop through all nested backed models in the collection and + // find one with a dirty additional data map testUser.getBackingStore().setReturnOnlyChangedValues(true); assertNotNull(testUser.getColleagues()); var changedValues = testUser.getBackingStore().enumerate(); @@ -563,7 +547,8 @@ void testCollectionPropertyConsistencyChecksSizeChangesInAllNestedItemsInCollect } @Test - void testCollectionPropertyConsistencyChecksEnumeratesNestedBackedModelsInAllNestedCollections() { + void + testCollectionPropertyConsistencyChecksEnumeratesNestedBackedModelsInAllNestedCollections() { var colleagues = new ArrayList(); for (int i = 0; i < 10; i++) { var colleague = new TestEntity(); @@ -589,9 +574,12 @@ void testCollectionPropertyConsistencyChecksEnumeratesNestedBackedModelsInAllNes // Update nested backed model testUser.getBackingStore().setReturnOnlyChangedValues(false); - ((TestEntity) testUser.getColleagues().get(9).getAdditionalData().get("manager")).getAdditionalData().put("moreRandom", 123); + ((TestEntity) testUser.getColleagues().get(9).getAdditionalData().get("manager")) + .getAdditionalData() + .put("moreRandom", 123); - // collection consistency should loop through all nested backed models in the collection and find one with a dirty additional data map + // collection consistency should loop through all nested backed models in the collection and + // find one with a dirty additional data map testUser.getBackingStore().setReturnOnlyChangedValues(true); var changedValues = testUser.getBackingStore().enumerate(); assertNotNull(testUser.getColleagues());