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        "PMD.CommentDefaultAccessModifier" })
058    private static class Data<C extends Channel, A> {
059        final WeakReference<C> channel;
060        A associated;
061
062        /**
063         * Instantiates a new value.
064         *
065         * @param channel the channel
066         */
067        Data(C channel) {
068            this.channel = new WeakReference<>(channel);
069        }
070    }
071
072    @Override
073    public Set<K> keys() {
074        return entries.keySet();
075    }
076
077    @Override
078    public Collection<Value<C, A>> values() {
079        var result = new ArrayList<Value<C, A>>();
080        for (var itr = entries.entrySet().iterator(); itr.hasNext();) {
081            var value = itr.next().getValue();
082            var channel = value.channel.get();
083            if (channel == null) {
084                itr.remove();
085                continue;
086            }
087            result.add(new Value<>(channel, value.associated));
088        }
089        return result;
090    }
091
092    /**
093     * Returns the channel and associates data registered for the key
094     * or an empty optional if no mapping exists.
095     * 
096     * @param key the key
097     * @return the result
098     */
099    @Override
100    public Optional<Value<C, A>> value(K key) {
101        var value = entries.get(key);
102        if (value == null) {
103            return Optional.empty();
104        }
105        var channel = value.channel.get();
106        if (channel == null) {
107            // Cleanup old reference
108            entries.remove(key);
109            return Optional.empty();
110        }
111        return Optional.of(new Value<>(channel, value.associated));
112    }
113
114    /**
115     * Store the given data.
116     *
117     * @param key the key
118     * @param channel the channel
119     * @param associated the associated
120     * @return the channel manager
121     */
122    public ChannelTracker<K, C, A> put(K key, C channel, A associated) {
123        Data<C, A> data = new Data<>(channel);
124        data.associated = associated;
125        entries.put(key, data);
126        return this;
127    }
128
129    /**
130     * Store the given data.
131     *
132     * @param key the key
133     * @param channel the channel
134     * @return the channel manager
135     */
136    public ChannelTracker<K, C, A> put(K key, C channel) {
137        put(key, channel, null);
138        return this;
139    }
140
141    /**
142     * Associate the entry for the channel with the given data. The entry
143     * for the channel must already exist.
144     *
145     * @param key the key
146     * @param data the data
147     * @return the channel manager
148     */
149    public ChannelTracker<K, C, A> associate(K key, A data) {
150        Optional.ofNullable(entries.get(key))
151            .ifPresent(v -> v.associated = data);
152        return this;
153    }
154
155    /**
156     * Removes the channel with the given name.
157     *
158     * @param name the name
159     */
160    public void remove(K name) {
161        entries.remove(name);
162    }
163}