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}