All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.awt.ContextManager Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * 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 enabler) {
        Lookup.Result result = findResult(type);
        
        boolean e = isEnabledOnData(result, type, selectMode);
        if (enabler != null) {
            if (e) {
                List 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 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 all;
        final Class type;
        public LkpAE(List 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 perf, final Class type, ContextSelection selectMode) {
        Lookup.Result result = findResult(type);
        final List all = listFromResult(result);

        perf.actionPerformed(e, Collections.unmodifiableList(all), new LkpAE(all, type));
    }

    private  List listFromResult(Lookup.Result result) {
        Collection 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 all;
        if (col instanceof List) {
            all = (List)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> allItems;
        private Collection 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> allItems() {
            Collection> res = delegate.allItems();
            synchronized (this) {
                if (!res.isEmpty()) {
                    allItems = res;
                }
                return allItems;
            }
        }

        @Override
        public Collection allInstances() {
            Collection 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> 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