This is an automated email from the ASF dual-hosted git repository.

tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new 2f6090adca [incubator-kie-drools-6733] Preserve insertion order in 
ListDataStore (#6734)
2f6090adca is described below

commit 2f6090adcaf9184203ccd44af8b1a4a38a46fec7
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Fri May 29 17:00:40 2026 +0900

    [incubator-kie-drools-6733] Preserve insertion order in ListDataStore 
(#6734)
    
    ListDataStore used IdentityHashMap which does not preserve insertion
    order, causing subscribe() to replay events in arbitrary order and
    breaking sliding window CEP behavior. Added an ArrayList<DataHandle>
    alongside the IdentityHashMap to maintain insertion order for iteration
    and replay while preserving identity-based lookup semantics.
    
    Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../ruleunits/impl/datasources/ListDataStore.java  |  11 +-
 .../drools/ruleunits/impl/DataStoreOrderUnit.java  |  48 +++++++
 .../drools/ruleunits/impl/ListDataStoreTest.java   | 143 +++++++++++++++++++++
 .../org/drools/ruleunits/impl/DataStoreOrder.drl   |  27 ++++
 4 files changed, 227 insertions(+), 2 deletions(-)

diff --git 
a/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
 
b/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
index 5134c3b48c..be76aaea1e 100644
--- 
a/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
+++ 
b/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
@@ -18,8 +18,10 @@
  */
 package org.drools.ruleunits.impl.datasources;
 
+import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import org.drools.base.definitions.rule.impl.RuleImpl;
@@ -38,19 +40,22 @@ import org.kie.api.runtime.rule.RuleContext;
 public class ListDataStore<T> extends AbstractDataSource<T> implements  
Iterable<T>, DataStore<T>, InternalStoreCallback {
 
     private final Map<T, DataHandle> store = new IdentityHashMap<>();
+    private final List<DataHandle> insertionOrder = new ArrayList<>();
 
     protected ListDataStore() {
 
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public Iterator<T> iterator() {
-        return store.keySet().iterator();
+        return insertionOrder.stream().map(dh -> (T) 
dh.getObject()).iterator();
     }
 
     public DataHandle add(T t) {
         DataHandle dh = createDataHandle(t);
         store.put(t, dh);
+        insertionOrder.add(dh);
         forEachSubscriber(s -> internalInsert(dh, s));
         return dh;
     }
@@ -89,12 +94,13 @@ public class ListDataStore<T> extends AbstractDataSource<T> 
implements  Iterable
     public void remove(DataHandle handle) {
         forEachSubscriber(s -> s.delete(handle));
         store.remove(handle.getObject());
+        insertionOrder.remove(handle);
     }
 
     @Override
     public void subscribe(DataProcessor processor) {
         super.subscribe(processor);
-        store.values().forEach(dh -> internalInsert(dh, processor));
+        insertionOrder.forEach(dh -> internalInsert(dh, processor));
     }
 
     @Override
@@ -129,6 +135,7 @@ public class ListDataStore<T> extends AbstractDataSource<T> 
implements  Iterable
         entryPointSubscribers.forEach(s -> s.delete(dh, rule, terminalNode, 
fhState));
         subscribers.forEach(s -> s.delete(dh));
         store.remove(fh.getObject());
+        insertionOrder.remove(dh);
     }
 
     private void internalInsert(DataHandle dh, DataProcessor s) {
diff --git 
a/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
 
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
new file mode 100644
index 0000000000..5e0477ff2d
--- /dev/null
+++ 
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitData;
+
+public class DataStoreOrderUnit implements RuleUnitData {
+
+    private final DataStore<String> strings;
+    private final List<String> results = new ArrayList<>();
+
+    public DataStoreOrderUnit() {
+        this(DataSource.createStore());
+    }
+
+    public DataStoreOrderUnit(DataStore<String> strings) {
+        this.strings = strings;
+    }
+
+    public DataStore<String> getStrings() {
+        return strings;
+    }
+
+    public List<String> getResults() {
+        return results;
+    }
+}
diff --git 
a/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
 
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
new file mode 100644
index 0000000000..5c0016f900
--- /dev/null
+++ 
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.drools.ruleunits.api.DataHandle;
+import org.drools.ruleunits.api.DataProcessor;
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitInstance;
+import org.drools.ruleunits.api.RuleUnitProvider;
+import org.drools.ruleunits.api.conf.RuleConfig;
+import org.junit.jupiter.api.Test;
+import org.kie.api.event.rule.ObjectDeletedEvent;
+import org.kie.api.event.rule.ObjectInsertedEvent;
+import org.kie.api.event.rule.ObjectUpdatedEvent;
+import org.kie.api.event.rule.RuleRuntimeEventListener;
+import org.kie.api.runtime.rule.FactHandle;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ListDataStoreTest {
+
+    @Test
+    public void subscribeReplaysInInsertionOrder() {
+        DataStore<String> store = DataSource.createStore();
+        store.add("A");
+        store.add("B");
+        store.add("C");
+        store.add("D");
+        store.add("E");
+
+        RecordingProcessor<String> recorder = new RecordingProcessor<>();
+        store.subscribe(recorder);
+
+        assertThat(recorder.insertedObjects).containsExactly("A", "B", "C", 
"D", "E");
+    }
+
+    @Test
+    public void iteratorReturnsInsertionOrder() {
+        DataStore<String> store = DataSource.createStore();
+        store.add("X");
+        store.add("Y");
+        store.add("Z");
+
+        List<String> iterated = new ArrayList<>();
+        for (String s : (Iterable<String>) store) {
+            iterated.add(s);
+        }
+
+        assertThat(iterated).containsExactly("X", "Y", "Z");
+    }
+
+    @Test
+    public void subscribeAfterRemovePreservesOrder() {
+        DataStore<String> store = DataSource.createStore();
+        DataHandle dhA = store.add("A");
+        store.add("B");
+        DataHandle dhC = store.add("C");
+        store.add("D");
+
+        store.remove(dhA);
+        store.remove(dhC);
+
+        RecordingProcessor<String> recorder = new RecordingProcessor<>();
+        store.subscribe(recorder);
+
+        assertThat(recorder.insertedObjects).containsExactly("B", "D");
+    }
+
+    @Test
+    public void ruleUnitDataStoreReplayOrder() {
+        DataStoreOrderUnit unit = new DataStoreOrderUnit();
+        unit.getStrings().add("A");
+        unit.getStrings().add("B");
+        unit.getStrings().add("C");
+        unit.getStrings().add("D");
+        unit.getStrings().add("E");
+
+        List<String> insertedObjects = new ArrayList<>();
+
+        RuleConfig ruleConfig = RuleUnitProvider.get().newRuleConfig();
+        ruleConfig.getRuleRuntimeListeners().add(new 
RuleRuntimeEventListener() {
+            @Override
+            public void objectInserted(ObjectInsertedEvent event) {
+                if (event.getObject() instanceof String) {
+                    insertedObjects.add((String) event.getObject());
+                }
+            }
+
+            @Override
+            public void objectUpdated(ObjectUpdatedEvent event) {
+            }
+
+            @Override
+            public void objectDeleted(ObjectDeletedEvent event) {
+            }
+        });
+
+        try (RuleUnitInstance<DataStoreOrderUnit> instance =
+                     RuleUnitProvider.get().createRuleUnitInstance(unit, 
ruleConfig)) {
+            assertThat(insertedObjects).containsExactly("A", "B", "C", "D", 
"E");
+        }
+    }
+
+    private static class RecordingProcessor<T> implements DataProcessor<T> {
+
+        final List<T> insertedObjects = new ArrayList<>();
+
+        @Override
+        public FactHandle insert(DataHandle handle, T object) {
+            insertedObjects.add(object);
+            return null;
+        }
+
+        @Override
+        public void update(DataHandle handle, T object) {
+        }
+
+        @Override
+        public void delete(DataHandle handle) {
+        }
+    }
+}
diff --git 
a/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
 
b/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
new file mode 100644
index 0000000000..d2dbfc46c5
--- /dev/null
+++ 
b/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+unit DataStoreOrderUnit;
+
+rule "RecordEach"
+when
+    $s: /strings
+then
+    results.add($s);
+end


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to