/* * Copyright (c) 2018 ZTE Corp. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.mdsal.dom.broker; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableTable; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; /** * Abstract routing table definition for Action and RPC. * * @param instance type of RPC or Acton * @param identifier type of RPC or Acton * @param implementation type of RPC or Acton * @param listener type of RPC or Acton * @param routing entry type of RPC or Acton * @param routing key type */ @Beta abstract class AbstractDOMRoutingTable> { private final Map operations; private final EffectiveModelContext schemaContext; AbstractDOMRoutingTable(final Map operations, final EffectiveModelContext schemaContext) { this.operations = requireNonNull(operations); this.schemaContext = schemaContext; } AbstractDOMRoutingTable setSchemaContext(final EffectiveModelContext context) { final Builder b = ImmutableMap.builder(); for (Entry e : operations.entrySet()) { final E entry = createOperationEntry(context, e.getKey(), e.getValue().getImplementations()); if (entry != null) { b.put(e.getKey(), entry); } } return newInstance(b.build(), context); } AbstractDOMRoutingTable add(final M implementation, final Set oprsToAdd) { if (oprsToAdd.isEmpty()) { return this; } // First decompose the identifiers to a multimap final ListMultimap toAdd = decomposeIdentifiers(oprsToAdd); final Builder mb = ImmutableMap.builder(); // Now iterate over existing entries, modifying them as appropriate... for (Entry re : operations.entrySet()) { List newOperations = new ArrayList<>(toAdd.removeAll(re.getKey())); if (!newOperations.isEmpty()) { final E ne = (E) re.getValue().add(implementation, newOperations); mb.put(re.getKey(), ne); } else { mb.put(re); } } // Finally add whatever is left in the decomposed multimap for (Entry> e : toAdd.asMap().entrySet()) { final Builder> vb = ImmutableMap.builder(); final List v = ImmutableList.of(implementation); for (D i : e.getValue()) { vb.put(i, v); } final E entry = createOperationEntry(schemaContext, e.getKey(), vb.build()); if (entry != null) { mb.put(e.getKey(), entry); } } return newInstance(mb.build(), schemaContext); } AbstractDOMRoutingTable addAll(final ImmutableTable impls) { if (impls.isEmpty()) { return this; } // Create a temporary map, which we will mutatate final var toAdd = HashBasedTable.create(impls); final var mb = ImmutableMap.builder(); // Now iterate over existing entries, modifying them as appropriate... for (Entry re : operations.entrySet()) { final var newImpls = toAdd.rowMap().remove(re.getKey()); if (newImpls == null) { mb.put(re); continue; } var ne = re; for (var oper : newImpls.entrySet()) { @SuppressWarnings("unchecked") final E newVal = (E) ne.getValue().add(oper.getValue(), Lists.newArrayList(oper.getKey())); ne = Map.entry(ne.getKey(), newVal); } mb.put(ne); } // Finally add whatever is left in the decomposed multimap for (Entry> e : toAdd.rowMap().entrySet()) { final E entry = createOperationEntry(schemaContext, e.getKey(), ImmutableMap.copyOf( Maps.>transformValues(e.getValue(), ImmutableList::of))); if (entry != null) { mb.put(e.getKey(), entry); } } return newInstance(mb.build(), schemaContext); } AbstractDOMRoutingTable remove(final M implementation, final Set instances) { if (instances.isEmpty()) { return this; } // First decompose the identifiers to a multimap final ListMultimap toRemove = decomposeIdentifiers(instances); // Now iterate over existing entries, modifying them as appropriate... final Builder b = ImmutableMap.builder(); for (Entry e : operations.entrySet()) { final List removed = new ArrayList<>(toRemove.removeAll(e.getKey())); if (!removed.isEmpty()) { final E ne = (E) e.getValue().remove(implementation, removed); if (ne != null) { b.put(e.getKey(), ne); } } else { b.put(e); } } // All done, whatever is in toRemove, was not there in the first place return newInstance(b.build(), schemaContext); } AbstractDOMRoutingTable removeAll(final ImmutableTable impls) { if (impls.isEmpty()) { return this; } // Create a temporary map, which we will mutatate final var toRemove = HashBasedTable.create(impls); final var mb = ImmutableMap.builder(); // Now iterate over existing entries, modifying them as appropriate... for (Entry re : operations.entrySet()) { final var oldImpls = toRemove.rowMap().remove(re.getKey()); if (oldImpls == null) { mb.put(re); continue; } var ne = re; for (var oper : oldImpls.entrySet()) { if (ne != null) { @SuppressWarnings("unchecked") final E newVal = (E) ne.getValue().remove(oper.getValue(), Lists.newArrayList(oper.getKey())); if (newVal != null) { ne = Map.entry(ne.getKey(), newVal); } else { ne = null; } } } if (ne != null) { mb.put(ne); } } // All done, whatever is in toRemove, was not there in the first place return newInstance(mb.build(), schemaContext); } static final HashMultimap invertImplementationsMap(final Map map) { return Multimaps.invertFrom(Multimaps.forMap(map), HashMultimap.create()); } @VisibleForTesting Map> getOperations() { return Maps.transformValues(operations, AbstractDOMRoutingTableEntry::registeredIdentifiers); } Map> getOperations(final L listener) { final Map> ret = new HashMap<>(operations.size()); for (Entry e : operations.entrySet()) { final Set ids = e.getValue().registeredIdentifiers(listener); if (!ids.isEmpty()) { ret.put(e.getKey(), ids); } } return ret; } @Nullable AbstractDOMRoutingTableEntry getEntry(final @NonNull K type) { return operations.get(type); } protected abstract AbstractDOMRoutingTable newInstance(Map operations, EffectiveModelContext schemaContext); abstract ListMultimap decomposeIdentifiers(Set instances); abstract @Nullable E createOperationEntry(EffectiveModelContext context, K key, Map> implementations); }