org.openide.awt.ContextManager Maven / Gradle / Ivy
/*
* 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.openide.awt;
import java.awt.event.ActionEvent;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.openide.util.Lookup;
import org.openide.util.Lookup.Item;
import org.openide.util.Lookup.Provider;
import org.openide.util.Lookup.Result;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Mutex;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.WeakSet;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
/** Listener on a global context.
*/
class ContextManager extends Object {
private static final Logger LOG = GeneralAction.LOG;
private static final Map> CACHE = new HashMap>();
private static final Map> SURVIVE = new HashMap>();
private Map listeners;
private Lookup lookup;
private LSet selectionAll;
private ContextManager(Lookup lookup) {
this.listeners = new HashMap();
this.lookup = lookup;
}
public static ContextManager findManager(Lookup context, boolean survive) {
synchronized (CACHE) {
Map> map = survive ? SURVIVE : CACHE;
LookupRef lr = new LookupRef(context);
Reference ref = map.get(lr);
ContextManager g = ref == null ? null : ref.get();
if (g == null) {
g = survive ? new SurviveManager(context) : new ContextManager(context);
ref = new GMReference(g, lr, survive);
map.put(lr, ref);
}
return g;
}
}
static void clearCache(LookupRef lr, GMReference ref, boolean survive) {
synchronized (CACHE) {
Map> map = survive ? SURVIVE : CACHE;
if (map.get(lr) == ref) {
map.remove(lr);
}
}
}
public void registerListener(Class type, ContextAction a) {
synchronized (CACHE) {
LSet existing = findLSet(type);
if (existing == null) {
Lookup.Result result = createResult(lookup.lookupResult(type));
existing = new LSet(result, type);
listeners.put(type, existing);
}
existing.add(a);
// TBD: a.updateState(new ActionMap(), actionMap.get());
if (a.selectMode == ContextSelection.ALL) {
initSelectionAll();
selectionAll.add(a);
}
}
}
public void unregisterListener(Class type, ContextAction a) {
synchronized (CACHE) {
LSet existing = findLSet(type);
if (existing != null) {
existing.remove(a);
if (existing.isEmpty()) {
listeners.remove(type);
existing.cleanup();
}
}
if (a.selectMode == ContextSelection.ALL && selectionAll != null) {
selectionAll.remove(a);
if (selectionAll.isEmpty() && !isSurvive()) {
selectionAll = null;
}
}
}
}
/** Does not survive focus change */
public boolean isSurvive() {
return false;
}
/** Checks whether a type is enabled.
*/
public boolean isEnabled(Class type, ContextSelection selectMode, ContextAction.Performer super T> enabler) {
Lookup.Result result = findResult(type);
boolean e = isEnabledOnData(result, type, selectMode);
if (enabler != null) {
if (e) {
List extends T> all = listFromResult(result);
e = enabler.enabled(all, new LkpAE(all, type));
} else if (enabler != null) {
enabler.detach();
}
}
return e;
}
/** Checks whether a type is enabled.
*/
public boolean runEnabled(Class type, ContextSelection selectMode, BiFunction, Lookup.Provider, Boolean> callback) {
Lookup.Result result = findResult(type);
boolean e = isEnabledOnData(result, type, selectMode);
if (e) {
List extends T> all = listFromResult(result);
e = callback.apply(all, new LkpAE(all, type));
}
return e;
}
private boolean isEnabledOnData(Lookup.Result result, Class type, ContextSelection selectMode) {
boolean res = isEnabledOnDataImpl(result, type, selectMode);
LOG.log(Level.FINE, "isEnabledOnData(result, {0}, {1}) = {2}", new Object[]{type, selectMode, res});
return res;
}
private boolean isEnabledOnDataImpl(Lookup.Result result, Class type, ContextSelection selectMode) {
switch (selectMode) {
case EXACTLY_ONE:
Collection> instances = new HashSet>(result.allItems());
return instances.size() == 1;
case ANY:
return !result.allItems().isEmpty();
case EACH: {
if (result.allItems().isEmpty()) {
return false;
}
Lookup.Result items = lookup.lookupResult(Lookup.Provider.class);
if (result.allItems().size() != items.allItems().size()) {
return false;
}
Lookup.Template template = new Lookup.Template(type);
for (Lookup.Provider prov : items.allInstances()) {
if (prov.getLookup().lookupItem(template) == null) {
return false;
}
}
return true;
}
case ALL: {
if (result.allItems().isEmpty()) {
return false;
}
Lookup.Result items = lookup.lookupResult(Lookup.Provider.class);
if (result.allItems().size() < items.allItems().size()) {
return false;
}
Lookup.Template template = new Lookup.Template(type);
for (Lookup.Provider prov : items.allInstances()) {
if (prov.getLookup().lookupItem(template) == null) {
return false;
}
}
return true;
}
}
return false;
}
// package private for tests only
@SuppressWarnings("unchecked")
LSet findLSet(Class type) {
synchronized (CACHE) {
return listeners.get(type);
}
}
private Lookup.Result findResult(final Class type) {
LSet lset = findLSet(type);
Lookup.Result result;
if (lset != null) {
result = lset.result;
} else {
result = lookup.lookupResult(type);
}
return result;
}
protected Lookup.Result createResult(Lookup.Result res) {
return res;
}
private final class LkpAE implements Lookup.Provider {
final List extends T> all;
final Class type;
public LkpAE(List extends T> all, Class type) {
this.all = all;
this.type = type;
}
private Lookup lookup;
public Lookup getLookup() {
if (lookup == null) {
lookup = new ProxyLookup(
Lookups.fixed(all.toArray()),
Lookups.exclude(ContextManager.this.lookup, type)
);
}
return lookup;
}
}
public void actionPerformed(final ActionEvent e, ContextAction.Performer super T> perf, final Class type, ContextSelection selectMode) {
Lookup.Result result = findResult(type);
final List extends T> all = listFromResult(result);
perf.actionPerformed(e, Collections.unmodifiableList(all), new LkpAE(all, type));
}
private List extends T> listFromResult(Lookup.Result result) {
Collection extends T> col = result.allInstances();
Collection tmp = new LinkedHashSet(col);
if (tmp.size() != col.size()) {
Collection nt = new ArrayList(tmp.size());
nt.addAll(tmp);
col = nt;
}
List extends T> all;
if (col instanceof List) {
all = (List extends T>)col;
} else {
ArrayList arr = new ArrayList();
arr.addAll(col);
all = arr;
}
return all;
}
private Lookup.Result initSelectionAll() {
assert Thread.holdsLock(CACHE);
if (selectionAll == null) {
Lookup.Result result = lookup.lookupResult(Lookup.Provider.class);
selectionAll = new LSet(result, Lookup.Provider.class);
}
return selectionAll.result;
}
private static final class GMReference extends WeakReference
implements Runnable {
private LookupRef context;
private boolean survive;
public GMReference(ContextManager m, LookupRef context, boolean survive) {
super(m, Utilities.activeReferenceQueue());
this.context = context;
this.survive = survive;
}
public void run() {
clearCache(context, this, survive);
}
} // end of GMReference
/** Manager with special behaviour.
*/
private static final class SurviveManager extends ContextManager {
private SurviveManager(Lookup context) {
super(context);
}
@Override
public boolean isSurvive() {
return true;
}
@Override
protected Result createResult(Result res) {
return new NeverEmptyResult(res, super.initSelectionAll());
}
}
private static final class NeverEmptyResult extends Lookup.Result
implements LookupListener {
private final Lookup.Result delegate;
private final Lookup.Result nodes;
private final Collection listeners;
private Collection extends Item> allItems;
private Collection extends T> allInstances;
private Set> allClasses;
public NeverEmptyResult(Result delegate, Result nodes) {
this.delegate = delegate;
this.nodes = nodes;
this.listeners = new CopyOnWriteArrayList();
// add weak listeners so this can be GCed when listeners are empty
this.delegate.addLookupListener(WeakListeners.create(LookupListener.class, this, this.delegate));
this.nodes.addLookupListener(WeakListeners.create(LookupListener.class, this, this.nodes));
initValues();
}
@Override
public void addLookupListener(LookupListener l) {
listeners.add(l);
}
@Override
public void removeLookupListener(LookupListener l) {
listeners.remove(l);
}
@Override
public Collection extends Item> allItems() {
Collection extends Item> res = delegate.allItems();
synchronized (this) {
if (!res.isEmpty()) {
allItems = res;
}
return allItems;
}
}
@Override
public Collection extends T> allInstances() {
Collection extends T> res = delegate.allInstances();
synchronized (this) {
if (!res.isEmpty()) {
allInstances = res;
}
return allInstances;
}
}
@Override
public Set> allClasses() {
Set> res = delegate.allClasses();
synchronized (this) {
if (!res.isEmpty()) {
allClasses = res;
}
return allClasses;
}
}
@Override
public void resultChanged(LookupEvent ev) {
if (ev.getSource() == nodes) {
Collection extends Item> arr = nodes.allItems();
if (arr.size() == 1 && arr.iterator().next().getInstance() == null) {
return;
}
initValues();
return;
}
final LookupEvent mev = new LookupEvent(this);
for (LookupListener ll : listeners) {
ll.resultChanged(mev);
}
}
private synchronized void initValues() {
allItems = Collections.emptyList();
allInstances = Collections.emptyList();
allClasses = Collections.emptySet();
}
} // end of NeverEmptyResult
/** Special set, that is weakly holding its actions, but also
* listens on changes in lookup.
*/
static final class LSet extends WeakSet
implements LookupListener, Runnable {
final Lookup.Result result;
public LSet(Lookup.Result context, Class type) {
this.result = context;
this.result.addLookupListener(this);
// activate listener
this.result.allItems();
}
@Override
public boolean add(ContextAction e) {
assert e != null;
return super.add(e);
}
@Override
public void resultChanged(LookupEvent ev) {
Mutex.EVENT.readAccess(this);
}
@Override
public void run() {
ContextAction[] arr;
synchronized (CACHE) {
arr = toArray(new ContextAction[0]);
}
long now = 0;
assert (now = System.currentTimeMillis()) >= 0;
for (ContextAction a : arr) {
if (a != null) {
a.updateState();
}
}
long took = 0;
assert (took = System.currentTimeMillis() - now) >= 0;
if (took > 2000) {
LOG.log(Level.WARNING, "Updating state of {1} actions took {0} ms. here is the action list:", new Object[] { took, arr.length });
for (ContextAction a : arr) {
LOG.log(Level.INFO, " {0}", a);
}
}
}
private void cleanup() {
this.result.removeLookupListener(this);
}
}
static class LookupRef extends WeakReference {
private final int hashCode;
public LookupRef(Lookup referent) {
super(referent);
hashCode = System.identityHashCode(referent);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LookupRef) {
LookupRef lr = (LookupRef)obj;
return get() == lr.get();
}
return false;
}
@Override
public int hashCode() {
return hashCode;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy