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}