001package conexp.fx.core.collections; 002 003/*- 004 * #%L 005 * Concept Explorer FX 006 * %% 007 * Copyright (C) 2010 - 2023 Francesco Kriegel 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import java.util.AbstractCollection; 026import java.util.AbstractMap; 027import java.util.AbstractSet; 028import java.util.Collection; 029import java.util.Iterator; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.Set; 033import java.util.concurrent.ConcurrentHashMap; 034import java.util.concurrent.atomic.AtomicBoolean; 035import java.util.stream.StreamSupport; 036 037import com.google.common.collect.Collections2; 038import com.google.common.collect.Iterators; 039import com.google.common.collect.Maps; 040import com.google.common.collect.Multimap; 041import com.google.common.collect.Multiset; 042import com.google.common.collect.SetMultimap; 043import com.google.common.collect.Sets; 044 045public final class ConcurrentHashSetMultimap<K, V> implements SetMultimap<K, V> { 046 047 private final Map<K, Set<V>> map; 048 049 public ConcurrentHashSetMultimap() { 050 super(); 051 this.map = new ConcurrentHashMap<>(); 052 } 053 054 @Override 055 public int size() { 056 return this.map.keySet().parallelStream().map(key -> map.get(key).size()).reduce(0, Integer::sum); 057 } 058 059 @Override 060 public boolean isEmpty() { 061 return this.map.values().parallelStream().allMatch(Collection::isEmpty); 062 } 063 064 @Override 065 public boolean containsKey(Object key) { 066 return this.map.containsKey(key) && !this.map.get(key).isEmpty(); 067 } 068 069 @Override 070 public boolean containsValue(Object value) { 071 return this.map.values().parallelStream().anyMatch(set -> set.contains(value)); 072 } 073 074 @Override 075 public boolean containsEntry(Object key, Object value) { 076 return this.map.containsKey(key) && this.map.get(key).contains(value); 077 } 078 079 @Override 080 public boolean put(K key, V value) { 081 return this.map.computeIfAbsent(key, __ -> Sets.newConcurrentHashSet()).add(value); 082 } 083 084 @Override 085 public boolean remove(Object key, Object value) { 086 return this.map.containsKey(key) && this.map.get(key).remove(value); 087 } 088 089 @Override 090 public boolean putAll(K key, Iterable<? extends V> values) { 091 final Set<V> set = this.map.computeIfAbsent(key, __ -> Sets.newConcurrentHashSet()); 092 final AtomicBoolean result = new AtomicBoolean(false); 093 StreamSupport.stream(values.spliterator(), true).forEach(value -> { 094 if (set.add(value)) 095 result.lazySet(true); 096 }); 097 return result.get(); 098 } 099 100 @Override 101 public boolean putAll(Multimap<? extends K, ? extends V> multimap) { 102 final AtomicBoolean result = new AtomicBoolean(false); 103 multimap.entries().parallelStream().forEach(entry -> { 104 if (this.put((K) entry.getKey(), (V) entry.getValue())) 105 result.lazySet(true); 106 }); 107// multimap.keySet().parallelStream().forEach(key -> { 108// if (this.putAll((K) key, Collections2.transform(multimap.get((K) key), element -> (V) element))) 109// result.lazySet(true); 110// }); 111 return result.get(); 112 } 113 114 @Override 115 public void clear() { 116 this.map.clear(); 117 } 118 119 @Override 120 public Set<K> keySet() { 121 return this.map.keySet(); 122 } 123 124 @Override 125 public Multiset<K> keys() { 126 throw new UnsupportedOperationException("Operation is not implemented."); 127 } 128 129 @Override 130 public Collection<V> values() { 131 return new AbstractCollection<V>() { 132 133 @Override 134 public Iterator<V> iterator() { 135 return Iterators 136 .concat( 137 Collections2 138 .transform( 139 ConcurrentHashSetMultimap.this.map.keySet(), 140 key -> ConcurrentHashSetMultimap.this.map.get(key).iterator()) 141 .iterator()); 142 } 143 144 @Override 145 public int size() { 146 return ConcurrentHashSetMultimap.this.size(); 147 } 148 149 }; 150 } 151 152 @Override 153 public Set<V> get(K key) { 154 return this.map.get(key); 155 } 156 157 @Override 158 public Set<V> removeAll(Object key) { 159 return this.map.remove(key); 160 } 161 162 @Override 163 public Set<V> replaceValues(K key, Iterable<? extends V> values) { 164 final Set<V> set = this.map.computeIfAbsent(key, __ -> Sets.newConcurrentHashSet()); 165 StreamSupport.stream(values.spliterator(), true).forEach(value -> { 166 set.add(value); 167 }); 168 return set; 169 } 170 171 @Override 172 public Set<Entry<K, V>> entries() { 173 return new AbstractSet<Entry<K, V>>() { 174 175 @Override 176 public Iterator<Entry<K, V>> iterator() { 177 return Iterators 178 .concat( 179 Collections2 180 .transform( 181 ConcurrentHashSetMultimap.this.map.keySet(), 182 key -> Iterators 183 .transform( 184 ConcurrentHashSetMultimap.this.map.get(key).iterator(), 185 value -> new AbstractMap.SimpleEntry<>(key, value))) 186 .iterator()); 187 } 188 189 @Override 190 public int size() { 191 return ConcurrentHashSetMultimap.this.size(); 192 } 193 }; 194 } 195 196 @Override 197 public Map<K, Collection<V>> asMap() { 198 return Maps.transformValues(this.map, set -> (Collection<V>) set); 199 } 200 201}