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}