66import jakarta .annotation .Nullable ;
77
88import java .util .ArrayList ;
9+ import java .util .Collection ;
910import java .util .HashMap ;
1011import java .util .List ;
1112import java .util .Map ;
@@ -33,15 +34,15 @@ public A getValue0() {
3334 }
3435
3536 public Pair <A , B > setValue0 (A value0 ) {
36- return new Pair (value0 , value1 );
37+ return new Pair <> (value0 , value1 );
3738 }
3839
3940 public B getValue1 () {
4041 return value1 ;
4142 }
4243
4344 public Pair <A , B > setValue1 (B value1 ) {
44- return new Pair (value0 , value1 );
45+ return new Pair <> (value0 , value1 );
4546 }
4647 }
4748
@@ -55,7 +56,15 @@ public void setIsInitializationCompleted(final boolean value) {
5556 this .isInitializationCompleted = value ;
5657 for (final Map .Entry <String , Pair <Boolean , Object >> entry : this .store .entrySet ()) {
5758 final Pair <Boolean , Object > wrapper = entry .getValue ();
58- final Pair <Boolean , Object > updatedValue = wrapper .setValue0 (Boolean .valueOf (!value ));
59+ if (wrapper .getValue1 () instanceof BackedModel ) {
60+ BackedModel backedModel = (BackedModel ) wrapper .getValue1 ();
61+ backedModel
62+ .getBackingStore ()
63+ .setIsInitializationCompleted (value ); // propagate initialization
64+ }
65+ ensureCollectionPropertyIsConsistent (
66+ entry .getKey (), this .store .get (entry .getKey ()).getValue1 ());
67+ final Pair <Boolean , Object > updatedValue = wrapper .setValue0 (!value );
5968 entry .setValue (updatedValue );
6069 }
6170 }
@@ -80,10 +89,12 @@ public void clear() {
8089 final Map <String , Object > result = new HashMap <>();
8190 for (final Map .Entry <String , Pair <Boolean , Object >> entry : this .store .entrySet ()) {
8291 final Pair <Boolean , Object > wrapper = entry .getValue ();
83- final Object value = this .getValueFromWrapper (wrapper );
92+ final Object value = this .getValueFromWrapper (entry . getKey (), wrapper );
8493
8594 if (value != null ) {
8695 result .put (entry .getKey (), wrapper .getValue1 ());
96+ } else if (Boolean .TRUE .equals (wrapper .getValue0 ())) {
97+ result .put (entry .getKey (), null );
8798 }
8899 }
89100 return result ;
@@ -101,13 +112,15 @@ public void clear() {
101112 return result ;
102113 }
103114
104- private Object getValueFromWrapper (final Pair <Boolean , Object > wrapper ) {
115+ private Object getValueFromWrapper (final String entryKey , final Pair <Boolean , Object > wrapper ) {
105116 if (wrapper != null ) {
106117 final Boolean hasChanged = wrapper .getValue0 ();
107- if (!this .returnOnlyChangedValues
108- || (this .returnOnlyChangedValues
109- && hasChanged != null
110- && hasChanged .booleanValue ())) {
118+ if (!this .returnOnlyChangedValues || Boolean .TRUE .equals (hasChanged )) {
119+ ensureCollectionPropertyIsConsistent (entryKey , wrapper .getValue1 ());
120+ if (wrapper .getValue1 () instanceof Pair ) {
121+ Pair <?, ?> collectionTuple = (Pair <?, ?>) wrapper .getValue1 ();
122+ return collectionTuple .getValue0 ();
123+ }
111124 return wrapper .getValue1 ();
112125 }
113126 }
@@ -118,7 +131,7 @@ private Object getValueFromWrapper(final Pair<Boolean, Object> wrapper) {
118131 @ Nullable public <T > T get (@ Nonnull final String key ) {
119132 Objects .requireNonNull (key );
120133 final Pair <Boolean , Object > wrapper = this .store .get (key );
121- final Object value = this .getValueFromWrapper (wrapper );
134+ final Object value = this .getValueFromWrapper (key , wrapper );
122135 try {
123136 return (T ) value ;
124137 } catch (ClassCastException ex ) {
@@ -128,11 +141,39 @@ private Object getValueFromWrapper(final Pair<Boolean, Object> wrapper) {
128141
129142 public <T > void set (@ Nonnull final String key , @ Nullable final T value ) {
130143 Objects .requireNonNull (key );
131- final Pair <Boolean , Object > valueToAdd =
132- new Pair (Boolean .valueOf (this .isInitializationCompleted ), value );
144+ Pair <Boolean , Object > valueToAdd = new Pair <>(this .isInitializationCompleted , value );
145+ if (value instanceof Collection ) {
146+ valueToAdd = valueToAdd .setValue1 (new Pair <>(value , ((Collection <?>) value ).size ()));
147+ final Collection <Object > items = (Collection <Object >) value ;
148+ setupNestedSubscriptions (items , key , value );
149+ } else if (value instanceof Map ) {
150+ valueToAdd = valueToAdd .setValue1 (new Pair <>(value , ((Map <?, ?>) value ).size ()));
151+ final Map <?, Object > items = (Map <?, Object >) value ;
152+ setupNestedSubscriptions (items .values (), key , value );
153+ } else if (value instanceof BackedModel ) {
154+ final BackedModel backedModel = (BackedModel ) value ;
155+ backedModel
156+ .getBackingStore ()
157+ .subscribe (
158+ key ,
159+ (keyString , oldObject , newObject ) -> {
160+ backedModel
161+ .getBackingStore ()
162+ .setIsInitializationCompleted (
163+ false ); // All its properties are dirty as the model
164+ // has been touched.
165+ set (key , value );
166+ }); // use property name(key) as subscriptionId to prevent excess
167+ // subscription creation in the event this is called again
168+ }
169+
133170 final Pair <Boolean , Object > oldValue = this .store .put (key , valueToAdd );
134171 for (final TriConsumer <String , Object , Object > callback : this .subscriptionStore .values ()) {
135- callback .accept (key , oldValue .getValue1 (), value );
172+ if (oldValue != null ) {
173+ callback .accept (key , oldValue .getValue1 (), value );
174+ } else {
175+ callback .accept (key , null , value );
176+ }
136177 }
137178 }
138179
@@ -154,4 +195,53 @@ public void subscribe(
154195 Objects .requireNonNull (subscriptionId );
155196 this .subscriptionStore .put (subscriptionId , callback );
156197 }
198+
199+ private void setupNestedSubscriptions (
200+ final Collection <Object > items , final String key , final Object value ) {
201+ for (final Object item : items ) {
202+ if (item instanceof BackedModel ) {
203+ final BackedModel backedModel = (BackedModel ) item ;
204+ backedModel .getBackingStore ().setIsInitializationCompleted (false );
205+ backedModel
206+ .getBackingStore ()
207+ .subscribe (key , (keyString , oldObject , newObject ) -> set (key , value ));
208+ }
209+ }
210+ }
211+
212+ private void ensureCollectionPropertyIsConsistent (final String key , final Object storeItem ) {
213+ if (storeItem instanceof Pair ) { // check if we put in a collection annotated with the size
214+ final Pair <?, Integer > collectionTuple = (Pair <?, Integer >) storeItem ;
215+ Object [] items ;
216+ if (collectionTuple .getValue0 () instanceof Collection ) {
217+ items = ((Collection <Object >) collectionTuple .getValue0 ()).toArray ();
218+ } else { // it is a map
219+ items = ((Map <?, Object >) collectionTuple .getValue0 ()).values ().toArray ();
220+ }
221+
222+ for (final Object item : items ) {
223+ touchNestedProperties (item ); // call get on nested properties
224+ }
225+
226+ if (collectionTuple .getValue1 ()
227+ != items .length ) { // and the size has changed since we last updated
228+ set (
229+ key ,
230+ collectionTuple .getValue0 ()); // ensure the store is notified the collection
231+ // property is "dirty"
232+ }
233+ }
234+ touchNestedProperties (storeItem ); // call get on nested properties
235+ }
236+
237+ private void touchNestedProperties (final Object nestedObject ) {
238+ if (nestedObject instanceof BackedModel ) {
239+ // Call Get<>() on nested properties so that this method may be called recursively to
240+ // ensure collections are consistent
241+ final BackedModel backedModel = (BackedModel ) nestedObject ;
242+ for (final String itemKey : backedModel .getBackingStore ().enumerate ().keySet ()) {
243+ backedModel .getBackingStore ().get (itemKey );
244+ }
245+ }
246+ }
157247}
0 commit comments