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