fix EffectiveContextMap.entrySet using parent first merging of variables to ensure local variables 'shadow' parent variables
diff --git a/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java b/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
index 0640062..127cb6f 100644
--- a/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
+++ b/src/main/java/org/apache/commons/scxml2/env/EffectiveContextMap.java
@@ -18,7 +18,9 @@
import java.io.Serializable;
import java.util.AbstractMap;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
import org.apache.commons.scxml2.Context;
@@ -40,6 +42,13 @@
*/
public EffectiveContextMap(final Context ctx) {
super();
+ Context current = ctx;
+ while (current != null) {
+ if (current.getVars() instanceof EffectiveContextMap) {
+ throw new IllegalArgumentException("Context or parent Context already wrapped by EffectiveContextMap");
+ }
+ current = current.getParent();
+ }
this.leaf = ctx;
}
@@ -48,13 +57,21 @@
*/
@Override
public Set<Entry<String, Object>> entrySet() {
- Set<Entry<String, Object>> entrySet = new HashSet<Entry<String, Object>>();
- Context current = leaf;
- while (current != null) {
- entrySet.addAll(current.getVars().entrySet());
- current = current.getParent();
+ Map<String, Object> map = new HashMap<>();
+ mergeVars(leaf, map);
+ return Collections.unmodifiableMap(map).entrySet();
+ }
+
+ /**
+ * Parent Context first merging of all Context vars, to ensure same named 'local' vars shadows parent var
+ * @param leaf current leaf Context
+ * @param map Map to merge vars into
+ */
+ protected void mergeVars(Context leaf, Map<String, Object> map) {
+ if (leaf != null) {
+ mergeVars(leaf.getParent(), map);
+ map.putAll(leaf.getVars());
}
- return entrySet;
}
/**
diff --git a/src/test/java/org/apache/commons/scxml2/env/SimpleContextTest.java b/src/test/java/org/apache/commons/scxml2/env/SimpleContextTest.java
index 32fe49a..f3ab7b9 100644
--- a/src/test/java/org/apache/commons/scxml2/env/SimpleContextTest.java
+++ b/src/test/java/org/apache/commons/scxml2/env/SimpleContextTest.java
@@ -34,7 +34,7 @@
@Test
public void testHasTrue() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -44,7 +44,7 @@
@Test
public void testHasNullParent() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -54,12 +54,12 @@
@Test
public void testHasParentWrongKey() {
- Map<String, Object> parentVars = new HashMap<String, Object>();
+ Map<String, Object> parentVars = new HashMap<>();
parentVars.put("key", "value");
SimpleContext parentContext = new SimpleContext(null, parentVars);
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -70,12 +70,12 @@
@Test
public void testHasParentCorrectKey() {
- Map<String, Object> parentVars = new HashMap<String, Object>();
+ Map<String, Object> parentVars = new HashMap<>();
parentVars.put("differentKey", "value");
SimpleContext parentContext = new SimpleContext(null, parentVars);
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -93,7 +93,7 @@
@Test
public void testGetValue() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -103,12 +103,12 @@
@Test
public void testGetParentValue() {
- Map<String, Object> parentVars = new HashMap<String, Object>();
+ Map<String, Object> parentVars = new HashMap<>();
parentVars.put("differentKey", "differentValue");
SimpleContext parentContext = new SimpleContext(null, parentVars);
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -119,7 +119,7 @@
@Test
public void testGetParentNull() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -129,12 +129,12 @@
@Test
public void testGetParentWrongValue() {
- Map<String, Object> parentVars = new HashMap<String, Object>();
+ Map<String, Object> parentVars = new HashMap<>();
parentVars.put("differentKey", "differentValue");
SimpleContext parentContext = new SimpleContext(null, parentVars);
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -145,7 +145,7 @@
@Test
public void testSetVarsChangeValue() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -157,7 +157,7 @@
@Test
public void testSetVarsEmpty() {
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
context.setVars(vars);
context.set("key", "newValue");
@@ -167,12 +167,12 @@
@Test
public void testSetVarsParent() {
- Map<String, Object> parentVars = new HashMap<String, Object>();
+ Map<String, Object> parentVars = new HashMap<>();
parentVars.put("differentKey", "differentValue");
SimpleContext parentContext = new SimpleContext(null, parentVars);
- Map<String, Object> vars = new HashMap<String, Object>();
+ Map<String, Object> vars = new HashMap<>();
vars.put("key", "value");
context.setVars(vars);
@@ -182,4 +182,35 @@
Assert.assertEquals("newValue", context.get("differentKey"));
}
+
+ @Test
+ public void testNestedEffectiveContextMapWrappingFails() {
+ SimpleContext rootContext = new SimpleContext();
+ rootContext.set("key", "root");
+ SimpleContext rootEffectiveContext = new SimpleContext(rootContext, new EffectiveContextMap(rootContext));
+ SimpleContext parentContext = new SimpleContext(rootEffectiveContext);
+ try {
+ new EffectiveContextMap(parentContext);
+ Assert.fail("Nested EffectiveContextMap wrapping should fail");
+ }
+ catch (IllegalArgumentException e) {
+ // good
+ }
+ }
+
+ @Test
+ public void testEffectiveContextMapMergeStragegy() {
+ SimpleContext rootContext = new SimpleContext();
+ rootContext.set("key", "root");
+ SimpleContext parentContext = new SimpleContext(rootContext);
+ parentContext.setLocal("key", "parent");
+ SimpleContext effectiveContext = new SimpleContext(parentContext, new EffectiveContextMap(parentContext));
+ Assert.assertEquals("parent", effectiveContext.get("key"));
+ // ensure EffectiveContextMap provides complete local variable shadowing
+ for (Map.Entry<String,Object> entry : effectiveContext.getVars().entrySet()) {
+ if (entry.getKey().equals("key")) {
+ Assert.assertEquals("parent", entry.getValue());
+ }
+ }
+ }
}