Fix AbstractDOMRoutingTable.{add,remove}All()
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / AbstractDOMRoutingTable.java
1 /*
2  * Copyright (c) 2018 ZTE Corp. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.dom.broker;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.HashBasedTable;
15 import com.google.common.collect.HashMultimap;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import com.google.common.collect.ImmutableTable;
20 import com.google.common.collect.ListMultimap;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Maps;
23 import com.google.common.collect.Multimaps;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.EventListener;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Set;
32 import org.eclipse.jdt.annotation.NonNull;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
35
36 /**
37  * Abstract routing table definition for Action and RPC.
38  *
39  * @param <I> instance type of RPC or Acton
40  * @param <D> identifier type of RPC or Acton
41  * @param <M> implementation type of RPC or Acton
42  * @param <L> listener type of RPC or Acton
43  * @param <E> routing entry type of RPC or Acton
44  * @param <K> routing key type
45  */
46 @Beta
47 abstract class AbstractDOMRoutingTable<I, D, M, L extends EventListener, K,
48         E extends AbstractDOMRoutingTableEntry<D, M, L, K>> {
49     private final Map<K, E> operations;
50     private final EffectiveModelContext schemaContext;
51
52     AbstractDOMRoutingTable(final Map<K, E> operations, final EffectiveModelContext schemaContext) {
53         this.operations = requireNonNull(operations);
54         this.schemaContext = schemaContext;
55     }
56
57     AbstractDOMRoutingTable<I, D, M, L, K, E> setSchemaContext(final EffectiveModelContext context) {
58         final Builder<K, E> b = ImmutableMap.builder();
59
60         for (Entry<K, E> e : operations.entrySet()) {
61             final E entry = createOperationEntry(context, e.getKey(), e.getValue().getImplementations());
62             if (entry != null) {
63                 b.put(e.getKey(), entry);
64             }
65         }
66
67         return newInstance(b.build(), context);
68     }
69
70     AbstractDOMRoutingTable<I, D, M, L, K, E> add(final M implementation, final Set<I> oprsToAdd) {
71         if (oprsToAdd.isEmpty()) {
72             return this;
73         }
74
75         // First decompose the identifiers to a multimap
76         final ListMultimap<K, D> toAdd = decomposeIdentifiers(oprsToAdd);
77
78         final Builder<K, E> mb = ImmutableMap.builder();
79
80         // Now iterate over existing entries, modifying them as appropriate...
81         for (Entry<K, E> re : this.operations.entrySet()) {
82             List<D> newOperations = new ArrayList<>(toAdd.removeAll(re.getKey()));
83             if (!newOperations.isEmpty()) {
84                 final E ne = (E) re.getValue().add(implementation, newOperations);
85                 mb.put(re.getKey(), ne);
86             } else {
87                 mb.put(re);
88             }
89         }
90
91         // Finally add whatever is left in the decomposed multimap
92         for (Entry<K, Collection<D>> e : toAdd.asMap().entrySet()) {
93             final Builder<D, List<M>> vb = ImmutableMap.builder();
94             final List<M> v = ImmutableList.of(implementation);
95             for (D i : e.getValue()) {
96                 vb.put(i, v);
97             }
98
99             final E entry = createOperationEntry(schemaContext, e.getKey(), vb.build());
100             if (entry != null) {
101                 mb.put(e.getKey(), entry);
102             }
103         }
104
105         return newInstance(mb.build(), schemaContext);
106     }
107
108     AbstractDOMRoutingTable<I, D, M, L, K, E> addAll(final ImmutableTable<K, D, M> impls) {
109         if (impls.isEmpty()) {
110             return this;
111         }
112
113         // Create a temporary map, which we will mutatate
114         final var toAdd = HashBasedTable.create(impls);
115         final var mb = ImmutableMap.<K, E>builder();
116
117         // Now iterate over existing entries, modifying them as appropriate...
118         for (Entry<K, E> re : this.operations.entrySet()) {
119             final var newImpls = toAdd.rowMap().remove(re.getKey());
120             if (newImpls == null) {
121                 mb.put(re);
122                 continue;
123             }
124
125             var ne = re;
126             for (var oper : newImpls.entrySet()) {
127                 @SuppressWarnings("unchecked")
128                 final E newVal = (E) ne.getValue().add(oper.getValue(), Lists.newArrayList(oper.getKey()));
129                 ne = Map.entry(ne.getKey(), newVal);
130             }
131             mb.put(ne);
132         }
133
134         // Finally add whatever is left in the decomposed multimap
135         for (Entry<K, Map<D, M>> e : toAdd.rowMap().entrySet()) {
136             final E entry = createOperationEntry(schemaContext, e.getKey(), ImmutableMap.copyOf(
137                 Maps.<D, M, List<M>>transformValues(e.getValue(), ImmutableList::of)));
138             if (entry != null) {
139                 mb.put(e.getKey(), entry);
140             }
141         }
142
143         return newInstance(mb.build(), schemaContext);
144     }
145
146     AbstractDOMRoutingTable<I, D, M, L, K, E> remove(final M implementation, final Set<I> instances) {
147         if (instances.isEmpty()) {
148             return this;
149         }
150
151         // First decompose the identifiers to a multimap
152         final ListMultimap<K, D> toRemove = decomposeIdentifiers(instances);
153
154         // Now iterate over existing entries, modifying them as appropriate...
155         final Builder<K, E> b = ImmutableMap.builder();
156         for (Entry<K, E> e : this.operations.entrySet()) {
157             final List<D> removed = new ArrayList<>(toRemove.removeAll(e.getKey()));
158             if (!removed.isEmpty()) {
159                 final E ne = (E) e.getValue().remove(implementation, removed);
160                 if (ne != null) {
161                     b.put(e.getKey(), ne);
162                 }
163             } else {
164                 b.put(e);
165             }
166         }
167
168         // All done, whatever is in toRemove, was not there in the first place
169         return newInstance(b.build(), schemaContext);
170     }
171
172     AbstractDOMRoutingTable<I, D, M, L, K, E> removeAll(final ImmutableTable<K, D, M> impls) {
173         if (impls.isEmpty()) {
174             return this;
175         }
176
177         // Create a temporary map, which we will mutatate
178         final var toRemove = HashBasedTable.create(impls);
179         final var mb = ImmutableMap.<K, E>builder();
180
181         // Now iterate over existing entries, modifying them as appropriate...
182         for (Entry<K, E> re : this.operations.entrySet()) {
183             final var oldImpls = toRemove.rowMap().remove(re.getKey());
184             if (oldImpls == null) {
185                 mb.put(re);
186                 continue;
187             }
188
189             var ne = re;
190             for (var oper : oldImpls.entrySet()) {
191                 if (ne != null) {
192                     @SuppressWarnings("unchecked")
193                     final E newVal = (E) ne.getValue().remove(oper.getValue(), Lists.newArrayList(oper.getKey()));
194                     if (newVal != null) {
195                         ne = Map.entry(ne.getKey(), newVal);
196                     } else {
197                         ne = null;
198                     }
199                 }
200             }
201             if (ne != null) {
202                 mb.put(ne);
203             }
204         }
205
206         // All done, whatever is in toRemove, was not there in the first place
207         return newInstance(mb.build(), schemaContext);
208     }
209
210     static final <K, V> HashMultimap<V, K> invertImplementationsMap(final Map<K, V> map) {
211         return Multimaps.invertFrom(Multimaps.forMap(map), HashMultimap.create());
212     }
213
214     @VisibleForTesting
215     Map<K, Set<D>> getOperations() {
216         return Maps.transformValues(operations, AbstractDOMRoutingTableEntry::registeredIdentifiers);
217     }
218
219     Map<K, Set<D>> getOperations(final L listener) {
220         final Map<K, Set<D>> ret = new HashMap<>(operations.size());
221         for (Entry<K, E> e : operations.entrySet()) {
222             final Set<D> ids = e.getValue().registeredIdentifiers(listener);
223             if (!ids.isEmpty()) {
224                 ret.put(e.getKey(), ids);
225             }
226         }
227
228         return ret;
229     }
230
231     @Nullable AbstractDOMRoutingTableEntry<D, M, L, K> getEntry(final @NonNull K type) {
232         return operations.get(type);
233     }
234
235     protected abstract AbstractDOMRoutingTable<I, D, M, L, K, E> newInstance(Map<K, E> operations,
236             EffectiveModelContext schemaContext);
237
238     abstract ListMultimap<K, D> decomposeIdentifiers(Set<I> instances);
239
240     abstract @Nullable E createOperationEntry(EffectiveModelContext context, K key, Map<D, List<M>> implementations);
241 }