001/*
002 * VM-Operator
003 * Copyright (C) 2023 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.vmoperator.manager.events;
020
021import java.lang.ref.WeakReference;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.concurrent.ConcurrentHashMap;
028import org.jgrapes.core.Channel;
029
030/**
031 * Used to track mapping from a key to a channel. Entries must
032 * be maintained by handlers for "add/remove" (or "open/close")
033 * events delivered on the channels that are to be
034 * made available by the tracker.
035 * 
036 * The channels are stored in the dictionary using {@link WeakReference}s.
037 * Removing entries is therefore best practice but not an absolute necessity
038 * as entries for cleared references are removed when one of the methods
039 * {@link #values()}, {@link #channels()} or {@link #associated()} is called.
040 *
041 * @param <K> the key type
042 * @param <C> the channel type
043 * @param <A> the type of the associated data
044 */
045public class ChannelTracker<K, C extends Channel, A>
046        implements ChannelDictionary<K, C, A> {
047
048    private final Map<K, Data<C, A>> entries = new ConcurrentHashMap<>();
049
050    /**
051     * Combines the channel and associated data.
052     *
053     * @param <C> the generic type
054     * @param <A> the generic type
055     */
056    @SuppressWarnings("PMD.ShortClassName")
057    private static class Data<C extends Channel, A> {
058        public final WeakReference<C> channel;
059        public A associated;
060
061        /**
062         * Instantiates a new value.
063         *
064         * @param channel the channel
065         */
066        public Data(C channel) {
067            this.channel = new WeakReference<>(channel);
068        }
069    }
070
071    @Override
072    public Set<K> keys() {
073        return entries.keySet();
074    }
075
076    @Override
077    public Collection<Value<C, A>> values() {
078        var result = new ArrayList<Value<C, A>>();
079        for (var itr = entries.entrySet().iterator(); itr.hasNext();) {
080            var value = itr.next().getValue();
081            var channel = value.channel.get();
082            if (channel == null) {
083                itr.remove();
084                continue;
085            }
086            result.add(new Value<>(channel, value.associated));
087        }
088        return result;
089    }
090
091    /**
092     * Returns the channel and associates data registered for the key
093     * or an empty optional if no mapping exists.
094     * 
095     * @param key the key
096     * @return the result
097     */
098    public Optional<Value<C, A>> value(K key) {
099        var value = entries.get(key);
100        if (value == null) {
101            return Optional.empty();
102        }
103        var channel = value.channel.get();
104        if (channel == null) {
105            // Cleanup old reference
106            entries.remove(key);
107            return Optional.empty();
108        }
109        return Optional.of(new Value<>(channel, value.associated));
110    }
111
112    /**
113     * Store the given data.
114     *
115     * @param key the key
116     * @param channel the channel
117     * @param associated the associated
118     * @return the channel manager
119     */
120    public ChannelTracker<K, C, A> put(K key, C channel, A associated) {
121        Data<C, A> data = new Data<>(channel);
122        data.associated = associated;
123        entries.put(key, data);
124        return this;
125    }
126
127    /**
128     * Store the given data.
129     *
130     * @param key the key
131     * @param channel the channel
132     * @return the channel manager
133     */
134    public ChannelTracker<K, C, A> put(K key, C channel) {
135        put(key, channel, null);
136        return this;
137    }
138
139    /**
140     * Associate the entry for the channel with the given data. The entry
141     * for the channel must already exist.
142     *
143     * @param key the key
144     * @param data the data
145     * @return the channel manager
146     */
147    public ChannelTracker<K, C, A> associate(K key, A data) {
148        Optional.ofNullable(entries.get(key))
149            .ifPresent(v -> v.associated = data);
150        return this;
151    }
152
153    /**
154     * Removes the channel with the given name.
155     *
156     * @param name the name
157     */
158    public void remove(String name) {
159        entries.remove(name);
160    }
161}