You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by sd...@apache.org on 2019/11/13 13:16:04 UTC
[netbeans] branch master updated: [NETBEANS-1985] Honour initial
values of stateful delegate actions. (#1579)
This is an automated email from the ASF dual-hosted git repository.
sdedic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new 97b6f5e [NETBEANS-1985] Honour initial values of stateful delegate actions. (#1579)
97b6f5e is described below
commit 97b6f5e3ac44870a0b85a4ab95a2750fdd9d8bb1
Author: Svatopluk Dedic <sv...@oracle.com>
AuthorDate: Wed Nov 13 14:15:57 2019 +0100
[NETBEANS-1985] Honour initial values of stateful delegate actions. (#1579)
[NETBEANS-1985] Honour initial values of stateful delegate actions.
---
.../src/org/openide/awt/{awt => }/ActionState.java | 0
.../src/org/openide/awt/ContextAction.java | 8 +-
.../src/org/openide/awt/GeneralAction.java | 8 +-
.../org/openide/awt/{awt => }/PropertyMonitor.java | 0
.../org/openide/awt/{awt => }/StatefulAction.java | 28 +++
.../org/openide/awt/ContextActionNonAWTTest.java | 16 ++
.../src/org/openide/awt/ContextActionTest.java | 231 ++++++++++++++++++++-
7 files changed, 283 insertions(+), 8 deletions(-)
diff --git a/platform/openide.awt/src/org/openide/awt/awt/ActionState.java b/platform/openide.awt/src/org/openide/awt/ActionState.java
similarity index 100%
rename from platform/openide.awt/src/org/openide/awt/awt/ActionState.java
rename to platform/openide.awt/src/org/openide/awt/ActionState.java
diff --git a/platform/openide.awt/src/org/openide/awt/ContextAction.java b/platform/openide.awt/src/org/openide/awt/ContextAction.java
index bea899b..3510e38 100644
--- a/platform/openide.awt/src/org/openide/awt/ContextAction.java
+++ b/platform/openide.awt/src/org/openide/awt/ContextAction.java
@@ -276,10 +276,7 @@ implements Action, ContextAwareAction, ChangeListener, Runnable {
@Override
public int hashCode() {
- int t = type.hashCode();
- int m = selectMode.hashCode();
- int p = performer.hashCode();
- return (t << 2) + (m << 1) + p;
+ return Objects.hash(type, selectMode, performer, enableMonitor);
}
@Override
@@ -293,7 +290,8 @@ implements Action, ContextAwareAction, ChangeListener, Runnable {
return type.equals(c.type) &&
selectMode.equals(c.selectMode) &&
- performer.equals(c.performer);
+ performer.equals(c.performer) &&
+ Objects.equals(enableMonitor, c.enableMonitor);
}
return false;
}
diff --git a/platform/openide.awt/src/org/openide/awt/GeneralAction.java b/platform/openide.awt/src/org/openide/awt/GeneralAction.java
index 960caac..eda09c6 100644
--- a/platform/openide.awt/src/org/openide/awt/GeneralAction.java
+++ b/platform/openide.awt/src/org/openide/awt/GeneralAction.java
@@ -470,7 +470,13 @@ final class GeneralAction {
}
BaseDelAction other = copyDelegate(f, actionContext);
if (attrs != null) {
- other.attrs = new HashMap<String,Object>(attrs);
+ if (other.attrs == null) {
+ other.attrs = new HashMap<>(attrs);
+ } else {
+ for (String k : attrs.keySet()) {
+ other.attrs.putIfAbsent(k, attrs.get(k));
+ }
+ }
}
return other;
}
diff --git a/platform/openide.awt/src/org/openide/awt/awt/PropertyMonitor.java b/platform/openide.awt/src/org/openide/awt/PropertyMonitor.java
similarity index 100%
rename from platform/openide.awt/src/org/openide/awt/awt/PropertyMonitor.java
rename to platform/openide.awt/src/org/openide/awt/PropertyMonitor.java
diff --git a/platform/openide.awt/src/org/openide/awt/awt/StatefulAction.java b/platform/openide.awt/src/org/openide/awt/StatefulAction.java
similarity index 84%
rename from platform/openide.awt/src/org/openide/awt/awt/StatefulAction.java
rename to platform/openide.awt/src/org/openide/awt/StatefulAction.java
index 8c2cd1f..a1bfa69 100644
--- a/platform/openide.awt/src/org/openide/awt/awt/StatefulAction.java
+++ b/platform/openide.awt/src/org/openide/awt/StatefulAction.java
@@ -19,6 +19,7 @@
package org.openide.awt;
import java.util.Collections;
+import java.util.Objects;
import java.util.logging.Level;
import javax.swing.Action;
import org.openide.util.Lookup;
@@ -82,6 +83,33 @@ final class StatefulAction<T> extends ContextAction<T> {
}
@Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 97 * hash + Objects.hashCode(this.checkValueMonitor);
+ return hash;
+ }
+
+ /**
+ * Overrides ContextAction. Must found in the {@link #checkValueMonitor} otherwise
+ * two actions driven by different property could clash in {@link ContextManager}
+ * and won't get 'enable' notification when data appears.
+ *
+ * @param obj other object
+ * @return true, if equal
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ final StatefulAction<?> other = (StatefulAction<?>) obj;
+ if (!Objects.equals(this.checkValueMonitor, other.checkValueMonitor)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public Object getValue(String key) {
if (SELECTED_KEY.equals(key)) {
LOG.log(Level.FINER, "Action {0} state: {1}", new Object[] {
diff --git a/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionNonAWTTest.java b/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionNonAWTTest.java
index 1697fd5..b053c15 100644
--- a/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionNonAWTTest.java
+++ b/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionNonAWTTest.java
@@ -52,6 +52,22 @@ public class ContextActionNonAWTTest extends ContextActionTest {
SwingUtilities.invokeAndWait(run);
return run.is;
}
+
+ @Override
+ protected boolean getIsChecked(final Action a1) throws InterruptedException, InvocationTargetException {
+ assertFalse("Not in AWT", EventQueue.isDispatchThread());
+
+ class R implements Runnable {
+ boolean is;
+ public void run() {
+ is = Boolean.TRUE.equals(a1.getValue(Action.SELECTED_KEY));
+ }
+ }
+ R run = new R();
+ SwingUtilities.invokeAndWait(run);
+ return run.is;
+ }
+
protected void doActionPerformed(final Action a1, final ActionEvent ev) throws InterruptedException, InvocationTargetException {
assertFalse("Not in AWT", EventQueue.isDispatchThread());
diff --git a/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionTest.java b/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionTest.java
index 7208cc9..96938bf 100644
--- a/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionTest.java
+++ b/platform/openide.awt/test/unit/src/org/openide/awt/ContextActionTest.java
@@ -23,26 +23,31 @@ import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
import org.netbeans.junit.NbTestCase;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.ContextAwareAction;
+import org.openide.util.ContextGlobalProvider;
import org.openide.util.Lookup;
+import org.openide.util.Utilities;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
+import org.openide.util.test.MockLookup;
/** Test that cookie actions are in fact sensitive to the correct cookies in the
* correct numbers, and that changes to either node selection or cookies on the
@@ -66,6 +71,25 @@ implements Lookup.Provider, ContextActionEnabler<ContextActionTest.Openable> {
private int expectedEnabledmentCount = 2;
+ private static class CGP implements ContextGlobalProvider, Lookup.Provider {
+ private final Lookup prx = Lookups.proxy(this);
+
+ volatile Lookup current;
+
+ @Override
+ public Lookup createGlobalContext() {
+ return prx;
+ }
+
+ @Override
+ public Lookup getLookup() {
+ Lookup c = current;
+ return c != null ? c : Lookup.EMPTY;
+ }
+ }
+
+ static CGP actionLookup = new CGP();
+
@Override
protected void setUp() throws Exception {
lookup = Lookup.EMPTY;
@@ -82,6 +106,18 @@ implements Lookup.Provider, ContextActionEnabler<ContextActionTest.Openable> {
n4 = new LookupWithOpenable(n1.lookup(Openable.class)); // share the same cookie instance with n1
SimpleCookieAction.runOn.clear();
+
+ actionLookup.current = lookupProxy;
+
+ MockLookup.setLookup(Lookups.metaInfServices(getClass().getClassLoader()),
+ lookupProxy,
+ Lookups.fixed(actionLookup));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ actionLookup.current = null;
+ super.tearDown();
}
@Override
@@ -619,6 +655,13 @@ implements Lookup.Provider, ContextActionEnabler<ContextActionTest.Openable> {
return a1.isEnabled();
}
+
+ protected boolean getIsChecked(final Action a1) throws InterruptedException, InvocationTargetException {
+ assertTrue("In AWT", EventQueue.isDispatchThread());
+
+ return Boolean.TRUE.equals(a1.getValue(Action.SELECTED_KEY));
+ }
+
protected void doActionPerformed(final Action a1, final ActionEvent ev) throws InterruptedException, InvocationTargetException {
assertTrue("In AWT", EventQueue.isDispatchThread());
a1.actionPerformed(ev);
@@ -696,5 +739,189 @@ implements Lookup.Provider, ContextActionEnabler<ContextActionTest.Openable> {
}
}
+ private static class TestData {
+
+ private boolean stateValue;
+
+ public static final String PROP_STATEVALUE = "stateValue";
+
+ public boolean isStateValue() {
+ return stateValue;
+ }
+
+ public void setStateValue(boolean stateValue) {
+ boolean oldStateValue = this.stateValue;
+ this.stateValue = stateValue;
+ propertyChangeSupport.firePropertyChange(PROP_STATEVALUE, oldStateValue, stateValue);
+ }
+
+ private transient final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
+
+ public void addPropertyChangeListener(PropertyChangeListener listener) {
+ propertyChangeSupport.addPropertyChangeListener(listener);
+ }
+
+ public void removePropertyChangeListener(PropertyChangeListener listener) {
+ propertyChangeSupport.removePropertyChangeListener(listener);
+ }
+ }
+
+ /**
+ * Test action, whose state is driven by a model
+ */
+ @ActionID(category = "Test", id = "CheckedTest")
+ @ActionRegistration(displayName = "Test Action",
+ checkedOn = @ActionState(
+ type = TestData.class,
+ property = "stateValue"
+ )
+ )
+ public static class CheckAction extends AbstractAction {
+ private final TestData data;
+
+ public CheckAction(TestData data) {
+ this.data = data;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ }
+ }
+
+ /**
+ * Checks that a stateful action properly changes its state, based
+ * on the supplied data.
+ * @throws Exception
+ */
+ public void testActionGlobalState() throws Exception {
+ Action baseA = Actions.forID("Test", "CheckedTest");
+
+ // baseAction is not enabled - no data:
+ assertFalse("No data, action must be disabled", getIsEnabled(baseA));
+ // baseAction is also not checked:
+ assertFalse("No data, action cannot be checked", getIsChecked(baseA));
+
+ // now provide the action:
+ TestData data = new TestData();
+ activate(Lookups.fixed(data));
+
+ assertTrue("Data was published, action must enable", getIsEnabled(baseA));
+ assertFalse("Data is false, action must be unchedked", getIsChecked(baseA));
+
+ data.setStateValue(true);
+
+ assertTrue(getIsEnabled(baseA));
+ assertTrue("Data changed to true, action must be checked", getIsChecked(baseA));
+ }
+ public void testActionGlobalStateStartUnchecked() throws Exception {
+ TestData data = new TestData();
+
+ activate(Lookups.fixed(data));
+ Action baseA = Actions.forID("Test", "CheckedTest");
+
+ // baseAction is not enabled - no data:
+ assertTrue("Action must start up enabled", getIsEnabled(baseA));
+ assertFalse("Data is false, action must be unchecked", getIsChecked(baseA));
+
+ data.setStateValue(true);
+ assertTrue("Data changed to true, action checked", getIsChecked(baseA));
+ }
+
+ public void testActionGlobalStateStartChecked() throws Exception {
+ TestData data = new TestData();
+ data.setStateValue(true);
+
+ activate(Lookups.fixed(data));
+ Action baseA = Actions.forID("Test", "CheckedTest");
+
+ // baseAction is not enabled - no data:
+ assertTrue(getIsEnabled(baseA));
+ // baseAction is also not checked:
+ assertTrue(getIsChecked(baseA));
+
+ data.setStateValue(false);
+ assertFalse("Data is false, action must be unchecked", getIsChecked(baseA));
+ }
+
+ public void testContextDelegate() throws Exception {
+ TestData data = new TestData();
+ data.setStateValue(true);
+
+ TestData otherData = new TestData();
+
+
+ Action baseA = Actions.forID("Test", "CheckedTest");
+
+ // must change main action Lookup after the main delegate exists,
+ // so it gets property change and changes its Action.SELECTED_KEY
+ // otherwise the .map is null and action just delegates to Fallback
+ activate(Lookups.fixed(data));
+
+ assertSame(data, Utilities.actionsGlobalContext().lookup(TestData.class));
+
+ InstanceContent ic = new InstanceContent();
+ Lookup context = new AbstractLookup(ic);
+ ic.add(data);
+ assertTrue(getIsEnabled(baseA));
+ assertTrue(getIsChecked(baseA));
+
+ Action actionA = ((ContextAwareAction)baseA).createContextAwareInstance(context);
+
+ assertTrue(getIsEnabled(actionA));
+ assertTrue(getIsChecked(actionA));
+
+ // let's have completely different local context:
+ Lookup context2 = Lookups.fixed(otherData);
+ Action actionB = ((ContextAwareAction)baseA).createContextAwareInstance(context2);
+ assertTrue(getIsEnabled(actionB));
+ // in this context, action should be enabled, but UNchecked.
+ assertFalse(getIsChecked(actionB));
+
+ class PCL implements PropertyChangeListener {
+ Set<String> propChanges = Collections.synchronizedSet(new HashSet<>());
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String n = evt.getPropertyName();
+ if (n != null) {
+ propChanges.add(n);
+ }
+ }
+ }
+
+ PCL listenerBase = new PCL();
+ PCL listenerA = new PCL();
+ PCL listenerB = new PCL();
+ baseA.addPropertyChangeListener(listenerBase);
+ actionA.addPropertyChangeListener(listenerA);
+ actionB.addPropertyChangeListener(listenerB);
+
+ TestData data3 = new TestData();
+ // the data has property false, so actionA should fire & change, not the other ones
+ ic.set(Collections.singleton(data3), null);
+
+ // also potentially replans to AWT, so the pending change event from lookup
+ // will be probably processed.
+ assertFalse(getIsChecked(actionA));
+ assertTrue(getIsChecked(baseA));
+
+ assertTrue(listenerBase.propChanges.isEmpty());
+ assertTrue(listenerB.propChanges.isEmpty());
+
+ assertTrue(listenerA.propChanges.contains(Action.SELECTED_KEY));
+
+ listenerA.propChanges.clear();
+
+ otherData.setStateValue(true);
+
+ // again sync with AWT
+ assertTrue(getIsChecked(baseA));
+ assertTrue(getIsChecked(actionB));
+
+ assertTrue(listenerBase.propChanges.isEmpty());
+ assertTrue(listenerA.propChanges.isEmpty());
+
+ assertTrue(listenerB.propChanges.contains(Action.SELECTED_KEY));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists