/* * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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.binding.generator.impl; import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.binding.model.api.GeneratedType; import org.opendaylight.mdsal.binding.model.api.Type; import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeTypes; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode; import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement; import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; final class DefaultBindingRuntimeTypes implements BindingRuntimeTypes { private static final Logger LOG = LoggerFactory.getLogger(DefaultBindingRuntimeTypes.class); private final @NonNull EffectiveModelContext schemaContext; private final ImmutableMap typeToAugmentation; private final ImmutableMap typeToSchema; private final ImmutableMultimap choiceToCases; private final ImmutableMap identities; // Not Immutable as we use two different implementations private final Map schemaToType; DefaultBindingRuntimeTypes(final EffectiveModelContext schemaContext, final Map typeToAugmentation, final Map typeToSchema, final Map schemaToType, final Map identities) { this.schemaContext = requireNonNull(schemaContext); this.typeToAugmentation = ImmutableMap.copyOf(typeToAugmentation); this.typeToSchema = ImmutableMap.copyOf(typeToSchema); this.identities = ImmutableMap.copyOf(identities); // Careful to use identity for SchemaNodes, but only if needed // FIXME: 8.0.0: YT should be switching to identity for equals(), so this should become unnecessary Map copy; try { copy = ImmutableMap.copyOf(schemaToType); } catch (IllegalArgumentException e) { LOG.debug("Equality-duplicates found in {}", schemaToType.keySet()); copy = new IdentityHashMap<>(schemaToType); } this.schemaToType = copy; // Two-phase indexing of choice/case nodes. First we load all choices. Note we are using typeToSchema argument, // not field, so as not to instantiate its entrySet. final Set choiceTypes = typeToSchema.entrySet().stream() .filter(entry -> entry.getValue() instanceof ChoiceEffectiveStatement) .map(entry -> { final Type key = entry.getKey(); verify(key instanceof GeneratedType, "Unexpected choice type %s", key); return (GeneratedType) key; }) .collect(Collectors.toUnmodifiableSet()); final Multimap builder = MultimapBuilder.hashKeys(choiceTypes.size()).arrayListValues().build(); for (Entry entry : typeToSchema.entrySet()) { if (entry.getValue() instanceof CaseEffectiveStatement) { final Type type = entry.getKey(); verify(type instanceof GeneratedType, "Unexpected case type %s", type); builder.put(verifyNotNull(implementedChoiceType(((GeneratedType) type).getImplements(), choiceTypes), "Cannot determine choice type for %s", type), type); } } choiceToCases = ImmutableMultimap.copyOf(builder); } private static GeneratedType implementedChoiceType(final List impls, final Set choiceTypes) { for (Type impl : impls) { if (impl instanceof GeneratedType && choiceTypes.contains(impl)) { return (GeneratedType) impl; } } return null; } DefaultBindingRuntimeTypes(final EffectiveModelContext schemaContext, final Map typeToAugmentation, final BiMap typeToDefiningSchema, final Map identities) { this(schemaContext, typeToAugmentation, typeToDefiningSchema, typeToDefiningSchema.inverse(), identities); } @Override public EffectiveModelContext getEffectiveModelContext() { return schemaContext; } @Override public Optional findAugmentation(final Type type) { return Optional.ofNullable(typeToAugmentation.get(type)); } @Override public Optional findIdentity(final QName qname) { return Optional.ofNullable(identities.get(qname)); } @Override public Optional findSchema(final Type type) { return Optional.ofNullable(typeToSchema.get(type)); } @Override public Optional findType(final WithStatus schema) { return Optional.ofNullable(schemaToType.get(schema)); } @Override public Optional findOriginalAugmentationType(final AugmentationSchemaNode augment) { // If the augment statement does not contain any child nodes, we did not generate an augmentation, as it would // be plain littering. // FIXME: MDSAL-695: this check is rather costly (involves filtering), can we just rely on the not being found // in the end? all we are saving is essentially two map lookups after all... if (augment.getChildNodes().isEmpty()) { return Optional.empty(); } // FIXME: MDSAL-695: We should have enough information from mdsal-binding-generator to receive a (sparse) Map // for current -> original lookup. When combined with schemaToType, this amounts to the // inverse view of what 'typeToSchema' holds AugmentationSchemaNode current = augment; while (true) { // If this augmentation has been added through 'uses foo { augment bar { ... } }', we need to invert that // walk and arrive at the original declaration site, as that is where we generated 'grouping foo's // augmentation. That site may have a different module, hence the augment namespace may be different. final Optional original = current.getOriginalDefinition(); if (original.isEmpty()) { return findType(current); } current = original.orElseThrow(); } } @Override public Multimap getChoiceToCases() { return choiceToCases; } @Override public Collection findCases(final Type choiceType) { return choiceToCases.get(choiceType); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("typeToAugmentation", typeToAugmentation) .add("typeToSchema", typeToSchema) .add("choiceToCases", choiceToCases) .add("identities", identities) .toString(); } }