blob: ee6b33a5eddc4664514005a24da78d3403b5cb65 [file] [log] [blame]
/*
* 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.netbeans.spi.actions;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.openide.util.Lookup;
import org.openide.util.Parameters;
/**
* Merges multiple NbActions or their stubs.
*
* @author Tim Boudreau
*/
final class MergeAction extends NbAction implements PropertyChangeListener {
private NbAction[] actions;
private Map<String, Object> knownValues = new HashMap<String, Object>();
private Action delegateAction;
private volatile boolean enabled;
final boolean allowOnlyOne;
public MergeAction(NbAction[] actions, boolean allowOnlyOne) {
this.actions = actions;
this.allowOnlyOne = allowOnlyOne;
assert actions.length > 0;
assert new HashSet<Action>(Arrays.asList(actions)).size() == actions.length :
"Duplicate actions in " + Arrays.asList(actions);
for (int i = 0; i < actions.length; i++) {
Parameters.notNull("Action " + i, actions[i]); //NOI18N
}
//prime our key set common keys
knownValues.put(NAME, null);
knownValues.put(ACCELERATOR_KEY, null);
knownValues.put(LONG_DESCRIPTION, null);
knownValues.put(SMALL_ICON, null);
knownValues.put(SHORT_DESCRIPTION, null);
knownValues.put(LONG_DESCRIPTION, null);
knownValues.put(SMALL_ICON, null);
knownValues.put(MNEMONIC_KEY, null);
knownValues.put("noIconInMenu", null);
knownValues.put(PROP_ENABLED, null);
}
public MergeAction(NbAction[] actions) {
this (actions, false);
}
@Override
public boolean equals(Object o) {
return o != null && MergeAction.class == o.getClass() && Arrays.equals(((MergeAction)o).actions, actions);
}
@Override
public int hashCode() {
return Arrays.hashCode(actions);
}
Action updateDelegateAction() {
synchronized (this) {
return setDelegateAction(findEnabledAction());
}
}
Action setDelegateAction(Action a) {
assert Thread.holdsLock(this);
synchronized (this) {
Action old = this.delegateAction;
if (old != a) {
delegateAction = a;
boolean nowEnabled = getDelegateAction().isEnabled();
if (nowEnabled != enabled) {
enabled = nowEnabled;
firePropertyChange(PROP_ENABLED, !enabled, enabled);
}
if (a != null) {
sievePropertyChanges();
}
}
}
return a;
}
Action findEnabledAction() {
Action result = null;
int enaCount = 0;
//We want to, if necessary, briefly addNotify() the action,
//so run it inside an ActionRunnable.
ActionRunnable<Boolean> ar = new ActionRunnable<Boolean>() {
public Boolean run(NbAction a) {
return a.isEnabled();
}
};
for (NbAction a : actions) {
if (runActive(ar, a)) {
enaCount++;
if (!allowOnlyOne) {
result = a;
break;
} else if (result == null) {
result = a;
}
}
}
if (allowOnlyOne && enaCount > 1 && result != null) {
result = null;
}
return result;
}
Action getDelegateAction() {
Action result = null;
synchronized (this) {
result = delegateAction;
if (result == null || !result.isEnabled()) {
if (attached()) {
result = updateDelegateAction();
} else {
result = findEnabledAction();
}
}
}
if (result == null) {
result = actions[0];
}
return result;
}
private void sievePropertyChanges() {
Map<String, Object> nue = new HashMap<String, Object>();
for (Map.Entry<String, Object> entry : knownValues.entrySet()) {
String key = entry.getKey();
Object expected = entry.getValue();
Object found = getValue(key);//del.getValue(key);
if (found != expected) {
nue.put(key, found);
firePropertyChange(key, expected, found);
}
}
}
@Override
protected synchronized void addNotify() {
for (Action a : actions) {
a.addPropertyChangeListener(this);
}
updateDelegateAction();
}
@Override
protected synchronized void removeNotify() {
for (Action a : actions) {
a.removePropertyChangeListener(this);
}
setDelegateAction(null);
}
@Override
protected NbAction internalCreateContextAwareInstance(Lookup actionContext) {
NbAction[] stubs = new NbAction[actions.length];
for (int i = 0; i < stubs.length; i++) {
stubs[i] = (NbAction)
actions[i].createContextAwareInstance(actionContext);
}
MergeAction result = new MergeAction(stubs, allowOnlyOne);
result.knownValues.putAll(knownValues);
result.pairs.putAll(pairs);
return result;
}
@Override
public Object getValue(final String key) {
Object result = super.getValue(key);
if (result == null) {
if (isEnabled()) {
Action del = getDelegateAction();
result = del.getValue(key);
}
}
if (result == null) {
ActionRunnable<Object> ar = new ActionRunnable<Object>() {
public Object run(NbAction a) {
return a.getValue(key);
}
};
for (NbAction a : actions) {
result = runActive(ar, a);
if (result != null) {
break;
}
}
}
this.knownValues.put(key, result);
return result;
}
boolean logged;
@Override
public void putValue(String key, Object value) {
if (!logged) {
Logger.getLogger(MergeAction.class.getName()).log(Level.INFO,
"putValue (" + key + ',' + value + //NOI18N
"called on merged action. This is probably a mistake."); //NOI18N
}
super.putValue(key, value);
}
public boolean isEnabled() {
boolean result = updateEnabled();
return result;
}
boolean updateEnabled() {
enabled = getDelegateAction().isEnabled();
if (allowOnlyOne && enabled) {
if (findEnabledAction() == null) {
enabled = false;
}
}
return enabled;
}
public void actionPerformed(ActionEvent e) {
Action a = getDelegateAction();
if (a == null) {
throw new IllegalStateException("Not enabled or no delegate: " + //NOI18N
this);
}
a.actionPerformed(e);
}
public void propertyChange(PropertyChangeEvent evt) {
boolean old = enabled;
synchronized (this) {
if (attached()) {
updateDelegateAction();
}
}
boolean nowEnabled = isEnabled();
if (!PROP_ENABLED.equals(evt.getPropertyName())) {
Object last = knownValues.get(evt.getPropertyName());
Object mine = getValue(evt.getPropertyName());
if (mine != last) {
firePropertyChange(evt.getPropertyName(), last, mine);
}
knownValues.put(evt.getPropertyName(), evt.getNewValue());
}
if (old != nowEnabled) {
firePropertyChange(PROP_ENABLED, old, nowEnabled);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder (super.toString());
sb.append ('['); //NOI18N
for (int i = 0; i < actions.length; i++) {
sb.append (actions[i]);
if (i != actions.length - 1) {
sb.append (','); //NOI18N
}
}
sb.append(']'); //NOI18N
return sb.toString();
}
}